use miden_core::field::PrimeCharacteristicRing;
use miden_crypto::stark::air::{AirBuilder, LiftedAirBuilder};
use crate::{
MainTraceRow,
constraints::{
op_flags::{ExprDecoderAccess, OpFlags},
tagging::{
TagGroup, TaggingAirBuilderExt, ids::TAG_DECODER_BASE, tagged_assert_zero,
tagged_assert_zero_integrity,
},
},
trace::decoder as decoder_cols,
};
pub mod bus;
#[cfg(test)]
pub mod tests;
const ADDR_OFFSET: usize = 0;
const OP_BITS_OFFSET: usize = 1;
const HASH_CYCLE_LEN: u64 = 32;
const NUM_OP_BITS: usize = 7;
const IN_SPAN_OFFSET: usize = 16;
const GROUP_COUNT_OFFSET: usize = 17;
const OP_INDEX_OFFSET: usize = 18;
const BATCH_FLAGS_OFFSET: usize = 19;
const NUM_BATCH_FLAGS: usize = 3;
const EXTRA_COLS_OFFSET: usize = 22;
#[allow(dead_code)]
pub const NUM_CONSTRAINTS: usize = 57;
const DECODER_BASE_ID: usize = TAG_DECODER_BASE;
const DECODER_NAMES: [&str; NUM_CONSTRAINTS] = [
"decoder.in_span.first_row",
"decoder.in_span.binary",
"decoder.in_span.span",
"decoder.in_span.respan",
"decoder.op_bits.b0.binary",
"decoder.op_bits.b1.binary",
"decoder.op_bits.b2.binary",
"decoder.op_bits.b3.binary",
"decoder.op_bits.b4.binary",
"decoder.op_bits.b5.binary",
"decoder.op_bits.b6.binary",
"decoder.extra.e0",
"decoder.extra.e1",
"decoder.op_bits.u32_prefix.b0",
"decoder.op_bits.very_high.b0",
"decoder.op_bits.very_high.b1",
"decoder.batch_flags.c0.binary",
"decoder.batch_flags.c1.binary",
"decoder.batch_flags.c2.binary",
"decoder.general.split_loop.s0.binary",
"decoder.general.dyn.h4.zero",
"decoder.general.dyn.h5.zero",
"decoder.general.dyn.h6.zero",
"decoder.general.dyn.h7.zero",
"decoder.general.repeat.s0.one",
"decoder.general.repeat.h4.one",
"decoder.general.end.loop.s0.zero",
"decoder.general.end_repeat.h0.carry",
"decoder.general.end_repeat.h1.carry",
"decoder.general.end_repeat.h2.carry",
"decoder.general.end_repeat.h3.carry",
"decoder.general.end_repeat.h4.carry",
"decoder.general.halt.next",
"decoder.group_count.delta.binary",
"decoder.group_count.decrement.h0_or_imm",
"decoder.group_count.span_decrement",
"decoder.group_count.end_or_respan.hold",
"decoder.group_count.end.zero",
"decoder.op_group.shift",
"decoder.op_group.end_or_respan.h0.zero",
"decoder.op_index.span_respan.reset",
"decoder.op_index.new_group.reset",
"decoder.op_index.increment",
"decoder.op_index.range",
"decoder.batch_flags.span_sum",
"decoder.batch_flags.zero_when_not_span",
"decoder.batch_flags.h4.zero",
"decoder.batch_flags.h5.zero",
"decoder.batch_flags.h6.zero",
"decoder.batch_flags.h7.zero",
"decoder.batch_flags.h2.zero",
"decoder.batch_flags.h3.zero",
"decoder.batch_flags.h1.zero",
"decoder.addr.hold_in_span",
"decoder.addr.respan.increment",
"decoder.addr.halt.zero",
"decoder.control_flow.sp_complement",
];
const DECODER_TAGS: TagGroup = TagGroup {
base: DECODER_BASE_ID,
names: &DECODER_NAMES,
};
const IN_SPAN_BASE: usize = 0;
const OP_BITS_BASE: usize = IN_SPAN_BASE + 4;
const EXTRA_BASE: usize = OP_BITS_BASE + NUM_OP_BITS;
const OP_BIT_GROUP_BASE: usize = EXTRA_BASE + 2;
const BATCH_FLAGS_BINARY_BASE: usize = OP_BIT_GROUP_BASE + 3;
const GENERAL_BASE: usize = BATCH_FLAGS_BINARY_BASE + NUM_BATCH_FLAGS;
const GROUP_COUNT_BASE: usize = GENERAL_BASE + 14;
const OP_GROUP_DECODING_BASE: usize = GROUP_COUNT_BASE + 5;
const OP_INDEX_BASE: usize = OP_GROUP_DECODING_BASE + 2;
const BATCH_FLAGS_BASE: usize = OP_INDEX_BASE + 4;
const ADDR_BASE: usize = BATCH_FLAGS_BASE + 9;
const CONTROL_FLOW_BASE: usize = ADDR_BASE + 3;
#[allow(dead_code)]
pub const CONSTRAINT_DEGREES: [usize; NUM_CONSTRAINTS] = [
2, 2, 2, 2, 2, 2, 2, 4, 3, 4, 3, 3, 2, 2, 2, 7, 6, 6, 6, 6, 5, 5, 6, 9, 9, 9, 9, 9, 8, 1, 2, 6, 5, 3, 8, 6, 5, 5, 6, 6, 6, 6, 7, 9, 5, 6, 4, 4, 4, 4, 4, 4, 4, 2, 5, 5, 5, ];
fn assert_binary<AB>(builder: &mut AB, idx: usize, value: AB::Expr)
where
AB: TaggingAirBuilderExt,
{
assert_zero_integrity(builder, idx, value.clone() * (value - AB::Expr::ONE));
}
fn op_bits_to_value<AB>(bits: &[AB::Expr; NUM_OP_BITS]) -> AB::Expr
where
AB: LiftedAirBuilder,
{
bits.iter().enumerate().fold(AB::Expr::ZERO, |acc, (i, bit)| {
acc + bit.clone() * AB::Expr::from_u16(1u16 << i)
})
}
pub fn enforce_main<AB>(
builder: &mut AB,
local: &MainTraceRow<AB::Var>,
next: &MainTraceRow<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let cols: DecoderColumns<AB::Expr> = DecoderColumns::from_row::<AB>(local);
let cols_next: DecoderColumns<AB::Expr> = DecoderColumns::from_row::<AB>(next);
let op_flags_next = OpFlags::new(ExprDecoderAccess::<AB::Var, AB::Expr>::new(next));
enforce_in_span_constraints(builder, &cols, &cols_next, op_flags);
enforce_op_bits_binary(builder, &cols);
enforce_extra_columns(builder, &cols);
enforce_op_bit_group_constraints(builder, &cols);
enforce_batch_flags_binary(builder, &cols);
enforce_general_constraints(builder, local, next, op_flags, &op_flags_next);
enforce_group_count_constraints(builder, &cols, &cols_next, local, op_flags, &op_flags_next);
enforce_op_group_decoding_constraints(
builder,
&cols,
&cols_next,
local,
next,
op_flags,
&op_flags_next,
);
enforce_op_index_constraints(builder, &cols, &cols_next, op_flags);
enforce_batch_flags_constraints(builder, &cols, local, op_flags);
enforce_block_address_constraints(builder, &cols, &cols_next, op_flags);
enforce_control_flow_constraints(builder, &cols, op_flags);
}
pub struct DecoderColumns<E> {
pub addr: E,
pub op_bits: [E; NUM_OP_BITS],
pub in_span: E,
pub group_count: E,
pub op_index: E,
pub batch_flags: [E; NUM_BATCH_FLAGS],
pub extra: [E; 2],
}
impl<E: Clone> DecoderColumns<E> {
pub fn from_row<AB>(row: &MainTraceRow<AB::Var>) -> Self
where
AB: LiftedAirBuilder,
AB::Var: Into<E> + Clone,
{
DecoderColumns {
addr: row.decoder[ADDR_OFFSET].clone().into(),
op_bits: core::array::from_fn(|i| row.decoder[OP_BITS_OFFSET + i].clone().into()),
in_span: row.decoder[IN_SPAN_OFFSET].clone().into(),
group_count: row.decoder[GROUP_COUNT_OFFSET].clone().into(),
op_index: row.decoder[OP_INDEX_OFFSET].clone().into(),
batch_flags: core::array::from_fn(|i| {
row.decoder[BATCH_FLAGS_OFFSET + i].clone().into()
}),
extra: core::array::from_fn(|i| row.decoder[EXTRA_COLS_OFFSET + i].clone().into()),
}
}
}
fn enforce_in_span_constraints<AB>(
builder: &mut AB,
cols: &DecoderColumns<AB::Expr>,
cols_next: &DecoderColumns<AB::Expr>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let sp = cols.in_span.clone();
let sp_next = cols_next.in_span.clone();
assert_zero_first_row(builder, IN_SPAN_BASE, sp.clone());
let sp_binary = sp.clone() * (sp - AB::Expr::ONE);
assert_zero_integrity(builder, IN_SPAN_BASE + 1, sp_binary);
let span_flag = op_flags.span();
assert_zero_transition(
builder,
IN_SPAN_BASE + 2,
span_flag * (AB::Expr::ONE - sp_next.clone()),
);
let respan_flag = op_flags.respan();
assert_zero_transition(builder, IN_SPAN_BASE + 3, respan_flag * (AB::Expr::ONE - sp_next));
}
fn enforce_op_bits_binary<AB>(builder: &mut AB, cols: &DecoderColumns<AB::Expr>)
where
AB: TaggingAirBuilderExt,
{
for i in 0..NUM_OP_BITS {
assert_binary(builder, OP_BITS_BASE + i, cols.op_bits[i].clone());
}
}
fn enforce_extra_columns<AB>(builder: &mut AB, cols: &DecoderColumns<AB::Expr>)
where
AB: TaggingAirBuilderExt,
{
let b4 = cols.op_bits[4].clone();
let b5 = cols.op_bits[5].clone();
let b6 = cols.op_bits[6].clone();
let e0 = cols.extra[0].clone();
let e1 = cols.extra[1].clone();
let expected_e0 = b6.clone() * (AB::Expr::ONE - b5.clone()) * b4;
assert_zero_integrity(builder, EXTRA_BASE, e0 - expected_e0);
let expected_e1 = b6 * b5;
assert_zero_integrity(builder, EXTRA_BASE + 1, e1 - expected_e1);
}
fn enforce_op_bit_group_constraints<AB>(builder: &mut AB, cols: &DecoderColumns<AB::Expr>)
where
AB: TaggingAirBuilderExt,
{
let b0 = cols.op_bits[0].clone();
let b1 = cols.op_bits[1].clone();
let b4 = cols.op_bits[4].clone();
let b5 = cols.op_bits[5].clone();
let b6 = cols.op_bits[6].clone();
let u32_prefix = b6.clone() * (AB::Expr::ONE - b5.clone()) * (AB::Expr::ONE - b4);
assert_zero_integrity(builder, OP_BIT_GROUP_BASE, u32_prefix * b0.clone());
let very_high_prefix = b6 * b5;
assert_zero_integrity(builder, OP_BIT_GROUP_BASE + 1, very_high_prefix.clone() * b0);
assert_zero_integrity(builder, OP_BIT_GROUP_BASE + 2, very_high_prefix * b1);
}
fn enforce_batch_flags_binary<AB>(builder: &mut AB, cols: &DecoderColumns<AB::Expr>)
where
AB: TaggingAirBuilderExt,
{
for i in 0..NUM_BATCH_FLAGS {
assert_binary(builder, BATCH_FLAGS_BINARY_BASE + i, cols.batch_flags[i].clone());
}
}
fn enforce_general_constraints<AB>(
builder: &mut AB,
local: &MainTraceRow<AB::Var>,
next: &MainTraceRow<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
op_flags_next: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let s0: AB::Expr = local.stack[0].clone().into();
let split_or_loop = op_flags.split() + op_flags.loop_op();
let s0_binary = s0.clone() * (s0.clone() - AB::Expr::ONE);
assert_zero_integrity(builder, GENERAL_BASE, split_or_loop * s0_binary);
let f_dyn = op_flags.dyn_op();
for i in 0..4 {
let hi: AB::Expr = local.decoder[decoder_cols::HASHER_STATE_OFFSET + 4 + i].clone().into();
assert_zero_integrity(builder, GENERAL_BASE + 1 + i, f_dyn.clone() * hi);
}
let f_repeat = op_flags.repeat();
let h4: AB::Expr = local.decoder[decoder_cols::IS_LOOP_BODY_FLAG_COL_IDX].clone().into();
assert_zero_integrity(
builder,
GENERAL_BASE + 5,
f_repeat.clone() * (AB::Expr::ONE - s0.clone()),
);
assert_zero_integrity(builder, GENERAL_BASE + 6, f_repeat * (AB::Expr::ONE - h4));
let f_end = op_flags.end();
let h5: AB::Expr = local.decoder[decoder_cols::IS_LOOP_FLAG_COL_IDX].clone().into();
assert_zero_integrity(builder, GENERAL_BASE + 7, f_end.clone() * h5 * s0);
let f_repeat_next = op_flags_next.repeat();
for i in 0..5 {
let hi: AB::Expr = local.decoder[decoder_cols::HASHER_STATE_OFFSET + i].clone().into();
let hi_next: AB::Expr = next.decoder[decoder_cols::HASHER_STATE_OFFSET + i].clone().into();
assert_zero_transition(
builder,
GENERAL_BASE + 8 + i,
f_end.clone() * f_repeat_next.clone() * (hi_next - hi),
);
}
let f_halt = op_flags.halt();
let f_halt_next = op_flags_next.halt();
assert_zero_transition(builder, GENERAL_BASE + 13, f_halt * (AB::Expr::ONE - f_halt_next));
}
fn enforce_group_count_constraints<AB>(
builder: &mut AB,
cols: &DecoderColumns<AB::Expr>,
cols_next: &DecoderColumns<AB::Expr>,
local: &MainTraceRow<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
op_flags_next: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let sp = cols.in_span.clone();
let gc = cols.group_count.clone();
let gc_next = cols_next.group_count.clone();
let h0: AB::Expr = local.decoder[decoder_cols::HASHER_STATE_OFFSET].clone().into();
let delta_gc = gc.clone() - gc_next;
let is_push = op_flags.push();
assert_zero_transition(
builder,
GROUP_COUNT_BASE,
sp.clone() * delta_gc.clone() * (delta_gc.clone() - AB::Expr::ONE),
);
assert_zero_transition(
builder,
GROUP_COUNT_BASE + 1,
sp.clone() * delta_gc.clone() * (AB::Expr::ONE - is_push.clone()) * h0,
);
let span_flag = op_flags.span();
let respan_flag = op_flags.respan();
assert_zero_transition(
builder,
GROUP_COUNT_BASE + 2,
(span_flag + respan_flag + is_push) * (delta_gc.clone() - AB::Expr::ONE),
);
let end_next = op_flags_next.end();
let respan_next = op_flags_next.respan();
assert_zero_transition(
builder,
GROUP_COUNT_BASE + 3,
delta_gc.clone() * (end_next + respan_next),
);
let end_flag = op_flags.end();
assert_zero_integrity(builder, GROUP_COUNT_BASE + 4, end_flag * gc);
}
fn enforce_op_group_decoding_constraints<AB>(
builder: &mut AB,
cols: &DecoderColumns<AB::Expr>,
cols_next: &DecoderColumns<AB::Expr>,
local: &MainTraceRow<AB::Var>,
next: &MainTraceRow<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
op_flags_next: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let sp = cols.in_span.clone();
let sp_next = cols_next.in_span.clone();
let gc = cols.group_count.clone();
let gc_next = cols_next.group_count.clone();
let delta_gc = gc - gc_next;
let f_span = op_flags.span();
let f_respan = op_flags.respan();
let is_push = op_flags.push();
let f_sgc = sp.clone() * sp_next * (AB::Expr::ONE - delta_gc.clone());
let h0: AB::Expr = local.decoder[decoder_cols::HASHER_STATE_OFFSET].clone().into();
let h0_next: AB::Expr = next.decoder[decoder_cols::HASHER_STATE_OFFSET].clone().into();
let op_next = op_bits_to_value::<AB>(&cols_next.op_bits);
let op_group_base = AB::Expr::from_u16(1u16 << 7);
let h0_shift = h0.clone() - h0_next * op_group_base - op_next;
assert_zero_transition(
builder,
OP_GROUP_DECODING_BASE,
(f_span + f_respan + is_push + f_sgc) * h0_shift,
);
let end_next = op_flags_next.end();
let respan_next = op_flags_next.respan();
assert_zero_transition(builder, OP_GROUP_DECODING_BASE + 1, sp * (end_next + respan_next) * h0);
}
fn enforce_op_index_constraints<AB>(
builder: &mut AB,
cols: &DecoderColumns<AB::Expr>,
cols_next: &DecoderColumns<AB::Expr>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let sp = cols.in_span.clone();
let sp_next = cols_next.in_span.clone();
let gc = cols.group_count.clone();
let gc_next = cols_next.group_count.clone();
let ox = cols.op_index.clone();
let ox_next = cols_next.op_index.clone();
let delta_gc = gc.clone() - gc_next;
let is_push = op_flags.push();
let ng = delta_gc - is_push;
let span_flag = op_flags.span();
let respan_flag = op_flags.respan();
assert_zero_transition(builder, OP_INDEX_BASE, (span_flag + respan_flag) * ox_next.clone());
assert_zero_transition(builder, OP_INDEX_BASE + 1, sp.clone() * ng.clone() * ox_next.clone());
let delta_ox = ox_next - ox.clone() - AB::Expr::ONE;
assert_zero_transition(
builder,
OP_INDEX_BASE + 2,
sp * sp_next * (AB::Expr::ONE - ng) * delta_ox,
);
let mut range_check = ox.clone();
for i in 1..=8u64 {
range_check *= ox.clone() - AB::Expr::from_u16(i as u16);
}
assert_zero_integrity(builder, OP_INDEX_BASE + 3, range_check);
}
fn enforce_batch_flags_constraints<AB>(
builder: &mut AB,
cols: &DecoderColumns<AB::Expr>,
local: &MainTraceRow<AB::Var>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let bc0 = cols.batch_flags[0].clone();
let bc1 = cols.batch_flags[1].clone();
let bc2 = cols.batch_flags[2].clone();
let f_g8 = bc0.clone();
let f_g4 = (AB::Expr::ONE - bc0.clone()) * bc1.clone() * (AB::Expr::ONE - bc2.clone());
let f_g2 = (AB::Expr::ONE - bc0.clone()) * (AB::Expr::ONE - bc1.clone()) * bc2.clone();
let f_g1 = (AB::Expr::ONE - bc0) * bc1 * bc2;
let f_span = op_flags.span();
let f_respan = op_flags.respan();
let span_or_respan = f_span + f_respan;
assert_zero_integrity(
builder,
BATCH_FLAGS_BASE,
span_or_respan.clone() - (f_g1.clone() + f_g2.clone() + f_g4.clone() + f_g8),
);
assert_zero_integrity(
builder,
BATCH_FLAGS_BASE + 1,
(AB::Expr::ONE - span_or_respan)
* (cols.batch_flags[0].clone()
+ cols.batch_flags[1].clone()
+ cols.batch_flags[2].clone()),
);
let small_batch = f_g1.clone() + f_g2.clone() + f_g4.clone();
for i in 0..4 {
let hi: AB::Expr = local.decoder[decoder_cols::HASHER_STATE_OFFSET + 4 + i].clone().into();
assert_zero_integrity(builder, BATCH_FLAGS_BASE + 2 + i, small_batch.clone() * hi);
}
let tiny_batch = f_g1.clone() + f_g2.clone();
for i in 0..2 {
let hi: AB::Expr = local.decoder[decoder_cols::HASHER_STATE_OFFSET + 2 + i].clone().into();
assert_zero_integrity(builder, BATCH_FLAGS_BASE + 6 + i, tiny_batch.clone() * hi);
}
let h1: AB::Expr = local.decoder[decoder_cols::HASHER_STATE_OFFSET + 1].clone().into();
assert_zero_integrity(builder, BATCH_FLAGS_BASE + 8, f_g1 * h1);
}
fn enforce_block_address_constraints<AB>(
builder: &mut AB,
cols: &DecoderColumns<AB::Expr>,
cols_next: &DecoderColumns<AB::Expr>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let sp = cols.in_span.clone();
let addr = cols.addr.clone();
let addr_next = cols_next.addr.clone();
assert_zero_transition(builder, ADDR_BASE, sp * (addr_next.clone() - addr.clone()));
let hash_cycle_len: AB::Expr = AB::Expr::from_u16(HASH_CYCLE_LEN as u16);
let respan_flag = op_flags.respan();
assert_zero_transition(
builder,
ADDR_BASE + 1,
respan_flag * (addr_next - addr.clone() - hash_cycle_len),
);
let halt_flag = op_flags.halt();
assert_zero_integrity(builder, ADDR_BASE + 2, halt_flag * addr);
}
fn enforce_control_flow_constraints<AB>(
builder: &mut AB,
cols: &DecoderColumns<AB::Expr>,
op_flags: &OpFlags<AB::Expr>,
) where
AB: TaggingAirBuilderExt,
{
let sp = cols.in_span.clone();
let ctrl_flag = op_flags.control_flow();
assert_zero_integrity(builder, CONTROL_FLOW_BASE, AB::Expr::ONE - sp - ctrl_flag);
}
fn assert_zero_transition<AB: TaggingAirBuilderExt>(builder: &mut AB, idx: usize, expr: AB::Expr) {
let mut idx = idx;
tagged_assert_zero(builder, &DECODER_TAGS, &mut idx, expr);
}
fn assert_zero_integrity<AB: TaggingAirBuilderExt>(builder: &mut AB, idx: usize, expr: AB::Expr) {
let mut idx = idx;
tagged_assert_zero_integrity(builder, &DECODER_TAGS, &mut idx, expr);
}
fn assert_zero_first_row<AB: TaggingAirBuilderExt>(builder: &mut AB, idx: usize, expr: AB::Expr) {
let id = DECODER_BASE_ID + idx;
let name = DECODER_NAMES[idx];
builder.tagged(id, name, |builder| {
builder.when_first_row().assert_zero(expr);
});
}