1use alloc::{format, 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::{Digest, TRUE_DIGEST},
11 program::ExecutionClaim,
12 serde::{
13 BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
14 SliceReader,
15 },
16};
17
18use crate::{
19 Felt, MIN_STACK_DEPTH, ProgramInfo, StackInputs, StackOutputs, Word, ZERO,
20 fast::ExecutionOutput, field::QuadFelt, utils::RowMajorMatrix,
21};
22
23pub(crate) mod utils;
24use utils::ChipletTraceFragment;
25
26pub mod chiplets;
27pub(crate) mod execution_tracer;
28
29mod block_stack;
30mod parallel;
31mod range;
32mod stack;
33mod trace_state;
34
35#[cfg(test)]
36mod tests;
37
38pub(crate) use execution_tracer::TraceReplay;
42pub use miden_air::trace::RowIndex;
43pub use miden_core::deferred::PrecompileWitness;
44pub use parallel::{
45 CORE_TRACE_WIDTH, DEFAULT_MAX_PROVER_MEMORY_BYTES, build_trace, build_trace_with_budget,
46};
47#[cfg(feature = "std")]
51pub(crate) use parallel::{MAX_TRACE_LEN, build_hasher_chiplet, build_trace_with_prebuilt_hasher};
52#[cfg(feature = "std")]
53pub(crate) use trace_state::ResolvedHasherOp;
54pub use utils::{ChipletsLengths, TraceLenSummary};
55
56#[derive(Debug)]
64pub struct ExecutionWitness {
65 vm: VmWitness,
66 precompile: Option<PrecompileWitness>,
67}
68
69impl ExecutionWitness {
70 pub(crate) fn from_execution(
71 program_info: ProgramInfo,
72 stack_inputs: StackInputs,
73 execution_output: ExecutionOutput,
74 trace: TraceReplay,
75 ) -> Self {
76 let ExecutionOutput {
77 stack: stack_outputs,
78 advice: _,
79 memory: _,
80 precompile_witness: precompile,
81 } = execution_output;
82 let precompile_root =
83 precompile.as_ref().map_or(TRUE_DIGEST, PrecompileWitness::root_unchecked);
84 let vm = VmWitness {
85 program_info,
86 stack_inputs,
87 stack_outputs,
88 trace,
89 precompile_root,
90 };
91
92 Self { vm, precompile }
93 }
94
95 pub fn claim(&self) -> ExecutionClaim {
97 self.vm.claim()
98 }
99
100 pub const fn has_precompiles(&self) -> bool {
102 self.precompile.is_some()
103 }
104
105 pub fn into_parts(self) -> (VmWitness, Option<PrecompileWitness>) {
110 (self.vm, self.precompile)
111 }
112
113 #[track_caller]
122 pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
123 let budget = bytes.len().saturating_mul(EXECUTION_WITNESS_BYTE_READ_BUDGET_MULTIPLIER);
124 let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
125 let witness = <Self as Deserializable>::read_from(&mut reader)?;
126
127 if reader.has_more_bytes() {
128 return Err(DeserializationError::InvalidValue(
129 "extra bytes after execution witness payload".into(),
130 ));
131 }
132 Ok(witness)
133 }
134
135 #[track_caller]
141 pub fn read_from_bytes_trusted(bytes: &[u8]) -> Result<Self, DeserializationError> {
142 let budget = bytes.len().saturating_mul(EXECUTION_WITNESS_BYTE_READ_BUDGET_MULTIPLIER);
143 let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
144 <Self as Deserializable>::read_from(&mut reader)
145 }
146}
147
148const EXECUTION_WITNESS_BYTE_READ_BUDGET_MULTIPLIER: usize = 4;
150
151const EXECUTION_WITNESS_WIRE_VERSION: u8 = 2;
156
157impl Serializable for ExecutionWitness {
158 fn write_into<W: ByteWriter>(&self, target: &mut W) {
159 EXECUTION_WITNESS_WIRE_VERSION.write_into(target);
160 self.vm.write_into(target);
161 match &self.precompile {
162 Some(precompile) => {
163 target.write_u8(1);
164 precompile.write_into(target);
165 },
166 None => target.write_u8(0),
167 }
168 }
169}
170
171impl Deserializable for ExecutionWitness {
172 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
173 let version = u8::read_from(source)?;
174 if version != EXECUTION_WITNESS_WIRE_VERSION {
175 return Err(DeserializationError::InvalidValue(format!(
176 "unsupported execution witness wire version {version} (expected \
177 {EXECUTION_WITNESS_WIRE_VERSION})"
178 )));
179 }
180 let vm = VmWitness::read_from(source)?;
181 let precompile = match source.read_u8()? {
182 0 => {
183 if vm.precompile_root != TRUE_DIGEST {
184 return Err(DeserializationError::InvalidValue(
185 "VM witness claims deferred work but no precompile witness is present"
186 .into(),
187 ));
188 }
189 None
190 },
191 1 => {
192 let witness = PrecompileWitness::read_from(source)?;
193 if witness.root_unchecked() != vm.precompile_root {
194 return Err(DeserializationError::InvalidValue(
195 "precompile witness root does not match the VM witness precompile root"
196 .into(),
197 ));
198 }
199 Some(witness)
200 },
201 tag => {
202 return Err(DeserializationError::InvalidValue(format!(
203 "invalid precompile witness option tag {tag}"
204 )));
205 },
206 };
207 Ok(Self { vm, precompile })
208 }
209
210 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
211 ExecutionWitness::read_from_bytes(bytes)
212 }
213}
214
215#[derive(Debug)]
222pub struct VmWitness {
223 program_info: ProgramInfo,
224 stack_inputs: StackInputs,
225 stack_outputs: StackOutputs,
226 trace: TraceReplay,
227 precompile_root: Digest,
228}
229
230impl VmWitness {
231 pub fn claim(&self) -> ExecutionClaim {
233 ExecutionClaim::from_program_info(
234 self.program_info.clone(),
235 self.stack_inputs,
236 self.stack_outputs,
237 )
238 }
239
240 pub fn has_precompiles(&self) -> bool {
242 self.precompile_root != TRUE_DIGEST
243 }
244
245 #[cfg(feature = "std")]
250 pub(crate) fn take_hasher_replay(&mut self) -> trace_state::HasherRequestReplay {
251 core::mem::take(&mut self.trace.hasher_for_chiplet)
252 }
253
254 #[cfg(any(test, feature = "testing"))]
256 #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
257 pub(crate) fn trace_replay(&self) -> &TraceReplay {
258 &self.trace
259 }
260
261 #[cfg(any(test, feature = "testing"))]
263 #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
264 pub(crate) fn trace_replay_mut(&mut self) -> &mut TraceReplay {
265 &mut self.trace
266 }
267
268 #[cfg(any(test, feature = "testing"))]
273 #[allow(dead_code)]
274 pub fn mast_forest_count(&self) -> usize {
275 self.trace.mast_forest_store.len()
276 }
277}
278
279impl Serializable for VmWitness {
280 fn write_into<W: ByteWriter>(&self, target: &mut W) {
281 self.program_info.write_into(target);
282 self.stack_inputs.write_into(target);
283 self.stack_outputs.write_into(target);
284 self.trace.write_into(target);
285 self.precompile_root.write_into(target);
286 }
287}
288
289impl Deserializable for VmWitness {
290 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
291 Ok(Self {
292 program_info: ProgramInfo::read_from(source)?,
293 stack_inputs: StackInputs::read_from(source)?,
294 stack_outputs: StackOutputs::read_from(source)?,
295 trace: TraceReplay::read_from(source)?,
296 precompile_root: Digest::read_from(source)?,
297 })
298 }
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::{ExecutionWitness, Serializable};
502 use crate::{DefaultHost, FastProcessor, StackInputs, mast::MastNodeId};
503
504 fn execution_witness(source: &str) -> ExecutionWitness {
505 let program = Assembler::default()
506 .assemble_program("program", source)
507 .expect("program should compile")
508 .unwrap_program();
509 let mut host = DefaultHost::default();
510 FastProcessor::new(StackInputs::default())
511 .execute_for_proving_sync(&program, &mut host)
512 .expect("execution should produce a witness")
513 }
514
515 fn deferred_witness() -> ExecutionWitness {
516 execution_witness("begin log_deferred end")
517 }
518
519 fn deferred_witness_bytes() -> alloc::vec::Vec<u8> {
520 deferred_witness().to_bytes()
521 }
522
523 #[test]
524 fn witness_reports_precompile_state() {
525 let plain = execution_witness("begin push.1 drop end");
526 assert!(!plain.has_precompiles());
527
528 let deferred = execution_witness("begin log_deferred end");
529 assert!(deferred.has_precompiles());
530 }
531
532 #[test]
533 fn witness_wire_rejects_unsupported_version() {
534 let mut bytes = deferred_witness_bytes();
535 assert!(ExecutionWitness::read_from_bytes(&bytes).is_ok());
536
537 for version in [0, 1, super::EXECUTION_WITNESS_WIRE_VERSION + 1] {
539 bytes[0] = version;
540 let err = ExecutionWitness::read_from_bytes(&bytes)
541 .expect_err("unsupported witness format must be rejected");
542 assert!(format!("{err:?}").contains("unsupported execution witness wire version"));
543 }
544 }
545
546 #[test]
547 fn witness_wire_rejects_trailing_bytes() {
548 let mut bytes = deferred_witness_bytes();
549 bytes.push(0);
550
551 let err = ExecutionWitness::read_from_bytes(&bytes)
552 .expect_err("witness payload with trailing bytes should be rejected");
553 assert!(
554 format!("{err:?}").contains("extra bytes after execution witness payload"),
555 "unexpected error: {err:?}"
556 );
557
558 assert!(
559 ExecutionWitness::read_from_bytes_trusted(&bytes).is_ok(),
560 "the explicit trusted reader should preserve the old permissive behavior"
561 );
562 }
563
564 #[test]
565 fn witness_wire_accepts_large_minimally_encoded_continuation_stack() {
566 let mut witness = deferred_witness();
567 let continuation = &mut witness
568 .vm
569 .trace_replay_mut()
570 .core_trace_contexts
571 .first_mut()
572 .expect("witness should contain a trace fragment")
573 .continuation;
574 for _ in 0..4096 {
575 continuation.push_start_node(MastNodeId::from(0));
576 }
577
578 let bytes = witness.to_bytes();
579 let restored = ExecutionWitness::read_from_bytes(&bytes)
580 .expect("valid witness should fit its input-proportional allocation budget");
581
582 assert_eq!(restored.to_bytes(), bytes);
583 }
584
585 #[test]
586 fn witness_wire_rejects_mismatched_precompile_root() {
587 let bytes = deferred_witness_bytes();
588 let restored = ExecutionWitness::read_from_bytes(&bytes).expect("witness round trip");
589 let (vm, precompile) = restored.into_parts();
590 let precompile = precompile.expect("deferred execution should carry a precompile witness");
591 assert_ne!(vm.precompile_root, TRUE_DIGEST);
592
593 let tampered = ExecutionWitness {
596 vm: super::VmWitness { precompile_root: TRUE_DIGEST, ..vm },
597 precompile: Some(precompile),
598 };
599 let err = ExecutionWitness::read_from_bytes(&tampered.to_bytes())
600 .expect_err("tampered witness should be rejected");
601 assert!(
602 format!("{err:?}")
603 .contains("precompile witness root does not match the VM witness precompile root"),
604 "unexpected error: {err:?}"
605 );
606
607 assert!(ExecutionWitness::read_from_bytes(&bytes).is_ok());
609 }
610
611 #[test]
612 fn witness_wire_rejects_missing_precompile_witness() {
613 let bytes = deferred_witness_bytes();
614 let restored = ExecutionWitness::read_from_bytes(&bytes).expect("witness round trip");
615 let (vm, precompile) = restored.into_parts();
616 assert!(precompile.is_some(), "deferred execution should carry a precompile witness");
617
618 let stripped = ExecutionWitness { vm, precompile: None };
621 let err = ExecutionWitness::read_from_bytes(&stripped.to_bytes())
622 .expect_err("witness without its precompile half should be rejected");
623 assert!(
624 format!("{err:?}")
625 .contains("VM witness claims deferred work but no precompile witness is present"),
626 "unexpected error: {err:?}"
627 );
628 }
629}