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
12#[cfg(feature = "arbitrary")]
13use miden_core::program::Kernel;
14use miden_core::{
15 WORD_SIZE, Word,
16 field::ExtensionField,
17 precompile::PrecompileTranscriptState,
18 program::{MIN_STACK_DEPTH, ProgramInfo, StackInputs, StackOutputs},
19};
20use miden_crypto::stark::air::{
21 ReducedAuxValues, ReductionError, VarLenPublicInputs, WindowAccess,
22};
23#[cfg(feature = "arbitrary")]
24use proptest::prelude::*;
25
26pub mod ace;
27pub mod config;
28mod constraints;
29pub mod lookup;
30pub mod trace;
31
32pub mod logup {
38 pub use crate::constraints::lookup::{
39 BusId, MIDEN_MAX_MESSAGE_WIDTH, messages::*, miden_air::NUM_LOGUP_COMMITTED_FINALS,
40 };
41}
42
43use constraints::{
44 columns::MainCols,
45 lookup::{
46 chiplet_air::{ChipletLookupAir, ChipletLookupBuilder},
47 main_air::{MainLookupAir, MainLookupBuilder},
48 miden_air::{MIDEN_COLUMN_SHAPE, emit_miden_boundary},
49 },
50};
51use logup::{BusId, MIDEN_MAX_MESSAGE_WIDTH, NUM_LOGUP_COMMITTED_FINALS};
52use lookup::{
53 BoundaryBuilder, Challenges, ConstraintLookupBuilder, LookupAir, LookupMessage,
54 build_logup_aux_trace,
55};
56use miden_core::utils::RowMajorMatrix;
57use trace::TRACE_WIDTH;
58
59mod export {
62 pub use miden_core::{
63 Felt,
64 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
65 utils::ToElements,
66 };
67 pub use miden_crypto::stark::{
68 air::{
69 AirBuilder, AuxBuilder, BaseAir, ExtensionBuilder, LiftedAir, LiftedAirBuilder,
70 PermutationAirBuilder,
71 },
72 debug,
73 };
74 pub use miden_lifted_stark::AirWitness;
75}
76
77pub use export::*;
78
79pub trait MidenAirBuilder: LiftedAirBuilder<F = Felt> {}
87impl<T: LiftedAirBuilder<F = Felt>> MidenAirBuilder for T {}
88
89#[derive(Debug, Clone, PartialEq, Eq)]
93#[cfg_attr(
94 all(feature = "arbitrary", test),
95 miden_test_serde_macros::serde_test(binary_serde(true), serde_test(false))
96)]
97pub struct PublicInputs {
98 program_info: ProgramInfo,
99 stack_inputs: StackInputs,
100 stack_outputs: StackOutputs,
101 pc_transcript_state: PrecompileTranscriptState,
102}
103
104impl PublicInputs {
105 pub fn new(
108 program_info: ProgramInfo,
109 stack_inputs: StackInputs,
110 stack_outputs: StackOutputs,
111 pc_transcript_state: PrecompileTranscriptState,
112 ) -> Self {
113 Self {
114 program_info,
115 stack_inputs,
116 stack_outputs,
117 pc_transcript_state,
118 }
119 }
120
121 pub fn stack_inputs(&self) -> StackInputs {
122 self.stack_inputs
123 }
124
125 pub fn stack_outputs(&self) -> StackOutputs {
126 self.stack_outputs
127 }
128
129 pub fn program_info(&self) -> ProgramInfo {
130 self.program_info.clone()
131 }
132
133 pub fn pc_transcript_state(&self) -> PrecompileTranscriptState {
135 self.pc_transcript_state
136 }
137
138 pub fn to_air_inputs(&self) -> (Vec<Felt>, Vec<Felt>) {
150 let mut public_values = Vec::with_capacity(NUM_PUBLIC_VALUES);
151 public_values.extend_from_slice(self.program_info.program_hash().as_elements());
152 public_values.extend_from_slice(self.stack_inputs.as_ref());
153 public_values.extend_from_slice(self.stack_outputs.as_ref());
154 public_values.extend_from_slice(self.pc_transcript_state.as_ref());
155
156 let kernel_felts: Vec<Felt> =
157 Word::words_as_elements(self.program_info.kernel_procedures()).to_vec();
158
159 (public_values, kernel_felts)
160 }
161
162 pub fn to_elements(&self) -> Vec<Felt> {
168 let mut result = self.program_info.to_elements();
169 result.extend_from_slice(self.stack_inputs.as_ref());
170 result.extend_from_slice(self.stack_outputs.as_ref());
171 result.extend_from_slice(self.pc_transcript_state.as_ref());
172 result
173 }
174}
175
176#[cfg(feature = "arbitrary")]
177impl Arbitrary for PublicInputs {
178 type Parameters = ();
179 type Strategy = BoxedStrategy<Self>;
180
181 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
182 fn felt_strategy() -> impl Strategy<Value = Felt> {
183 any::<u32>().prop_map(Felt::from)
184 }
185
186 fn word_strategy() -> impl Strategy<Value = Word> {
187 any::<[u32; WORD_SIZE]>().prop_map(|values| Word::new(values.map(Felt::from)))
188 }
189
190 let program_info = word_strategy()
191 .prop_map(|program_hash| ProgramInfo::new(program_hash, Kernel::default()));
192 let stack_inputs = proptest::collection::vec(felt_strategy(), 0..=MIN_STACK_DEPTH)
193 .prop_map(|values| StackInputs::new(&values).expect("generated stack inputs fit"));
194 let stack_outputs = proptest::collection::vec(felt_strategy(), 0..=MIN_STACK_DEPTH)
195 .prop_map(|values| StackOutputs::new(&values).expect("generated stack outputs fit"));
196
197 (program_info, stack_inputs, stack_outputs, word_strategy())
198 .prop_map(|(program_info, stack_inputs, stack_outputs, pc_transcript_state)| {
199 Self::new(program_info, stack_inputs, stack_outputs, pc_transcript_state)
200 })
201 .boxed()
202 }
203}
204
205impl Serializable for PublicInputs {
209 fn write_into<W: ByteWriter>(&self, target: &mut W) {
210 self.program_info.write_into(target);
211 self.stack_inputs.write_into(target);
212 self.stack_outputs.write_into(target);
213 self.pc_transcript_state.write_into(target);
214 }
215}
216
217impl Deserializable for PublicInputs {
218 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
219 let program_info = ProgramInfo::read_from(source)?;
220 let stack_inputs = StackInputs::read_from(source)?;
221 let stack_outputs = StackOutputs::read_from(source)?;
222 let pc_transcript_state = PrecompileTranscriptState::read_from(source)?;
223
224 Ok(PublicInputs {
225 program_info,
226 stack_inputs,
227 stack_outputs,
228 pc_transcript_state,
229 })
230 }
231}
232
233pub const NUM_PUBLIC_VALUES: usize = WORD_SIZE + MIN_STACK_DEPTH + MIN_STACK_DEPTH + WORD_SIZE;
244
245pub const LOGUP_AUX_TRACE_WIDTH: usize = 7;
247
248const PV_PROGRAM_HASH: usize = 0;
250const PV_TRANSCRIPT_STATE: usize = NUM_PUBLIC_VALUES - WORD_SIZE;
251
252#[derive(Copy, Clone, Debug, Default)]
261pub struct ProcessorAir;
262
263impl BaseAir<Felt> for ProcessorAir {
266 fn width(&self) -> usize {
267 TRACE_WIDTH
268 }
269
270 fn num_public_values(&self) -> usize {
271 NUM_PUBLIC_VALUES
272 }
273}
274
275impl<EF: ExtensionField<Felt>> LiftedAir<Felt, EF> for ProcessorAir {
278 fn periodic_columns(&self) -> Vec<Vec<Felt>> {
279 constraints::chiplets::columns::PeriodicCols::periodic_columns()
280 }
281
282 fn num_randomness(&self) -> usize {
283 trace::AUX_TRACE_RAND_CHALLENGES
284 }
285
286 fn aux_width(&self) -> usize {
287 LOGUP_AUX_TRACE_WIDTH
291 }
292
293 fn num_aux_values(&self) -> usize {
294 NUM_LOGUP_COMMITTED_FINALS
295 }
296
297 fn num_var_len_public_inputs(&self) -> usize {
304 1
305 }
306
307 fn reduced_aux_values(
308 &self,
309 aux_values: &[EF],
310 challenges: &[EF],
311 public_values: &[Felt],
312 var_len_public_inputs: VarLenPublicInputs<'_, Felt>,
313 ) -> Result<ReducedAuxValues<EF>, ReductionError>
314 where
315 EF: ExtensionField<Felt>,
316 {
317 if public_values.len() != NUM_PUBLIC_VALUES {
331 return Err(format!(
332 "expected {} public values, got {}",
333 NUM_PUBLIC_VALUES,
334 public_values.len()
335 )
336 .into());
337 }
338 if var_len_public_inputs.len() != 1 {
339 return Err(format!(
340 "expected 1 var-len public input slice, got {}",
341 var_len_public_inputs.len()
342 )
343 .into());
344 }
345 if !var_len_public_inputs[0].len().is_multiple_of(WORD_SIZE) {
346 return Err(format!(
347 "kernel digest felts length {} is not a multiple of {}",
348 var_len_public_inputs[0].len(),
349 WORD_SIZE
350 )
351 .into());
352 }
353
354 let challenges = Challenges::<EF>::new(
355 challenges[0],
356 challenges[1],
357 MIDEN_MAX_MESSAGE_WIDTH,
358 BusId::COUNT,
359 );
360
361 let mut reducer = ReduceBoundaryBuilder {
362 challenges: &challenges,
363 public_values,
364 var_len_public_inputs,
365 sum: EF::ZERO,
366 error: None,
367 };
368 emit_miden_boundary(&mut reducer);
369 let total_correction = reducer.finalize()?;
370
371 for unused_aux in aux_values.iter().skip(1) {
377 if !unused_aux.is_zero() {
378 return Err("padding aux value is non-zero".into());
379 }
380 }
381 let aux_sum: EF = aux_values.iter().copied().sum();
382
383 Ok(ReducedAuxValues {
384 prod: EF::ONE,
385 sum: aux_sum + total_correction,
386 })
387 }
388
389 fn eval<AB: MidenAirBuilder>(&self, builder: &mut AB) {
390 let main = builder.main();
391
392 let local = main.current_slice();
394 let next = main.next_slice();
395
396 let local: &MainCols<AB::Var> = (*local).borrow();
398 let next: &MainCols<AB::Var> = (*next).borrow();
399
400 let selectors =
402 constraints::chiplets::selectors::build_chiplet_selectors(builder, local, next);
403 let op_flags =
404 constraints::op_flags::OpFlags::new(&local.decoder, &local.stack, &next.decoder);
405
406 constraints::enforce_main(builder, local, next, &selectors, &op_flags);
408
409 {
410 let mut lb = ConstraintLookupBuilder::new(builder, self);
411 <Self as LookupAir<_>>::eval(self, &mut lb);
412 }
413
414 constraints::public_inputs::enforce_main(builder, local);
416 }
417
418 fn log_quotient_degree(&self) -> usize
419 where
420 Self: Sized,
421 {
422 3
424 }
425}
426
427impl<LB> LookupAir<LB> for ProcessorAir
430where
431 LB: MainLookupBuilder + ChipletLookupBuilder,
432{
433 fn num_columns(&self) -> usize {
434 MIDEN_COLUMN_SHAPE.len()
435 }
436
437 fn column_shape(&self) -> &[usize] {
438 &MIDEN_COLUMN_SHAPE
439 }
440
441 fn max_message_width(&self) -> usize {
442 MIDEN_MAX_MESSAGE_WIDTH
449 }
450
451 fn num_bus_ids(&self) -> usize {
452 BusId::COUNT
453 }
454
455 fn eval(&self, builder: &mut LB) {
456 MainLookupAir.eval(builder);
457 ChipletLookupAir.eval(builder);
458 }
459
460 fn eval_boundary<B>(&self, boundary: &mut B)
461 where
462 B: BoundaryBuilder<F = LB::F, EF = LB::EF>,
463 {
464 emit_miden_boundary(boundary);
465 }
466}
467
468impl<EF> AuxBuilder<Felt, EF> for ProcessorAir
471where
472 EF: ExtensionField<Felt>,
473{
474 fn build_aux_trace(
475 &self,
476 main: &RowMajorMatrix<Felt>,
477 challenges: &[EF],
478 ) -> (RowMajorMatrix<EF>, Vec<EF>) {
479 let (aux_trace, mut committed) = build_logup_aux_trace(self, main, challenges);
480 debug_assert_eq!(
483 committed.len(),
484 1,
485 "build_logup_aux_trace should return exactly one real committed final"
486 );
487 committed.push(EF::ZERO);
488 (aux_trace, committed)
489 }
490}
491
492struct ReduceBoundaryBuilder<'a, EF: ExtensionField<Felt>> {
509 challenges: &'a Challenges<EF>,
510 public_values: &'a [Felt],
511 var_len_public_inputs: VarLenPublicInputs<'a, Felt>,
512 sum: EF,
513 error: Option<ReductionError>,
514}
515
516impl<'a, EF: ExtensionField<Felt>> ReduceBoundaryBuilder<'a, EF> {
517 fn finalize(self) -> Result<EF, ReductionError> {
518 match self.error {
519 Some(err) => Err(err),
520 None => Ok(self.sum),
521 }
522 }
523}
524
525impl<'a, EF: ExtensionField<Felt>> BoundaryBuilder for ReduceBoundaryBuilder<'a, EF> {
526 type F = Felt;
527 type EF = EF;
528
529 fn public_values(&self) -> &[Felt] {
530 self.public_values
531 }
532
533 fn var_len_public_inputs(&self) -> &[&[Felt]] {
534 self.var_len_public_inputs
535 }
536
537 fn insert<M>(&mut self, _name: &'static str, multiplicity: Felt, msg: M)
538 where
539 M: LookupMessage<Felt, EF>,
540 {
541 if self.error.is_some() {
542 return;
543 }
544 match msg.encode(self.challenges).try_inverse() {
545 Some(inv) => self.sum += inv * multiplicity,
546 None => {
547 self.error = Some("LogUp boundary denominator was zero".into());
548 },
549 }
550 }
551}