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}