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