Skip to main content

miden_processor/trace/
mod.rs

1use alloc::vec::Vec;
2#[cfg(any(test, feature = "testing"))]
3use core::ops::Range;
4
5use miden_air::{
6    AirWitness, AuxBuilder, ProcessorAir, PublicInputs, debug,
7    trace::{
8        DECODER_TRACE_OFFSET, MainTrace, PADDED_TRACE_WIDTH, TRACE_WIDTH,
9        decoder::{NUM_USER_OP_HELPERS, USER_OP_HELPERS_OFFSET},
10    },
11};
12use miden_core::{crypto::hash::Blake3_256, serde::Serializable};
13
14use crate::{
15    Felt, MIN_STACK_DEPTH, Program, ProgramInfo, StackInputs, StackOutputs, Word, ZERO,
16    fast::ExecutionOutput,
17    field::{ExtensionField, QuadFelt},
18    precompile::{PrecompileRequest, PrecompileTranscript, PrecompileTranscriptDigest},
19    utils::{ColMatrix, Matrix, RowMajorMatrix},
20};
21
22pub(crate) mod utils;
23use miden_air::trace::Challenges;
24use utils::{AuxColumnBuilder, TraceFragment};
25
26pub mod chiplets;
27pub(crate) mod execution_tracer;
28
29mod decoder;
30mod parallel;
31mod range;
32mod stack;
33mod trace_state;
34
35#[cfg(test)]
36mod tests;
37
38// RE-EXPORTS
39// ================================================================================================
40
41pub use execution_tracer::TraceGenerationContext;
42pub use miden_air::trace::RowIndex;
43pub use parallel::{CORE_TRACE_WIDTH, build_trace, build_trace_with_max_len};
44pub use utils::{ChipletsLengths, TraceLenSummary};
45
46/// Inputs required to build an execution trace from pre-executed data.
47#[derive(Debug)]
48pub struct TraceBuildInputs {
49    trace_output: TraceBuildOutput,
50    trace_generation_context: TraceGenerationContext,
51    program_info: ProgramInfo,
52}
53
54#[derive(Debug)]
55pub(crate) struct TraceBuildOutput {
56    stack_outputs: StackOutputs,
57    final_precompile_transcript: PrecompileTranscript,
58    precompile_requests: Vec<PrecompileRequest>,
59    precompile_requests_digest: [u8; 32],
60}
61
62impl TraceBuildOutput {
63    fn from_execution_output(execution_output: ExecutionOutput) -> Self {
64        let ExecutionOutput {
65            stack,
66            mut advice,
67            memory: _,
68            final_precompile_transcript,
69        } = execution_output;
70
71        Self {
72            stack_outputs: stack,
73            final_precompile_transcript,
74            precompile_requests: advice.take_precompile_requests(),
75            precompile_requests_digest: [0; 32],
76        }
77        .with_precompile_requests_digest()
78    }
79
80    fn with_precompile_requests_digest(mut self) -> Self {
81        self.precompile_requests_digest =
82            Blake3_256::hash(&self.precompile_requests.to_bytes()).into();
83        self
84    }
85
86    fn has_matching_precompile_requests_digest(&self) -> bool {
87        let expected_digest: [u8; 32] =
88            Blake3_256::hash(&self.precompile_requests.to_bytes()).into();
89        self.precompile_requests_digest == expected_digest
90    }
91}
92
93impl TraceBuildInputs {
94    pub(crate) fn from_execution(
95        program: &Program,
96        execution_output: ExecutionOutput,
97        trace_generation_context: TraceGenerationContext,
98    ) -> Self {
99        let trace_output = TraceBuildOutput::from_execution_output(execution_output);
100        let program_info = program.to_info();
101        Self {
102            trace_output,
103            trace_generation_context,
104            program_info,
105        }
106    }
107
108    /// Returns the stack outputs captured for the execution being replayed.
109    pub fn stack_outputs(&self) -> &StackOutputs {
110        &self.trace_output.stack_outputs
111    }
112
113    /// Returns deferred precompile requests generated during execution.
114    pub fn precompile_requests(&self) -> &[PrecompileRequest] {
115        &self.trace_output.precompile_requests
116    }
117
118    /// Returns the final precompile transcript observed during execution.
119    pub fn final_precompile_transcript(&self) -> &PrecompileTranscript {
120        &self.trace_output.final_precompile_transcript
121    }
122
123    /// Returns the digest of the final precompile transcript observed during execution.
124    pub fn precompile_transcript_digest(&self) -> PrecompileTranscriptDigest {
125        self.final_precompile_transcript().finalize()
126    }
127
128    /// Returns the program info captured for the execution being replayed.
129    pub fn program_info(&self) -> &ProgramInfo {
130        &self.program_info
131    }
132
133    // Kept for mismatch and edge-case tests that mutate replay inputs directly.
134    #[cfg(any(test, feature = "testing"))]
135    #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
136    pub(crate) fn into_parts(self) -> (TraceBuildOutput, TraceGenerationContext, ProgramInfo) {
137        (self.trace_output, self.trace_generation_context, self.program_info)
138    }
139
140    #[cfg(any(test, feature = "testing"))]
141    /// Returns the trace replay context captured during execution.
142    pub fn trace_generation_context(&self) -> &TraceGenerationContext {
143        &self.trace_generation_context
144    }
145
146    // Kept for tests that force invalid replay contexts without widening the public API.
147    #[cfg(any(test, feature = "testing"))]
148    #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
149    pub(crate) fn trace_generation_context_mut(&mut self) -> &mut TraceGenerationContext {
150        &mut self.trace_generation_context
151    }
152
153    #[cfg(test)]
154    pub(crate) fn from_parts(
155        trace_output: TraceBuildOutput,
156        trace_generation_context: TraceGenerationContext,
157        program_info: ProgramInfo,
158    ) -> Self {
159        Self {
160            trace_output,
161            trace_generation_context,
162            program_info,
163        }
164    }
165}
166
167// VM EXECUTION TRACE
168// ================================================================================================
169
170/// Execution trace which is generated when a program is executed on the VM.
171///
172/// The trace consists of the following components:
173/// - Main traces of System, Decoder, Operand Stack, Range Checker, and Chiplets.
174/// - Auxiliary trace builders.
175/// - Information about the program (program hash and the kernel).
176/// - Information about execution outputs (stack state, deferred precompile requests, and the final
177///   precompile transcript).
178/// - Summary of trace lengths of the main trace components.
179#[derive(Debug)]
180pub struct ExecutionTrace {
181    main_trace: MainTrace,
182    aux_trace_builders: AuxTraceBuilders,
183    program_info: ProgramInfo,
184    stack_outputs: StackOutputs,
185    precompile_requests: Vec<PrecompileRequest>,
186    final_precompile_transcript: PrecompileTranscript,
187    trace_len_summary: TraceLenSummary,
188}
189
190impl ExecutionTrace {
191    // CONSTRUCTOR
192    // --------------------------------------------------------------------------------------------
193
194    pub(crate) fn new_from_parts(
195        program_info: ProgramInfo,
196        trace_output: TraceBuildOutput,
197        main_trace: MainTrace,
198        aux_trace_builders: AuxTraceBuilders,
199        trace_len_summary: TraceLenSummary,
200    ) -> Self {
201        let TraceBuildOutput {
202            stack_outputs,
203            final_precompile_transcript,
204            precompile_requests,
205            ..
206        } = trace_output;
207
208        Self {
209            main_trace,
210            aux_trace_builders,
211            program_info,
212            stack_outputs,
213            precompile_requests,
214            final_precompile_transcript,
215            trace_len_summary,
216        }
217    }
218
219    // PUBLIC ACCESSORS
220    // --------------------------------------------------------------------------------------------
221
222    /// Returns the program info of this execution trace.
223    pub fn program_info(&self) -> &ProgramInfo {
224        &self.program_info
225    }
226
227    /// Returns hash of the program execution of which resulted in this execution trace.
228    pub fn program_hash(&self) -> &Word {
229        self.program_info.program_hash()
230    }
231
232    /// Returns outputs of the program execution which resulted in this execution trace.
233    pub fn stack_outputs(&self) -> &StackOutputs {
234        &self.stack_outputs
235    }
236
237    /// Returns the public inputs for this execution trace.
238    pub fn public_inputs(&self) -> PublicInputs {
239        PublicInputs::new(
240            self.program_info.clone(),
241            self.init_stack_state(),
242            self.stack_outputs,
243            self.final_precompile_transcript.state(),
244        )
245    }
246
247    /// Returns the public values for this execution trace.
248    pub fn to_public_values(&self) -> Vec<Felt> {
249        self.public_inputs().to_elements()
250    }
251
252    /// Returns a clone of the auxiliary trace builders.
253    pub fn aux_trace_builders(&self) -> AuxTraceBuilders {
254        self.aux_trace_builders.clone()
255    }
256
257    /// Returns a reference to the main trace.
258    pub fn main_trace(&self) -> &MainTrace {
259        &self.main_trace
260    }
261
262    /// Returns a mutable reference to the main trace.
263    pub fn main_trace_mut(&mut self) -> &mut MainTrace {
264        &mut self.main_trace
265    }
266
267    /// Returns the precompile requests generated during program execution.
268    pub fn precompile_requests(&self) -> &[PrecompileRequest] {
269        &self.precompile_requests
270    }
271
272    /// Returns the final precompile transcript observed during execution.
273    pub fn final_precompile_transcript(&self) -> PrecompileTranscript {
274        self.final_precompile_transcript
275    }
276
277    /// Returns the digest of the final precompile transcript observed during execution.
278    pub fn precompile_transcript_digest(&self) -> PrecompileTranscriptDigest {
279        self.final_precompile_transcript().finalize()
280    }
281
282    /// Returns the owned execution outputs required for proof packaging.
283    pub fn into_outputs(self) -> (StackOutputs, Vec<PrecompileRequest>, PrecompileTranscript) {
284        (self.stack_outputs, self.precompile_requests, self.final_precompile_transcript)
285    }
286
287    /// Returns the initial state of the top 16 stack registers.
288    pub fn init_stack_state(&self) -> StackInputs {
289        let mut result = [ZERO; MIN_STACK_DEPTH];
290        let row = RowIndex::from(0_u32);
291        for (i, result) in result.iter_mut().enumerate() {
292            *result = self.main_trace.stack_element(i, row);
293        }
294        result.into()
295    }
296
297    /// Returns the final state of the top 16 stack registers.
298    pub fn last_stack_state(&self) -> StackOutputs {
299        let last_step = RowIndex::from(self.last_step());
300        let mut result = [ZERO; MIN_STACK_DEPTH];
301        for (i, result) in result.iter_mut().enumerate() {
302            *result = self.main_trace.stack_element(i, last_step);
303        }
304        result.into()
305    }
306
307    /// Returns helper registers state at the specified `clk` of the VM
308    pub fn get_user_op_helpers_at(&self, clk: u32) -> [Felt; NUM_USER_OP_HELPERS] {
309        let mut result = [ZERO; NUM_USER_OP_HELPERS];
310        let row = RowIndex::from(clk);
311        for (i, result) in result.iter_mut().enumerate() {
312            *result = self.main_trace.get(row, DECODER_TRACE_OFFSET + USER_OP_HELPERS_OFFSET + i);
313        }
314        result
315    }
316
317    /// Returns the trace length.
318    pub fn get_trace_len(&self) -> usize {
319        self.main_trace.num_rows()
320    }
321
322    /// Returns the length of the trace (number of rows in the main trace).
323    pub fn length(&self) -> usize {
324        self.get_trace_len()
325    }
326
327    /// Returns a summary of the lengths of main, range and chiplet traces.
328    pub fn trace_len_summary(&self) -> &TraceLenSummary {
329        &self.trace_len_summary
330    }
331
332    // DEBUG CONSTRAINT CHECKING
333    // --------------------------------------------------------------------------------------------
334
335    /// Validates this execution trace against all AIR constraints without generating a STARK
336    /// proof.
337    ///
338    /// This is the recommended way to test trace correctness. It is much faster than full STARK
339    /// proving and provides better error diagnostics (panics on the first constraint violation
340    /// with the instance and row index).
341    ///
342    /// # Panics
343    ///
344    /// Panics if any AIR constraint evaluates to nonzero.
345    pub fn check_constraints(&self) {
346        let public_inputs = self.public_inputs();
347        let trace_matrix = self.to_row_major_matrix();
348
349        let (public_values, kernel_felts) = public_inputs.to_air_inputs();
350        let var_len_public_inputs: &[&[Felt]] = &[&kernel_felts];
351
352        let aux_builder = self.aux_trace_builders();
353
354        // Derive deterministic challenges by hashing public values with Poseidon2.
355        // The 4-element digest maps directly to 2 QuadFelt challenges.
356        let digest = crate::crypto::hash::Poseidon2::hash_elements(&public_values);
357        let challenges =
358            [QuadFelt::new([digest[0], digest[1]]), QuadFelt::new([digest[2], digest[3]])];
359
360        let witness = AirWitness::new(&trace_matrix, &public_values, var_len_public_inputs);
361        debug::check_constraints(&ProcessorAir, witness, &aux_builder, &challenges);
362    }
363
364    /// Returns the main trace as a row-major matrix for proving.
365    ///
366    /// Only includes the first [`TRACE_WIDTH`] columns (excluding padding columns added for
367    /// Poseidon2 rate alignment), which is the width expected by the AIR.
368    // TODO: the padding columns can be removed once we use the lifted-stark's virtual trace
369    // alignment, which pads to the required rate width without materializing extra columns.
370    pub fn to_row_major_matrix(&self) -> RowMajorMatrix<Felt> {
371        let stored_w = self.main_trace.width();
372        if stored_w == TRACE_WIDTH {
373            return self.main_trace.to_row_major();
374        }
375
376        assert_eq!(stored_w, PADDED_TRACE_WIDTH);
377        self.main_trace.to_row_major_stripped(TRACE_WIDTH)
378    }
379
380    // HELPER METHODS
381    // --------------------------------------------------------------------------------------------
382
383    /// Returns the index of the last row in the trace.
384    fn last_step(&self) -> usize {
385        self.length() - 1
386    }
387
388    // TEST HELPERS
389    // --------------------------------------------------------------------------------------------
390    #[cfg(feature = "std")]
391    pub fn print(&self) {
392        use miden_air::trace::TRACE_WIDTH;
393
394        let mut row = [ZERO; PADDED_TRACE_WIDTH];
395        for i in 0..self.length() {
396            self.main_trace.read_row_into(i, &mut row);
397            std::println!(
398                "{:?}",
399                row.iter().take(TRACE_WIDTH).map(|v| v.as_canonical_u64()).collect::<Vec<_>>()
400            );
401        }
402    }
403
404    #[cfg(any(test, feature = "testing"))]
405    pub fn get_column_range(&self, range: Range<usize>) -> Vec<Vec<Felt>> {
406        self.main_trace.get_column_range(range)
407    }
408
409    pub fn build_aux_trace<E>(&self, rand_elements: &[E]) -> Option<ColMatrix<E>>
410    where
411        E: ExtensionField<Felt>,
412    {
413        let aux_columns =
414            self.aux_trace_builders.build_aux_columns(&self.main_trace, rand_elements);
415
416        Some(ColMatrix::new(aux_columns))
417    }
418}
419
420// AUX TRACE BUILDERS
421// ================================================================================================
422
423#[derive(Debug, Clone)]
424pub struct AuxTraceBuilders {
425    pub(crate) decoder: decoder::AuxTraceBuilder,
426    pub(crate) stack: stack::AuxTraceBuilder,
427    pub(crate) range: range::AuxTraceBuilder,
428    pub(crate) chiplets: chiplets::AuxTraceBuilder,
429}
430
431impl AuxTraceBuilders {
432    /// Builds auxiliary columns for all trace segments given the main trace and challenges.
433    ///
434    /// This is the internal column-major version used by the processor.
435    pub fn build_aux_columns<E>(&self, main_trace: &MainTrace, challenges: &[E]) -> Vec<Vec<E>>
436    where
437        E: ExtensionField<Felt>,
438    {
439        // Expand raw challenges (alpha, beta) into coefficient array once, then pass
440        // the expanded challenges to all sub-builders.
441        let challenges = Challenges::<E>::new(challenges[0], challenges[1]);
442
443        let (decoder_cols, stack_cols, range_cols, chiplets_cols) = {
444            let ((decoder_cols, stack_cols), (range_cols, chiplets_cols)) = rayon::join(
445                || {
446                    rayon::join(
447                        || self.decoder.build_aux_columns(main_trace, &challenges),
448                        || self.stack.build_aux_columns(main_trace, &challenges),
449                    )
450                },
451                || {
452                    rayon::join(
453                        || self.range.build_aux_columns(main_trace, &challenges),
454                        || {
455                            let [a, b, c] =
456                                self.chiplets.build_aux_columns(main_trace, &challenges);
457                            vec![a, b, c]
458                        },
459                    )
460                },
461            );
462            (decoder_cols, stack_cols, range_cols, chiplets_cols)
463        };
464
465        decoder_cols
466            .into_iter()
467            .chain(stack_cols)
468            .chain(range_cols)
469            .chain(chiplets_cols)
470            .collect()
471    }
472}
473
474// PLONKY3 AUX TRACE BUILDER
475// ================================================================================================
476
477impl<EF: ExtensionField<Felt>> AuxBuilder<Felt, EF> for AuxTraceBuilders {
478    fn build_aux_trace(
479        &self,
480        main: &RowMajorMatrix<Felt>,
481        challenges: &[EF],
482    ) -> (RowMajorMatrix<EF>, Vec<EF>) {
483        let _span = tracing::info_span!("build_aux_trace").entered();
484
485        // Transpose the row-major main trace into column-major `MainTrace` needed by the
486        // auxiliary trace builders. The last program row is the point where the clock
487        // (column 0) stops incrementing.
488        let main_for_aux = {
489            let num_rows = main.height();
490            // Find the last program row by binary search on the clock column.
491            let clk0 = main.get(0, 0).expect("valid indices");
492            let last_program_row = if num_rows <= 1 {
493                0
494            } else if main.get(num_rows - 1, 0).expect("valid indices")
495                == clk0 + Felt::new((num_rows - 1) as u64)
496            {
497                num_rows - 1
498            } else {
499                let mut lo = 1usize;
500                let mut hi = num_rows - 1;
501                while lo < hi {
502                    let mid = lo + (hi - lo) / 2;
503                    let expected = clk0 + Felt::new(mid as u64);
504                    if main.get(mid, 0).expect("valid indices") == expected {
505                        lo = mid + 1;
506                    } else {
507                        hi = mid;
508                    }
509                }
510                lo - 1
511            };
512            let transposed = main.transpose();
513            MainTrace::from_transposed(transposed, RowIndex::from(last_program_row))
514        };
515
516        let aux_columns = self.build_aux_columns(&main_for_aux, challenges);
517        assert!(!aux_columns.is_empty(), "aux columns should not be empty");
518
519        let trace_len = main.height();
520        let num_ef_cols = aux_columns.len();
521        for col in &aux_columns {
522            debug_assert_eq!(col.len(), trace_len, "aux column length must match main height");
523        }
524
525        let mut flat = Vec::with_capacity(trace_len * num_ef_cols);
526        for col in &aux_columns {
527            flat.extend_from_slice(col);
528        }
529        let aux_trace = RowMajorMatrix::new(flat, trace_len).transpose();
530
531        // Extract the last row from the row-major aux trace for Fiat-Shamir.
532        let last_row = aux_trace
533            .row_slice(trace_len - 1)
534            .expect("aux trace has at least one row")
535            .to_vec();
536
537        (aux_trace, last_row)
538    }
539}