pub mod trace;
use alloc::{vec, vec::Vec};
use core::array;
use miden_core::{
Felt,
field::{Algebra, PrimeCharacteristicRing, QuadFelt},
utils::RowMajorMatrix,
};
use miden_crypto::stark::air::ExtensionBuilder;
use miden_lifted_air::{BaseAir, LiftedAir, LiftedAirBuilder};
use crate::{
logup::{
Challenges, CyclicConstraintLookupBuilder, Deg, LookupAir, LookupBatch, LookupBuilder,
LookupColumn, LookupGroup, LookupMessage, NUM_PUBLIC_VALUES, NUM_RANDOMNESS,
NUM_SIGMA_VALUES,
},
primitives::byte_pair_lut::Range16Msg,
relations::{BusId, MAX_MESSAGE_WIDTH, NUM_BUS_IDS},
uint::{UintLimbsMsg, UintValMsg},
utils::{current_main, next_main},
};
#[derive(Debug, Clone)]
pub struct UintMulMsg<E> {
pub kappa_a: E,
pub kappa_c: E,
pub a_ptr: E,
pub b_ptr: E,
pub c_ptr: E,
pub r_ptr: E,
pub bound_ptr: E,
pub is_sub: E,
}
impl<E, EF> LookupMessage<E, EF> for UintMulMsg<E>
where
E: Algebra<E>,
EF: Algebra<E>,
{
fn encode(&self, challenges: &Challenges<EF>) -> EF {
challenges.encode(
BusId::UintMul as usize,
[
self.kappa_a.clone(),
self.kappa_c.clone(),
self.a_ptr.clone(),
self.b_ptr.clone(),
self.c_ptr.clone(),
self.r_ptr.clone(),
self.bound_ptr.clone(),
self.is_sub.clone(),
],
)
}
}
pub const NUM_CELLS: usize = 19;
pub const COL_A_PTR: usize = NUM_CELLS;
pub const COL_B_PTR: usize = NUM_CELLS + 1;
pub const COL_R_PTR: usize = NUM_CELLS + 2;
pub const COL_BOUND_PTR: usize = NUM_CELLS + 3;
pub const COL_KAPPA_A: usize = NUM_CELLS + 4;
pub const COL_ACT: usize = NUM_CELLS + 5;
pub const COL_BORROW: usize = NUM_CELLS + 6;
pub const NUM_MAIN_COLS: usize = NUM_CELLS + 7;
pub const PERIOD: usize = 8;
pub const ROW_A: usize = 0;
pub const ROW_B: usize = 1;
pub const ROW_P: usize = 2;
pub const ROW_Q: usize = 3;
pub const ROW_R: usize = 4;
pub const ROW_G0: usize = 5;
pub const ROW_G1: usize = 6;
pub const ROW_C: usize = 7;
pub const S_KEEP: [u64; PERIOD] = [1, 0, 1, 0, 0, 0, 0, 0];
const PCOL_S_KEEP: usize = PERIOD;
const NUM_PERIODIC: usize = PERIOD + 1;
pub const TERM_CELL_MULT: usize = 8;
pub const TERM_CELL_C_PTR: usize = 9;
pub const TERM_CELL_KAPPA_C: usize = 10;
pub const TERM_CELL_IS_SUB: usize = 11;
pub const TERM_CELL_KAPPA_C_SIGNED: usize = 12;
pub const NUM_Q_LIMBS: usize = 17;
pub const NUM_GAMMA: usize = 31;
pub const NUM_GAMMA_SLOTS: usize = 2 * NUM_GAMMA;
pub const GAMMA_SLOTS: [(usize, usize); NUM_GAMMA_SLOTS] = gamma_slots();
const fn gamma_slots() -> [(usize, usize); NUM_GAMMA_SLOTS] {
let mut slots = [(0usize, 0usize); NUM_GAMMA_SLOTS];
let mut s = 0;
let mut cell = 0;
while cell < NUM_CELLS {
slots[s] = (ROW_G0, cell);
s += 1;
cell += 1;
}
let mut cell = 0;
while cell < 15 {
slots[s] = (ROW_G1, cell);
s += 1;
cell += 1;
}
let solid16 = [ROW_A, ROW_B, ROW_P];
let mut i = 0;
while i < solid16.len() {
let mut cell = 16;
while cell < NUM_CELLS {
slots[s] = (solid16[i], cell);
s += 1;
cell += 1;
}
i += 1;
}
let mut cell = NUM_Q_LIMBS;
while cell < NUM_CELLS {
slots[s] = (ROW_Q, cell);
s += 1;
cell += 1;
}
let mut cell = 8;
while cell < NUM_CELLS {
slots[s] = (ROW_R, cell);
s += 1;
cell += 1;
}
let mut cell = TERM_CELL_KAPPA_C_SIGNED + 1;
while cell < NUM_CELLS {
slots[s] = (ROW_C, cell);
s += 1;
cell += 1;
}
slots
}
const NUM_RAW_CONSUMES: usize = 3; const NUM_RAW_CONSUME_COLS: usize = NUM_RAW_CONSUMES.div_ceil(2);
const NUM_RANGE16_COLS: usize = NUM_CELLS.div_ceil(2);
pub(crate) const NUM_LOGUP_COLS: usize = 1 + NUM_RAW_CONSUME_COLS + NUM_RANGE16_COLS + 1 + 1; const REG_ID: usize = NUM_LOGUP_COLS;
const REG_S: usize = NUM_LOGUP_COLS + 1;
const AUX_WIDTH: usize = NUM_LOGUP_COLS + 2;
const fn column_shape() -> [usize; NUM_LOGUP_COLS] {
let mut shape = [2usize; NUM_LOGUP_COLS];
shape[0] = 1;
if NUM_RAW_CONSUMES % 2 == 1 {
shape[NUM_RAW_CONSUME_COLS] = 1;
}
if NUM_CELLS % 2 == 1 {
shape[1 + NUM_RAW_CONSUME_COLS + NUM_RANGE16_COLS - 1] = 1;
}
shape
}
pub(crate) const COLUMN_SHAPE: [usize; NUM_LOGUP_COLS] = column_shape();
pub(crate) const GAMMA_OFFSET: u32 = 1 << 31;
#[derive(Debug, Default, Clone, Copy)]
pub struct UintMulAir;
impl BaseAir<Felt> for UintMulAir {
fn width(&self) -> usize {
NUM_MAIN_COLS
}
fn num_public_values(&self) -> usize {
NUM_PUBLIC_VALUES
}
fn periodic_columns(&self) -> Vec<Vec<Felt>> {
(0..PERIOD)
.map(|row| {
let mut col = vec![Felt::ZERO; PERIOD];
col[row] = Felt::ONE;
col
})
.chain(core::iter::once(S_KEEP.iter().map(|&g| Felt::from(g as u32)).collect()))
.collect()
}
}
impl LiftedAir<Felt, QuadFelt> for UintMulAir {
fn num_randomness(&self) -> usize {
NUM_RANDOMNESS
}
fn aux_width(&self) -> usize {
AUX_WIDTH
}
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 sel: [AB::Expr; NUM_PERIODIC] = {
let p = builder.periodic_values();
array::from_fn(|i| p[i].into())
};
let beta: AB::ExprEF = builder.permutation_randomness()[1].into();
let mut bp: Vec<AB::ExprEF> = Vec::with_capacity(NUM_GAMMA + 1);
bp.push(AB::ExprEF::ONE);
for i in 1..NUM_GAMMA + 1 {
bp.push(bp[i - 1].clone() * beta.clone());
}
let t16: AB::Expr = AB::Expr::from(Felt::from(1u32 << 16));
let x_minus_t: AB::ExprEF = beta - t16.clone();
let offset: AB::Expr = AB::Expr::from(Felt::from(GAMMA_OFFSET));
let kappa_a: AB::Expr = local[COL_KAPPA_A].into();
let act: AB::Expr = local[COL_ACT].into();
let kappa_c_signed_local: AB::Expr = local[TERM_CELL_KAPPA_C_SIGNED].into();
let id: AB::ExprEF =
current_main::<_, AB::VarEF, 1>(builder.permutation(), REG_ID)[0].into();
let id_next: AB::ExprEF =
next_main::<_, AB::VarEF, 1>(builder.permutation(), REG_ID)[0].into();
let s: AB::ExprEF = current_main::<_, AB::VarEF, 1>(builder.permutation(), REG_S)[0].into();
let s_next: AB::ExprEF =
next_main::<_, AB::VarEF, 1>(builder.permutation(), REG_S)[0].into();
let full16_sum: AB::ExprEF =
(0..16).fold(AB::ExprEF::ZERO, |acc, i| acc + bp[i].clone() * AB::Expr::from(local[i]));
let full_q_sum: AB::ExprEF = (0..NUM_Q_LIMBS)
.fold(AB::ExprEF::ZERO, |acc, i| acc + bp[i].clone() * AB::Expr::from(local[i]));
let val_sum: AB::ExprEF = (0..8)
.fold(AB::ExprEF::ZERO, |acc, m| acc + bp[2 * m].clone() * AB::Expr::from(local[m]));
let build: AB::ExprEF = full16_sum.clone() * (sel[ROW_A].clone() * kappa_a)
+ full16_sum.clone() * sel[ROW_P].clone();
let keep: AB::Expr = sel[PCOL_S_KEEP].clone();
builder.when_first_row().assert_zero_ext(s.clone());
builder.when_transition().assert_zero_ext(s_next - s.clone() * keep - build);
let product = s.clone() * full16_sum.clone() * sel[ROW_B].clone();
let quotient = (s + AB::ExprEF::ONE) * full_q_sum * sel[ROW_Q].clone();
let linear = val_sum.clone() * (sel[ROW_C].clone() * kappa_c_signed_local.clone())
- val_sum.clone() * sel[ROW_R].clone();
let mut carries = AB::ExprEF::ZERO;
for (slot, &(row, cell)) in GAMMA_SLOTS.iter().enumerate() {
let k = slot / 2;
let mut w = x_minus_t.clone() * bp[k].clone();
if slot % 2 == 1 {
w *= t16.clone();
}
let mut gated: AB::Expr = sel[row].clone() * AB::Expr::from(local[cell]);
if slot % 2 == 0 {
gated -= sel[row].clone() * act.clone() * offset.clone();
}
carries += w * gated;
}
let borrow: AB::Expr = local[COL_BORROW].into();
let borrow_contrib: AB::ExprEF =
(full16_sum + AB::ExprEF::ONE) * (sel[ROW_P].clone() * borrow.clone());
let contrib: AB::ExprEF = product - quotient + linear + carries + borrow_contrib;
builder.when_first_row().assert_zero_ext(id.clone());
builder.when_transition().assert_zero_ext(id_next - id.clone() - contrib);
let mut c_own: AB::ExprEF = val_sum * kappa_c_signed_local.clone();
for (slot, &(row, cell)) in GAMMA_SLOTS.iter().enumerate() {
if row == ROW_C {
let k = slot / 2;
let mut w = x_minus_t.clone() * bp[k].clone();
if slot % 2 == 1 {
w *= t16.clone();
}
let mut gated: AB::Expr = AB::Expr::from(local[cell]);
if slot % 2 == 0 {
gated -= act.clone() * offset.clone();
}
c_own += w * gated;
}
}
builder.assert_zero_ext((id + c_own) * sel[ROW_C].clone());
builder.assert_zero(act.clone() * (AB::Expr::ONE - act.clone()));
let is_sub: AB::Expr = local[TERM_CELL_IS_SUB].into();
builder.assert_zero(sel[ROW_C].clone() * is_sub.clone() * (AB::Expr::ONE - is_sub.clone()));
let kappa_c_local: AB::Expr = local[TERM_CELL_KAPPA_C].into();
let c_sign_local: AB::Expr = AB::Expr::ONE - is_sub.double();
builder.assert_zero(
sel[ROW_C].clone() * (kappa_c_signed_local - kappa_c_local * c_sign_local),
);
let two = AB::Expr::from(Felt::from(2u32));
builder.assert_zero(
borrow.clone() * (borrow.clone() - AB::Expr::ONE) * (borrow.clone() - two),
);
builder.assert_zero(sel[ROW_C].clone() * borrow * (AB::Expr::ONE - is_sub));
builder
.assert_zero(sel[ROW_C].clone() * (AB::Expr::ONE - act) * local[TERM_CELL_MULT].into());
let not_term: AB::Expr = AB::Expr::ONE - sel[ROW_C].clone();
for col in
[COL_A_PTR, COL_B_PTR, COL_R_PTR, COL_BOUND_PTR, COL_KAPPA_A, COL_ACT, COL_BORROW]
{
let here: AB::Expr = local[col].into();
let there: AB::Expr = next[col].into();
builder.assert_zero(not_term.clone() * (there - here));
}
let mut lb =
CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0);
<Self as LookupAir<_>>::eval(self, &mut lb);
}
}
impl<LB> LookupAir<LB> for UintMulAir
where
LB: LookupBuilder<F = Felt>,
{
fn num_columns(&self) -> usize {
NUM_LOGUP_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 sel: [LB::Expr; NUM_PERIODIC] = {
let p = builder.periodic_values();
array::from_fn(|i| p[i].into())
};
let a_ptr: LB::Expr = local[COL_A_PTR].into();
let b_ptr: LB::Expr = local[COL_B_PTR].into();
let r_ptr: LB::Expr = local[COL_R_PTR].into();
let bound_ptr: LB::Expr = local[COL_BOUND_PTR].into();
let kappa_a: LB::Expr = local[COL_KAPPA_A].into();
let act: LB::Expr = local[COL_ACT].into();
let c_ptr_local: LB::Expr = local[TERM_CELL_C_PTR].into();
let kappa_c_local: LB::Expr = local[TERM_CELL_KAPPA_C].into();
let neg_mult: LB::Expr = LB::Expr::ZERO - local[TERM_CELL_MULT].into();
let provide_deg = Deg { v: 2, u: 1 };
let consume_deg = Deg { v: 2, u: 1 };
let rc_deg = Deg { v: 2, u: 1 };
let pair_deg = Deg { v: 3, u: 2 };
let raw_lo: [LB::Expr; 8] = array::from_fn(|i| local[i].into());
let raw_hi: [LB::Expr; 8] = array::from_fn(|i| local[8 + i].into());
let val_lo: [LB::Expr; 4] = array::from_fn(|k| local[k].into());
let val_hi: [LB::Expr; 4] = array::from_fn(|k| local[4 + k].into());
builder.next_column(
|col| {
col.group(
"uintmul",
|g| {
g.batch(
"f",
LB::Expr::ONE,
|b| {
b.insert(
"provide-uintmul",
neg_mult.clone() * sel[ROW_C].clone(),
UintMulMsg {
kappa_a: kappa_a.clone(),
kappa_c: kappa_c_local.clone(),
a_ptr: a_ptr.clone(),
b_ptr: b_ptr.clone(),
c_ptr: c_ptr_local.clone(),
r_ptr: r_ptr.clone(),
bound_ptr: bound_ptr.clone(),
is_sub: local[TERM_CELL_IS_SUB].into(),
},
provide_deg,
);
},
provide_deg,
);
},
provide_deg,
);
},
provide_deg,
);
let raw_consumes: Vec<(LB::Expr, LB::Expr, [LB::Expr; 16])> =
[(ROW_A, a_ptr.clone()), (ROW_B, b_ptr.clone()), (ROW_P, bound_ptr.clone())]
.into_iter()
.map(|(row, ptr)| {
let mult = sel[row].clone() * act.clone();
let limbs: [LB::Expr; 16] = array::from_fn(|i| {
if i < 8 {
raw_lo[i].clone()
} else {
raw_hi[i - 8].clone()
}
});
(mult, ptr, limbs)
})
.collect();
for group in raw_consumes
.chunks(2)
.map(
<[(
<LB as LookupBuilder>::Expr,
<LB as LookupBuilder>::Expr,
[<LB as LookupBuilder>::Expr; 16],
)]>::to_vec,
)
.collect::<Vec<_>>()
{
builder.next_column(
|col| {
col.group(
"uintlimbs",
|g| {
g.batch(
"f",
LB::Expr::ONE,
|b| {
for (mult, ptr, limbs) in group {
b.insert(
"consume-uintlimbs",
mult,
UintLimbsMsg {
ptr,
bound_ptr: bound_ptr.clone(),
limbs,
},
consume_deg,
);
}
},
pair_deg,
);
},
pair_deg,
);
},
pair_deg,
);
}
let raw16_gate = |cell: usize| -> LB::Expr {
if cell < NUM_Q_LIMBS {
sel[ROW_Q].clone()
} else {
LB::Expr::ZERO
}
};
let gamma_gate = |cell: usize| -> LB::Expr {
GAMMA_SLOTS
.iter()
.filter(|&&(_, c)| c == cell)
.fold(LB::Expr::ZERO, |acc, &(row, _)| acc + sel[row].clone())
};
let cell_gate = |cell: usize| -> LB::Expr { raw16_gate(cell) + gamma_gate(cell) };
let cell_specs: Vec<(LB::Expr, usize)> =
(0..NUM_CELLS).map(|cell| (cell_gate(cell) * act.clone(), cell)).collect();
for group in cell_specs
.chunks(2)
.map(<[(<LB as LookupBuilder>::Expr, usize)]>::to_vec)
.collect::<Vec<_>>()
{
builder.next_column(
|col| {
col.group(
"range16-cells",
|g| {
g.batch(
"f",
LB::Expr::ONE,
|b| {
for (mult, cell) in group {
b.insert(
"range16-cell",
mult,
Range16Msg { w: local[cell].into() },
rc_deg,
);
}
},
pair_deg,
);
},
pair_deg,
);
},
pair_deg,
);
}
builder.next_column(
|col| {
col.group(
"range16-kappa",
|g| {
g.batch(
"f",
LB::Expr::ONE,
|b| {
b.insert(
"range16-kappa-a",
sel[ROW_C].clone() * act.clone(),
Range16Msg { w: kappa_a.clone() },
rc_deg,
);
b.insert(
"range16-kappa-c",
sel[ROW_C].clone() * act.clone(),
Range16Msg { w: kappa_c_local.clone() },
rc_deg,
);
},
pair_deg,
);
},
pair_deg,
);
},
pair_deg,
);
let val_full: [LB::Expr; 8] = array::from_fn(|i| {
if i < 4 {
val_lo[i].clone()
} else {
val_hi[i - 4].clone()
}
});
let val_consumes: [(usize, LB::Expr); 2] = [(ROW_R, r_ptr.clone()), (ROW_C, c_ptr_local)];
builder.next_column(
|col| {
col.group(
"uintval",
|g| {
g.batch(
"f",
LB::Expr::ONE,
|b| {
for (row, ptr) in val_consumes {
b.insert(
"consume-uintval",
sel[row].clone() * act.clone(),
UintValMsg {
ptr,
bound_ptr: bound_ptr.clone(),
limbs: val_full.clone(),
},
consume_deg,
);
}
},
pair_deg,
);
},
pair_deg,
);
},
pair_deg,
);
}
}