Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#### Features

- Added trusted trace proving input serialization for remote proving ([#3314](https://github.com/0xMiden/miden-vm/pull/3314)).
- Added round-trip and proptest coverage for trusted trace proving input serialization ([#3315](https://github.com/0xMiden/miden-vm/pull/3315)).

## v0.29.0 (2026-08-04)

Expand Down
2 changes: 1 addition & 1 deletion air/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ default = ["std"]
arbitrary = ["std", "dep:proptest"]
std = ["miden-core/std", "proptest?/std", "thiserror/std"]
concurrent = ["std"]
testing = []
testing = ["arbitrary"]

[dependencies]
# Miden dependencies
Expand Down
2 changes: 1 addition & 1 deletion crates/assembly/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ std = [
"tempfile/getrandom",
"thiserror/std",
]
testing = ["logging", "miden-assembly-syntax/testing"]
testing = ["arbitrary", "logging", "miden-assembly-syntax/testing"]
logging = ["dep:env_logger"]

[dependencies]
Expand Down
1 change: 1 addition & 0 deletions crates/debug-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ std = [
]
arbitrary = ["std", "dep:proptest"]
serde = ["dep:serde", "dep:serde_spanned", "serde_spanned?/serde"]
testing = ["arbitrary"]

[dependencies]
# Miden dependencies
Expand Down
1 change: 1 addition & 0 deletions crates/mast-package/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ default = ["std"]
arbitrary = ["std", "dep:proptest", "dep:proptest-derive", "miden-assembly-syntax/arbitrary", "miden-core/arbitrary"]
std = ["miden-assembly-syntax/std", "miden-core/std", "miden-utils-indexing/std", "rustc-hash/std", "serde?/std", "thiserror/std"]
serde = ["dep:serde", "miden-assembly-syntax/serde", "miden-core/serde", "miden-utils-indexing/serde"]
testing = ["arbitrary"]

[dependencies]
# Miden dependencies
Expand Down
1 change: 1 addition & 0 deletions crates/package-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ arbitrary = ["std", "dep:proptest", "miden-mast-package/arbitrary"]
resolver = ["std", "dep:pubgrub", "dep:smallvec"]
std = ["miden-assembly-syntax/std", "miden-core/std", "proptest?/std", "serde?/std"]
serde = ["dep:serde", "miden-assembly-syntax/serde", "miden-core/serde", "miden-mast-package/serde"]
testing = ["arbitrary"]

[dependencies]
miden-assembly-syntax.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/project/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ arbitrary = ["std", "dep:proptest", "dep:proptest-derive", "miden-assembly-synta
resolver = ["std", "miden-package-registry/resolver"]
std = ["miden-assembly-syntax/std", "miden-package-registry/std", "miden-package-registry/resolver", "proptest?/std", "serde?/std", "tempfile/getrandom", "thiserror/std", "toml/std"]
serde = ["dep:serde", "dep:serde-untagged", "miden-assembly-syntax/serde", "miden-core/serde", "miden-package-registry/serde", "toml/serde", "toml/parse", "toml/display"]
testing = ["arbitrary"]

[dependencies]
miden-assembly-syntax.workspace = true
Expand Down
1 change: 1 addition & 0 deletions crates/utils-indexing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ arbitrary = ["std", "dep:proptest"]
default = ["std"]
std = ["proptest?/std"]
serde = ["dep:serde"]
testing = ["arbitrary"]

[dependencies]
# Required dependencies
Expand Down
141 changes: 132 additions & 9 deletions miden-vm/tests/integration/prove_verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,18 +237,24 @@ mod fast_parallel {

use miden_assembly::{Assembler, DefaultSourceManager};
use miden_core::{
Word,
mast::{
BasicBlockNodeBuilder, ExternalNodeBuilder, JoinNodeBuilder, MastForest, MastNodeExt,
},
operations::Operation,
program::ExecutionClaim,
proof::{DeferredProof, ExecutionProof, HashFunction},
};
use miden_processor::{
DefaultHost, ExecutionOptions, FastProcessor, StackInputs, advice::AdviceInputs,
trace::build_trace,
DefaultHost, ExecutionOptions, FastProcessor, HostLibrary, StackInputs,
advice::AdviceInputs, trace::build_trace,
};
use miden_prover::{
ProvingOptions, TraceProvingInputs, config, prove_from_trace_sync,
prove_partial_from_trace_sync, prove_stark,
serde::{Deserializable, Serializable},
};
use miden_verifier::verify;
use miden_verifier::{VerificationError, Verifier, verify};
use miden_vm::{Program, TraceBuildInputs};

/// Default fragment size for parallel trace generation
Expand Down Expand Up @@ -276,6 +282,38 @@ mod fast_parallel {
DefaultHost::default().with_source_manager(Arc::new(DefaultSourceManager::default()))
}

fn create_simple_library() -> HostLibrary {
let mut mast_forest = MastForest::new();
let swap_block = BasicBlockNodeBuilder::new(vec![Operation::Swap, Operation::Swap])
.add_to_forest(&mut mast_forest)
.unwrap();
mast_forest.make_root(swap_block);
HostLibrary::from(Arc::new(mast_forest))
}

fn external_lib_proc_digest() -> Word {
let mut forest = MastForest::new();
let swap_block = BasicBlockNodeBuilder::new(vec![Operation::Swap, Operation::Swap])
.add_to_forest(&mut forest)
.unwrap();
forest.get_node_by_id(swap_block).unwrap().digest()
}

fn external_program() -> Program {
let mut program = MastForest::new();
let basic_block = BasicBlockNodeBuilder::new(vec![Operation::Pad, Operation::Drop])
.add_to_forest(&mut program)
.unwrap();
let external_node = ExternalNodeBuilder::new(external_lib_proc_digest())
.add_to_forest(&mut program)
.unwrap();
let root = JoinNodeBuilder::new([basic_block, external_node])
.add_to_forest(&mut program)
.unwrap();
program.make_root(root);
Program::new(Arc::new(program), root)
}

/// Test that proves and verifies using the fast processor + parallel trace generation path.
/// This verifies the complete code path works end-to-end.
///
Expand Down Expand Up @@ -372,7 +410,79 @@ mod fast_parallel {
}

#[test]
fn test_prove_from_trace_sync_preserves_deferred_wire() {
fn test_trace_proving_inputs_round_trip_proves_external_library_program() {
std::thread::Builder::new()
.name("trace-proving-inputs-round-trip".into())
.stack_size(8 * 1024 * 1024)
.spawn(trace_proving_inputs_round_trip_proves_external_library_program)
.expect("failed to spawn round-trip test thread")
.join()
.expect("round-trip test thread panicked");
}

fn trace_proving_inputs_round_trip_proves_external_library_program() {
let program = external_program();
let stack_inputs = StackInputs::default();
let advice_inputs = AdviceInputs::default();
let mut host = default_source_manager_host();
host.load_library(create_simple_library())
.expect("failed to load test library into host");
let trace_inputs =
execute_parallel_trace_inputs(&program, stack_inputs, advice_inputs, &mut host);

let trace_inputs_bytes = trace_inputs.to_bytes();
let original_trace = build_trace(trace_inputs).expect("original trace inputs build trace");
let restored_trace_inputs = TraceBuildInputs::read_from_bytes(&trace_inputs_bytes)
.expect("trace inputs round trip");
assert!(
restored_trace_inputs.trace_generation_context().mast_forest_store.len() > 1,
"expected dynamic library execution to serialize multiple MAST forests"
);
let restored_trace =
build_trace(restored_trace_inputs).expect("restored trace inputs build trace");
assert_eq!(restored_trace.stack_outputs(), original_trace.stack_outputs());
assert_eq!(restored_trace.program_info(), original_trace.program_info());
assert_eq!(restored_trace.trace_len_summary(), original_trace.trace_len_summary());
assert_eq!(
restored_trace.public_inputs().to_air_inputs(),
original_trace.public_inputs().to_air_inputs()
);

let trace_inputs = TraceBuildInputs::read_from_bytes(&trace_inputs_bytes)
.expect("trace inputs round trip");
let proving_inputs = TraceProvingInputs::new(
trace_inputs,
ProvingOptions::with_96_bit_security(HashFunction::Blake3_256),
);
let proving_inputs_bytes = proving_inputs.to_bytes();
let proving_inputs_budget =
proving_inputs_bytes.len().checked_mul(4).expect("test input budget overflow");
let mut proving_inputs_with_trailing_byte = proving_inputs_bytes.clone();
proving_inputs_with_trailing_byte.push(0);
let err = TraceProvingInputs::read_from_bytes_with_budget(
&proving_inputs_with_trailing_byte,
proving_inputs_with_trailing_byte
.len()
.checked_mul(4)
.expect("test input budget overflow"),
)
.unwrap_err();
assert!(err.to_string().contains("TraceProvingInputs payload has trailing bytes"));
let restored_proving_inputs = TraceProvingInputs::read_from_bytes_with_budget(
&proving_inputs_bytes,
proving_inputs_budget,
)
.expect("trace proving inputs round trip");

let (stack_outputs, proof) =
prove_from_trace_sync(restored_proving_inputs).expect("prove_from_trace_sync failed");

let claim = ExecutionClaim::from_program_info(program.into(), stack_inputs, stack_outputs);
verify(proof, claim).expect("Verification failed");
}

#[test]
fn test_prove_partial_from_trace_sync_preserves_deferred_wire() {
let source = "begin log_deferred end";
let program = Assembler::default()
.assemble_program("program", source)
Expand All @@ -383,6 +493,7 @@ mod fast_parallel {
let mut host = default_source_manager_host();
let trace_inputs =
execute_parallel_trace_inputs(&program, stack_inputs, advice_inputs, &mut host);
let expected_deferred_root = trace_inputs.deferred_state().root();
let expected_wire = trace_inputs
.deferred_state()
.to_wire()
Expand All @@ -398,13 +509,25 @@ mod fast_parallel {
))
.expect("prove_partial_from_trace_sync failed");

assert_eq!(proof.deferred_proof().as_wire(), Some(&expected_wire));
assert!(!proof.is_final());
assert_eq!(proof.deferred_proof(), &DeferredProof::Wire(expected_wire.clone()));
let claim = ExecutionClaim::from_program_info(program.into(), stack_inputs, stack_outputs);
let (_, pending) = miden_verifier::Verifier::new()
let err = Verifier::new().verify(proof.clone(), claim.clone()).unwrap_err();
assert!(
matches!(err, VerificationError::UnsupportedDeferredProof),
"wire-backed partial proofs should be rejected by final verification, got {err:?}"
);

let (security_level, pending) = Verifier::new()
.verify_partial(proof, claim)
.expect("partial verification failed");
assert_ne!(pending.root(), miden_core::deferred::TRUE_DIGEST);
let _state = pending.into_state();
.expect("wire-backed partial proof should verify and hydrate deferred state");
assert_eq!(security_level, 96);
assert_eq!(pending.root(), expected_deferred_root);
let hydrated_state = pending.into_state();
assert_eq!(
hydrated_state.to_wire().expect("hydrated state should serialize to wire"),
expected_wire
);
}
}

Expand Down
3 changes: 3 additions & 0 deletions processor/src/trace/trace_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2154,3 +2154,6 @@ mod tests {

#[cfg(feature = "arbitrary")]
mod arbitrary;

#[cfg(test)]
mod serialization_tests;
121 changes: 121 additions & 0 deletions processor/src/trace/trace_state/serialization_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
use alloc::vec::Vec;

use miden_core::serde::{BudgetedReader, SliceReader};

use super::*;
use crate::mast::MastForestId;

#[test]
fn memory_replay_queues_reject_oversized_lengths_before_allocation() {
let mut bytes = Vec::new();
usize::MAX.write_into(&mut bytes);

let mut reader = BudgetedReader::new(SliceReader::new(&bytes), bytes.len());
let err = read_memory_element_queue(&mut reader).unwrap_err();
assert!(err.to_string().contains("exceeds reader allocation bound"));

let mut reader = BudgetedReader::new(SliceReader::new(&bytes), bytes.len());
let err = read_memory_word_queue(&mut reader).unwrap_err();
assert!(err.to_string().contains("exceeds reader allocation bound"));
}

#[test]
fn stack_state_read_rejects_depth_below_minimum() {
let mut bytes = Vec::new();
[ZERO; MIN_STACK_DEPTH].write_into(&mut bytes);
(MIN_STACK_DEPTH - 1).write_into(&mut bytes);
ZERO.write_into(&mut bytes);

let err = StackState::read_from_bytes(&bytes).unwrap_err();
assert!(err.to_string().contains("below minimum"));
}

#[test]
fn execution_replay_round_trip_preserves_replay_order() {
let mut replay = ExecutionReplay::default();
replay.block_stack.record_node_start_parent_addr(Felt::from_u32(7));
replay
.block_stack
.record_node_end(Felt::from_u32(9), Felt::from_u32(8), Felt::from_u32(7));
replay.execution_context.record_execution_context(ExecutionContextSystemInfo {
parent_ctx: ContextId::from(3),
parent_fn_hash: Word::from([ONE, ZERO, ZERO, ZERO]),
});
replay
.stack_overflow
.record_pop_overflow(Felt::from_u32(11), Felt::from_u32(10));
replay
.stack_overflow
.record_restore_context_overflow_addr(17, Felt::from_u32(16));
replay.memory_reads.record_read_element(
Felt::from_u32(21),
Felt::from_u32(20),
ContextId::from(4),
RowIndex::from(5_u32),
);
replay.memory_reads.record_read_word(
Word::from([Felt::from_u32(1), Felt::from_u32(2), Felt::from_u32(3), Felt::from_u32(4)]),
Felt::from_u32(24),
ContextId::from(4),
RowIndex::from(6_u32),
);
replay.advice.record_pop_stack(Felt::from_u32(31));
replay
.advice
.record_pop_stack_word(Word::from([Felt::from_u32(5), ZERO, ZERO, ZERO]));
replay.advice.record_pop_stack_dword([
Word::from([Felt::from_u32(6), ZERO, ZERO, ZERO]),
Word::default(),
]);
replay.hasher.record_permute(Felt::from_u32(40), [Felt::from_u32(41); 12]);
replay.block_address.record_block_address(Felt::from_u32(50));
replay
.mast_forest_resolution
.record_resolution(MastNodeId::from(2), MastForestId::from(1));

let bytes = replay.to_bytes();
let mut restored = ExecutionReplay::read_from_bytes(&bytes).unwrap();

assert_eq!(restored.block_stack.replay_node_start_parent_addr().unwrap(), Felt::from_u32(7));
let node_end = restored.block_stack.replay_node_end().unwrap();
assert_eq!(node_end.ended_node_addr, Felt::from_u32(9));
assert_eq!(node_end.prev_addr, Felt::from_u32(8));
assert_eq!(node_end.prev_parent_addr, Felt::from_u32(7));
let ctx = restored.execution_context.replay_execution_context().unwrap();
assert_eq!(ctx.parent_ctx, ContextId::from(3));
assert_eq!(ctx.parent_fn_hash, Word::from([ONE, ZERO, ZERO, ZERO]));
assert_eq!(
restored.stack_overflow.replay_pop_overflow().unwrap(),
(Felt::from_u32(11), Felt::from_u32(10))
);
assert_eq!(
restored.stack_overflow.replay_restore_context_overflow_addr().unwrap(),
(17, Felt::from_u32(16))
);
assert_eq!(
restored.memory_reads.replay_read_element(Felt::from_u32(20)).unwrap(),
Felt::from_u32(21)
);
assert_eq!(
restored.memory_reads.replay_read_word(Felt::from_u32(24)).unwrap(),
Word::from([Felt::from_u32(1), Felt::from_u32(2), Felt::from_u32(3), Felt::from_u32(4)])
);
assert_eq!(restored.advice.replay_pop_stack().unwrap(), Felt::from_u32(31));
assert_eq!(
restored.advice.replay_pop_stack_word().unwrap(),
Word::from([Felt::from_u32(5), ZERO, ZERO, ZERO])
);
assert_eq!(
restored.advice.replay_pop_stack_dword().unwrap(),
[Word::from([Felt::from_u32(6), ZERO, ZERO, ZERO]), Word::default()]
);
assert_eq!(
restored.hasher.replay_permute().unwrap(),
(Felt::from_u32(40), [Felt::from_u32(41); 12])
);
assert_eq!(restored.block_address.replay_block_address().unwrap(), Felt::from_u32(50));
assert_eq!(
restored.mast_forest_resolution.replay_resolution().unwrap(),
(MastNodeId::from(2), MastForestId::from(1))
);
}
Loading