miden_protocol/transaction/kernel/
mod.rs1use alloc::string::ToString;
2use alloc::sync::Arc;
3use alloc::vec::Vec;
4
5use miden_core_lib::CoreLibrary;
6
7use crate::account::{AccountHeader, AccountId};
8#[cfg(any(feature = "testing", test))]
9use crate::assembly::Library;
10use crate::assembly::debuginfo::SourceManagerSync;
11use crate::assembly::{Assembler, DefaultSourceManager, KernelLibrary};
12use crate::asset::FungibleAsset;
13use crate::block::BlockNumber;
14use crate::crypto::SequentialCommit;
15use crate::errors::TransactionOutputError;
16use crate::protocol::ProtocolLib;
17use crate::transaction::{OutputNote, OutputNotes, TransactionInputs, TransactionOutputs};
18use crate::utils::serde::Deserializable;
19use crate::utils::sync::LazyLock;
20use crate::vm::{AdviceInputs, Program, ProgramInfo, StackInputs, StackOutputs};
21use crate::{Felt, Hasher, Word};
22
23mod procedures;
24
25pub mod memory;
26
27mod advice_inputs;
28mod tx_event_id;
29
30pub use advice_inputs::TransactionAdviceInputs;
31pub use tx_event_id::TransactionEventId;
32
33static KERNEL_LIB: LazyLock<KernelLibrary> = LazyLock::new(|| {
38 let kernel_lib_bytes =
39 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_kernel.masl"));
40 KernelLibrary::read_from_bytes(kernel_lib_bytes)
41 .expect("failed to deserialize transaction kernel library")
42});
43
44static KERNEL_MAIN: LazyLock<Program> = LazyLock::new(|| {
46 let kernel_main_bytes =
47 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_kernel.masb"));
48 Program::read_from_bytes(kernel_main_bytes)
49 .expect("failed to deserialize transaction kernel runtime")
50});
51
52static TX_SCRIPT_MAIN: LazyLock<Program> = LazyLock::new(|| {
54 let tx_script_main_bytes =
55 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_script_main.masb"));
56 Program::read_from_bytes(tx_script_main_bytes)
57 .expect("failed to deserialize tx script executor runtime")
58});
59
60pub struct TransactionKernel;
64
65impl TransactionKernel {
66 pub const PROCEDURES: &'static [Word] = &procedures::KERNEL_PROCEDURES;
71
72 pub fn kernel() -> KernelLibrary {
80 KERNEL_LIB.clone()
81 }
82
83 pub fn main() -> Program {
88 KERNEL_MAIN.clone()
89 }
90
91 pub fn tx_script_main() -> Program {
96 TX_SCRIPT_MAIN.clone()
97 }
98
99 pub fn program_info() -> ProgramInfo {
104 let program_hash = Self::main().hash();
106 let kernel = Self::kernel().kernel().clone();
107
108 ProgramInfo::new(program_hash, kernel)
109 }
110
111 pub fn prepare_inputs(tx_inputs: &TransactionInputs) -> (StackInputs, TransactionAdviceInputs) {
114 let account = tx_inputs.account();
115
116 let stack_inputs = TransactionKernel::build_input_stack(
117 account.id(),
118 account.initial_commitment(),
119 tx_inputs.input_notes().commitment(),
120 tx_inputs.block_header().commitment(),
121 tx_inputs.block_header().block_num(),
122 );
123
124 let tx_advice_inputs = TransactionAdviceInputs::new(tx_inputs);
125
126 (stack_inputs, tx_advice_inputs)
127 }
128
129 pub fn assembler() -> Assembler {
135 Self::assembler_with_source_manager(Arc::new(DefaultSourceManager::default()))
136 }
137
138 pub fn assembler_with_source_manager(source_manager: Arc<dyn SourceManagerSync>) -> Assembler {
141 #[cfg(all(any(feature = "testing", test), feature = "std"))]
142 source_manager_ext::load_masm_source_files(&source_manager);
143
144 Assembler::with_kernel(source_manager, Self::kernel())
145 .with_dynamic_library(CoreLibrary::default())
146 .expect("failed to load std-lib")
147 .with_dynamic_library(ProtocolLib::default())
148 .expect("failed to load miden-lib")
149 }
150
151 pub fn build_input_stack(
176 account_id: AccountId,
177 initial_account_commitment: Word,
178 input_notes_commitment: Word,
179 block_commitment: Word,
180 block_num: BlockNumber,
181 ) -> StackInputs {
182 let mut inputs: Vec<Felt> = Vec::with_capacity(14);
184 inputs.push(Felt::from(block_num));
185 inputs.push(account_id.suffix());
186 inputs.push(account_id.prefix().as_felt());
187 inputs.extend(input_notes_commitment);
188 inputs.extend_from_slice(initial_account_commitment.as_elements());
189 inputs.extend_from_slice(block_commitment.as_elements());
190 StackInputs::new(inputs)
191 .map_err(|e| e.to_string())
192 .expect("Invalid stack input")
193 }
194
195 pub fn build_output_stack(
214 final_account_commitment: Word,
215 account_delta_commitment: Word,
216 output_notes_commitment: Word,
217 fee: FungibleAsset,
218 expiration_block_num: BlockNumber,
219 ) -> StackOutputs {
220 let account_update_commitment =
221 Hasher::merge(&[final_account_commitment, account_delta_commitment]);
222 let mut outputs: Vec<Felt> = Vec::with_capacity(9);
223 outputs.push(Felt::from(expiration_block_num));
224 outputs.extend(Word::from(fee));
225 outputs.extend(account_update_commitment);
226 outputs.extend(output_notes_commitment);
227 outputs.reverse();
228 StackOutputs::new(outputs)
229 .map_err(|e| e.to_string())
230 .expect("Invalid stack output")
231 }
232
233 pub fn parse_output_stack(
261 stack: &StackOutputs, ) -> Result<(Word, Word, FungibleAsset, BlockNumber), TransactionOutputError> {
263 let output_notes_commitment = stack
264 .get_stack_word_be(TransactionOutputs::OUTPUT_NOTES_COMMITMENT_WORD_IDX * 4)
265 .expect("output_notes_commitment (first word) missing");
266
267 let account_update_commitment = stack
268 .get_stack_word_be(TransactionOutputs::ACCOUNT_UPDATE_COMMITMENT_WORD_IDX * 4)
269 .expect("account_update_commitment (second word) missing");
270
271 let fee = stack
272 .get_stack_word_be(TransactionOutputs::FEE_ASSET_WORD_IDX * 4)
273 .expect("fee_asset (third word) missing");
274
275 let expiration_block_num = stack
276 .get_stack_item(TransactionOutputs::EXPIRATION_BLOCK_ELEMENT_IDX)
277 .expect("tx_expiration_block_num (element on index 12) missing");
278
279 let expiration_block_num = u32::try_from(expiration_block_num.as_int())
280 .map_err(|_| {
281 TransactionOutputError::OutputStackInvalid(
282 "expiration block number should be smaller than u32::MAX".into(),
283 )
284 })?
285 .into();
286
287 if stack.get_stack_word_be(12).expect("fourth word missing").as_elements()[..3]
290 != Word::empty().as_elements()[..3]
291 {
292 return Err(TransactionOutputError::OutputStackInvalid(
293 "indices 13, 14 and 15 on the output stack should be ZERO".into(),
294 ));
295 }
296
297 let fee = FungibleAsset::try_from(fee)
298 .map_err(TransactionOutputError::FeeAssetNotFungibleAsset)?;
299
300 Ok((output_notes_commitment, account_update_commitment, fee, expiration_block_num))
301 }
302
303 pub fn from_transaction_parts(
333 stack: &StackOutputs,
334 advice_inputs: &AdviceInputs,
335 output_notes: Vec<OutputNote>,
336 ) -> Result<TransactionOutputs, TransactionOutputError> {
337 let (output_notes_commitment, account_update_commitment, fee, expiration_block_num) =
338 Self::parse_output_stack(stack)?;
339
340 let (final_account_commitment, account_delta_commitment) =
341 Self::parse_account_update_commitment(account_update_commitment, advice_inputs)?;
342
343 let final_account_data = advice_inputs
345 .map
346 .get(&final_account_commitment)
347 .ok_or(TransactionOutputError::FinalAccountCommitmentMissingInAdviceMap)?;
348
349 let account = AccountHeader::try_from_elements(final_account_data)
350 .map_err(TransactionOutputError::FinalAccountHeaderParseFailure)?;
351
352 let output_notes = OutputNotes::new(output_notes)?;
354 if output_notes_commitment != output_notes.commitment() {
355 return Err(TransactionOutputError::OutputNotesCommitmentInconsistent {
356 actual: output_notes.commitment(),
357 expected: output_notes_commitment,
358 });
359 }
360
361 Ok(TransactionOutputs {
362 account,
363 account_delta_commitment,
364 output_notes,
365 fee,
366 expiration_block_num,
367 })
368 }
369
370 fn parse_account_update_commitment(
373 account_update_commitment: Word,
374 advice_inputs: &AdviceInputs,
375 ) -> Result<(Word, Word), TransactionOutputError> {
376 let account_update_data =
377 advice_inputs.map.get(&account_update_commitment).ok_or_else(|| {
378 TransactionOutputError::AccountUpdateCommitment(
379 "failed to find ACCOUNT_UPDATE_COMMITMENT in advice map".into(),
380 )
381 })?;
382
383 if account_update_data.len() != 8 {
384 return Err(TransactionOutputError::AccountUpdateCommitment(
385 "expected account update commitment advice map entry to contain exactly 8 elements"
386 .into(),
387 ));
388 }
389
390 let final_account_commitment = Word::from(
393 <[Felt; 4]>::try_from(&account_update_data[0..4])
394 .expect("we should have sliced off exactly four elements"),
395 );
396 let account_delta_commitment = Word::from(
397 <[Felt; 4]>::try_from(&account_update_data[4..8])
398 .expect("we should have sliced off exactly four elements"),
399 );
400
401 let computed_account_update_commitment =
402 Hasher::merge(&[final_account_commitment, account_delta_commitment]);
403
404 if computed_account_update_commitment != account_update_commitment {
405 let err_message = format!(
406 "transaction outputs account update commitment {account_update_commitment} but commitment computed from its advice map entries was {computed_account_update_commitment}"
407 );
408 return Err(TransactionOutputError::AccountUpdateCommitment(err_message.into()));
409 }
410
411 Ok((final_account_commitment, account_delta_commitment))
412 }
413
414 pub fn to_commitment(&self) -> Word {
419 <Self as SequentialCommit>::to_commitment(self)
420 }
421}
422
423#[cfg(any(feature = "testing", test))]
424impl TransactionKernel {
425 const KERNEL_TESTING_LIB_BYTES: &'static [u8] =
426 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/kernel_library.masl"));
427
428 pub fn library() -> Library {
430 Library::read_from_bytes(Self::KERNEL_TESTING_LIB_BYTES)
431 .expect("failed to deserialize transaction kernel library")
432 }
433}
434
435impl SequentialCommit for TransactionKernel {
436 type Commitment = Word;
437
438 fn to_elements(&self) -> Vec<Felt> {
440 Word::words_as_elements(Self::PROCEDURES).to_vec()
441 }
442}
443
444#[cfg(all(any(feature = "testing", test), feature = "std"))]
445pub(crate) mod source_manager_ext {
446 use std::path::{Path, PathBuf};
447 use std::vec::Vec;
448 use std::{fs, io};
449
450 use crate::assembly::SourceManager;
451 use crate::assembly::debuginfo::SourceManagerExt;
452
453 pub fn load_masm_source_files(source_manager: &dyn SourceManager) {
460 if let Err(err) = load(source_manager) {
461 std::eprintln!("failed to load MASM sources into source manager: {err}");
464 }
465 }
466
467 fn load(source_manager: &dyn SourceManager) -> io::Result<()> {
469 for file in get_masm_files(concat!(env!("OUT_DIR"), "/asm"))? {
470 source_manager.load_file(&file).map_err(io::Error::other)?;
471 }
472
473 Ok(())
474 }
475
476 fn get_masm_files<P: AsRef<Path>>(dir_path: P) -> io::Result<Vec<PathBuf>> {
481 let mut files = Vec::new();
482
483 match fs::read_dir(dir_path) {
484 Ok(entries) => {
485 for entry in entries {
486 match entry {
487 Ok(entry) => {
488 let entry_path = entry.path();
489 if entry_path.is_dir() {
490 files.extend(get_masm_files(entry_path)?);
491 } else if entry_path
492 .extension()
493 .map(|ext| ext == "masm")
494 .unwrap_or(false)
495 {
496 files.push(entry_path);
497 }
498 },
499 Err(e) => {
500 return Err(io::Error::other(format!(
501 "error reading directory entry: {e}",
502 )));
503 },
504 }
505 }
506 },
507 Err(e) => {
508 return Err(io::Error::other(format!("error reading directory: {e}")));
509 },
510 }
511
512 Ok(files)
513 }
514}