miden_air/
lib.rs

1#![no_std]
2#![expect(dead_code)]
3
4#[macro_use]
5extern crate alloc;
6
7#[cfg(feature = "std")]
8extern crate std;
9
10use alloc::{borrow::ToOwned, vec::Vec};
11
12use miden_core::{
13    ExtensionOf, ONE, ProgramInfo, StackInputs, StackOutputs, Word, ZERO,
14    precompile::PrecompileTranscriptState,
15    utils::{ByteReader, ByteWriter, Deserializable, Serializable},
16};
17use winter_air::{
18    Air, AirContext, Assertion, EvaluationFrame, ProofOptions as WinterProofOptions, TraceInfo,
19    TransitionConstraintDegree,
20};
21use winter_prover::{
22    crypto::{RandomCoin, RandomCoinError},
23    math::get_power_series,
24    matrix::ColMatrix,
25};
26
27mod constraints;
28pub use constraints::stack;
29use constraints::{chiplets, range};
30
31pub mod trace;
32pub use trace::rows::RowIndex;
33use trace::*;
34
35mod errors;
36mod options;
37mod proof;
38
39mod utils;
40
41// RE-EXPORTS
42// ================================================================================================
43
44pub use errors::ExecutionOptionsError;
45pub use miden_core::{
46    Felt, FieldElement, StarkField,
47    utils::{DeserializationError, ToElements},
48};
49pub use options::{ExecutionOptions, ProvingOptions};
50pub use proof::{ExecutionProof, HashFunction};
51use utils::TransitionConstraintRange;
52pub use winter_air::{AuxRandElements, FieldExtension, PartitionOptions};
53
54/// Selects whether to include all existing constraints or only the ones currently encoded in
55/// the ACE circuit in the recursive verifier.
56const IS_FULL_CONSTRAINT_SET: bool = false;
57
58// PROCESSOR AIR
59// ================================================================================================
60
61/// Algebraic Intermediate Representation (AIR) for Miden VM.
62///
63/// Defines execution constraints for Miden programs. Contains the public inputs (program hash,
64/// stack state, kernel procedures) and enforces transition and boundary constraints across all VM
65/// components: system, stack, range checker, and chiplets (hasher, bitwise, memory, kernel ROM).
66///
67/// `IS_FULL_CONSTRAINT_SET` controls which constraints are active - when true, all constraints are
68/// enforced; when false, only those needed for recursive verification are included.
69pub struct ProcessorAir {
70    context: AirContext<Felt>,
71    stack_inputs: StackInputs,
72    stack_outputs: StackOutputs,
73    program_digest: Word,
74    kernel_digests: Vec<Word>,
75    pc_transcript_state: PrecompileTranscriptState,
76    constraint_ranges: TransitionConstraintRange,
77}
78
79impl ProcessorAir {
80    /// Returns last step of the execution trace.
81    pub fn last_step(&self) -> usize {
82        self.trace_length() - self.context().num_transition_exemptions()
83    }
84}
85
86impl Air for ProcessorAir {
87    type BaseField = Felt;
88    type PublicInputs = PublicInputs;
89
90    fn new(trace_info: TraceInfo, pub_inputs: PublicInputs, options: WinterProofOptions) -> Self {
91        // --- system -----------------------------------------------------------------------------
92        let mut main_degrees = vec![
93            TransitionConstraintDegree::new(1), // clk' = clk + 1
94        ];
95
96        if IS_FULL_CONSTRAINT_SET {
97            // --- stack constraints
98            // ---------------------------------------------------------------------
99            let mut stack_degrees = stack::get_transition_constraint_degrees();
100            main_degrees.append(&mut stack_degrees);
101
102            // --- range checker
103            // ----------------------------------------------------------------------
104            let mut range_checker_degrees = range::get_transition_constraint_degrees();
105            main_degrees.append(&mut range_checker_degrees);
106
107            // --- chiplets (hasher, bitwise, memory) -------------------------
108            let mut chiplets_degrees = chiplets::get_transition_constraint_degrees();
109            main_degrees.append(&mut chiplets_degrees);
110        }
111
112        let aux_degrees = range::get_aux_transition_constraint_degrees();
113
114        // Define the transition constraint ranges.
115        let constraint_ranges = TransitionConstraintRange::new(
116            1,
117            stack::get_transition_constraint_count(),
118            range::get_transition_constraint_count(),
119            chiplets::get_transition_constraint_count(),
120        );
121
122        // Define the number of boundary constraints for the main execution trace segment.
123        // TODO: determine dynamically
124        let num_main_assertions = if IS_FULL_CONSTRAINT_SET {
125            2 + stack::NUM_ASSERTIONS + range::NUM_ASSERTIONS
126        } else {
127            1
128        };
129
130        // Define the number of boundary constraints for the auxiliary execution trace segment.
131        // Includes vtable boundary constraint for precompile transcript validation.
132        let num_aux_assertions = if IS_FULL_CONSTRAINT_SET {
133            stack::NUM_AUX_ASSERTIONS + range::NUM_AUX_ASSERTIONS + 1
134        } else {
135            3 + 1 // kernel_rom + range + vtable
136        };
137
138        // Create the context and set the number of transition constraint exemptions to two; this
139        // allows us to inject random values into the last row of the execution trace.
140        let context = AirContext::new_multi_segment(
141            trace_info,
142            main_degrees,
143            aux_degrees,
144            num_main_assertions,
145            num_aux_assertions,
146            options,
147        )
148        .set_num_transition_exemptions(2);
149
150        Self {
151            context,
152            stack_inputs: pub_inputs.stack_inputs,
153            stack_outputs: pub_inputs.stack_outputs,
154            constraint_ranges,
155            program_digest: pub_inputs.program_info.program_hash().to_owned(),
156            kernel_digests: pub_inputs.program_info.kernel_procedures().to_owned(),
157            pc_transcript_state: pub_inputs.pc_transcript_state,
158        }
159    }
160
161    // PERIODIC COLUMNS
162    // --------------------------------------------------------------------------------------------
163
164    /// Returns a set of periodic columns for the ProcessorAir.
165    fn get_periodic_column_values(&self) -> Vec<Vec<Felt>> {
166        chiplets::get_periodic_column_values()
167    }
168
169    // ASSERTIONS
170    // --------------------------------------------------------------------------------------------
171
172    fn get_assertions(&self) -> Vec<Assertion<Felt>> {
173        // --- set assertions for the first step --------------------------------------------------
174        // first value of clk is 0
175        let mut result = vec![Assertion::single(CLK_COL_IDX, 0, ZERO)];
176
177        if IS_FULL_CONSTRAINT_SET {
178            // add initial assertions for the stack.
179            stack::get_assertions_first_step(&mut result, &*self.stack_inputs);
180
181            // Add initial assertions for the range checker.
182            range::get_assertions_first_step(&mut result);
183
184            // --- set assertions for the last step
185            // ---------------------------------------------------
186            let last_step = self.last_step();
187
188            // add the stack's assertions for the last step.
189            stack::get_assertions_last_step(&mut result, last_step, &self.stack_outputs);
190
191            // Add the range checker's assertions for the last step.
192            range::get_assertions_last_step(&mut result, last_step);
193        }
194
195        result
196    }
197
198    fn get_aux_assertions<E: FieldElement<BaseField = Self::BaseField>>(
199        &self,
200        aux_rand_elements: &AuxRandElements<E>,
201    ) -> Vec<Assertion<E>> {
202        let mut result: Vec<Assertion<E>> = Vec::new();
203
204        // Add initial assertions for the range checker's auxiliary columns.
205        range::get_aux_assertions_first_step::<E>(&mut result);
206
207        // Add initial assertion for the chiplets' bus auxiliary column.
208        chiplets::get_aux_assertions_first_step::<E>(
209            &mut result,
210            &self.kernel_digests,
211            aux_rand_elements,
212            self.pc_transcript_state,
213        );
214
215        // --- set assertions for the first step --------------------------------------------------
216        if IS_FULL_CONSTRAINT_SET {
217            // add initial assertions for the stack's auxiliary columns.
218            stack::get_aux_assertions_first_step(&mut result);
219
220            // --- set assertions for the last step
221            // ---------------------------------------------------
222            let last_step = self.last_step();
223
224            // add the stack's auxiliary column assertions for the last step.
225            stack::get_aux_assertions_last_step(&mut result, last_step);
226        }
227        // Add the range checker's auxiliary column assertions for the last step.
228        let last_step = self.last_step();
229        range::get_aux_assertions_last_step::<E>(&mut result, last_step);
230
231        result
232    }
233
234    // TRANSITION CONSTRAINTS
235    // --------------------------------------------------------------------------------------------
236
237    fn evaluate_transition<E: FieldElement<BaseField = Felt>>(
238        &self,
239        frame: &EvaluationFrame<E>,
240        periodic_values: &[E],
241        result: &mut [E],
242    ) {
243        let current = frame.current();
244        let next = frame.next();
245
246        // --- system -----------------------------------------------------------------------------
247        // clk' = clk + 1
248        result[0] = next[CLK_COL_IDX] - (current[CLK_COL_IDX] + E::ONE);
249
250        if IS_FULL_CONSTRAINT_SET {
251            // --- stack operations
252            // -------------------------------------------------------------------
253            stack::enforce_constraints::<E>(
254                frame,
255                select_result_range!(result, self.constraint_ranges.stack),
256            );
257
258            // --- range checker
259            // ----------------------------------------------------------------------
260            range::enforce_constraints::<E>(
261                frame,
262                select_result_range!(result, self.constraint_ranges.range_checker),
263            );
264
265            // --- chiplets (hasher, bitwise, memory) -------------------------
266            chiplets::enforce_constraints::<E>(
267                frame,
268                periodic_values,
269                select_result_range!(result, self.constraint_ranges.chiplets),
270            );
271        }
272    }
273
274    fn evaluate_aux_transition<F, E>(
275        &self,
276        main_frame: &EvaluationFrame<F>,
277        aux_frame: &EvaluationFrame<E>,
278        _periodic_values: &[F],
279        aux_rand_elements: &AuxRandElements<E>,
280        result: &mut [E],
281    ) where
282        F: FieldElement<BaseField = Felt>,
283        E: FieldElement<BaseField = Felt> + ExtensionOf<F>,
284    {
285        // --- range checker ----------------------------------------------------------------------
286        range::enforce_aux_constraints::<F, E>(
287            main_frame,
288            aux_frame,
289            aux_rand_elements.rand_elements(),
290            result,
291        );
292    }
293
294    fn context(&self) -> &AirContext<Felt> {
295        &self.context
296    }
297
298    fn get_aux_rand_elements<E, R>(
299        &self,
300        public_coin: &mut R,
301    ) -> Result<AuxRandElements<E>, RandomCoinError>
302    where
303        E: FieldElement<BaseField = Self::BaseField>,
304        R: RandomCoin<BaseField = Self::BaseField>,
305    {
306        let num_elements = self.trace_info().get_num_aux_segment_rand_elements();
307        let mut rand_elements = Vec::with_capacity(num_elements);
308        let max_message_length = num_elements - 1;
309
310        let alpha = public_coin.draw()?;
311        let beta = public_coin.draw()?;
312
313        let betas = get_power_series(beta, max_message_length);
314
315        rand_elements.push(alpha);
316        rand_elements.extend_from_slice(&betas);
317
318        Ok(AuxRandElements::new(rand_elements))
319    }
320}
321
322// PUBLIC INPUTS
323// ================================================================================================
324
325#[derive(Debug)]
326pub struct PublicInputs {
327    program_info: ProgramInfo,
328    stack_inputs: StackInputs,
329    stack_outputs: StackOutputs,
330    pc_transcript_state: PrecompileTranscriptState,
331}
332
333impl PublicInputs {
334    /// Creates a new instance of `PublicInputs` from program information, stack inputs and outputs,
335    /// and the precompile transcript state (capacity of an internal sponge).
336    pub fn new(
337        program_info: ProgramInfo,
338        stack_inputs: StackInputs,
339        stack_outputs: StackOutputs,
340        pc_transcript_state: PrecompileTranscriptState,
341    ) -> Self {
342        Self {
343            program_info,
344            stack_inputs,
345            stack_outputs,
346            pc_transcript_state,
347        }
348    }
349}
350
351impl miden_core::ToElements<Felt> for PublicInputs {
352    fn to_elements(&self) -> Vec<Felt> {
353        let mut result = self.stack_inputs.to_vec();
354        result.append(&mut self.stack_outputs.to_vec());
355        result.append(&mut self.program_info.to_elements());
356        let pc_state: [Felt; 4] = self.pc_transcript_state.into();
357        result.extend_from_slice(&pc_state);
358        result
359    }
360}
361
362// SERIALIZATION
363// ================================================================================================
364
365impl Serializable for PublicInputs {
366    fn write_into<W: ByteWriter>(&self, target: &mut W) {
367        self.program_info.write_into(target);
368        self.stack_inputs.write_into(target);
369        self.stack_outputs.write_into(target);
370        self.pc_transcript_state.write_into(target);
371    }
372}
373
374impl Deserializable for PublicInputs {
375    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
376        let program_info = ProgramInfo::read_from(source)?;
377        let stack_inputs = StackInputs::read_from(source)?;
378        let stack_outputs = StackOutputs::read_from(source)?;
379        let pc_transcript_state = Word::read_from(source)?;
380
381        Ok(PublicInputs {
382            program_info,
383            stack_inputs,
384            stack_outputs,
385            pc_transcript_state,
386        })
387    }
388}