use alloc::{collections::BTreeMap, vec::Vec};
use miden_core::{Felt, field::QuadFelt, utils::RowMajorMatrix};
use miden_precompiles::CurveId;
use super::{
COL_A_PTR, COL_ACT, COL_B_PTR, COL_BETA_PTR, COL_BOUND_PTR, COL_ECPOINT_MULT, COL_GROUP_PTR,
COL_IS_CERT, COL_IS_PAI, COL_LAMBDA_PTR, COL_PTR, COL_SBOUND_PTR, COL_U_PTR, COL_W_PTR,
COL_X_PTR, COL_Y_PTR, EcPointStoreAir, NUM_MAIN_COLS,
groups::{
COL_A_PTR as G_COL_A_PTR, COL_B_PTR as G_COL_B_PTR, COL_BETA_PTR as G_COL_BETA_PTR,
COL_BOUND_PTR as G_COL_BOUND_PTR, COL_LAMBDA_PTR as G_COL_LAMBDA_PTR,
COL_MULT as G_COL_MULT, COL_PTR as G_COL_PTR, COL_SBOUND_PTR as G_COL_SBOUND_PTR,
EcGroupsAir, NUM_MAIN_COLS as G_NUM_MAIN_COLS,
},
};
use crate::{logup::build_logup_aux_trace, relations::ProvideMult, uint::trace::UintPtr};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EcGroupPtr(u32);
impl EcGroupPtr {
pub fn addr(self) -> u32 {
self.0
}
pub fn from_addr(addr: u32) -> Self {
Self(addr)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EcPointPtr(u32);
impl EcPointPtr {
pub fn addr(self) -> u32 {
self.0
}
pub fn from_addr(addr: u32) -> Self {
Self(addr)
}
}
#[derive(Debug, Clone, Copy)]
struct Group {
a: UintPtr,
b: UintPtr,
bound: UintPtr,
scalar_bound: Option<UintPtr>,
beta: UintPtr,
lambda: UintPtr,
}
#[derive(Debug, Clone, Copy)]
struct PointBinding {
x: UintPtr,
y: UintPtr,
membership: Option<(UintPtr, UintPtr)>,
}
#[derive(Debug, Clone, Copy)]
struct Point {
group: EcGroupPtr,
binding: Option<PointBinding>,
}
#[derive(Debug)]
pub struct EcStoreRequires {
groups: Vec<Group>,
points: Vec<Point>,
by_coords: BTreeMap<(EcGroupPtr, UintPtr, UintPtr), EcPointPtr>,
by_curve: BTreeMap<(UintPtr, UintPtr, UintPtr), EcGroupPtr>,
group_demand: BTreeMap<EcGroupPtr, ProvideMult>,
point_demand: BTreeMap<EcPointPtr, ProvideMult>,
pai_rows: BTreeMap<EcGroupPtr, EcPointPtr>,
}
impl Default for EcStoreRequires {
fn default() -> Self {
let mut store = Self {
groups: Vec::new(),
points: Vec::new(),
by_coords: BTreeMap::new(),
by_curve: BTreeMap::new(),
group_demand: BTreeMap::new(),
point_demand: BTreeMap::new(),
pai_rows: BTreeMap::new(),
};
for curve in CurveId::ALL {
let ptr = EcGroupPtr(curve.group_ptr());
debug_assert_eq!(ptr.0 as usize, store.groups.len() + 1);
let (beta, lambda) = match curve.endomorphism() {
Some(endo) => {
(UintPtr::from_addr(endo.beta_ptr), UintPtr::from_addr(endo.lambda_ptr))
},
None => (UintPtr::from_addr(0), UintPtr::from_addr(0)),
};
let group = Group {
a: UintPtr::from_addr(curve.a_ptr()),
b: UintPtr::from_addr(curve.b_ptr()),
bound: UintPtr::from_addr(curve.base_domain().bound_ptr()),
scalar_bound: Some(UintPtr::from_addr(curve.scalar_domain().bound_ptr())),
beta,
lambda,
};
store.by_curve.insert((group.a, group.b, group.bound), ptr);
store.groups.push(group);
}
store
}
}
impl EcStoreRequires {
pub fn new() -> Self {
Self::default()
}
pub fn create_group(&mut self, a: UintPtr, b: UintPtr, bound: UintPtr) -> EcGroupPtr {
if let Some(&existing) = self.by_curve.get(&(a, b, bound)) {
return existing;
}
let ptr = EcGroupPtr(self.groups.len() as u32 + 1);
self.groups.push(Group {
a,
b,
bound,
scalar_bound: None,
beta: UintPtr::from_addr(0),
lambda: UintPtr::from_addr(0),
});
self.by_curve.insert((a, b, bound), ptr);
ptr
}
pub fn set_scalar_bound(&mut self, group: EcGroupPtr, sbound: UintPtr) {
let entry = &mut self.groups[group.0 as usize - 1].scalar_bound;
match entry {
None => *entry = Some(sbound),
Some(prev) => assert_eq!(*prev, sbound, "conflicting scalar bound for the group"),
}
}
pub fn add_point(
&mut self,
group: EcGroupPtr,
x: UintPtr,
y: UintPtr,
u: UintPtr,
w: UintPtr,
) -> EcPointPtr {
if let Some(&existing) = self.by_coords.get(&(group, x, y)) {
return existing;
}
*self.group_demand.entry(group).or_insert(0) += 1;
let ptr = EcPointPtr(self.points.len() as u32 + 1);
self.points.push(Point {
group,
binding: Some(PointBinding { x, y, membership: Some((u, w)) }),
});
self.by_coords.insert((group, x, y), ptr);
ptr
}
pub fn add_point_cert(
&mut self,
group: EcGroupPtr,
x: UintPtr,
y: UintPtr,
) -> (EcPointPtr, bool) {
if let Some(&existing) = self.by_coords.get(&(group, x, y)) {
return (existing, false);
}
*self.group_demand.entry(group).or_insert(0) += 1;
let ptr = EcPointPtr(self.points.len() as u32 + 1);
self.points.push(Point {
group,
binding: Some(PointBinding { x, y, membership: None }),
});
self.by_coords.insert((group, x, y), ptr);
(ptr, true)
}
pub fn point_by_coords(&self, group: EcGroupPtr, x: UintPtr, y: UintPtr) -> Option<EcPointPtr> {
self.by_coords.get(&(group, x, y)).copied()
}
pub fn add_pai(&mut self, group: EcGroupPtr) -> EcPointPtr {
if let Some(&existing) = self.pai_rows.get(&group) {
return existing;
}
*self.group_demand.entry(group).or_insert(0) += 1;
let ptr = EcPointPtr(self.points.len() as u32 + 1);
self.points.push(Point { group, binding: None });
self.pai_rows.insert(group, ptr);
ptr
}
pub fn require_ecgroup(&mut self, group: EcGroupPtr) {
*self.group_demand.entry(group).or_insert(0) += 1;
}
pub fn require_fixed_groups(&mut self) {
for curve in CurveId::ALL {
let group = EcGroupPtr::from_addr(curve.group_ptr());
debug_assert_eq!(
self.group_params(group),
(
UintPtr::from_addr(curve.a_ptr()),
UintPtr::from_addr(curve.b_ptr()),
UintPtr::from_addr(curve.base_domain().bound_ptr()),
)
);
debug_assert_eq!(
self.group_sbound(group),
UintPtr::from_addr(curve.scalar_domain().bound_ptr())
);
self.require_ecgroup(group);
}
}
pub fn require_ecpoint(&mut self, point: EcPointPtr) {
*self.point_demand.entry(point).or_insert(0) += 1;
}
pub fn group_params(&self, group: EcGroupPtr) -> (UintPtr, UintPtr, UintPtr) {
let g = &self.groups[group.0 as usize - 1];
(g.a, g.b, g.bound)
}
pub fn group_glv_params(&self, group: EcGroupPtr) -> (UintPtr, UintPtr) {
let g = &self.groups[group.0 as usize - 1];
(g.beta, g.lambda)
}
pub fn group_sbound(&self, group: EcGroupPtr) -> UintPtr {
let g = &self.groups[group.0 as usize - 1];
g.scalar_bound.unwrap_or(g.bound)
}
pub fn point_params(&self, point: EcPointPtr) -> (EcGroupPtr, Option<(UintPtr, UintPtr)>) {
let p = &self.points[point.0 as usize - 1];
(p.group, p.binding.map(|b| (b.x, b.y)))
}
pub fn group_pai(&self, group: EcGroupPtr) -> EcPointPtr {
self.pai_rows
.get(&group)
.copied()
.unwrap_or_else(|| panic!("group {} has no PAI row", group.0))
}
}
pub fn generate_traces(requires: EcStoreRequires) -> (RowMajorMatrix<Felt>, RowMajorMatrix<Felt>) {
(groups_trace(&requires), points_trace(&requires))
}
fn groups_trace(requires: &EcStoreRequires) -> RowMajorMatrix<Felt> {
groups_trace_padded_to(requires, 0)
}
pub(crate) fn groups_trace_padded_to(
requires: &EcStoreRequires,
min_height: usize,
) -> RowMajorMatrix<Felt> {
debug_assert!(min_height == 0 || min_height.is_power_of_two());
let height = requires.groups.len().next_power_of_two().max(2).max(min_height);
let mut vals = Vec::with_capacity(height * G_NUM_MAIN_COLS);
for i in 0..height {
let ptr = i as u32 + 1;
let mut row = [Felt::ZERO; G_NUM_MAIN_COLS];
row[G_COL_PTR] = Felt::from(ptr);
if let Some(group) = requires.groups.get(i) {
row[G_COL_A_PTR] = Felt::from(group.a.addr());
row[G_COL_B_PTR] = Felt::from(group.b.addr());
row[G_COL_BOUND_PTR] = Felt::from(group.bound.addr());
row[G_COL_SBOUND_PTR] = Felt::from(group.scalar_bound.unwrap_or(group.bound).addr());
row[G_COL_BETA_PTR] = Felt::from(group.beta.addr());
row[G_COL_LAMBDA_PTR] = Felt::from(group.lambda.addr());
row[G_COL_MULT] =
Felt::from(requires.group_demand.get(&EcGroupPtr(ptr)).copied().unwrap_or(0));
}
vals.extend(row);
}
RowMajorMatrix::new(vals, G_NUM_MAIN_COLS)
}
pub(crate) fn points_trace(requires: &EcStoreRequires) -> RowMajorMatrix<Felt> {
let height = requires.points.len().next_power_of_two().max(2);
let mut vals = Vec::with_capacity(height * NUM_MAIN_COLS);
for (i, point) in requires.points.iter().enumerate() {
let ptr = i as u32 + 1;
let (a, b, bound) = requires.group_params(point.group);
let mut row = [Felt::ZERO; NUM_MAIN_COLS];
row[COL_PTR] = Felt::from(ptr);
row[COL_GROUP_PTR] = Felt::from(point.group.addr());
row[COL_A_PTR] = Felt::from(a.addr());
row[COL_B_PTR] = Felt::from(b.addr());
row[COL_BOUND_PTR] = Felt::from(bound.addr());
row[COL_SBOUND_PTR] = Felt::from(requires.group_sbound(point.group).addr());
let (beta, lambda) = requires.group_glv_params(point.group);
row[COL_BETA_PTR] = Felt::from(beta.addr());
row[COL_LAMBDA_PTR] = Felt::from(lambda.addr());
row[COL_X_PTR] = Felt::from(point.binding.map_or(0, |b| b.x.addr()));
row[COL_Y_PTR] = Felt::from(point.binding.map_or(0, |b| b.y.addr()));
let membership = point.binding.and_then(|b| b.membership);
row[COL_U_PTR] = Felt::from(membership.map_or(0, |(u, _)| u.addr()));
row[COL_W_PTR] = Felt::from(membership.map_or(0, |(_, w)| w.addr()));
row[COL_IS_PAI] = Felt::from(point.binding.is_none() as u32);
row[COL_IS_CERT] = Felt::from(point.binding.is_some_and(|b| b.membership.is_none()) as u32);
row[COL_ECPOINT_MULT] =
Felt::from(requires.point_demand.get(&EcPointPtr(ptr)).copied().unwrap_or(0));
row[COL_ACT] = Felt::ONE;
vals.extend(row);
}
vals.resize(height * NUM_MAIN_COLS, Felt::ZERO);
RowMajorMatrix::new(vals, NUM_MAIN_COLS)
}
pub(crate) fn build_groups_aux(
main: &RowMajorMatrix<Felt>,
challenges: &[QuadFelt],
) -> (RowMajorMatrix<QuadFelt>, Vec<QuadFelt>) {
build_logup_aux_trace(&EcGroupsAir, main, challenges)
}
pub(crate) fn build_points_aux(
main: &RowMajorMatrix<Felt>,
challenges: &[QuadFelt],
) -> (RowMajorMatrix<QuadFelt>, Vec<QuadFelt>) {
build_logup_aux_trace(&EcPointStoreAir, main, challenges)
}