pub mod message;
pub mod trace;
use alloc::vec::Vec;
use core::array;
pub use message::ChunkChainMsg;
use miden_core::{
Felt,
deferred::Tag,
field::{PrimeCharacteristicRing, QuadFelt},
utils::RowMajorMatrix,
};
use miden_lifted_air::{AirBuilder, BaseAir, LiftedAir, LiftedAirBuilder};
use crate::{
hash::memory64::{CHUNK_ADDR_BASE, Memory64Msg},
logup::{
CyclicConstraintLookupBuilder, Deg, LookupAir, LookupBatch, LookupBuilder, LookupColumn,
LookupGroup, NUM_PUBLIC_VALUES, NUM_RANDOMNESS, NUM_SIGMA_VALUES, frac_col,
},
relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS},
transcript::poseidon2::Poseidon2InMsg,
utils::{current_main, next_main},
};
pub const COL_CHUNK_SEQ_ID: usize = 0;
pub const COL_PERM_SEQ_ID: usize = 1;
pub const COL_ACT: usize = 2;
pub const COL_IS_HEAD: usize = 3;
pub const COL_F_BEGIN: usize = 4;
pub const NUM_F: usize = 8;
pub const COL_F_END: usize = COL_F_BEGIN + NUM_F;
pub const NUM_MAIN_COLS: usize = COL_F_END;
pub const NUM_AUX_COLS: usize = 5;
const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 2, 2, 1];
#[derive(Debug, Default, Clone, Copy)]
pub struct ChunkAir;
impl BaseAir<Felt> for ChunkAir {
fn width(&self) -> usize {
NUM_MAIN_COLS
}
fn num_public_values(&self) -> usize {
NUM_PUBLIC_VALUES
}
}
impl LiftedAir<Felt, QuadFelt> for ChunkAir {
fn num_randomness(&self) -> usize {
NUM_RANDOMNESS
}
fn aux_width(&self) -> usize {
NUM_AUX_COLS
}
fn num_aux_values(&self) -> usize {
NUM_SIGMA_VALUES
}
fn build_aux_trace(
&self,
main: &RowMajorMatrix<Felt>,
_air_inputs: &[Felt],
_aux_inputs: &[Felt],
challenges: &[QuadFelt],
) -> (RowMajorMatrix<QuadFelt>, Vec<QuadFelt>) {
trace::build_aux(main, challenges)
}
fn eval<AB: LiftedAirBuilder<F = Felt>>(&self, builder: &mut AB) {
let local: [AB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0);
let next: [AB::Var; NUM_MAIN_COLS] = next_main(builder.main(), 0);
let chunk_seq_id: AB::Expr = local[COL_CHUNK_SEQ_ID].into();
let chunk_seq_id_next: AB::Expr = next[COL_CHUNK_SEQ_ID].into();
let perm_seq_id: AB::Expr = local[COL_PERM_SEQ_ID].into();
let perm_seq_id_next: AB::Expr = next[COL_PERM_SEQ_ID].into();
let act: AB::Expr = local[COL_ACT].into();
let act_next: AB::Expr = next[COL_ACT].into();
let is_head: AB::Expr = local[COL_IS_HEAD].into();
let is_head_next: AB::Expr = next[COL_IS_HEAD].into();
builder.when_first_row().assert_zero(chunk_seq_id.clone());
builder
.when_transition()
.assert_zero(chunk_seq_id_next - chunk_seq_id - AB::Expr::ONE);
builder.when_transition().assert_zero(
(AB::Expr::ONE - is_head_next) * (perm_seq_id_next - perm_seq_id - AB::Expr::ONE),
);
builder.assert_bool(local[COL_ACT]);
builder.when_transition().assert_zero((AB::Expr::ONE - act.clone()) * act_next);
builder.assert_bool(local[COL_IS_HEAD]);
builder.assert_zero(is_head * (AB::Expr::ONE - act));
let mut lb =
CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0);
<Self as LookupAir<_>>::eval(self, &mut lb);
}
}
impl<LB> LookupAir<LB> for ChunkAir
where
LB: LookupBuilder<F = Felt>,
{
fn num_columns(&self) -> usize {
NUM_AUX_COLS
}
fn column_shape(&self) -> &[usize] {
&COLUMN_SHAPE
}
fn max_message_width(&self) -> usize {
MAX_MESSAGE_WIDTH
}
fn num_bus_ids(&self) -> usize {
NUM_BUS_IDS
}
fn eval(&self, builder: &mut LB) {
let local: [LB::Var; NUM_MAIN_COLS] = current_main(builder.main(), 0);
let chunk_seq_id: LB::Expr = local[COL_CHUNK_SEQ_ID].into();
let perm_seq_id: LB::Expr = local[COL_PERM_SEQ_ID].into();
let act: LB::Expr = local[COL_ACT].into();
let is_head: LB::Expr = local[COL_IS_HEAD].into();
let f: [LB::Expr; NUM_F] = array::from_fn(|i| local[COL_F_BEGIN + i].into());
let chunk_addr_base =
Felt::new(CHUNK_ADDR_BASE).expect("CHUNK_ADDR_BASE fits in canonical Goldilocks");
let addr0 =
LB::Expr::from(chunk_addr_base) + LB::Expr::from(Felt::from(4u8)) * chunk_seq_id;
let addr1 = addr0.clone() + LB::Expr::ONE;
let addr2 = addr0.clone() + LB::Expr::from(Felt::from(2u8));
let addr3 = addr0.clone() + LB::Expr::from(Felt::from(3u8));
let neg_act: LB::Expr = LB::Expr::ZERO - act.clone();
let pos_act: LB::Expr = act.clone();
let pos_act_head: LB::Expr = act * is_head;
let rate0_chunk = [f[0].clone(), f[1].clone(), f[2].clone(), f[3].clone()];
let rate1_chunk = [f[4].clone(), f[5].clone(), f[6].clone(), f[7].clone()];
let cap_chunk = Tag::CHUNKS.as_word().map(LB::Expr::from);
let interaction_deg = Deg { v: 1, u: 1 };
let provides_deg = Deg { v: 1, u: 2 };
let pair_deg = Deg { v: 3, u: 2 };
frac_col!(
builder,
"memory64",
provides_deg,
(
"lane0",
neg_act.clone(),
Memory64Msg {
addr: addr0,
lo: f[0].clone(),
hi: f[1].clone()
},
interaction_deg
),
);
frac_col!(
builder,
"memory64",
pair_deg,
(
"lane1",
neg_act.clone(),
Memory64Msg {
addr: addr1,
lo: f[2].clone(),
hi: f[3].clone()
},
interaction_deg
),
(
"lane2",
neg_act.clone(),
Memory64Msg {
addr: addr2,
lo: f[4].clone(),
hi: f[5].clone()
},
interaction_deg
),
);
frac_col!(
builder,
"chunk-flatten",
pair_deg,
(
"lane3",
neg_act,
Memory64Msg {
addr: addr3,
lo: f[6].clone(),
hi: f[7].clone()
},
interaction_deg
),
(
"rate0",
pos_act.clone(),
Poseidon2InMsg::rate0(perm_seq_id.clone(), rate0_chunk),
interaction_deg
),
);
frac_col!(
builder,
"poseidon2-in",
pair_deg,
(
"rate1",
pos_act,
Poseidon2InMsg::rate1(perm_seq_id.clone(), rate1_chunk),
interaction_deg
),
(
"cap",
pos_act_head.clone(),
Poseidon2InMsg::cap(perm_seq_id.clone(), cap_chunk),
interaction_deg
),
);
let neg_act_head: LB::Expr = LB::Expr::ZERO - pos_act_head;
frac_col!(
builder,
"chunk-chain",
provides_deg,
(
"emit",
neg_act_head,
ChunkChainMsg {
chunk_seq_id_head: local[COL_CHUNK_SEQ_ID].into(),
perm_seq_id_head: perm_seq_id,
},
interaction_deg
),
);
}
}