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