miden_protocol/transaction/kernel/advice_inputs.rs
1use alloc::vec::Vec;
2
3use miden_processor::advice::AdviceMutation;
4
5use crate::account::{AccountHeader, PartialAccount};
6use crate::block::account_tree::{AccountIdKey, AccountWitness};
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, 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 = AccountIdKey::from(partial_native_acc.id());
60 inputs.add_map_entry(account_id_key.as_word(), 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 // MUTATORS
108 // --------------------------------------------------------------------------------------------
109
110 /// Extends these advice inputs with the provided advice inputs.
111 pub fn extend(&mut self, adv_inputs: AdviceInputs) {
112 self.0.extend(adv_inputs);
113 }
114
115 /// Adds the provided account inputs into the advice inputs.
116 pub fn add_foreign_accounts<'inputs>(
117 &mut self,
118 foreign_account_inputs: impl IntoIterator<Item = &'inputs AccountInputs>,
119 ) {
120 for foreign_acc in foreign_account_inputs {
121 self.add_account(foreign_acc.account());
122 self.add_account_witness(foreign_acc.witness());
123
124 // for foreign accounts, we need to insert the id to state mapping
125 // NOTE: keep this in sync with the account::load_from_advice procedure
126 let account_id_key = AccountIdKey::from(foreign_acc.id());
127 let header = AccountHeader::from(foreign_acc.account());
128
129 // ACCOUNT_ID |-> [ID_AND_NONCE, VAULT_ROOT, STORAGE_COMMITMENT, CODE_COMMITMENT]
130 self.add_map_entry(account_id_key.as_word(), header.to_elements());
131 }
132 }
133
134 /// Extend the advice stack with the transaction inputs.
135 ///
136 /// The following data is pushed to the advice stack (words shown in memory-order):
137 ///
138 /// [
139 /// PARENT_BLOCK_COMMITMENT,
140 /// PARTIAL_BLOCKCHAIN_COMMITMENT,
141 /// ACCOUNT_ROOT,
142 /// NULLIFIER_ROOT,
143 /// TX_COMMITMENT,
144 /// TX_KERNEL_COMMITMENT
145 /// VALIDATOR_KEY_COMMITMENT,
146 /// [block_num, version, timestamp, 0],
147 /// [0, verification_base_fee, native_asset_id_suffix, native_asset_id_prefix]
148 /// [0, 0, 0, 0]
149 /// NOTE_ROOT,
150 /// kernel_version
151 /// [account_nonce, 0, account_id_suffix, account_id_prefix],
152 /// ACCOUNT_VAULT_ROOT,
153 /// ACCOUNT_STORAGE_COMMITMENT,
154 /// ACCOUNT_CODE_COMMITMENT,
155 /// number_of_input_notes,
156 /// TX_SCRIPT_ROOT,
157 /// TX_SCRIPT_ARGS,
158 /// AUTH_ARGS,
159 /// ]
160 fn build_stack(&mut self, tx_inputs: &TransactionInputs) {
161 let header = tx_inputs.block_header();
162
163 // --- block header data (keep in sync with kernel's process_block_data) --
164 self.extend_stack(header.prev_block_commitment());
165 self.extend_stack(header.chain_commitment());
166 self.extend_stack(header.account_root());
167 self.extend_stack(header.nullifier_root());
168 self.extend_stack(header.tx_commitment());
169 self.extend_stack(header.tx_kernel_commitment());
170 self.extend_stack(header.validator_key().to_commitment());
171 self.extend_stack([
172 header.block_num().into(),
173 Felt::from(header.version()),
174 Felt::from(header.timestamp()),
175 ZERO,
176 ]);
177 self.extend_stack([
178 ZERO,
179 Felt::from(header.fee_parameters().verification_base_fee()),
180 header.fee_parameters().native_asset_id().suffix(),
181 header.fee_parameters().native_asset_id().prefix().as_felt(),
182 ]);
183 self.extend_stack([ZERO, ZERO, ZERO, ZERO]);
184 self.extend_stack(header.note_root());
185
186 // --- core account items (keep in sync with process_account_data) ----
187 let account = tx_inputs.account();
188 self.extend_stack([
189 account.nonce(),
190 ZERO,
191 account.id().suffix(),
192 account.id().prefix().as_felt(),
193 ]);
194 self.extend_stack(account.vault().root());
195 self.extend_stack(account.storage().commitment());
196 self.extend_stack(account.code().commitment());
197
198 // --- number of notes, script root and args --------------------------
199 self.extend_stack([Felt::from(tx_inputs.input_notes().num_notes())]);
200 let tx_args = tx_inputs.tx_args();
201 self.extend_stack(tx_args.tx_script().map_or(Word::empty(), |script| script.root()));
202 self.extend_stack(tx_args.tx_script_args());
203
204 // --- auth procedure args --------------------------------------------
205 self.extend_stack(tx_args.auth_args());
206 }
207
208 // BLOCKCHAIN INJECTIONS
209 // --------------------------------------------------------------------------------------------
210
211 /// Inserts the partial blockchain data into the provided advice inputs.
212 ///
213 /// Inserts the following items into the Merkle store:
214 /// - Inner nodes of all authentication paths contained in the partial blockchain.
215 ///
216 /// Inserts the following data to the advice map:
217 ///
218 /// > {MMR_ROOT: [[num_blocks, 0, 0, 0], PEAK_1, ..., PEAK_N]}
219 ///
220 /// Where:
221 /// - MMR_ROOT, is the sequential hash of the padded MMR peaks
222 /// - num_blocks, is the number of blocks in the MMR.
223 /// - PEAK_1 .. PEAK_N, are the MMR peaks.
224 fn add_partial_blockchain(&mut self, mmr: &PartialBlockchain) {
225 // NOTE: keep this code in sync with the `process_chain_data` kernel procedure
226 // add authentication paths from the MMR to the Merkle store
227 self.extend_merkle_store(mmr.inner_nodes());
228
229 // insert MMR peaks info into the advice map
230 let peaks = mmr.peaks();
231 let mut elements = vec![Felt::new(peaks.num_leaves() as u64), ZERO, ZERO, ZERO];
232 elements.extend(peaks.flatten_and_pad_peaks());
233 self.add_map_entry(peaks.hash_peaks(), elements);
234 }
235
236 // KERNEL INJECTIONS
237 // --------------------------------------------------------------------------------------------
238
239 /// Inserts the kernel commitment and its procedure roots into the advice map.
240 ///
241 /// Inserts the following entries into the advice map:
242 /// - The commitment of the kernel |-> array of the kernel's procedure roots.
243 fn add_kernel_commitment(&mut self) {
244 // insert the kernel commitment with its procedure roots into the advice map
245 self.add_map_entry(TransactionKernel.to_commitment(), TransactionKernel.to_elements());
246 }
247
248 // ACCOUNT INJECTION
249 // --------------------------------------------------------------------------------------------
250
251 /// Inserts account data into the advice inputs.
252 ///
253 /// Inserts the following items into the Merkle store:
254 /// - The Merkle nodes associated with the account vault tree.
255 /// - If present, the Merkle nodes associated with the account storage maps.
256 ///
257 /// Inserts the following entries into the advice map:
258 /// - The account storage commitment |-> storage slots and types vector.
259 /// - The account code commitment |-> procedures vector.
260 /// - The leaf hash |-> (key, value), for all leaves of the partial vault.
261 /// - If present, the Merkle leaves associated with the account storage maps.
262 fn add_account(&mut self, account: &PartialAccount) {
263 // --- account code -------------------------------------------------------
264
265 // CODE_COMMITMENT -> [[ACCOUNT_PROCEDURE_DATA]]
266 let code = account.code();
267 self.add_map_entry(code.commitment(), code.as_elements());
268
269 // --- account storage ----------------------------------------------------
270
271 // STORAGE_COMMITMENT |-> [[STORAGE_SLOT_DATA]]
272 let storage_header = account.storage().header();
273 self.add_map_entry(storage_header.to_commitment(), storage_header.to_elements());
274
275 // populate Merkle store and advice map with nodes info needed to access storage map entries
276 self.extend_merkle_store(account.storage().inner_nodes());
277 self.extend_map(
278 account
279 .storage()
280 .leaves()
281 .map(|leaf| (leaf.hash(), leaf.to_elements().collect())),
282 );
283
284 // --- account vault ------------------------------------------------------
285
286 // populate Merkle store and advice map with nodes info needed to access vault assets
287 self.extend_merkle_store(account.vault().inner_nodes());
288 self.extend_map(
289 account.vault().leaves().map(|leaf| (leaf.hash(), leaf.to_elements().collect())),
290 );
291 }
292
293 /// Adds an account witness to the advice inputs.
294 ///
295 /// This involves extending the map to include the leaf's hash mapped to its elements, as well
296 /// as extending the merkle store with the nodes of the witness.
297 fn add_account_witness(&mut self, witness: &AccountWitness) {
298 // populate advice map with the account's leaf
299 let leaf = witness.leaf();
300 self.add_map_entry(leaf.hash(), leaf.to_elements().collect());
301
302 // extend the merkle store and map with account witnesses merkle path
303 self.extend_merkle_store(witness.authenticated_nodes());
304 }
305
306 // NOTE INJECTION
307 // --------------------------------------------------------------------------------------------
308
309 /// Populates the advice inputs for all input notes.
310 ///
311 /// The advice provider is populated with:
312 ///
313 /// - For each note:
314 /// - The note's details (serial number, script root, and its storage / assets commitment).
315 /// - The note's private arguments.
316 /// - The note's public metadata (sender account ID, note type, note tag, attachment kind /
317 /// scheme and the attachment content).
318 /// - The note's storage (unpadded).
319 /// - The note's assets (key and value words).
320 /// - For authenticated notes (determined by the `is_authenticated` flag):
321 /// - The note's authentication path against its block's note tree.
322 /// - The block number, sub commitment, note root.
323 /// - The note's position in the note tree
324 ///
325 /// The data above is processed by `prologue::process_input_notes_data`.
326 fn add_input_notes(&mut self, tx_inputs: &TransactionInputs) {
327 if tx_inputs.input_notes().is_empty() {
328 return;
329 }
330
331 let mut note_data = Vec::new();
332 for input_note in tx_inputs.input_notes().iter() {
333 let note = input_note.note();
334 let assets = note.assets();
335 let recipient = note.recipient();
336 let note_arg = tx_inputs.tx_args().get_note_args(note.id()).unwrap_or(&EMPTY_WORD);
337
338 // recipient storage
339 self.add_map_entry(recipient.storage().commitment(), recipient.storage().to_elements());
340 // assets commitments
341 self.add_map_entry(assets.commitment(), assets.to_elements());
342 // array attachments
343 if let NoteAttachmentContent::Array(array_attachment) =
344 note.metadata().attachment().content()
345 {
346 self.add_map_entry(
347 array_attachment.commitment(),
348 array_attachment.as_slice().to_vec(),
349 );
350 }
351
352 // note details / metadata
353 note_data.extend(recipient.serial_num());
354 note_data.extend(*recipient.script().root());
355 note_data.extend(*recipient.storage().commitment());
356 note_data.extend(*assets.commitment());
357 note_data.extend(*note_arg);
358 note_data.extend(note.metadata().to_attachment_word());
359 note_data.extend(note.metadata().to_header_word());
360 note_data.push(Felt::from(recipient.storage().num_items()));
361 note_data.push(Felt::from(assets.num_assets() as u32));
362 note_data.extend(assets.to_elements());
363
364 // authentication vs unauthenticated
365 match input_note {
366 InputNote::Authenticated { note, proof } => {
367 // Push the `is_authenticated` flag
368 note_data.push(Felt::ONE);
369
370 // Merkle path
371 self.extend_merkle_store(proof.authenticated_nodes(note.commitment()));
372
373 let block_num = proof.location().block_num();
374 let block_header = if block_num == tx_inputs.block_header().block_num() {
375 tx_inputs.block_header()
376 } else {
377 tx_inputs
378 .blockchain()
379 .get_block(block_num)
380 .expect("block not found in partial blockchain")
381 };
382
383 note_data.push(block_num.into());
384 note_data.extend(block_header.sub_commitment());
385 note_data.extend(block_header.note_root());
386 note_data.push(Felt::from(proof.location().node_index_in_block()));
387 },
388 InputNote::Unauthenticated { .. } => {
389 // push the `is_authenticated` flag
390 note_data.push(Felt::ZERO)
391 },
392 }
393 }
394
395 self.add_map_entry(tx_inputs.input_notes().commitment(), note_data);
396 }
397
398 // HELPER METHODS
399 // --------------------------------------------------------------------------------------------
400
401 /// Extends the map of values with the given argument, replacing previously inserted items.
402 fn extend_map(&mut self, iter: impl IntoIterator<Item = (Word, Vec<Felt>)>) {
403 self.0.map.extend(iter);
404 }
405
406 fn add_map_entry(&mut self, key: Word, values: Vec<Felt>) {
407 self.0.map.extend([(key, values)]);
408 }
409
410 /// Extends the stack with the given elements.
411 fn extend_stack(&mut self, iter: impl IntoIterator<Item = Felt>) {
412 self.0.stack.extend(iter);
413 }
414
415 /// Extends the [`MerkleStore`](crate::crypto::merkle::MerkleStore) with the given
416 /// nodes.
417 fn extend_merkle_store(&mut self, iter: impl Iterator<Item = InnerNodeInfo>) {
418 self.0.store.extend(iter);
419 }
420}
421
422// CONVERSIONS
423// ================================================================================================
424
425impl From<TransactionAdviceInputs> for AdviceInputs {
426 fn from(wrapper: TransactionAdviceInputs) -> Self {
427 wrapper.0
428 }
429}
430
431impl From<AdviceInputs> for TransactionAdviceInputs {
432 fn from(inner: AdviceInputs) -> Self {
433 Self(inner)
434 }
435}