miden_lib/transaction/
mod.rs1use alloc::{string::ToString, sync::Arc, vec::Vec};
2
3use miden_objects::{
4 Digest, EMPTY_WORD, Felt, TransactionInputError, TransactionOutputError,
5 account::{AccountCode, AccountId},
6 assembly::{Assembler, DefaultSourceManager, KernelLibrary},
7 block::BlockNumber,
8 transaction::{
9 OutputNote, OutputNotes, TransactionArgs, TransactionInputs, TransactionOutputs,
10 },
11 utils::{serde::Deserializable, sync::LazyLock},
12 vm::{AdviceInputs, AdviceMap, Program, ProgramInfo, StackInputs, StackOutputs},
13};
14use miden_stdlib::StdLibrary;
15use outputs::EXPIRATION_BLOCK_ELEMENT_IDX;
16
17use super::MidenLib;
18
19pub mod memory;
20
21mod events;
22pub use events::TransactionEvent;
23
24mod inputs;
25
26mod outputs;
27pub use outputs::{
28 FINAL_ACCOUNT_COMMITMENT_WORD_IDX, OUTPUT_NOTES_COMMITMENT_WORD_IDX, parse_final_account_header,
29};
30
31mod errors;
32pub use errors::{TransactionEventError, TransactionKernelError, TransactionTraceParsingError};
33
34mod procedures;
35
36static KERNEL_LIB: LazyLock<KernelLibrary> = LazyLock::new(|| {
41 let kernel_lib_bytes =
42 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_kernel.masl"));
43 KernelLibrary::read_from_bytes(kernel_lib_bytes)
44 .expect("failed to deserialize transaction kernel library")
45});
46
47static KERNEL_MAIN: LazyLock<Program> = LazyLock::new(|| {
49 let kernel_main_bytes =
50 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_kernel.masb"));
51 Program::read_from_bytes(kernel_main_bytes)
52 .expect("failed to deserialize transaction kernel runtime")
53});
54
55static TX_SCRIPT_MAIN: LazyLock<Program> = LazyLock::new(|| {
57 let tx_script_main_bytes =
58 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/tx_script_main.masb"));
59 Program::read_from_bytes(tx_script_main_bytes)
60 .expect("failed to deserialize tx script executor runtime")
61});
62
63pub struct TransactionKernel;
67
68impl TransactionKernel {
69 pub fn kernel() -> KernelLibrary {
77 KERNEL_LIB.clone()
78 }
79
80 pub fn main() -> Program {
85 KERNEL_MAIN.clone()
86 }
87
88 pub fn tx_script_main() -> Program {
93 TX_SCRIPT_MAIN.clone()
94 }
95
96 pub fn program_info() -> ProgramInfo {
101 let program_hash = Self::main().hash();
103 let kernel = Self::kernel().kernel().clone();
104
105 ProgramInfo::new(program_hash, kernel)
106 }
107
108 pub fn prepare_inputs(
113 tx_inputs: &TransactionInputs,
114 tx_args: &TransactionArgs,
115 init_advice_inputs: Option<AdviceInputs>,
116 ) -> Result<(StackInputs, AdviceInputs), TransactionInputError> {
117 let account = tx_inputs.account();
118
119 let stack_inputs = TransactionKernel::build_input_stack(
120 account.id(),
121 account.init_commitment(),
122 tx_inputs.input_notes().commitment(),
123 tx_inputs.block_header().commitment(),
124 tx_inputs.block_header().block_num(),
125 );
126
127 let mut advice_inputs = init_advice_inputs.unwrap_or_default();
128 inputs::extend_advice_inputs(tx_inputs, tx_args, &mut advice_inputs)?;
129
130 Ok((stack_inputs, advice_inputs))
131 }
132
133 pub fn assembler() -> Assembler {
139 let source_manager = Arc::new(DefaultSourceManager::default());
140 Assembler::with_kernel(source_manager, Self::kernel())
141 .with_library(StdLibrary::default())
142 .expect("failed to load std-lib")
143 .with_library(MidenLib::default())
144 .expect("failed to load miden-lib")
145 }
146
147 pub fn build_input_stack(
172 account_id: AccountId,
173 init_account_commitment: Digest,
174 input_notes_commitment: Digest,
175 block_commitment: Digest,
176 block_num: BlockNumber,
177 ) -> StackInputs {
178 let mut inputs: Vec<Felt> = Vec::with_capacity(14);
180 inputs.push(Felt::from(block_num));
181 inputs.push(account_id.suffix());
182 inputs.push(account_id.prefix().as_felt());
183 inputs.extend(input_notes_commitment);
184 inputs.extend_from_slice(init_account_commitment.as_elements());
185 inputs.extend_from_slice(block_commitment.as_elements());
186 StackInputs::new(inputs)
187 .map_err(|e| e.to_string())
188 .expect("Invalid stack input")
189 }
190
191 pub fn build_output_stack(
207 final_account_commitment: Digest,
208 output_notes_commitment: Digest,
209 expiration_block_num: BlockNumber,
210 ) -> StackOutputs {
211 let mut outputs: Vec<Felt> = Vec::with_capacity(9);
212 outputs.push(Felt::from(expiration_block_num));
213 outputs.extend(final_account_commitment);
214 outputs.extend(output_notes_commitment);
215 outputs.reverse();
216 StackOutputs::new(outputs)
217 .map_err(|e| e.to_string())
218 .expect("Invalid stack output")
219 }
220
221 pub fn parse_output_stack(
240 stack: &StackOutputs,
241 ) -> Result<(Digest, Digest, BlockNumber), TransactionOutputError> {
242 let output_notes_commitment = stack
243 .get_stack_word(OUTPUT_NOTES_COMMITMENT_WORD_IDX * 4)
244 .expect("first word missing")
245 .into();
246
247 let final_account_commitment = stack
248 .get_stack_word(FINAL_ACCOUNT_COMMITMENT_WORD_IDX * 4)
249 .expect("second word missing")
250 .into();
251
252 let expiration_block_num = stack
253 .get_stack_item(EXPIRATION_BLOCK_ELEMENT_IDX)
254 .expect("element on index 8 missing");
255
256 let expiration_block_num = u32::try_from(expiration_block_num.as_int())
257 .map_err(|_| {
258 TransactionOutputError::OutputStackInvalid(
259 "Expiration block number should be smaller than u32::MAX".into(),
260 )
261 })?
262 .into();
263
264 if stack.get_stack_word(12).expect("fourth word missing") != EMPTY_WORD {
265 return Err(TransactionOutputError::OutputStackInvalid(
266 "Fourth word on output stack should consist only of ZEROs".into(),
267 ));
268 }
269
270 Ok((final_account_commitment, output_notes_commitment, expiration_block_num))
271 }
272
273 pub fn from_transaction_parts(
294 stack: &StackOutputs,
295 adv_map: &AdviceMap,
296 output_notes: Vec<OutputNote>,
297 ) -> Result<TransactionOutputs, TransactionOutputError> {
298 let (final_account_commitment, output_notes_commitment, expiration_block_num) =
299 Self::parse_output_stack(stack)?;
300
301 let final_account_data = adv_map
303 .get(&final_account_commitment)
304 .ok_or(TransactionOutputError::FinalAccountHashMissingInAdviceMap)?;
305 let account = parse_final_account_header(final_account_data)
306 .map_err(TransactionOutputError::FinalAccountHeaderParseFailure)?;
307
308 let output_notes = OutputNotes::new(output_notes)?;
310 if output_notes_commitment != output_notes.commitment() {
311 return Err(TransactionOutputError::OutputNotesCommitmentInconsistent {
312 actual: output_notes.commitment(),
313 expected: output_notes_commitment,
314 });
315 }
316
317 Ok(TransactionOutputs {
318 account,
319 output_notes,
320 expiration_block_num,
321 })
322 }
323}
324
325#[cfg(any(feature = "testing", test))]
326impl TransactionKernel {
327 const KERNEL_TESTING_LIB_BYTES: &'static [u8] =
328 include_bytes!(concat!(env!("OUT_DIR"), "/assets/kernels/kernel_library.masl"));
329
330 pub fn kernel_as_library() -> miden_objects::assembly::Library {
331 miden_objects::assembly::Library::read_from_bytes(Self::KERNEL_TESTING_LIB_BYTES)
332 .expect("failed to deserialize transaction kernel library")
333 }
334
335 pub fn testing_assembler() -> Assembler {
343 let source_manager = Arc::new(DefaultSourceManager::default());
344 let kernel_library = Self::kernel_as_library();
345
346 Assembler::with_kernel(source_manager, Self::kernel())
347 .with_library(StdLibrary::default())
348 .expect("failed to load std-lib")
349 .with_library(MidenLib::default())
350 .expect("failed to load miden-lib")
351 .with_library(kernel_library)
352 .expect("failed to load kernel library (/lib)")
353 }
354
355 pub fn testing_assembler_with_mock_account() -> Assembler {
358 let assembler = Self::testing_assembler();
359 let library = AccountCode::mock_library(assembler.clone());
360
361 assembler.with_library(library).expect("failed to add mock account code")
362 }
363}