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::{
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 deferred_state: precompiles,
81 } = execution_output;
82 let precompile_root = precompiles.root();
83 let vm = VmWitness {
84 program_info,
85 stack_inputs,
86 stack_outputs,
87 trace,
88 precompile_root,
89 };
90 let precompile = (precompile_root != TRUE_DIGEST).then(|| {
91 PrecompileWitness::new(precompiles)
92 .expect("a non-TRUE execution root must produce a singleton precompile witness")
93 });
94
95 Self { vm, precompile }
96 }
97
98 pub fn claim(&self) -> ExecutionClaim {
100 self.vm.claim()
101 }
102
103 pub const fn has_precompiles(&self) -> bool {
105 self.precompile.is_some()
106 }
107
108 pub fn into_parts(self) -> (VmWitness, Option<PrecompileWitness>) {
113 (self.vm, self.precompile)
114 }
115
116 #[track_caller]
125 pub fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
126 let budget = bytes.len().saturating_mul(EXECUTION_WITNESS_BYTE_READ_BUDGET_MULTIPLIER);
127 let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
128 let witness = <Self as Deserializable>::read_from(&mut reader)?;
129
130 if reader.has_more_bytes() {
131 return Err(DeserializationError::InvalidValue(
132 "extra bytes after execution witness payload".into(),
133 ));
134 }
135 Ok(witness)
136 }
137
138 #[track_caller]
144 pub fn read_from_bytes_trusted(bytes: &[u8]) -> Result<Self, DeserializationError> {
145 let budget = bytes.len().saturating_mul(EXECUTION_WITNESS_BYTE_READ_BUDGET_MULTIPLIER);
146 let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
147 <Self as Deserializable>::read_from(&mut reader)
148 }
149}
150
151const EXECUTION_WITNESS_BYTE_READ_BUDGET_MULTIPLIER: usize = 4;
153
154const EXECUTION_WITNESS_WIRE_VERSION: u8 = 1;
160
161impl Serializable for ExecutionWitness {
162 fn write_into<W: ByteWriter>(&self, target: &mut W) {
163 EXECUTION_WITNESS_WIRE_VERSION.write_into(target);
164 self.vm.write_into(target);
165 match &self.precompile {
166 Some(precompile) => {
167 target.write_u8(1);
168 write_precompile_witness(precompile, target);
169 },
170 None => target.write_u8(0),
171 }
172 }
173}
174
175impl Deserializable for ExecutionWitness {
176 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
177 let version = u8::read_from(source)?;
178 if version != EXECUTION_WITNESS_WIRE_VERSION {
179 return Err(DeserializationError::InvalidValue(format!(
180 "unsupported execution witness wire version {version} (expected \
181 {EXECUTION_WITNESS_WIRE_VERSION})"
182 )));
183 }
184 let vm = VmWitness::read_from(source)?;
185 let precompile = match source.read_u8()? {
186 0 => {
187 if vm.precompile_root != TRUE_DIGEST {
188 return Err(DeserializationError::InvalidValue(
189 "VM witness claims deferred work but no precompile witness is present"
190 .into(),
191 ));
192 }
193 None
194 },
195 1 => {
196 let witness = read_precompile_witness(source)?;
197 let [witness_root] = witness.roots() else {
200 return Err(DeserializationError::InvalidValue(
201 "expected a singleton precompile witness".into(),
202 ));
203 };
204 if *witness_root != vm.precompile_root {
205 return Err(DeserializationError::InvalidValue(
206 "precompile witness root does not match the VM witness precompile root"
207 .into(),
208 ));
209 }
210 Some(witness)
211 },
212 tag => {
213 return Err(DeserializationError::InvalidValue(format!(
214 "invalid precompile witness option tag {tag}"
215 )));
216 },
217 };
218 Ok(Self { vm, precompile })
219 }
220
221 fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
222 ExecutionWitness::read_from_bytes(bytes)
223 }
224}
225
226#[derive(Debug)]
233pub struct VmWitness {
234 program_info: ProgramInfo,
235 stack_inputs: StackInputs,
236 stack_outputs: StackOutputs,
237 trace: TraceReplay,
238 precompile_root: Digest,
239}
240
241impl VmWitness {
242 pub fn claim(&self) -> ExecutionClaim {
244 ExecutionClaim::from_program_info(
245 self.program_info.clone(),
246 self.stack_inputs,
247 self.stack_outputs,
248 )
249 }
250
251 #[cfg(feature = "std")]
256 pub(crate) fn take_hasher_replay(&mut self) -> trace_state::HasherRequestReplay {
257 core::mem::take(&mut self.trace.hasher_for_chiplet)
258 }
259
260 #[cfg(any(test, feature = "testing"))]
262 #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
263 pub(crate) fn trace_replay(&self) -> &TraceReplay {
264 &self.trace
265 }
266
267 #[cfg(any(test, feature = "testing"))]
269 #[cfg_attr(all(feature = "testing", not(test)), expect(dead_code))]
270 pub(crate) fn trace_replay_mut(&mut self) -> &mut TraceReplay {
271 &mut self.trace
272 }
273
274 #[cfg(any(test, feature = "testing"))]
279 #[allow(dead_code)]
280 pub fn mast_forest_count(&self) -> usize {
281 self.trace.mast_forest_store.len()
282 }
283}
284
285impl Serializable for VmWitness {
286 fn write_into<W: ByteWriter>(&self, target: &mut W) {
287 self.program_info.write_into(target);
288 self.stack_inputs.write_into(target);
289 self.stack_outputs.write_into(target);
290 self.trace.write_into(target);
291 self.precompile_root.write_into(target);
292 }
293}
294
295impl Deserializable for VmWitness {
296 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
297 Ok(Self {
298 program_info: ProgramInfo::read_from(source)?,
299 stack_inputs: StackInputs::read_from(source)?,
300 stack_outputs: StackOutputs::read_from(source)?,
301 trace: TraceReplay::read_from(source)?,
302 precompile_root: Digest::read_from(source)?,
303 })
304 }
305}
306
307fn write_precompile_witness<W: ByteWriter>(witness: &PrecompileWitness, target: &mut W) {
310 let roots = witness.roots();
311 debug_assert_eq!(roots.len(), 1, "only singleton precompile witnesses are serializable");
312 target.write_usize(roots.len());
313 for root in roots {
314 root.write_into(target);
315 }
316 let deferred_wire = witness
317 .state()
318 .to_wire()
319 .expect("deferred state must serialize to canonical wire");
320 deferred_wire.write_into(target);
321}
322
323fn read_precompile_witness<R: ByteReader>(
325 source: &mut R,
326) -> Result<PrecompileWitness, DeserializationError> {
327 let roots = Vec::<Digest>::read_from(source)?;
328 if roots.len() != 1 {
329 return Err(DeserializationError::InvalidValue(
330 "expected a singleton precompile witness".into(),
331 ));
332 }
333 let deferred_wire = DeferredStateWire::read_from(source)?;
334 let deferred_state =
335 DeferredState::from_wire(Arc::new(miden_precompiles::registry()), &deferred_wire).map_err(
336 |err| DeserializationError::InvalidValue(format!("invalid deferred state: {err}")),
337 )?;
338
339 let witness = PrecompileWitness::new(deferred_state).map_err(|err| {
340 DeserializationError::InvalidValue(format!("invalid precompile witness: {err}"))
341 })?;
342 if witness.roots() != roots.as_slice() {
343 return Err(DeserializationError::InvalidValue(
344 "precompile witness roots do not match its deferred state".into(),
345 ));
346 }
347 Ok(witness)
348}
349
350#[derive(Debug)]
361pub struct VmTrace {
362 main_trace: MainTrace,
363 program_info: ProgramInfo,
364 stack_inputs: StackInputs,
365 stack_outputs: StackOutputs,
366 precompile_root: Digest,
367 trace_len_summary: TraceLenSummary,
368}
369
370impl VmTrace {
371 pub(crate) fn new_from_parts(
375 program_info: ProgramInfo,
376 stack_inputs: StackInputs,
377 stack_outputs: StackOutputs,
378 precompile_root: Digest,
379 main_trace: MainTrace,
380 trace_len_summary: TraceLenSummary,
381 ) -> Self {
382 Self {
383 main_trace,
384 program_info,
385 stack_inputs,
386 stack_outputs,
387 precompile_root,
388 trace_len_summary,
389 }
390 }
391
392 pub fn program_info(&self) -> &ProgramInfo {
397 &self.program_info
398 }
399
400 pub fn program_hash(&self) -> &Word {
402 self.program_info.program_hash()
403 }
404
405 pub fn stack_outputs(&self) -> &StackOutputs {
407 &self.stack_outputs
408 }
409
410 pub fn public_inputs(&self) -> PublicInputs {
412 PublicInputs::new(
413 self.program_info.clone(),
414 self.stack_inputs,
415 self.stack_outputs,
416 self.precompile_root,
417 )
418 }
419
420 pub fn to_public_values(&self) -> Vec<Felt> {
422 self.public_inputs().to_elements()
423 }
424
425 pub fn main_trace(&self) -> &MainTrace {
427 &self.main_trace
428 }
429
430 pub fn main_trace_mut(&mut self) -> &mut MainTrace {
432 &mut self.main_trace
433 }
434
435 pub fn precompile_root(&self) -> Digest {
437 self.precompile_root
438 }
439
440 pub fn into_outputs(self) -> StackOutputs {
442 self.stack_outputs
443 }
444
445 pub fn init_stack_state(&self) -> StackInputs {
447 self.stack_inputs
448 }
449
450 pub fn last_stack_state(&self) -> StackOutputs {
452 let last_step = RowIndex::from(self.last_step());
453 let mut result = [ZERO; MIN_STACK_DEPTH];
454 for (i, result) in result.iter_mut().enumerate() {
455 *result = self.main_trace.stack_element(i, last_step);
456 }
457 result.into()
458 }
459
460 pub fn get_user_op_helpers_at(&self, clk: u32) -> [Felt; NUM_USER_OP_HELPERS] {
462 let mut result = [ZERO; NUM_USER_OP_HELPERS];
463 let row = RowIndex::from(clk);
464 for (i, result) in result.iter_mut().enumerate() {
465 *result = self.main_trace.helper_register(i, row);
466 }
467 result
468 }
469
470 pub fn get_trace_len(&self) -> usize {
472 self.main_trace.num_rows()
473 }
474
475 pub fn length(&self) -> usize {
477 self.get_trace_len()
478 }
479
480 pub fn trace_len_summary(&self) -> &TraceLenSummary {
482 &self.trace_len_summary
483 }
484
485 pub fn check_constraints(&self) {
499 let public_inputs = self.public_inputs();
500 let (core_matrix, chiplets_matrix, poseidon2_matrix) = self.main_trace.to_air_matrices();
501
502 let (public_values, aux_inputs) = public_inputs.to_air_inputs();
503
504 let statement =
505 Statement::<Felt, QuadFelt, _>::new(MidenMultiAir::new(), public_values, aux_inputs)
506 .expect("valid statement inputs");
507 let prover_statement =
508 ProverStatement::new(statement, vec![core_matrix, chiplets_matrix, poseidon2_matrix])
509 .expect("valid trace shapes");
510
511 let config = config::poseidon2_config(config::pcs_params(), config::RELATION_DIGEST);
514 debug::check_constraints(&prover_statement, config.challenger());
515 }
516
517 pub fn to_air_matrices(
519 &self,
520 ) -> (RowMajorMatrix<Felt>, RowMajorMatrix<Felt>, RowMajorMatrix<Felt>) {
521 self.main_trace.to_air_matrices()
522 }
523
524 pub fn into_air_matrices(
526 self,
527 ) -> (RowMajorMatrix<Felt>, RowMajorMatrix<Felt>, RowMajorMatrix<Felt>) {
528 self.main_trace.into_air_matrices()
529 }
530
531 fn last_step(&self) -> usize {
536 self.main_trace.core_height() - 1
537 }
538
539 #[cfg(any(test, feature = "testing"))]
540 pub fn get_column_range(&self, range: Range<usize>) -> Vec<Vec<Felt>> {
541 self.main_trace.get_column_range(range)
542 }
543}
544
545#[cfg(test)]
546mod wire_tests {
547 use miden_assembly::Assembler;
548 use miden_core::deferred::TRUE_DIGEST;
549
550 use super::{ExecutionWitness, Serializable};
551 use crate::{DefaultHost, FastProcessor, StackInputs, mast::MastNodeId};
552
553 fn execution_witness(source: &str) -> ExecutionWitness {
554 let program = Assembler::default()
555 .assemble_program("program", source)
556 .expect("program should compile")
557 .unwrap_program();
558 let mut host = DefaultHost::default();
559 FastProcessor::new(StackInputs::default())
560 .execute_for_proving_sync(&program, &mut host)
561 .expect("execution should produce a witness")
562 }
563
564 fn deferred_witness() -> ExecutionWitness {
565 execution_witness("begin log_deferred end")
566 }
567
568 fn deferred_witness_bytes() -> alloc::vec::Vec<u8> {
569 deferred_witness().to_bytes()
570 }
571
572 #[test]
573 fn witness_reports_precompile_state() {
574 let plain = execution_witness("begin push.1 drop end");
575 assert!(!plain.has_precompiles());
576
577 let deferred = execution_witness("begin log_deferred end");
578 assert!(deferred.has_precompiles());
579 }
580
581 #[test]
582 fn witness_wire_rejects_unsupported_version() {
583 let mut bytes = deferred_witness_bytes();
584 assert!(ExecutionWitness::read_from_bytes(&bytes).is_ok());
585
586 bytes[0] = bytes[0].wrapping_add(1);
589 let err = ExecutionWitness::read_from_bytes(&bytes)
590 .expect_err("witness with an unknown wire version should be rejected");
591 assert!(
592 format!("{err:?}").contains("unsupported execution witness wire version"),
593 "unexpected error: {err:?}"
594 );
595 }
596
597 #[test]
598 fn witness_wire_rejects_trailing_bytes() {
599 let mut bytes = deferred_witness_bytes();
600 bytes.push(0);
601
602 let err = ExecutionWitness::read_from_bytes(&bytes)
603 .expect_err("witness payload with trailing bytes should be rejected");
604 assert!(
605 format!("{err:?}").contains("extra bytes after execution witness payload"),
606 "unexpected error: {err:?}"
607 );
608
609 assert!(
610 ExecutionWitness::read_from_bytes_trusted(&bytes).is_ok(),
611 "the explicit trusted reader should preserve the old permissive behavior"
612 );
613 }
614
615 #[test]
616 fn witness_wire_accepts_large_minimally_encoded_continuation_stack() {
617 let mut witness = deferred_witness();
618 let continuation = &mut witness
619 .vm
620 .trace_replay_mut()
621 .core_trace_contexts
622 .first_mut()
623 .expect("witness should contain a trace fragment")
624 .continuation;
625 for _ in 0..4096 {
626 continuation.push_start_node(MastNodeId::from(0));
627 }
628
629 let bytes = witness.to_bytes();
630 let restored = ExecutionWitness::read_from_bytes(&bytes)
631 .expect("valid witness should fit its input-proportional allocation budget");
632
633 assert_eq!(restored.to_bytes(), bytes);
634 }
635
636 #[test]
637 fn witness_wire_rejects_mismatched_precompile_root() {
638 let bytes = deferred_witness_bytes();
639 let restored = ExecutionWitness::read_from_bytes(&bytes).expect("witness round trip");
640 let (vm, precompile) = restored.into_parts();
641 let precompile = precompile.expect("deferred execution should carry a precompile witness");
642 assert_ne!(vm.precompile_root, TRUE_DIGEST);
643
644 let tampered = ExecutionWitness {
647 vm: super::VmWitness { precompile_root: TRUE_DIGEST, ..vm },
648 precompile: Some(precompile),
649 };
650 let err = ExecutionWitness::read_from_bytes(&tampered.to_bytes())
651 .expect_err("tampered witness should be rejected");
652 assert!(
653 format!("{err:?}")
654 .contains("precompile witness root does not match the VM witness precompile root"),
655 "unexpected error: {err:?}"
656 );
657
658 assert!(ExecutionWitness::read_from_bytes(&bytes).is_ok());
660 }
661
662 #[test]
663 fn witness_wire_rejects_missing_precompile_witness() {
664 let bytes = deferred_witness_bytes();
665 let restored = ExecutionWitness::read_from_bytes(&bytes).expect("witness round trip");
666 let (vm, precompile) = restored.into_parts();
667 assert!(precompile.is_some(), "deferred execution should carry a precompile witness");
668
669 let stripped = ExecutionWitness { vm, precompile: None };
672 let err = ExecutionWitness::read_from_bytes(&stripped.to_bytes())
673 .expect_err("witness without its precompile half should be rejected");
674 assert!(
675 format!("{err:?}")
676 .contains("VM witness claims deferred work but no precompile witness is present"),
677 "unexpected error: {err:?}"
678 );
679 }
680}