1#![no_std]
2
3#[macro_use]
4extern crate alloc;
5
6#[cfg(feature = "std")]
7extern crate std;
8
9use alloc::vec::Vec;
10use core::borrow::Borrow;
11
12use miden_core::{
13 WORD_SIZE, Word,
14 field::ExtensionField,
15 precompile::PrecompileTranscriptState,
16 program::{Kernel, MIN_STACK_DEPTH, ProgramInfo, StackInputs, StackOutputs},
17};
18use miden_crypto::stark::air::{ReductionError, WindowAccess};
19#[cfg(feature = "arbitrary")]
20use proptest::prelude::*;
21
22pub mod ace;
23pub mod config;
24mod constraints;
25pub mod lookup;
26pub mod trace;
27
28pub mod logup {
36 pub use crate::constraints::lookup::{
37 BusId, MIDEN_MAX_MESSAGE_WIDTH, messages::*, miden_air::NUM_LOGUP_COMMITTED_FINALS,
38 };
39}
40
41use constraints::lookup::{
42 chiplet_air::ChipletLookupBuilder,
43 main_air::{MainLookupAir, MainLookupBuilder},
44};
45pub use constraints::{
46 chiplets::columns::{
47 AceCols, AceEvalCols, AceReadCols, BitwiseCols, ControllerCols, KernelRomCols, MemoryCols,
48 PermutationCols,
49 },
50 columns::{ChipletCols, CoreCols},
51 decoder::columns::DecoderCols,
52 ext_field::QuadFeltExpr,
53 range::columns::RangeCols,
54 stack::columns::StackCols,
55 system::columns::SystemCols,
56};
57use logup::{BusId, MIDEN_MAX_MESSAGE_WIDTH};
58use lookup::{
59 BoundaryBuilder, Challenges, ConstraintLookupBuilder, LookupAir, LookupMessage,
60 build_logup_aux_trace,
61};
62use miden_core::utils::RowMajorMatrix;
63
64mod export {
67 pub use miden_core::{
68 Felt,
69 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
70 utils::ToElements,
71 };
72 pub use miden_crypto::stark::{
73 StarkConfig,
74 air::{
75 AirBuilder, BaseAir, ConstraintDegrees, ExtensionBuilder, LiftedAir, LiftedAirBuilder,
76 MultiAir, PermutationAirBuilder, ProverStatement, Statement,
77 },
78 debug,
79 };
80}
81
82pub use export::*;
83
84pub trait MidenAirBuilder: LiftedAirBuilder<F = Felt> {}
92impl<T: LiftedAirBuilder<F = Felt>> MidenAirBuilder for T {}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
98#[cfg_attr(
99 all(feature = "arbitrary", test),
100 miden_test_serde_macros::serde_test(binary_serde(true), serde_test(false))
101)]
102pub struct PublicInputs {
103 program_info: ProgramInfo,
104 stack_inputs: StackInputs,
105 stack_outputs: StackOutputs,
106 pc_transcript_state: PrecompileTranscriptState,
107}
108
109impl PublicInputs {
110 pub fn new(
113 program_info: ProgramInfo,
114 stack_inputs: StackInputs,
115 stack_outputs: StackOutputs,
116 pc_transcript_state: PrecompileTranscriptState,
117 ) -> Self {
118 Self {
119 program_info,
120 stack_inputs,
121 stack_outputs,
122 pc_transcript_state,
123 }
124 }
125
126 pub fn stack_inputs(&self) -> StackInputs {
127 self.stack_inputs
128 }
129
130 pub fn stack_outputs(&self) -> StackOutputs {
131 self.stack_outputs
132 }
133
134 pub fn program_info(&self) -> ProgramInfo {
135 self.program_info.clone()
136 }
137
138 pub fn pc_transcript_state(&self) -> PrecompileTranscriptState {
140 self.pc_transcript_state
141 }
142
143 pub fn to_air_inputs(&self) -> (Vec<Felt>, Vec<Felt>) {
155 let mut public_values = Vec::with_capacity(NUM_PUBLIC_VALUES);
156 public_values.extend_from_slice(self.program_info.program_hash().as_elements());
157 public_values.extend_from_slice(self.stack_inputs.as_ref());
158 public_values.extend_from_slice(self.stack_outputs.as_ref());
159 public_values.extend_from_slice(self.pc_transcript_state.as_ref());
160
161 let kernel_felts: Vec<Felt> =
162 Word::words_as_elements(self.program_info.kernel_procedures()).to_vec();
163
164 (public_values, kernel_felts)
165 }
166
167 pub fn to_elements(&self) -> Vec<Felt> {
173 let mut result = self.program_info.to_elements();
174 result.extend_from_slice(self.stack_inputs.as_ref());
175 result.extend_from_slice(self.stack_outputs.as_ref());
176 result.extend_from_slice(self.pc_transcript_state.as_ref());
177 result
178 }
179}
180
181#[cfg(feature = "arbitrary")]
182impl Arbitrary for PublicInputs {
183 type Parameters = ();
184 type Strategy = BoxedStrategy<Self>;
185
186 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
187 fn felt_strategy() -> impl Strategy<Value = Felt> {
188 any::<u32>().prop_map(Felt::from)
189 }
190
191 fn word_strategy() -> impl Strategy<Value = Word> {
192 any::<[u32; WORD_SIZE]>().prop_map(|values| Word::new(values.map(Felt::from)))
193 }
194
195 let program_info = word_strategy()
196 .prop_map(|program_hash| ProgramInfo::new(program_hash, Kernel::default()));
197 let stack_inputs = proptest::collection::vec(felt_strategy(), 0..=MIN_STACK_DEPTH)
198 .prop_map(|values| StackInputs::new(&values).expect("generated stack inputs fit"));
199 let stack_outputs = proptest::collection::vec(felt_strategy(), 0..=MIN_STACK_DEPTH)
200 .prop_map(|values| StackOutputs::new(&values).expect("generated stack outputs fit"));
201
202 (program_info, stack_inputs, stack_outputs, word_strategy())
203 .prop_map(|(program_info, stack_inputs, stack_outputs, pc_transcript_state)| {
204 Self::new(program_info, stack_inputs, stack_outputs, pc_transcript_state)
205 })
206 .boxed()
207 }
208}
209
210impl Serializable for PublicInputs {
214 fn write_into<W: ByteWriter>(&self, target: &mut W) {
215 self.program_info.write_into(target);
216 self.stack_inputs.write_into(target);
217 self.stack_outputs.write_into(target);
218 self.pc_transcript_state.write_into(target);
219 }
220}
221
222impl Deserializable for PublicInputs {
223 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
224 let program_info = ProgramInfo::read_from(source)?;
225 let stack_inputs = StackInputs::read_from(source)?;
226 let stack_outputs = StackOutputs::read_from(source)?;
227 let pc_transcript_state = PrecompileTranscriptState::read_from(source)?;
228
229 Ok(PublicInputs {
230 program_info,
231 stack_inputs,
232 stack_outputs,
233 pc_transcript_state,
234 })
235 }
236}
237
238pub const NUM_PUBLIC_VALUES: usize = WORD_SIZE + MIN_STACK_DEPTH + MIN_STACK_DEPTH + WORD_SIZE;
249
250pub const LOGUP_AUX_TRACE_WIDTH: usize = 7;
252
253const PV_PROGRAM_HASH: usize = 0;
255const PV_TRANSCRIPT_STATE: usize = NUM_PUBLIC_VALUES - WORD_SIZE;
256
257#[derive(Copy, Clone, Debug, Default)]
265pub struct CoreAir;
266
267impl CoreAir {
268 fn width(self) -> usize {
269 constraints::columns::NUM_CORE_COLS
270 }
271
272 fn periodic_columns(self) -> Vec<Vec<Felt>> {
273 Vec::new()
275 }
276
277 fn aux_width(self) -> usize {
278 constraints::lookup::main_air::MAIN_COLUMN_SHAPE.len()
279 }
280
281 fn boundary_correction<EF: ExtensionField<Felt>>(
284 self,
285 challenges: &Challenges<EF>,
286 public_values: &[Felt],
287 var_len_public_inputs: &[&[Felt]],
288 ) -> Result<EF, ReductionError> {
289 if !var_len_public_inputs.is_empty() {
290 return Err(format!(
291 "CoreAir expects 0 var-len public input slices, got {}",
292 var_len_public_inputs.len()
293 )
294 .into());
295 }
296
297 let mut reducer = ReduceBoundaryBuilder {
298 challenges,
299 public_values,
300 var_len_public_inputs,
301 sum: EF::ZERO,
302 error: None,
303 };
304 constraints::lookup::miden_air::emit_core_boundary(&mut reducer);
305 reducer.finalize()
306 }
307
308 fn eval<AB: MidenAirBuilder>(self, builder: &mut AB) {
309 let main = builder.main();
310 let local: &CoreCols<AB::Var> = (*main.current_slice()).borrow();
311 let next: &CoreCols<AB::Var> = (*main.next_slice()).borrow();
312
313 let op_flags =
314 constraints::op_flags::OpFlags::new(&local.decoder, &local.stack, &next.decoder);
315
316 constraints::enforce_core(builder, local, next, &op_flags);
317 constraints::public_inputs::enforce_main(builder, local);
318
319 let mut lb = ConstraintLookupBuilder::new(builder, &MidenAir::CORE);
320 self.lookup_eval(&mut lb);
321 }
322
323 fn lookup_num_columns(self) -> usize {
324 constraints::lookup::main_air::MAIN_COLUMN_SHAPE.len()
325 }
326
327 fn lookup_column_shape(self) -> &'static [usize] {
328 &constraints::lookup::main_air::MAIN_COLUMN_SHAPE
329 }
330
331 fn lookup_max_message_width(self) -> usize {
332 MIDEN_MAX_MESSAGE_WIDTH
333 }
334
335 fn lookup_num_bus_ids(self) -> usize {
336 BusId::COUNT
337 }
338
339 fn lookup_eval<LB: MainLookupBuilder>(self, builder: &mut LB) {
340 MainLookupAir.eval(builder);
341 }
342
343 fn lookup_eval_boundary<B: BoundaryBuilder>(self, boundary: &mut B) {
344 constraints::lookup::miden_air::emit_core_boundary(boundary);
345 }
346}
347
348#[derive(Copy, Clone, Debug, Default)]
355pub struct ChipletsAir;
356
357impl ChipletsAir {
360 fn width(self) -> usize {
361 constraints::columns::NUM_CHIPLETS_COLS
362 }
363
364 fn periodic_columns(self) -> Vec<Vec<Felt>> {
365 constraints::chiplets::columns::PeriodicCols::periodic_columns()
368 }
369
370 fn aux_width(self) -> usize {
371 constraints::lookup::chiplet_air::CHIPLET_COLUMN_SHAPE.len()
372 }
373
374 fn boundary_correction<EF: ExtensionField<Felt>>(
379 self,
380 challenges: &Challenges<EF>,
381 public_values: &[Felt],
382 var_len_public_inputs: &[&[Felt]],
383 ) -> Result<EF, ReductionError> {
384 if var_len_public_inputs.len() != 1 {
385 return Err(format!(
386 "ChipletsAir expects 1 var-len public input slice, got {}",
387 var_len_public_inputs.len()
388 )
389 .into());
390 }
391 if !var_len_public_inputs[0].len().is_multiple_of(WORD_SIZE) {
392 return Err(format!(
393 "kernel digest felts length {} is not a multiple of {}",
394 var_len_public_inputs[0].len(),
395 WORD_SIZE
396 )
397 .into());
398 }
399
400 let mut reducer = ReduceBoundaryBuilder {
401 challenges,
402 public_values,
403 var_len_public_inputs,
404 sum: EF::ZERO,
405 error: None,
406 };
407 constraints::lookup::miden_air::emit_chiplets_boundary(&mut reducer);
408 reducer.finalize()
409 }
410
411 fn eval<AB: MidenAirBuilder>(self, builder: &mut AB) {
412 let main = builder.main();
413 let local: &ChipletCols<AB::Var> = (*main.current_slice()).borrow();
414 let next: &ChipletCols<AB::Var> = (*main.next_slice()).borrow();
415
416 let selectors =
417 constraints::chiplets::selectors::build_chiplet_selectors(builder, local, next);
418
419 constraints::enforce_chiplets(builder, local, next, &selectors);
420
421 let mut lb = ConstraintLookupBuilder::new(builder, &MidenAir::CHIPLETS);
422 self.lookup_eval(&mut lb);
423 }
424
425 fn lookup_num_columns(self) -> usize {
426 constraints::lookup::chiplet_air::CHIPLET_COLUMN_SHAPE.len()
427 }
428
429 fn lookup_column_shape(self) -> &'static [usize] {
430 &constraints::lookup::chiplet_air::CHIPLET_COLUMN_SHAPE
431 }
432
433 fn lookup_max_message_width(self) -> usize {
434 MIDEN_MAX_MESSAGE_WIDTH
435 }
436
437 fn lookup_num_bus_ids(self) -> usize {
438 BusId::COUNT
439 }
440
441 fn lookup_eval<LB: ChipletLookupBuilder>(self, builder: &mut LB) {
442 let main = builder.main();
443 let local: &ChipletCols<_> = main.current_slice().borrow();
444 let next: &ChipletCols<_> = main.next_slice().borrow();
445
446 constraints::lookup::chiplet_air::emit_chiplet_lookup_columns(builder, local, next);
447 }
448
449 fn lookup_eval_boundary<B: BoundaryBuilder>(self, boundary: &mut B) {
450 constraints::lookup::miden_air::emit_chiplets_boundary(boundary);
451 }
452}
453
454#[derive(Copy, Clone, Debug)]
462pub enum MidenAir {
463 Core(CoreAir),
464 Chiplets(ChipletsAir),
465}
466
467impl MidenAir {
468 pub const CORE: Self = Self::Core(CoreAir);
469 pub const CHIPLETS: Self = Self::Chiplets(ChipletsAir);
470}
471
472impl BaseAir<Felt> for MidenAir {
473 fn width(&self) -> usize {
474 match self {
475 Self::Core(a) => a.width(),
476 Self::Chiplets(a) => a.width(),
477 }
478 }
479
480 fn num_public_values(&self) -> usize {
481 NUM_PUBLIC_VALUES
482 }
483
484 fn periodic_columns(&self) -> Vec<Vec<Felt>> {
485 match self {
486 Self::Core(a) => a.periodic_columns(),
487 Self::Chiplets(a) => a.periodic_columns(),
488 }
489 }
490}
491
492impl<EF: ExtensionField<Felt>> LiftedAir<Felt, EF> for MidenAir {
493 fn num_randomness(&self) -> usize {
494 trace::AUX_TRACE_RAND_CHALLENGES
496 }
497
498 fn aux_width(&self) -> usize {
499 match self {
500 Self::Core(a) => a.aux_width(),
501 Self::Chiplets(a) => a.aux_width(),
502 }
503 }
504
505 fn num_aux_values(&self) -> usize {
506 1
508 }
509
510 fn build_aux_trace(
511 &self,
512 main: &RowMajorMatrix<Felt>,
513 _air_inputs: &[Felt],
514 _aux_inputs: &[Felt],
515 challenges: &[EF],
516 ) -> (RowMajorMatrix<EF>, Vec<EF>) {
517 let (aux_trace, committed) = build_logup_aux_trace(self, main, challenges);
518 debug_assert_eq!(
519 committed.len(),
520 1,
521 "build_logup_aux_trace returns one committed final per AIR (col 0's terminal sum)"
522 );
523 (aux_trace, committed)
524 }
525
526 fn constraint_degree(&self) -> ConstraintDegrees {
527 ConstraintDegrees { base: 9, ext: 9 }
529 }
530
531 fn eval<AB: LiftedAirBuilder<F = Felt>>(&self, builder: &mut AB) {
532 match self {
533 Self::Core(a) => a.eval(builder),
534 Self::Chiplets(a) => a.eval(builder),
535 }
536 }
537}
538
539impl<LB> LookupAir<LB> for MidenAir
540where
541 LB: MainLookupBuilder + ChipletLookupBuilder,
542{
543 fn num_columns(&self) -> usize {
544 match self {
545 Self::Core(a) => a.lookup_num_columns(),
546 Self::Chiplets(a) => a.lookup_num_columns(),
547 }
548 }
549
550 fn column_shape(&self) -> &[usize] {
551 match self {
552 Self::Core(a) => a.lookup_column_shape(),
553 Self::Chiplets(a) => a.lookup_column_shape(),
554 }
555 }
556
557 fn max_message_width(&self) -> usize {
558 match self {
559 Self::Core(a) => a.lookup_max_message_width(),
560 Self::Chiplets(a) => a.lookup_max_message_width(),
561 }
562 }
563
564 fn num_bus_ids(&self) -> usize {
565 match self {
566 Self::Core(a) => a.lookup_num_bus_ids(),
567 Self::Chiplets(a) => a.lookup_num_bus_ids(),
568 }
569 }
570
571 fn eval(&self, builder: &mut LB) {
572 match self {
573 Self::Core(a) => a.lookup_eval(builder),
574 Self::Chiplets(a) => a.lookup_eval(builder),
575 }
576 }
577
578 fn eval_boundary<B>(&self, boundary: &mut B)
579 where
580 B: BoundaryBuilder<F = LB::F, EF = LB::EF>,
581 {
582 match self {
583 Self::Core(a) => a.lookup_eval_boundary(boundary),
584 Self::Chiplets(a) => a.lookup_eval_boundary(boundary),
585 }
586 }
587}
588
589#[derive(Copy, Clone, Debug)]
599pub struct MidenMultiAir {
600 airs: [MidenAir; 2],
601}
602
603impl MidenMultiAir {
604 pub const fn new() -> Self {
606 Self {
607 airs: [MidenAir::CORE, MidenAir::CHIPLETS],
608 }
609 }
610}
611
612impl Default for MidenMultiAir {
613 fn default() -> Self {
614 Self::new()
615 }
616}
617
618impl<EF: ExtensionField<Felt>> MultiAir<Felt, EF> for MidenMultiAir {
619 type Air = MidenAir;
620
621 fn airs(&self) -> &[MidenAir] {
622 &self.airs
623 }
624
625 fn num_air_inputs(&self) -> usize {
626 NUM_PUBLIC_VALUES
627 }
628
629 fn max_aux_inputs(&self) -> usize {
630 Kernel::MAX_NUM_PROCEDURES * WORD_SIZE
633 }
634
635 fn eval_external(
639 &self,
640 challenges: &[EF],
641 air_inputs: &[Felt],
642 aux_inputs: &[Felt],
643 aux_values: &[&[EF]],
644 _log_trace_heights: &[u8],
645 ) -> Result<Vec<EF>, ReductionError> {
646 let challenges = Challenges::<EF>::new(
647 challenges[0],
648 challenges[1],
649 MIDEN_MAX_MESSAGE_WIDTH,
650 BusId::COUNT,
651 );
652
653 let core_correction = CoreAir.boundary_correction(&challenges, air_inputs, &[])?;
654 let chiplets_correction =
655 ChipletsAir.boundary_correction(&challenges, air_inputs, &[aux_inputs])?;
656
657 let aux_sum: EF = aux_values.iter().flat_map(|vals| vals.iter().copied()).sum();
658 Ok(vec![aux_sum + core_correction + chiplets_correction])
659 }
660}
661
662struct ReduceBoundaryBuilder<'a, EF: ExtensionField<Felt>> {
678 challenges: &'a Challenges<EF>,
679 public_values: &'a [Felt],
680 var_len_public_inputs: &'a [&'a [Felt]],
681 sum: EF,
682 error: Option<ReductionError>,
683}
684
685impl<'a, EF: ExtensionField<Felt>> ReduceBoundaryBuilder<'a, EF> {
686 fn finalize(self) -> Result<EF, ReductionError> {
687 match self.error {
688 Some(err) => Err(err),
689 None => Ok(self.sum),
690 }
691 }
692}
693
694impl<'a, EF: ExtensionField<Felt>> BoundaryBuilder for ReduceBoundaryBuilder<'a, EF> {
695 type F = Felt;
696 type EF = EF;
697
698 fn public_values(&self) -> &[Felt] {
699 self.public_values
700 }
701
702 fn var_len_public_inputs(&self) -> &[&[Felt]] {
703 self.var_len_public_inputs
704 }
705
706 fn insert<M>(&mut self, _name: &'static str, multiplicity: Felt, msg: M)
707 where
708 M: LookupMessage<Felt, EF>,
709 {
710 if self.error.is_some() {
711 return;
712 }
713 match msg.encode(self.challenges).try_inverse() {
714 Some(inv) => self.sum += inv * multiplicity,
715 None => {
716 self.error = Some("LogUp boundary denominator was zero".into());
717 },
718 }
719 }
720}
721
722#[cfg(test)]
726mod tests {
727 use miden_core::field::QuadFelt;
728
729 use super::*;
730
731 #[test]
734 fn constraint_degree_override_matches_symbolic() {
735 for air in [MidenAir::CORE, MidenAir::CHIPLETS] {
736 let symbolic = ConstraintDegrees::from_air::<Felt, QuadFelt, _>(&air);
737 let declared = <MidenAir as LiftedAir<Felt, QuadFelt>>::constraint_degree(&air);
738 assert_eq!(declared, symbolic, "static constraint_degree override is stale");
739 }
740 }
741}