use alloc::{vec, vec::Vec};
use core::borrow::Borrow;
use miden_core::{
Felt,
mast::{BasicBlockNodeBuilder, LoopNodeBuilder, MastForest},
operations::Operation,
program::{Program, StackOutputs},
utils::{Matrix, RowMajorMatrix},
};
use miden_processor::{DefaultHost, FastProcessor, StackInputs};
use crate::{
Prover,
repro_harness::{ReproTrace, core_row_mut},
};
struct ForgedRepeatTrace {
repro: ReproTrace,
core: RowMajorMatrix<Felt>,
chiplets: RowMajorMatrix<Felt>,
poseidon2: RowMajorMatrix<Felt>,
forged_outputs: StackOutputs,
}
fn build_program(root_ops: Vec<Operation>) -> Program {
let mut mast_forest = MastForest::new();
let root = BasicBlockNodeBuilder::new(root_ops).add_to_forest(&mut mast_forest).unwrap();
mast_forest.make_root(root);
Program::new(mast_forest.into(), root)
}
fn build_loop_program(body_ops: Vec<Operation>) -> Program {
let mut mast_forest = MastForest::new();
let body = BasicBlockNodeBuilder::new(body_ops).add_to_forest(&mut mast_forest).unwrap();
let root = LoopNodeBuilder::new(body).add_to_forest(&mut mast_forest).unwrap();
mast_forest.make_root(root);
Program::new(mast_forest.into(), root)
}
fn execute(program: &Program, stack: &[u64]) -> miden_processor::trace::VmTrace {
let stack = stack.iter().map(|&v| Felt::new_unchecked(v)).collect::<Vec<_>>();
let mut host = DefaultHost::default();
let (trace, precompile_witness) = FastProcessor::new(StackInputs::new(&stack).unwrap())
.execute_and_build_trace_sync(program, &mut host, Prover::DEFAULT_MAX_PROVER_MEMORY_BYTES)
.unwrap();
assert!(precompile_witness.is_none());
trace
}
fn matrix_row(matrix: &RowMajorMatrix<Felt>, row: usize) -> Vec<Felt> {
let width = matrix.width();
matrix.values[row * width..(row + 1) * width].to_vec()
}
fn set_matrix_row(matrix: &mut RowMajorMatrix<Felt>, row: usize, values: &[Felt]) {
let width = matrix.width();
assert_eq!(values.len(), width);
matrix.values[row * width..(row + 1) * width].copy_from_slice(values);
}
fn copy_matrix_row(
dst: &mut RowMajorMatrix<Felt>,
dst_row: usize,
src: &RowMajorMatrix<Felt>,
src_row: usize,
) {
assert_eq!(dst.width(), src.width());
let values = matrix_row(src, src_row);
set_matrix_row(dst, dst_row, &values);
}
fn core_row(matrix: &RowMajorMatrix<Felt>, row: usize) -> &miden_air::CoreCols<Felt> {
let width = matrix.width();
matrix.values[row * width..(row + 1) * width].borrow()
}
fn set_core_clk(matrix: &mut RowMajorMatrix<Felt>, row: usize) {
core_row_mut(matrix, row).system.clk = Felt::from_u32(row as u32);
}
fn copy_body_hash_evidence(
chiplets: &mut RowMajorMatrix<Felt>,
poseidon2: &mut RowMajorMatrix<Felt>,
body_chiplets: &RowMajorMatrix<Felt>,
body_poseidon2: &RowMajorMatrix<Felt>,
) {
copy_matrix_row(chiplets, 2, body_chiplets, 2);
copy_matrix_row(chiplets, 3, body_chiplets, 3);
for row in 16..32 {
copy_matrix_row(poseidon2, row, body_poseidon2, row);
}
}
fn build_forged_repeat_injection_trace() -> ForgedRepeatTrace {
let victim = build_program(vec![Operation::Noop]);
let victim_trace = execute(&victim, &[1, 1]);
let body_ops = vec![Operation::Not];
let one_iteration_loop = build_loop_program(body_ops.clone());
let one_iteration_trace = execute(&one_iteration_loop, &[1]);
let two_iteration_loop = build_loop_program(body_ops);
let two_iteration_trace = execute(&two_iteration_loop, &[0, 1]);
let (mut core, mut chiplets, mut poseidon2) = victim_trace.main_trace().to_air_matrices();
let (repeat_core, ..) = two_iteration_trace.main_trace().to_air_matrices();
let (body_core, body_chiplets, body_poseidon2) =
one_iteration_trace.main_trace().to_air_matrices();
let victim_end = matrix_row(&core, 2);
copy_matrix_row(&mut core, 2, &repeat_core, 4);
for (dst, src) in [(3, 1), (4, 2), (5, 3)] {
copy_matrix_row(&mut core, dst, &body_core, src);
}
set_matrix_row(&mut core, 6, &victim_end);
for row in 2..=6 {
set_core_clk(&mut core, row);
}
let final_stack = core_row(&core, 5).stack.clone();
core_row_mut(&mut core, 6).stack = final_stack.clone();
for row in 7..core.height() {
core_row_mut(&mut core, row).stack = final_stack.clone();
}
let forged_outputs = StackOutputs::from(final_stack.top);
copy_body_hash_evidence(&mut chiplets, &mut poseidon2, &body_chiplets, &body_poseidon2);
ForgedRepeatTrace {
repro: ReproTrace::new(&victim_trace),
core,
chiplets,
poseidon2,
forged_outputs,
}
}
fn build_direct_repeat_without_body_hash_evidence_trace() -> ForgedRepeatTrace {
let victim = build_program(vec![Operation::Noop]);
let victim_trace = execute(&victim, &[1, 1]);
let two_iteration_loop = build_loop_program(vec![Operation::Not]);
let two_iteration_trace = execute(&two_iteration_loop, &[0, 1]);
let (mut core, chiplets, poseidon2) = victim_trace.main_trace().to_air_matrices();
let (repeat_core, ..) = two_iteration_trace.main_trace().to_air_matrices();
copy_matrix_row(&mut core, 2, &repeat_core, 4);
set_core_clk(&mut core, 2);
let final_stack = core_row(&core, 2).stack.clone();
for row in 3..core.height() {
core_row_mut(&mut core, row).stack = final_stack.clone();
}
let forged_outputs = StackOutputs::from(final_stack.top);
ForgedRepeatTrace {
repro: ReproTrace::new(&victim_trace),
core,
chiplets,
poseidon2,
forged_outputs,
}
}
fn build_body_hash_evidence_without_direct_repeat_trace() -> ForgedRepeatTrace {
let victim = build_program(vec![Operation::Noop]);
let victim_trace = execute(&victim, &[1, 1]);
let body = build_loop_program(vec![Operation::Not]);
let body_trace = execute(&body, &[1]);
let (core, mut chiplets, mut poseidon2) = victim_trace.main_trace().to_air_matrices();
let (_, body_chiplets, body_poseidon2) = body_trace.main_trace().to_air_matrices();
copy_body_hash_evidence(&mut chiplets, &mut poseidon2, &body_chiplets, &body_poseidon2);
ForgedRepeatTrace {
repro: ReproTrace::new(&victim_trace),
core,
chiplets,
poseidon2,
forged_outputs: *victim_trace.stack_outputs(),
}
}
#[test]
fn forged_in_span_repeat_injection_is_rejected_by_proof_pipeline() {
let forged = build_forged_repeat_injection_trace();
assert_ne!(forged.forged_outputs, forged.repro.outputs());
let result = forged.repro.prove_and_verify_parts_allowing_lookup_rejection(
forged.core,
forged.chiplets,
forged.poseidon2,
forged.forged_outputs,
);
assert!(
result.is_err(),
"the proof pipeline must reject an in-span row exiting directly to REPEAT: {result:?}"
);
}
#[test]
fn direct_repeat_without_body_hash_evidence_is_rejected() {
let forged = build_direct_repeat_without_body_hash_evidence_trace();
let result = forged.repro.prove_and_verify_parts_allowing_lookup_rejection(
forged.core,
forged.chiplets,
forged.poseidon2,
forged.forged_outputs,
);
assert!(
result.is_err(),
"the proof pipeline must reject the direct REPEAT edge when no matching body-hash evidence \
is supplied: {result:?}"
);
}
#[test]
fn body_hash_evidence_without_direct_repeat_is_rejected() {
let forged = build_body_hash_evidence_without_direct_repeat_trace();
let result = forged.repro.prove_and_verify_parts_allowing_lookup_rejection(
forged.core,
forged.chiplets,
forged.poseidon2,
forged.forged_outputs,
);
assert!(
result.is_err(),
"the proof pipeline must reject attacker body-hash evidence when no direct REPEAT edge \
consumes it: {result:?}"
);
}