use alloc::{vec, vec::Vec};
use miden_air::trace::RowIndex;
use miden_core::{
Felt,
mast::{BasicBlockNodeBuilder, MastForest},
operations::{Operation, opcodes},
program::Program,
};
use miden_processor::{DefaultHost, FastProcessor, StackInputs};
use crate::{
Prover,
repro_harness::{ReproTrace, core_row_mut},
};
fn set_ctx(trace: &mut ReproTrace, rows: impl Iterator<Item = usize>, ctx: Felt) {
for row in rows {
core_row_mut(&mut trace.core, row).system.ctx = ctx;
}
}
fn set_fn_hash(trace: &mut ReproTrace, rows: impl Iterator<Item = usize>, fn_hash: [Felt; 4]) {
for row in rows {
core_row_mut(&mut trace.core, row).system.fn_hash = fn_hash;
}
}
const FORGED_CTX: Felt = Felt::new_unchecked(7);
const FORGED_FN_HASH: [Felt; 4] = [
Felt::new_unchecked(11),
Felt::new_unchecked(22),
Felt::new_unchecked(33),
Felt::new_unchecked(44),
];
struct BasicRows {
continuation_end: usize,
noop: usize,
height: usize,
}
fn build_basic_harness() -> (ReproTrace, BasicRows) {
let operations = vec![
Operation::Push(Felt::new_unchecked(101)),
Operation::Noop,
Operation::Drop,
Operation::Noop,
];
let mut mast_forest = MastForest::new();
let basic_block_id =
BasicBlockNodeBuilder::new(operations).add_to_forest(&mut mast_forest).unwrap();
mast_forest.make_root(basic_block_id);
let program = Program::new(mast_forest.into(), basic_block_id);
let stack_values = (1..17).rev().map(Felt::new_unchecked).collect::<Vec<_>>();
let stack_inputs = StackInputs::new(&stack_values).unwrap();
let mut host = DefaultHost::default();
let (trace, precompile_witness) = FastProcessor::new(stack_inputs)
.execute_and_build_trace_sync(&program, &mut host, Prover::DEFAULT_MAX_PROVER_MEMORY_BYTES)
.unwrap();
assert!(precompile_witness.is_none());
let main = trace.main_trace();
let height = main.core_height();
let op_at = |row: usize| main.get_op_code(RowIndex::from(row));
let continuation_end = (0..height)
.find(|&row| {
let idx = RowIndex::from(row);
op_at(row) == Felt::from_u8(opcodes::END)
&& main.restores_caller_frame_flag(idx) == Felt::ZERO
})
.expect("the basic block must end with a continuation END");
let noop = (0..height)
.find(|&row| op_at(row) == Felt::from_u8(opcodes::NOOP))
.expect("the program must contain a NOOP");
assert!(noop < continuation_end);
assert_eq!(main.ctx(RowIndex::from(continuation_end)), Felt::ZERO);
assert_eq!(main.fn_hash(RowIndex::from(continuation_end)), [Felt::ZERO; 4]);
let repro = ReproTrace::new(&trace);
(repro, BasicRows { continuation_end, noop, height })
}
#[test]
fn honest_basic_block_trace_verifies() {
let (repro, _) = build_basic_harness();
assert!(repro.prove_and_verify_current().is_ok(), "the honest trace must verify");
}
#[test]
fn forged_context_at_continuation_end_is_rejected() {
let (mut repro, rows) = build_basic_harness();
set_ctx(&mut repro, (rows.continuation_end + 1)..rows.height, FORGED_CTX);
let result = repro.prove_and_verify_current();
assert!(
result.is_err(),
"the verifier must reject a context change across a continuation END: {result:?}"
);
}
#[test]
fn forged_context_at_ordinary_row_is_rejected() {
let (mut repro, rows) = build_basic_harness();
set_ctx(&mut repro, (rows.noop + 1)..rows.height, FORGED_CTX);
let result = repro.prove_and_verify_current();
assert!(
result.is_err(),
"the verifier must reject a context change on an ordinary (non-END) transition; \
if this passes, the harness is not exercising what these tests claim: {result:?}"
);
}
#[test]
fn forged_fn_hash_at_continuation_end_is_rejected() {
let (mut repro, rows) = build_basic_harness();
set_fn_hash(&mut repro, (rows.continuation_end + 1)..rows.height, FORGED_FN_HASH);
let result = repro.prove_and_verify_current();
assert!(
result.is_err(),
"the verifier must reject a function-digest change across a continuation END: {result:?}"
);
}
#[test]
fn forged_fn_hash_at_ordinary_row_is_rejected() {
let (mut repro, rows) = build_basic_harness();
set_fn_hash(&mut repro, (rows.noop + 1)..rows.height, FORGED_FN_HASH);
let result = repro.prove_and_verify_current();
assert!(
result.is_err(),
"the verifier must reject a function-digest change on an ordinary transition: {result:?}"
);
}
struct CallRows {
ordinary_in_callee: usize,
nested_continuation_end: usize,
call_end: usize,
}
fn build_call_harness() -> (ReproTrace, CallRows) {
let source = "
proc inner
push.1
if.true
push.7 drop
else
push.8 drop
end
push.9 drop
end
begin
call.inner
end
";
let program = miden_assembly::Assembler::default()
.assemble_program("program", source)
.unwrap()
.unwrap_program();
let mut host = DefaultHost::default();
let (trace, precompile_witness) = FastProcessor::new(StackInputs::default())
.execute_and_build_trace_sync(&program, &mut host, Prover::DEFAULT_MAX_PROVER_MEMORY_BYTES)
.unwrap();
assert!(precompile_witness.is_none());
let main = trace.main_trace();
let height = main.core_height();
let op_at = |row: usize| main.get_op_code(RowIndex::from(row));
let call_row = (0..height)
.find(|&row| op_at(row) == Felt::from_u8(opcodes::CALL))
.expect("program must contain a CALL");
let call_end = (call_row..height)
.find(|&row| {
op_at(row) == Felt::from_u8(opcodes::END)
&& main.restores_caller_frame_flag(RowIndex::from(row)) == Felt::ONE
})
.expect("the CALL block must have a caller-frame END row");
let callee_ctx = main.ctx(RowIndex::from(call_row + 1));
assert_ne!(callee_ctx, Felt::ZERO, "the callee must run in a non-root context");
assert_eq!(main.ctx(RowIndex::from(call_end + 1)), Felt::ZERO);
let nested_continuation_end = ((call_row + 1)..call_end)
.find(|&row| {
let idx = RowIndex::from(row);
op_at(row) == Felt::from_u8(opcodes::END)
&& main.restores_caller_frame_flag(idx) == Felt::ZERO
})
.expect("the callee body must contain a nested continuation END");
assert!(
nested_continuation_end + 1 < call_end,
"there must be callee rows after the nested continuation END for the forgery to matter"
);
let ordinary_in_callee = ((call_row + 1)..nested_continuation_end)
.find(|&row| op_at(row) != Felt::from_u8(opcodes::END))
.expect("the callee body must contain an ordinary row");
assert_eq!(main.ctx(RowIndex::from(ordinary_in_callee)), callee_ctx);
let repro = ReproTrace::new(&trace);
(
repro,
CallRows {
ordinary_in_callee,
nested_continuation_end,
call_end,
},
)
}
#[test]
fn honest_call_program_trace_verifies() {
let (repro, _) = build_call_harness();
assert!(repro.prove_and_verify_current().is_ok(), "the honest trace must verify");
}
#[test]
fn callee_reentering_caller_context_at_nested_continuation_end_is_rejected() {
let (mut repro, rows) = build_call_harness();
set_ctx(&mut repro, (rows.nested_continuation_end + 1)..=rows.call_end, Felt::ZERO);
let result = repro.prove_and_verify_current();
assert!(
result.is_err(),
"the verifier must reject a callee re-entering the caller's context at a nested \
continuation END (memory-isolation break): {result:?}"
);
}
#[test]
fn callee_reentering_caller_context_at_ordinary_row_is_rejected() {
let (mut repro, rows) = build_call_harness();
set_ctx(&mut repro, (rows.ordinary_in_callee + 1)..=rows.call_end, Felt::ZERO);
let result = repro.prove_and_verify_current();
assert!(
result.is_err(),
"the verifier must reject a context change on an ordinary callee transition: {result:?}"
);
}