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::{MIN_STACK_DEPTH, ProgramInfo, StackInputs, StackOutputs},
17};
18use miden_crypto::stark::air::{
19 ReducedAuxValues, ReductionError, VarLenPublicInputs, WindowAccess,
20};
21
22pub mod config;
23mod constraints;
24
25pub mod trace;
26use trace::{AUX_TRACE_WIDTH, MainTraceRow, TRACE_WIDTH};
27
28mod export {
31 pub use miden_core::{
32 Felt,
33 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
34 utils::ToElements,
35 };
36 pub use miden_crypto::stark::{
37 air::{
38 AirBuilder, AirWitness, AuxBuilder, BaseAir, ExtensionBuilder, LiftedAir,
39 LiftedAirBuilder, PermutationAirBuilder,
40 },
41 debug,
42 };
43}
44
45pub use export::*;
46
47#[derive(Debug, Clone)]
51pub struct PublicInputs {
52 program_info: ProgramInfo,
53 stack_inputs: StackInputs,
54 stack_outputs: StackOutputs,
55 pc_transcript_state: PrecompileTranscriptState,
56}
57
58impl PublicInputs {
59 pub fn new(
62 program_info: ProgramInfo,
63 stack_inputs: StackInputs,
64 stack_outputs: StackOutputs,
65 pc_transcript_state: PrecompileTranscriptState,
66 ) -> Self {
67 Self {
68 program_info,
69 stack_inputs,
70 stack_outputs,
71 pc_transcript_state,
72 }
73 }
74
75 pub fn stack_inputs(&self) -> StackInputs {
76 self.stack_inputs
77 }
78
79 pub fn stack_outputs(&self) -> StackOutputs {
80 self.stack_outputs
81 }
82
83 pub fn program_info(&self) -> ProgramInfo {
84 self.program_info.clone()
85 }
86
87 pub fn pc_transcript_state(&self) -> PrecompileTranscriptState {
89 self.pc_transcript_state
90 }
91
92 pub fn to_air_inputs(&self) -> (Vec<Felt>, Vec<Felt>) {
104 let mut public_values = Vec::with_capacity(NUM_PUBLIC_VALUES);
105 public_values.extend_from_slice(self.program_info.program_hash().as_elements());
106 public_values.extend_from_slice(self.stack_inputs.as_ref());
107 public_values.extend_from_slice(self.stack_outputs.as_ref());
108 public_values.extend_from_slice(self.pc_transcript_state.as_ref());
109
110 let kernel_felts: Vec<Felt> =
111 Word::words_as_elements(self.program_info.kernel_procedures()).to_vec();
112
113 (public_values, kernel_felts)
114 }
115
116 pub fn to_elements(&self) -> Vec<Felt> {
122 let mut result = self.program_info.to_elements();
123 result.extend_from_slice(self.stack_inputs.as_ref());
124 result.extend_from_slice(self.stack_outputs.as_ref());
125 result.extend_from_slice(self.pc_transcript_state.as_ref());
126 result
127 }
128}
129
130impl Serializable for PublicInputs {
134 fn write_into<W: ByteWriter>(&self, target: &mut W) {
135 self.program_info.write_into(target);
136 self.stack_inputs.write_into(target);
137 self.stack_outputs.write_into(target);
138 self.pc_transcript_state.write_into(target);
139 }
140}
141
142impl Deserializable for PublicInputs {
143 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
144 let program_info = ProgramInfo::read_from(source)?;
145 let stack_inputs = StackInputs::read_from(source)?;
146 let stack_outputs = StackOutputs::read_from(source)?;
147 let pc_transcript_state = PrecompileTranscriptState::read_from(source)?;
148
149 Ok(PublicInputs {
150 program_info,
151 stack_inputs,
152 stack_outputs,
153 pc_transcript_state,
154 })
155 }
156}
157
158pub const NUM_PUBLIC_VALUES: usize = WORD_SIZE + MIN_STACK_DEPTH + MIN_STACK_DEPTH + WORD_SIZE;
169
170const PV_PROGRAM_HASH: usize = 0;
172const PV_TRANSCRIPT_STATE: usize = NUM_PUBLIC_VALUES - WORD_SIZE;
173
174#[derive(Copy, Clone, Debug, Default)]
183pub struct ProcessorAir;
184
185impl BaseAir<Felt> for ProcessorAir {
188 fn width(&self) -> usize {
189 TRACE_WIDTH
190 }
191
192 fn num_public_values(&self) -> usize {
193 NUM_PUBLIC_VALUES
194 }
195}
196
197impl<EF: ExtensionField<Felt>> LiftedAir<Felt, EF> for ProcessorAir {
200 fn periodic_columns(&self) -> Vec<Vec<Felt>> {
201 let mut cols = constraints::chiplets::hasher::periodic_columns();
202 cols.extend(constraints::chiplets::bitwise::periodic_columns());
203 cols
204 }
205
206 fn num_randomness(&self) -> usize {
207 trace::AUX_TRACE_RAND_CHALLENGES
208 }
209
210 fn aux_width(&self) -> usize {
211 AUX_TRACE_WIDTH
212 }
213
214 fn num_aux_values(&self) -> usize {
215 AUX_TRACE_WIDTH
216 }
217
218 fn num_var_len_public_inputs(&self) -> usize {
225 1
226 }
227
228 fn reduced_aux_values(
229 &self,
230 aux_values: &[EF],
231 challenges: &[EF],
232 public_values: &[Felt],
233 var_len_public_inputs: VarLenPublicInputs<'_, Felt>,
234 ) -> Result<ReducedAuxValues<EF>, ReductionError>
235 where
236 EF: ExtensionField<Felt>,
237 {
238 let p1 = aux_values[trace::DECODER_AUX_TRACE_OFFSET];
240 let p2 = aux_values[trace::DECODER_AUX_TRACE_OFFSET + 1];
241 let p3 = aux_values[trace::DECODER_AUX_TRACE_OFFSET + 2];
242 let s_aux = aux_values[trace::STACK_AUX_TRACE_OFFSET];
243 let b_range = aux_values[trace::RANGE_CHECK_AUX_TRACE_OFFSET];
244 let b_hash_kernel = aux_values[trace::HASH_KERNEL_VTABLE_AUX_TRACE_OFFSET];
245 let b_chiplets = aux_values[trace::CHIPLETS_BUS_AUX_TRACE_OFFSET];
246 let v_wiring = aux_values[trace::ACE_CHIPLET_WIRING_BUS_OFFSET];
247
248 if public_values.len() != NUM_PUBLIC_VALUES {
250 return Err(format!(
251 "expected {} public values, got {}",
252 NUM_PUBLIC_VALUES,
253 public_values.len()
254 )
255 .into());
256 }
257 let program_hash: Word = public_values[PV_PROGRAM_HASH..PV_PROGRAM_HASH + WORD_SIZE]
258 .try_into()
259 .map_err(|_| -> ReductionError { "invalid program hash slice".into() })?;
260 let pc_transcript_state: PrecompileTranscriptState = public_values
261 [PV_TRANSCRIPT_STATE..PV_TRANSCRIPT_STATE + WORD_SIZE]
262 .try_into()
263 .map_err(|_| -> ReductionError { "invalid transcript state slice".into() })?;
264
265 let challenges = trace::Challenges::<EF>::new(challenges[0], challenges[1]);
267
268 let ph_msg = program_hash_message(&challenges, &program_hash);
270
271 let (default_transcript_msg, final_transcript_msg) =
272 transcript_messages(&challenges, pc_transcript_state);
273
274 let kernel_reduced = kernel_reduced_from_var_len(&challenges, var_len_public_inputs)?;
275
276 let expected_denom = final_transcript_msg * kernel_reduced;
312 let expected_denom_inv = expected_denom
313 .try_inverse()
314 .ok_or_else(|| -> ReductionError { "zero denominator in reduced_aux_values".into() })?;
315
316 let prod = p1
317 * p2
318 * p3
319 * s_aux
320 * b_hash_kernel
321 * b_chiplets
322 * ph_msg
323 * default_transcript_msg
324 * expected_denom_inv;
325
326 let sum = b_range + v_wiring;
328
329 Ok(ReducedAuxValues { prod, sum })
330 }
331
332 fn eval<AB: LiftedAirBuilder<F = Felt>>(&self, builder: &mut AB) {
333 use crate::constraints;
334
335 let main = builder.main();
336
337 let local = main.current_slice();
339 let next = main.next_slice();
340
341 let local: &MainTraceRow<AB::Var> = (*local).borrow();
343 let next: &MainTraceRow<AB::Var> = (*next).borrow();
344
345 constraints::enforce_main(builder, local, next);
347
348 constraints::enforce_bus(builder, local, next);
350
351 constraints::public_inputs::enforce_main(builder, local);
353 }
354}
355
356fn program_hash_message<EF: ExtensionField<Felt>>(
364 challenges: &trace::Challenges<EF>,
365 program_hash: &Word,
366) -> EF {
367 challenges.encode([
368 Felt::ZERO, program_hash[0],
370 program_hash[1],
371 program_hash[2],
372 program_hash[3],
373 Felt::ZERO, Felt::ZERO, ])
376}
377
378fn transcript_messages<EF: ExtensionField<Felt>>(
384 challenges: &trace::Challenges<EF>,
385 final_state: PrecompileTranscriptState,
386) -> (EF, EF) {
387 let encode = |state: PrecompileTranscriptState| {
388 let cap: &[Felt] = state.as_ref();
389 challenges.encode([
390 Felt::from_u8(trace::LOG_PRECOMPILE_LABEL),
391 cap[0],
392 cap[1],
393 cap[2],
394 cap[3],
395 ])
396 };
397 (encode(PrecompileTranscriptState::default()), encode(final_state))
398}
399
400fn kernel_proc_message<EF: ExtensionField<Felt>>(
405 challenges: &trace::Challenges<EF>,
406 digest: &Word,
407) -> EF {
408 challenges.encode([
409 trace::chiplets::kernel_rom::KERNEL_PROC_INIT_LABEL,
410 digest[0],
411 digest[1],
412 digest[2],
413 digest[3],
414 ])
415}
416
417fn kernel_reduced_from_var_len<EF: ExtensionField<Felt>>(
422 challenges: &trace::Challenges<EF>,
423 var_len_public_inputs: VarLenPublicInputs<'_, Felt>,
424) -> Result<EF, ReductionError> {
425 if var_len_public_inputs.len() != 1 {
426 return Err(format!(
427 "expected 1 var-len public input slice, got {}",
428 var_len_public_inputs.len()
429 )
430 .into());
431 }
432 let kernel_felts = var_len_public_inputs[0];
433 if !kernel_felts.len().is_multiple_of(WORD_SIZE) {
434 return Err(format!(
435 "kernel digest felts length {} is not a multiple of {}",
436 kernel_felts.len(),
437 WORD_SIZE
438 )
439 .into());
440 }
441 let mut acc = EF::ONE;
442 for digest in kernel_felts.chunks_exact(WORD_SIZE) {
443 let word: Word = [digest[0], digest[1], digest[2], digest[3]].into();
444 acc *= kernel_proc_message(challenges, &word);
445 }
446 Ok(acc)
447}