pub mod columns;
use miden_crypto::stark::air::AirBuilder;
use p3_field::Dup;
use crate::{
CoreCols, MidenAirBuilder,
constraints::{constants::F_1, op_flags::OpFlags, utils::BoolNot},
};
pub fn enforce_main<AB>(
builder: &mut AB,
local: &CoreCols<AB::Var>,
next: &CoreCols<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: MidenAirBuilder,
{
{
let builder = &mut builder.when_first_row();
builder.assert_zero(local.system.clk);
builder.assert_zero(local.system.ctx);
for limb in local.system.fn_hash {
builder.assert_zero(limb);
}
}
{
builder.when_transition().assert_eq(next.system.clk, local.system.clk + F_1);
}
let f_call = op_flags.call();
let f_syscall = op_flags.syscall();
let f_dyncall = op_flags.dyncall();
let end_flags = local.decoder.end_block_flags();
let f_restore_caller_frame = op_flags.end() * end_flags.restores_caller_frame;
{
let ctx = local.system.ctx;
let ctx_next = next.system.ctx;
let clk = local.system.clk;
let call_dyncall_flag = f_call.dup() + f_dyncall.dup();
let change_ctx_flag =
f_call.dup() + f_syscall.dup() + f_dyncall.dup() + f_restore_caller_frame.dup();
let default_flag = change_ctx_flag.not();
builder.when(call_dyncall_flag).assert_eq(ctx_next, clk + F_1);
builder.when(f_syscall).assert_zero(ctx_next);
builder.when_transition().when(default_flag).assert_eq(ctx_next, ctx);
}
{
let f_load = f_call + f_dyncall;
let f_preserve = (f_load.dup() + f_restore_caller_frame).not();
{
let builder = &mut builder.when(f_load);
for i in 0..4 {
builder.assert_eq(next.system.fn_hash[i], local.decoder.hasher_state[i]);
}
}
builder
.when_transition()
.when(f_preserve)
.assert_eq_arrays(next.system.fn_hash, local.system.fn_hash);
}
}
#[cfg(test)]
mod tests {
use alloc::vec::Vec;
use miden_core::{
Felt,
field::{PrimeCharacteristicRing, QuadFelt},
operations::opcodes,
};
use miden_crypto::stark::{
air::{AirBuilder, ExtensionBuilder, PermutationAirBuilder, RowWindow},
matrix::RowMajorMatrix,
};
use super::enforce_main;
use crate::{
CoreCols,
constraints::{
op_flags::{OpFlags, generate_test_row},
system::columns::SystemCols,
},
trace::{AUX_TRACE_RAND_CHALLENGES, AUX_TRACE_WIDTH, TRACE_WIDTH},
};
struct ConstraintEvalBuilder {
main: RowMajorMatrix<Felt>,
aux: RowMajorMatrix<QuadFelt>,
randomness: Vec<QuadFelt>,
permutation_values: Vec<QuadFelt>,
periodic_values: Vec<Felt>,
preprocessed: RowWindow<'static, Felt>,
evaluations: Vec<QuadFelt>,
}
impl ConstraintEvalBuilder {
fn new() -> Self {
Self {
main: RowMajorMatrix::new(vec![Felt::ZERO; TRACE_WIDTH * 2], TRACE_WIDTH),
aux: RowMajorMatrix::new(
vec![QuadFelt::ZERO; AUX_TRACE_WIDTH * 2],
AUX_TRACE_WIDTH,
),
randomness: vec![QuadFelt::ZERO; AUX_TRACE_RAND_CHALLENGES],
permutation_values: vec![QuadFelt::ZERO; AUX_TRACE_WIDTH],
periodic_values: Vec::new(),
preprocessed: RowWindow::from_two_rows(&[], &[]),
evaluations: Vec::new(),
}
}
}
impl AirBuilder for ConstraintEvalBuilder {
type F = Felt;
type Expr = Felt;
type Var = Felt;
type PreprocessedWindow = RowWindow<'static, Felt>;
type MainWindow = RowMajorMatrix<Felt>;
type PublicVar = Felt;
type PeriodicVar = Felt;
fn main(&self) -> Self::MainWindow {
self.main.clone()
}
fn preprocessed(&self) -> &Self::PreprocessedWindow {
&self.preprocessed
}
fn is_first_row(&self) -> Self::Expr {
Felt::ONE
}
fn is_last_row(&self) -> Self::Expr {
Felt::ZERO
}
fn is_transition(&self) -> Self::Expr {
Felt::ONE
}
fn assert_zero<I: Into<Self::Expr>>(&mut self, x: I) {
self.evaluations.push(QuadFelt::from(x.into()));
}
fn public_values(&self) -> &[Self::PublicVar] {
&[]
}
fn periodic_values(&self) -> &[Self::PeriodicVar] {
&self.periodic_values
}
}
impl ExtensionBuilder for ConstraintEvalBuilder {
type EF = QuadFelt;
type ExprEF = QuadFelt;
type VarEF = QuadFelt;
fn assert_zero_ext<I>(&mut self, x: I)
where
I: Into<Self::ExprEF>,
{
self.evaluations.push(x.into());
}
}
impl PermutationAirBuilder for ConstraintEvalBuilder {
type MP = RowMajorMatrix<QuadFelt>;
type RandomVar = QuadFelt;
type PermutationVar = QuadFelt;
fn permutation(&self) -> Self::MP {
self.aux.clone()
}
fn permutation_randomness(&self) -> &[Self::RandomVar] {
&self.randomness
}
fn permutation_values(&self) -> &[Self::PermutationVar] {
&self.permutation_values
}
}
fn forged_system_state() -> SystemCols<Felt> {
SystemCols {
clk: Felt::ZERO,
ctx: Felt::new_unchecked(7),
fn_hash: [
Felt::new_unchecked(11),
Felt::new_unchecked(22),
Felt::new_unchecked(33),
Felt::new_unchecked(44),
],
}
}
#[test]
fn system_constraints_reject_nonzero_initial_context_and_fn_hash() {
let mut local = generate_test_row(0);
local.system = forged_system_state();
let mut next = generate_test_row(0);
next.system = forged_system_state();
next.system.clk = Felt::ONE;
let op_flags = OpFlags::new(&local.decoder, &local.stack, &next.decoder);
let mut builder = ConstraintEvalBuilder::new();
enforce_main(&mut builder, &local, &next, &op_flags);
assert!(
builder.evaluations.iter().any(|value| *value != QuadFelt::ZERO),
"system constraints should reject a forged initial context and function hash"
);
}
fn eval_system(local: &CoreCols<Felt>, next: &CoreCols<Felt>) -> Vec<QuadFelt> {
use crate::constraints::stack::test_utils::ConstraintEvalBuilder as SharedBuilder;
let op_flags = OpFlags::new(&local.decoder, &local.stack, &next.decoder);
let mut builder = SharedBuilder::new().with_row_flags(false, false, true);
enforce_main(&mut builder, local, next, &op_flags);
builder.evaluations
}
fn end_rows(restores_caller_frame: Felt) -> (CoreCols<Felt>, CoreCols<Felt>) {
let mut local = generate_test_row(opcodes::END.into());
local.decoder.hasher_state[6] = restores_caller_frame;
local.system.ctx = Felt::new_unchecked(7);
local.system.fn_hash = [Felt::new_unchecked(11); 4];
let mut next = generate_test_row(0);
next.system.clk = local.system.clk + Felt::ONE;
next.system.ctx = local.system.ctx;
next.system.fn_hash = local.system.fn_hash;
(local, next)
}
#[test]
fn continuation_end_preserves_context_and_fn_hash() {
let (local, next) = end_rows(Felt::ZERO);
assert!(
eval_system(&local, &next).iter().all(|v| *v == QuadFelt::ZERO),
"an ordinary END that preserves ctx/fn_hash must be accepted"
);
let mut changed_ctx = next.clone();
changed_ctx.system.ctx += Felt::ONE;
assert!(
eval_system(&local, &changed_ctx).iter().any(|v| *v != QuadFelt::ZERO),
"an ordinary END must not be able to change ctx"
);
let mut changed_hash = next;
changed_hash.system.fn_hash[0] += Felt::ONE;
assert!(
eval_system(&local, &changed_hash).iter().any(|v| *v != QuadFelt::ZERO),
"an ordinary END must not be able to change fn_hash"
);
}
#[test]
fn caller_frame_end_permits_restoration() {
let (local, next) = end_rows(Felt::ONE);
let mut restored = next;
restored.system.ctx = Felt::ZERO;
restored.system.fn_hash = [Felt::ZERO; 4];
assert!(
eval_system(&local, &restored).iter().all(|v| *v == QuadFelt::ZERO),
"a caller-frame END must permit ctx/fn_hash to be restored to the caller's values"
);
}
}