1use alloc::{format, sync::Arc, vec::Vec};
2#[cfg(any(test, feature = "testing"))]
3use core::ops::Range;
4
5use miden_air::{
6 MidenMultiAir, ProverStatement, PublicInputs, StarkConfig, Statement, config, debug,
7 trace::{MainTrace, decoder::NUM_USER_OP_HELPERS},
8};
9use miden_core::{
10 deferred::{DeferredState, DeferredStateWire, Digest, TRUE_DIGEST},
11 program::ExecutionClaim,
12 serde::{ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable},
13};
14
15use crate::{
16 Felt, MIN_STACK_DEPTH, ProgramInfo, StackInputs, StackOutputs, Word, ZERO,
17 fast::ExecutionOutput, field::QuadFelt, utils::RowMajorMatrix,
18};
19
20pub(crate) mod utils;
21use utils::ChipletTraceFragment;
22
23pub mod chiplets;
24pub(crate) mod execution_tracer;
25
26mod block_stack;
27mod parallel;
28mod range;
29mod stack;
30mod trace_state;
31
32#[cfg(test)]
33mod tests;
34
35pub(crate) use execution_tracer::TraceReplay;
39pub use miden_air::trace::RowIndex;
40pub use miden_core::deferred::PrecompileWitness;
41pub use parallel::{
42 CORE_TRACE_WIDTH, DEFAULT_MAX_PROVER_MEMORY_BYTES, build_trace, build_trace_with_budget,
43};
44#[cfg(feature = "std")]
48pub(crate) use parallel::{MAX_TRACE_LEN, build_hasher_chiplet, build_trace_with_prebuilt_hasher};
49#[cfg(feature = "std")]
50pub(crate) use trace_state::ResolvedHasherOp;
51pub use utils::{ChipletsLengths, TraceLenSummary};
52
53#[derive(Debug)]
61pub struct ExecutionWitness {
62 vm: VmWitness,
63 precompile: Option<PrecompileWitness>,
64}
65
66impl ExecutionWitness {
67 pub(crate) fn from_execution(
68 program_info: ProgramInfo,
69 stack_inputs: StackInputs,
70 execution_output: ExecutionOutput,
71 trace: TraceReplay,
72 ) -> Self {
73 let ExecutionOutput {
74 stack: stack_outputs,
75 advice: _,
76 memory: _,
77 deferred_state: precompiles,
78 } = execution_output;
79 let precompile_root = precompiles.root();
80 let vm = VmWitness {
81 program_info,
82 stack_inputs,
83 stack_outputs,
84 trace,
85 precompile_root,
86 };
87 let precompile = (precompile_root != TRUE_DIGEST).then(|| {
88 PrecompileWitness::new(precompiles)
89 .expect("a non-TRUE execution root must produce a singleton precompile witness")
90 });
91
92 Self { vm, precompile }
93 }
94
95 pub fn claim(&self) -> ExecutionClaim {
97 self.vm.claim()
98 }
99
100 pub fn into_parts(self) -> (VmWitness, Option<PrecompileWitness>) {
105 (self.vm, self.precompile)
106 }
107}
108
109const EXECUTION_WITNESS_WIRE_VERSION: u8 = 1;
115
116impl Serializable for ExecutionWitness {
117 fn write_into<W: ByteWriter>(&self, target: &mut W) {
118 EXECUTION_WITNESS_WIRE_VERSION.write_into(target);
119 self.vm.write_into(target);
120 match &self.precompile {
121 Some(precompile) => {
122 target.write_u8(1);
123 write_precompile_witness(precompile, target);
124 },
125 None => target.write_u8(0),
126 }
127 }
128}
129
130impl Deserializable for ExecutionWitness {
131 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
132 let version = u8::read_from(source)?;
133 if version != EXECUTION_WITNESS_WIRE_VERSION {
134 return Err(DeserializationError::InvalidValue(format!(
135 "unsupported execution witness wire version {version} (expected \
136 {EXECUTION_WITNESS_WIRE_VERSION})"
137 )));
138 }
139 let vm = VmWitness::read_from(source)?;
140 let precompile = match source.read_u8()? {
141 0 => {
142 if vm.precompile_root != TRUE_DIGEST {
143 return Err(DeserializationError::InvalidValue(
144 "VM witness claims deferred work but no precompile witness is present"
145 .into(),
146 ));
147 }
148 None
149 },
150 1 => {
151 let witness = read_precompile_witness(source)?;
152 let [witness_root] = witness.roots() else {
155 return Err(DeserializationError::InvalidValue(
156 "expected a singleton precompile witness".into(),
157 ));
158 };
159 if *witness_root != vm.precompile_root {
160 return Err(DeserializationError::InvalidValue(
161 "precompile witness root does not match the VM witness precompile root"
162 .into(),
163 ));
164 }
165 Some(witness)
166 },
167 tag => {
168 return Err(DeserializationError::InvalidValue(format!(
169 "invalid precompile witness option tag {tag}"
170 )));
171 },
172 };
173 Ok(Self { vm, precompile })
174 }
175}
176
177#[derive(Debug)]
184pub struct VmWitness {
185 program_info: ProgramInfo,
186 stack_inputs: StackInputs,
187 stack_outputs: StackOutputs,
188 trace: TraceReplay,
189 precompile_root: Digest,
190}
191
192impl VmWitness {
193 pub fn claim(&self) -> ExecutionClaim {
195 ExecutionClaim::from_program_info(
196 self.program_info.clone(),
197 self.stack_inputs,
198 self.stack_outputs,
199 )
200 }
201
202 #[cfg(feature = "std")]
207 pub(crate) fn take_hasher_replay(&mut self) -> trace_state::HasherRequestReplay {
208 core::mem::take(&mut self.trace.hasher_for_chiplet)
209 }
210
211 #[cfg(any(test, feature = "testing"))]
213 #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
214 pub(crate) fn trace_replay(&self) -> &TraceReplay {
215 &self.trace
216 }
217
218 #[cfg(any(test, feature = "testing"))]
220 #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
221 pub(crate) fn trace_replay_mut(&mut self) -> &mut TraceReplay {
222 &mut self.trace
223 }
224
225 #[cfg(any(test, feature = "testing"))]
230 #[allow(dead_code)]
231 pub fn mast_forest_count(&self) -> usize {
232 self.trace.mast_forest_store.len()
233 }
234}
235
236impl Serializable for VmWitness {
237 fn write_into<W: ByteWriter>(&self, target: &mut W) {
238 self.program_info.write_into(target);
239 self.stack_inputs.write_into(target);
240 self.stack_outputs.write_into(target);
241 self.trace.write_into(target);
242 self.precompile_root.write_into(target);
243 }
244}
245
246impl Deserializable for VmWitness {
247 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
248 Ok(Self {
249 program_info: ProgramInfo::read_from(source)?,
250 stack_inputs: StackInputs::read_from(source)?,
251 stack_outputs: StackOutputs::read_from(source)?,
252 trace: TraceReplay::read_from(source)?,
253 precompile_root: Digest::read_from(source)?,
254 })
255 }
256}
257
258fn write_precompile_witness<W: ByteWriter>(witness: &PrecompileWitness, target: &mut W) {
261 let roots = witness.roots();
262 debug_assert_eq!(roots.len(), 1, "only singleton precompile witnesses are serializable");
263 target.write_usize(roots.len());
264 for root in roots {
265 root.write_into(target);
266 }
267 let deferred_wire = witness
268 .state()
269 .to_wire()
270 .expect("deferred state must serialize to canonical wire");
271 deferred_wire.write_into(target);
272}
273
274fn read_precompile_witness<R: ByteReader>(
276 source: &mut R,
277) -> Result<PrecompileWitness, DeserializationError> {
278 let roots = Vec::<Digest>::read_from(source)?;
279 if roots.len() != 1 {
280 return Err(DeserializationError::InvalidValue(
281 "expected a singleton precompile witness".into(),
282 ));
283 }
284 let deferred_wire = DeferredStateWire::read_from(source)?;
285 let deferred_state =
286 DeferredState::from_wire(Arc::new(miden_precompiles::registry()), &deferred_wire).map_err(
287 |err| DeserializationError::InvalidValue(format!("invalid deferred state: {err}")),
288 )?;
289
290 let witness = PrecompileWitness::new(deferred_state).map_err(|err| {
291 DeserializationError::InvalidValue(format!("invalid precompile witness: {err}"))
292 })?;
293 if witness.roots() != roots.as_slice() {
294 return Err(DeserializationError::InvalidValue(
295 "precompile witness roots do not match its deferred state".into(),
296 ));
297 }
298 Ok(witness)
299}
300
301#[derive(Debug)]
312pub struct VmTrace {
313 main_trace: MainTrace,
314 program_info: ProgramInfo,
315 stack_inputs: StackInputs,
316 stack_outputs: StackOutputs,
317 precompile_root: Digest,
318 trace_len_summary: TraceLenSummary,
319}
320
321impl VmTrace {
322 pub(crate) fn new_from_parts(
326 program_info: ProgramInfo,
327 stack_inputs: StackInputs,
328 stack_outputs: StackOutputs,
329 precompile_root: Digest,
330 main_trace: MainTrace,
331 trace_len_summary: TraceLenSummary,
332 ) -> Self {
333 Self {
334 main_trace,
335 program_info,
336 stack_inputs,
337 stack_outputs,
338 precompile_root,
339 trace_len_summary,
340 }
341 }
342
343 pub fn program_info(&self) -> &ProgramInfo {
348 &self.program_info
349 }
350
351 pub fn program_hash(&self) -> &Word {
353 self.program_info.program_hash()
354 }
355
356 pub fn stack_outputs(&self) -> &StackOutputs {
358 &self.stack_outputs
359 }
360
361 pub fn public_inputs(&self) -> PublicInputs {
363 PublicInputs::new(
364 self.program_info.clone(),
365 self.stack_inputs,
366 self.stack_outputs,
367 self.precompile_root,
368 )
369 }
370
371 pub fn to_public_values(&self) -> Vec<Felt> {
373 self.public_inputs().to_elements()
374 }
375
376 pub fn main_trace(&self) -> &MainTrace {
378 &self.main_trace
379 }
380
381 pub fn main_trace_mut(&mut self) -> &mut MainTrace {
383 &mut self.main_trace
384 }
385
386 pub fn precompile_root(&self) -> Digest {
388 self.precompile_root
389 }
390
391 pub fn into_outputs(self) -> StackOutputs {
393 self.stack_outputs
394 }
395
396 pub fn init_stack_state(&self) -> StackInputs {
398 self.stack_inputs
399 }
400
401 pub fn last_stack_state(&self) -> StackOutputs {
403 let last_step = RowIndex::from(self.last_step());
404 let mut result = [ZERO; MIN_STACK_DEPTH];
405 for (i, result) in result.iter_mut().enumerate() {
406 *result = self.main_trace.stack_element(i, last_step);
407 }
408 result.into()
409 }
410
411 pub fn get_user_op_helpers_at(&self, clk: u32) -> [Felt; NUM_USER_OP_HELPERS] {
413 let mut result = [ZERO; NUM_USER_OP_HELPERS];
414 let row = RowIndex::from(clk);
415 for (i, result) in result.iter_mut().enumerate() {
416 *result = self.main_trace.helper_register(i, row);
417 }
418 result
419 }
420
421 pub fn get_trace_len(&self) -> usize {
423 self.main_trace.num_rows()
424 }
425
426 pub fn length(&self) -> usize {
428 self.get_trace_len()
429 }
430
431 pub fn trace_len_summary(&self) -> &TraceLenSummary {
433 &self.trace_len_summary
434 }
435
436 pub fn check_constraints(&self) {
450 let public_inputs = self.public_inputs();
451 let (core_matrix, chiplets_matrix, poseidon2_matrix) = self.main_trace.to_air_matrices();
452
453 let (public_values, aux_inputs) = public_inputs.to_air_inputs();
454
455 let statement =
456 Statement::<Felt, QuadFelt, _>::new(MidenMultiAir::new(), public_values, aux_inputs)
457 .expect("valid statement inputs");
458 let prover_statement =
459 ProverStatement::new(statement, vec![core_matrix, chiplets_matrix, poseidon2_matrix])
460 .expect("valid trace shapes");
461
462 let config = config::poseidon2_config(config::pcs_params(), config::RELATION_DIGEST);
465 debug::check_constraints(&prover_statement, config.challenger());
466 }
467
468 pub fn to_air_matrices(
470 &self,
471 ) -> (RowMajorMatrix<Felt>, RowMajorMatrix<Felt>, RowMajorMatrix<Felt>) {
472 self.main_trace.to_air_matrices()
473 }
474
475 pub fn into_air_matrices(
477 self,
478 ) -> (RowMajorMatrix<Felt>, RowMajorMatrix<Felt>, RowMajorMatrix<Felt>) {
479 self.main_trace.into_air_matrices()
480 }
481
482 fn last_step(&self) -> usize {
487 self.main_trace.core_height() - 1
488 }
489
490 #[cfg(any(test, feature = "testing"))]
491 pub fn get_column_range(&self, range: Range<usize>) -> Vec<Vec<Felt>> {
492 self.main_trace.get_column_range(range)
493 }
494}
495
496#[cfg(test)]
497mod wire_tests {
498 use miden_assembly::Assembler;
499 use miden_core::deferred::TRUE_DIGEST;
500
501 use super::{Deserializable, ExecutionWitness, Serializable};
502 use crate::{DefaultHost, FastProcessor, StackInputs};
503
504 fn deferred_witness_bytes() -> alloc::vec::Vec<u8> {
505 let program = Assembler::default()
506 .assemble_program("program", "begin log_deferred end")
507 .expect("program should compile")
508 .unwrap_program();
509 let mut host = DefaultHost::default();
510 let witness = FastProcessor::new(StackInputs::default())
511 .execute_for_proving_sync(&program, &mut host)
512 .expect("execution should produce a witness");
513 witness.to_bytes()
514 }
515
516 #[test]
517 fn witness_wire_rejects_unsupported_version() {
518 let mut bytes = deferred_witness_bytes();
519 assert!(ExecutionWitness::read_from_bytes(&bytes).is_ok());
520
521 bytes[0] = bytes[0].wrapping_add(1);
524 let err = ExecutionWitness::read_from_bytes(&bytes)
525 .expect_err("witness with an unknown wire version should be rejected");
526 assert!(
527 format!("{err:?}").contains("unsupported execution witness wire version"),
528 "unexpected error: {err:?}"
529 );
530 }
531
532 #[test]
533 fn witness_wire_rejects_mismatched_precompile_root() {
534 let bytes = deferred_witness_bytes();
535 let restored = ExecutionWitness::read_from_bytes(&bytes).expect("witness round trip");
536 let (vm, precompile) = restored.into_parts();
537 let precompile = precompile.expect("deferred execution should carry a precompile witness");
538 assert_ne!(vm.precompile_root, TRUE_DIGEST);
539
540 let tampered = ExecutionWitness {
543 vm: super::VmWitness { precompile_root: TRUE_DIGEST, ..vm },
544 precompile: Some(precompile),
545 };
546 let err = ExecutionWitness::read_from_bytes(&tampered.to_bytes())
547 .expect_err("tampered witness should be rejected");
548 assert!(
549 format!("{err:?}")
550 .contains("precompile witness root does not match the VM witness precompile root"),
551 "unexpected error: {err:?}"
552 );
553
554 assert!(ExecutionWitness::read_from_bytes(&bytes).is_ok());
556 }
557
558 #[test]
559 fn witness_wire_rejects_missing_precompile_witness() {
560 let bytes = deferred_witness_bytes();
561 let restored = ExecutionWitness::read_from_bytes(&bytes).expect("witness round trip");
562 let (vm, precompile) = restored.into_parts();
563 assert!(precompile.is_some(), "deferred execution should carry a precompile witness");
564
565 let stripped = ExecutionWitness { vm, precompile: None };
568 let err = ExecutionWitness::read_from_bytes(&stripped.to_bytes())
569 .expect_err("witness without its precompile half should be rejected");
570 assert!(
571 format!("{err:?}")
572 .contains("VM witness claims deferred work but no precompile witness is present"),
573 "unexpected error: {err:?}"
574 );
575 }
576}