pub mod trace;
use alloc::{vec, vec::Vec};
use core::array;
use miden_core::{
Felt,
field::{Algebra, PrimeCharacteristicRing, QuadFelt},
utils::RowMajorMatrix,
};
use miden_lifted_air::{BaseAir, LiftedAir, LiftedAirBuilder};
use crate::{
ec::{EcGroupMsg, EcPointMsg},
logup::{
Challenges, CyclicConstraintLookupBuilder, Deg, LookupAir, LookupBatch, LookupBuilder,
LookupColumn, LookupGroup, LookupMessage, NUM_PUBLIC_VALUES, NUM_RANDOMNESS,
NUM_SIGMA_VALUES, frac_col,
},
primitives::byte_pair_lut::Range16Msg,
relations::{BusId, MAX_MESSAGE_WIDTH, NUM_BUS_IDS},
uint::{add::UintAddMsg, mul::UintMulMsg},
utils::{current_main, next_main},
};
#[derive(Debug, Clone)]
pub struct EcGroupAddMsg<E> {
pub group_ptr: E,
pub p_ptr: E,
pub q_ptr: E,
pub r_ptr: E,
}
impl<E, EF> LookupMessage<E, EF> for EcGroupAddMsg<E>
where
E: Algebra<E>,
EF: Algebra<E>,
{
fn encode(&self, challenges: &Challenges<EF>) -> EF {
challenges.encode(
BusId::EcGroupAdd as usize,
[
self.group_ptr.clone(),
self.p_ptr.clone(),
self.q_ptr.clone(),
self.r_ptr.clone(),
],
)
}
}
#[derive(Debug, Clone)]
pub struct EcOnCurveCertMsg<E> {
pub group_ptr: E,
pub r_ptr: E,
}
impl<E, EF> LookupMessage<E, EF> for EcOnCurveCertMsg<E>
where
E: Algebra<E>,
EF: Algebra<E>,
{
fn encode(&self, challenges: &Challenges<EF>) -> EF {
challenges
.encode(BusId::EcOnCurveCert as usize, [self.group_ptr.clone(), self.r_ptr.clone()])
}
}
pub const NUM_CELLS: usize = 3;
pub const COL_PX: usize = 3;
pub const COL_PY: usize = 4;
pub const COL_QX: usize = 5;
pub const COL_QY: usize = 6;
pub const COL_A_PTR: usize = 7;
pub const COL_B_PTR: usize = 8;
pub const COL_BOUND_PTR: usize = 9;
pub const COL_PAI_P: usize = 10;
pub const COL_PAI_Q: usize = 11;
pub const COL_CANCEL: usize = 12;
pub const COL_DBL: usize = 13;
pub const COL_GEN: usize = 14;
pub const COL_ACT: usize = 15;
pub const COL_MINTS: usize = 16;
pub const COL_RP_LO: usize = 17;
pub const COL_RP_HI: usize = 18;
pub const COL_RQ_LO: usize = 19;
pub const COL_RQ_HI: usize = 20;
pub const NUM_MAIN_COLS: usize = 21;
pub const PERIOD: usize = 4;
pub const ROW_SLOPE: usize = 0;
pub const ROW_TAIL: usize = 1;
pub const ROW_RES: usize = 2;
pub const ROW_TERM: usize = 3;
pub const CELL_SLOPE_AUX: usize = 0;
pub const CELL_LAMBDA: usize = 1;
pub const CELL_T: usize = 2;
pub const CELL_Y3: usize = 0;
pub const CELL_E: usize = 1;
pub const CELL_X3: usize = 2;
pub const CELL_R: usize = 0;
pub const CELL_SBOUND: usize = 1;
pub const CELL_GROUP: usize = 2;
pub const TERM_CELL_MULT: usize = 0;
pub const TERM_CELL_P: usize = 1;
pub const TERM_CELL_Q: usize = 2;
const PCOL_SLOPE: usize = 0;
const PCOL_TAIL: usize = 1;
const PCOL_RES: usize = 2;
const PCOL_TERM: usize = 3;
const NUM_PERIODIC: usize = 4;
const ROLE_ROWS: [usize; NUM_PERIODIC] = [0, 1, 2, 3];
const NUM_LOGUP_COLS: usize = 12;
const AUX_WIDTH: usize = 12;
const COLUMN_SHAPE: [usize; NUM_LOGUP_COLS] = [1, 2, 2, 2, 2, 2, 2, 2, 1, 2, 2, 1];
#[derive(Debug, Default, Clone, Copy)]
pub struct EcGroupAddAir;
impl BaseAir<Felt> for EcGroupAddAir {
fn width(&self) -> usize {
NUM_MAIN_COLS
}
fn num_public_values(&self) -> usize {
NUM_PUBLIC_VALUES
}
fn periodic_columns(&self) -> Vec<Vec<Felt>> {
ROLE_ROWS
.iter()
.map(|&row| {
let mut col = vec![Felt::ZERO; PERIOD];
col[row] = Felt::ONE;
col
})
.collect()
}
}
impl LiftedAir<Felt, QuadFelt> for EcGroupAddAir {
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 pai_p: AB::Expr = local[COL_PAI_P].into();
let pai_q: AB::Expr = local[COL_PAI_Q].into();
let cancel: AB::Expr = local[COL_CANCEL].into();
let dbl: AB::Expr = local[COL_DBL].into();
let generic: AB::Expr = local[COL_GEN].into();
let act: AB::Expr = local[COL_ACT].into();
let mints: AB::Expr = local[COL_MINTS].into();
for flag in [&pai_p, &pai_q, &cancel, &dbl, &generic, &act, &mints] {
builder.assert_zero(flag.clone() * (AB::Expr::ONE - flag.clone()));
}
builder.assert_zero(
pai_p.clone() + pai_q.clone() + cancel + dbl + generic
- act
- pai_p.clone() * pai_q.clone(),
);
let px_eq: AB::Expr = local[COL_PX].into();
let qx_eq: AB::Expr = local[COL_QX].into();
let py_eq: AB::Expr = local[COL_PY].into();
let qy_eq: AB::Expr = local[COL_QY].into();
let cancel_eq: AB::Expr = local[COL_CANCEL].into();
let dbl_eq: AB::Expr = local[COL_DBL].into();
builder.assert_zero((cancel_eq + dbl_eq.clone()) * (px_eq - qx_eq));
builder.assert_zero(dbl_eq * (py_eq - qy_eq));
let r_cell: AB::Expr = local[CELL_R].into();
let p_cell: AB::Expr = next[TERM_CELL_P].into();
let q_cell: AB::Expr = next[TERM_CELL_Q].into();
builder.assert_zero(sel[PCOL_RES].clone() * pai_p * (r_cell.clone() - q_cell));
builder.assert_zero(sel[PCOL_RES].clone() * pai_q * (r_cell - p_cell));
let dbl_g: AB::Expr = local[COL_DBL].into();
let gen_g: AB::Expr = local[COL_GEN].into();
builder.assert_zero(mints.clone() * (AB::Expr::ONE - dbl_g - gen_g));
let r_res: AB::Expr = local[CELL_R].into();
let p_res: AB::Expr = next[TERM_CELL_P].into();
let q_res: AB::Expr = next[TERM_CELL_Q].into();
let two_16 = AB::Expr::from(Felt::from(1u32 << 16));
let rp_lo: AB::Expr = local[COL_RP_LO].into();
let rp_hi: AB::Expr = local[COL_RP_HI].into();
let rq_lo: AB::Expr = local[COL_RQ_LO].into();
let rq_hi: AB::Expr = local[COL_RQ_HI].into();
let at_res: AB::Expr = sel[PCOL_RES].clone();
builder.assert_zero(
at_res.clone()
* mints.clone()
* (r_res.clone() - p_res - AB::Expr::ONE - rp_lo - two_16.clone() * rp_hi),
);
builder
.assert_zero(at_res * mints * (r_res - q_res - AB::Expr::ONE - rq_lo - two_16 * rq_hi));
let not_term: AB::Expr = AB::Expr::ONE - sel[PCOL_TERM].clone();
for col in COL_PX..NUM_MAIN_COLS {
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 EcGroupAddAir
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 next: [LB::Var; NUM_MAIN_COLS] = next_main(builder.main(), 0);
let sel: [LB::Expr; NUM_PERIODIC] = {
let p = builder.periodic_values();
array::from_fn(|i| p[i].into())
};
let px: LB::Expr = local[COL_PX].into();
let py: LB::Expr = local[COL_PY].into();
let qx: LB::Expr = local[COL_QX].into();
let qy: LB::Expr = local[COL_QY].into();
let a_ptr: LB::Expr = local[COL_A_PTR].into();
let b_ptr: LB::Expr = local[COL_B_PTR].into();
let bound: LB::Expr = local[COL_BOUND_PTR].into();
let pai_p: LB::Expr = local[COL_PAI_P].into();
let pai_q: LB::Expr = local[COL_PAI_Q].into();
let cancel: LB::Expr = local[COL_CANCEL].into();
let dbl: LB::Expr = local[COL_DBL].into();
let generic: LB::Expr = local[COL_GEN].into();
let act: LB::Expr = local[COL_ACT].into();
let mints: LB::Expr = local[COL_MINTS].into();
let rp_lo: LB::Expr = local[COL_RP_LO].into();
let rp_hi: LB::Expr = local[COL_RP_HI].into();
let rq_lo: LB::Expr = local[COL_RQ_LO].into();
let rq_hi: LB::Expr = local[COL_RQ_HI].into();
let live: LB::Expr = cancel.clone() + dbl.clone() + generic.clone();
let tail: LB::Expr = dbl.clone() + generic.clone();
let at_slope: LB::Expr = sel[PCOL_SLOPE].clone();
let at_tail: LB::Expr = sel[PCOL_TAIL].clone();
let at_res: LB::Expr = sel[PCOL_RES].clone();
let slope_aux: LB::Expr = local[CELL_SLOPE_AUX].into();
let lambda: LB::Expr = local[CELL_LAMBDA].into();
let t: LB::Expr = local[CELL_T].into();
let e: LB::Expr = next[CELL_E].into();
let x3_next: LB::Expr = next[CELL_X3].into();
let y3_next: LB::Expr = next[CELL_Y3].into();
let x3_local: LB::Expr = local[CELL_X3].into();
let y3_local: LB::Expr = local[CELL_Y3].into();
let r_next: LB::Expr = next[CELL_R].into();
let group_next: LB::Expr = next[CELL_GROUP].into();
let r_local: LB::Expr = local[CELL_R].into();
let sbound: LB::Expr = local[CELL_SBOUND].into();
let group_local: LB::Expr = local[CELL_GROUP].into();
let neg_mult: LB::Expr = LB::Expr::ZERO - next[TERM_CELL_MULT].into();
let p_ptr: LB::Expr = next[TERM_CELL_P].into();
let q_ptr: LB::Expr = next[TERM_CELL_Q].into();
let one: LB::Expr = LB::Expr::ONE;
let zero: LB::Expr = LB::Expr::ZERO;
let two: LB::Expr = LB::Expr::from(Felt::from(2u32));
let three: LB::Expr = LB::Expr::from(Felt::from(3u32));
let f2 = Deg { v: 2, u: 1 };
let single_deg = Deg { v: 1, u: 2 };
let pair_deg = Deg { v: 3, u: 2 };
frac_col!(
builder,
"ec-add-bindings",
single_deg,
(
"provide-ecgroupadd",
neg_mult * at_res.clone(),
EcGroupAddMsg {
group_ptr: group_local.clone(),
p_ptr: p_ptr.clone(),
q_ptr: q_ptr.clone(),
r_ptr: r_local.clone(),
},
f2
),
);
frac_col!(
builder,
"ec-add-bindings",
pair_deg,
(
"consume-ecpoint-p",
act.clone() * at_res.clone(),
EcPointMsg {
point_ptr: p_ptr.clone(),
group_ptr: group_local.clone(),
x_ptr: px.clone(),
y_ptr: py.clone(),
is_pai: pai_p,
},
f2
),
(
"consume-ecpoint-q",
act.clone() * at_res.clone(),
EcPointMsg {
point_ptr: q_ptr.clone(),
group_ptr: group_local.clone(),
x_ptr: qx.clone(),
y_ptr: qy.clone(),
is_pai: pai_q,
},
f2
),
);
frac_col!(
builder,
"ec-add-bindings",
pair_deg,
(
"consume-ecpoint-r",
tail.clone() * at_tail,
EcPointMsg {
point_ptr: r_next,
group_ptr: group_next,
x_ptr: x3_local,
y_ptr: y3_local,
is_pai: zero.clone(),
},
f2
),
(
"consume-ecpoint-r-pai",
cancel.clone() * at_res.clone(),
EcPointMsg {
point_ptr: r_local,
group_ptr: group_local.clone(),
x_ptr: zero.clone(),
y_ptr: zero.clone(),
is_pai: one.clone(),
},
f2
),
);
frac_col!(
builder,
"ec-add-bindings",
pair_deg,
(
"consume-ecgroup",
live * at_res.clone(),
EcGroupMsg {
group_ptr: group_local.clone(),
a_ptr: a_ptr.clone(),
b_ptr: b_ptr.clone(),
bound_ptr: bound.clone(),
scalar_bound_ptr: sbound,
},
f2
),
(
"consume-cancel-zero",
cancel.clone() * at_res.clone(),
UintAddMsg {
bound_ptr: bound.clone(),
a_ptr: py.clone(),
b_ptr: qy.clone(),
c_ptr: zero.clone(),
nz: zero.clone(),
},
f2
),
);
frac_col!(
builder,
"ec-add-slope",
pair_deg,
(
"consume-d-sub",
generic.clone() * at_slope.clone(),
UintAddMsg {
bound_ptr: bound.clone(),
a_ptr: px.clone(),
b_ptr: slope_aux.clone(),
c_ptr: qx.clone(),
nz: one.clone(),
},
f2
),
(
"consume-chord",
generic.clone() * at_slope.clone(),
UintMulMsg {
kappa_a: one.clone(),
kappa_c: one.clone(),
a_ptr: lambda.clone(),
b_ptr: slope_aux.clone(),
c_ptr: py.clone(),
r_ptr: qy.clone(),
bound_ptr: bound.clone(),
is_sub: zero.clone(),
},
f2
),
);
frac_col!(
builder,
"ec-add-slope",
pair_deg,
(
"consume-tangent-s",
dbl.clone() * at_slope.clone(),
UintMulMsg {
kappa_a: three,
kappa_c: one.clone(),
a_ptr: px.clone(),
b_ptr: px.clone(),
c_ptr: a_ptr,
r_ptr: slope_aux.clone(),
bound_ptr: bound.clone(),
is_sub: zero.clone(),
},
f2
),
(
"consume-tangent-2ly",
dbl.clone() * at_slope.clone(),
UintMulMsg {
kappa_a: two.clone(),
kappa_c: zero.clone(),
a_ptr: lambda.clone(),
b_ptr: py.clone(),
c_ptr: bound.clone(),
r_ptr: slope_aux.clone(),
bound_ptr: bound.clone(),
is_sub: zero.clone(),
},
f2
),
);
frac_col!(
builder,
"ec-add-tail",
pair_deg,
(
"consume-t-add",
generic.clone() * at_slope.clone(),
UintAddMsg {
bound_ptr: bound.clone(),
a_ptr: px.clone(),
b_ptr: qx.clone(),
c_ptr: t.clone(),
nz: zero.clone(),
},
f2
),
(
"consume-x3-macsub-gen",
generic.clone() * at_slope.clone(),
UintMulMsg {
kappa_a: one.clone(),
kappa_c: one.clone(),
a_ptr: lambda.clone(),
b_ptr: lambda.clone(),
c_ptr: t,
r_ptr: x3_next.clone(),
bound_ptr: bound.clone(),
is_sub: one.clone(),
},
f2
),
);
frac_col!(
builder,
"ec-add-tail",
pair_deg,
(
"consume-e-sub",
tail.clone() * at_slope.clone(),
UintAddMsg {
bound_ptr: bound.clone(),
a_ptr: x3_next.clone(),
b_ptr: e.clone(),
c_ptr: px.clone(),
nz: zero.clone(),
},
f2
),
(
"consume-y3-macsub",
tail * at_slope.clone(),
UintMulMsg {
kappa_a: one.clone(),
kappa_c: one.clone(),
a_ptr: lambda.clone(),
b_ptr: e,
c_ptr: py.clone(),
r_ptr: y3_next,
bound_ptr: bound.clone(),
is_sub: one.clone(),
},
f2
),
);
frac_col!(
builder,
"ec-add-tail",
single_deg,
(
"consume-x3-macsub-dbl",
dbl * at_slope,
UintMulMsg {
kappa_a: one.clone(),
kappa_c: two,
a_ptr: lambda.clone(),
b_ptr: lambda,
c_ptr: px,
r_ptr: x3_next,
bound_ptr: bound.clone(),
is_sub: one,
},
f2
),
);
let gate = sel[PCOL_RES].clone() * mints;
frac_col!(
builder,
"ec-add-mint",
pair_deg,
("range16-rp-lo", gate.clone(), Range16Msg { w: rp_lo }, f2),
("range16-rp-hi", gate.clone(), Range16Msg { w: rp_hi }, f2),
);
frac_col!(
builder,
"ec-add-mint",
pair_deg,
("range16-rq-lo", gate.clone(), Range16Msg { w: rq_lo }, f2),
("range16-rq-hi", gate.clone(), Range16Msg { w: rq_hi }, f2),
);
let cert_group: LB::Expr = local[CELL_GROUP].into();
let cert_r: LB::Expr = local[CELL_R].into();
frac_col!(
builder,
"ec-add-mint",
single_deg,
(
"provide-ecgroupadd-cert",
LB::Expr::ZERO - gate,
EcOnCurveCertMsg { group_ptr: cert_group, r_ptr: cert_r },
f2
),
);
}
}