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