use alloc::{vec, vec::Vec};
use core::borrow::{Borrow, BorrowMut};
use miden_air::{CoreCols, PublicInputs, trace::RowIndex};
use miden_core::{
Felt,
deferred::TRUE_DIGEST,
mast::{BasicBlockNodeBuilder, MastForest},
operations::{Operation, opcodes},
program::{ExecutionClaim, Program, StackOutputs},
proof::{ExecutionProof, HashFunction, StarkProof, VmProof},
utils::{Matrix, RowMajorMatrix},
};
use miden_crypto::stark::verifier::VerifierError;
use miden_processor::{DefaultHost, FastProcessor, StackInputs};
use miden_verifier::{StarkVerificationError, VerificationError, Verifier};
use crate::{Prover, config, prove_stark};
fn core_row_mut(matrix: &mut RowMajorMatrix<Felt>, row: usize) -> &mut CoreCols<Felt> {
let width = matrix.width();
matrix.values[row * width..(row + 1) * width].borrow_mut()
}
fn core_row(matrix: &RowMajorMatrix<Felt>, row: usize) -> &CoreCols<Felt> {
let width = matrix.width();
matrix.values[row * width..(row + 1) * width].borrow()
}
#[test]
fn verifier_rejects_forged_overflow_pop_order() {
let operations = vec![
Operation::Push(Felt::new_unchecked(101)),
Operation::Push(Felt::new_unchecked(102)),
Operation::Noop,
Operation::Drop,
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());
assert_eq!(trace.precompile_root(), TRUE_DIGEST);
let main = trace.main_trace();
let push_rows = (0..main.core_height())
.filter(|&row| main.get_op_code(RowIndex::from(row)) == Felt::from_u8(opcodes::PUSH))
.collect::<Vec<_>>();
let drop_rows = (0..main.core_height())
.filter(|&row| main.get_op_code(RowIndex::from(row)) == Felt::from_u8(opcodes::DROP))
.collect::<Vec<_>>();
assert_eq!(push_rows, vec![1, 2]);
assert_eq!(drop_rows, vec![4, 6]);
let first_record_clk = main.clk(RowIndex::from(push_rows[0]));
let first_record_value = main.stack_element(15, RowIndex::from(push_rows[0]));
let first_record_prev = main.parent_overflow_address(RowIndex::from(push_rows[0]));
let second_record_clk = main.clk(RowIndex::from(push_rows[1]));
let second_record_value = main.stack_element(15, RowIndex::from(push_rows[1]));
let second_record_prev = main.parent_overflow_address(RowIndex::from(push_rows[1]));
assert_eq!(
(first_record_clk, first_record_value, first_record_prev),
(Felt::new_unchecked(1), Felt::new_unchecked(1), Felt::new_unchecked(0)),
);
assert_eq!(
(second_record_clk, second_record_value, second_record_prev),
(Felt::new_unchecked(2), Felt::new_unchecked(2), Felt::new_unchecked(1)),
);
let (mut core_matrix, chiplets_matrix, poseidon2_matrix) = main.to_air_matrices();
core_row_mut(&mut core_matrix, drop_rows[0]).stack.b1 = first_record_clk;
{
let row = core_row_mut(&mut core_matrix, drop_rows[0] + 1);
row.stack.top[15] = first_record_value;
row.stack.b1 = first_record_prev;
}
{
let row = core_row_mut(&mut core_matrix, drop_rows[1]);
row.stack.top[15] = first_record_value;
row.stack.b1 = second_record_clk;
}
for row_idx in (drop_rows[1] + 1)..core_matrix.height() {
let row = core_row_mut(&mut core_matrix, row_idx);
row.stack.top[14] = first_record_value;
row.stack.top[15] = second_record_value;
row.stack.b1 = if row_idx == drop_rows[1] + 1 {
second_record_prev
} else {
Felt::new_unchecked(0)
};
}
let first_drop = core_row(&core_matrix, drop_rows[0]);
let first_drop_next = core_row(&core_matrix, drop_rows[0] + 1);
assert_eq!(
(first_drop.stack.b1, first_drop_next.stack.top[15], first_drop_next.stack.b1),
(first_record_clk, first_record_value, first_record_prev),
);
let second_drop = core_row(&core_matrix, drop_rows[1]);
let second_drop_next = core_row(&core_matrix, drop_rows[1] + 1);
assert_eq!(
(second_drop.stack.b1, second_drop_next.stack.top[15], second_drop_next.stack.b1),
(second_record_clk, second_record_value, second_record_prev),
);
let mut output_elements =
core::array::from_fn(|i| trace.stack_outputs().get_element(i).unwrap());
output_elements.swap(14, 15);
let forged_outputs = StackOutputs::from(output_elements);
assert_ne!(forged_outputs, *trace.stack_outputs());
let public_inputs = PublicInputs::new(
trace.program_info().clone(),
trace.init_stack_state(),
forged_outputs,
trace.precompile_root(),
);
let (public_values, aux_inputs) = public_inputs.to_air_inputs();
let stark_config = config::poseidon2_config(config::pcs_params(), config::RELATION_DIGEST);
let proof_bytes = prove_stark(
&stark_config,
core_matrix,
chiplets_matrix,
poseidon2_matrix,
&public_values,
&aux_inputs,
)
.expect("the low-level prover should encode the forged trace for the verifier regression");
let proof = ExecutionProof::new(
VmProof {
proof: StarkProof::new(proof_bytes, HashFunction::Poseidon2),
precompile_root: TRUE_DIGEST,
},
miden_core::proof::PrecompileStatus::Empty,
);
let claim = ExecutionClaim::from_program_info(
trace.program_info().clone(),
trace.init_stack_state(),
forged_outputs,
);
let verification_result = Verifier::new().verify(&claim, &proof);
assert!(
matches!(
verification_result,
Err(VerificationError::StarkVerificationError(_, source))
if matches!(
*source,
StarkVerificationError::Verifier(VerifierError::ConstraintMismatch)
)
),
"the forged public output must fail an AIR constraint",
);
}