pub mod digest;
pub mod math;
pub mod messages;
pub mod program;
pub mod trace;
use alloc::vec::Vec;
use core::array;
pub use digest::{P2Cap, P2Digest};
pub use messages::{
POSEIDON2_IN_TAG_CAP, POSEIDON2_IN_TAG_RATE0, POSEIDON2_IN_TAG_RATE1, Poseidon2InMsg,
Poseidon2OutMsg,
};
use miden_core::{
Felt,
chiplets::hasher::Hasher,
field::{PrimeCharacteristicRing, QuadFelt},
utils::RowMajorMatrix,
};
use miden_lifted_air::{AirBuilder, BaseAir, LiftedAir, LiftedAirBuilder};
use crate::{
logup::{
CyclicConstraintLookupBuilder, Deg, LookupAir, LookupBatch, LookupBuilder, LookupColumn,
LookupGroup, NUM_PUBLIC_VALUES, NUM_RANDOMNESS, NUM_SIGMA_VALUES,
},
relations::{MAX_MESSAGE_WIDTH, NUM_BUS_IDS},
transcript::poseidon2::{
math::{
NUM_CUBE_REGS, STATE_WIDTH, apply_init_plus_ext, apply_internal_plus_ext,
apply_packed_internals, apply_single_ext,
},
program::{
ARK_INT_LAST_IDX, PCOL_ARK_BEGIN, PCOL_IS_EXT, PCOL_IS_INIT_EXT, PCOL_IS_INT_EXT,
PCOL_IS_PACKED_INT, poseidon2_program,
},
},
utils::{current_main, next_main},
};
pub const COL_PERM_SEQ_ID: usize = 0;
pub const COL_IN_MULTIPLICITY: usize = 1;
pub const COL_OUT_MULTIPLICITY: usize = 2;
pub const COL_IS_ABSORB: usize = 3;
pub const COL_STATE_BEGIN: usize = 4;
pub const COL_STATE_END: usize = COL_STATE_BEGIN + STATE_WIDTH;
pub const COL_WITNESS_BEGIN: usize = COL_STATE_END;
pub const NUM_WITNESSES: usize = 3;
pub const COL_WITNESS_END: usize = COL_WITNESS_BEGIN + NUM_WITNESSES;
pub const COL_CUBE_BEGIN: usize = COL_WITNESS_END;
pub const COL_CUBE_END: usize = COL_CUBE_BEGIN + NUM_CUBE_REGS;
pub const NUM_MAIN_COLS: usize = COL_CUBE_END;
pub const COL_CAPACITY_BEGIN: usize = COL_STATE_BEGIN + 8;
pub const NUM_AUX_COLS: usize = 3;
const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 1];
#[derive(Debug, Default, Clone, Copy)]
pub struct Poseidon2Air;
impl BaseAir<Felt> for Poseidon2Air {
fn width(&self) -> usize {
NUM_MAIN_COLS
}
fn num_public_values(&self) -> usize {
NUM_PUBLIC_VALUES
}
fn periodic_columns(&self) -> Vec<Vec<Felt>> {
poseidon2_program().to_vec()
}
}
impl LiftedAir<Felt, QuadFelt> for Poseidon2Air {
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 periodic = builder.periodic_values();
let is_init_ext: AB::Expr = periodic[PCOL_IS_INIT_EXT].into();
let is_ext: AB::Expr = periodic[PCOL_IS_EXT].into();
let is_packed_int: AB::Expr = periodic[PCOL_IS_PACKED_INT].into();
let is_int_ext: AB::Expr = periodic[PCOL_IS_INT_EXT].into();
let p_last_in_cycle: AB::Expr = AB::Expr::ONE
- is_init_ext.clone()
- is_ext.clone()
- is_packed_int.clone()
- is_int_ext.clone();
let ark: [AB::Expr; STATE_WIDTH] =
array::from_fn(|lane| periodic[PCOL_ARK_BEGIN + lane].into());
let state: [AB::Expr; STATE_WIDTH] = array::from_fn(|i| local[COL_STATE_BEGIN + i].into());
let state_next: [AB::Expr; STATE_WIDTH] =
array::from_fn(|i| next[COL_STATE_BEGIN + i].into());
let w: [AB::Expr; NUM_WITNESSES] = array::from_fn(|i| local[COL_WITNESS_BEGIN + i].into());
let cube_regs: Vec<AB::Expr> =
(0..NUM_CUBE_REGS).map(|i| local[COL_CUBE_BEGIN + i].into()).collect();
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 in_multiplicity: AB::Expr = local[COL_IN_MULTIPLICITY].into();
let in_multiplicity_next: AB::Expr = next[COL_IN_MULTIPLICITY].into();
let out_multiplicity: AB::Expr = local[COL_OUT_MULTIPLICITY].into();
let out_multiplicity_next: AB::Expr = next[COL_OUT_MULTIPLICITY].into();
let is_absorb: AB::Expr = local[COL_IS_ABSORB].into();
let is_absorb_next: AB::Expr = next[COL_IS_ABSORB].into();
let activity: AB::Expr = in_multiplicity.clone() + out_multiplicity.clone();
builder.when_first_row().assert_zero(perm_seq_id.clone());
builder.when_first_row().assert_zero(is_absorb.clone());
builder.assert_zero(
(AB::Expr::ONE - p_last_in_cycle.clone())
* (perm_seq_id_next.clone() - perm_seq_id.clone()),
);
builder.when_transition().assert_zero(
p_last_in_cycle.clone() * (perm_seq_id_next - perm_seq_id - AB::Expr::ONE),
);
builder.assert_zero(
(AB::Expr::ONE - p_last_in_cycle.clone()) * (in_multiplicity_next - in_multiplicity),
);
builder.assert_zero(
(AB::Expr::ONE - p_last_in_cycle.clone()) * (out_multiplicity_next - out_multiplicity),
);
builder.assert_bool(local[COL_IS_ABSORB]);
builder.assert_zero(
(AB::Expr::ONE - p_last_in_cycle.clone()) * (is_absorb_next.clone() - is_absorb),
);
for i in 8..STATE_WIDTH {
builder.assert_zero(
p_last_in_cycle.clone()
* is_absorb_next.clone()
* (state_next[i].clone() - state[i].clone()),
);
}
let mat_diag: [AB::Expr; STATE_WIDTH] = array::from_fn(|i| Hasher::MAT_DIAG[i].into());
let ark_int_last: AB::Expr = Hasher::ARK_INT[ARK_INT_LAST_IDX].into();
let (expected_init_ext, init_ext_cubes) = apply_init_plus_ext(&state, &ark, &cube_regs);
for i in 0..STATE_WIDTH {
builder.assert_zero(
activity.clone()
* is_init_ext.clone()
* (state_next[i].clone() - expected_init_ext[i].clone()),
);
}
for cube in &init_ext_cubes {
builder.assert_zero(activity.clone() * is_init_ext.clone() * cube.clone());
}
let (expected_ext, ext_cubes) = apply_single_ext(&state, &ark, &cube_regs);
for i in 0..STATE_WIDTH {
builder.assert_zero(
activity.clone()
* is_ext.clone()
* (state_next[i].clone() - expected_ext[i].clone()),
);
}
for cube in &ext_cubes {
builder.assert_zero(activity.clone() * is_ext.clone() * cube.clone());
}
let ark_int_3: [AB::Expr; 3] = array::from_fn(|i| ark[i].clone());
let (expected_packed, packed_checks, packed_cubes) =
apply_packed_internals(&state, &w, &ark_int_3, &mat_diag, &cube_regs);
for check in &packed_checks {
builder.assert_zero(activity.clone() * is_packed_int.clone() * check.clone());
}
for cube in &packed_cubes {
builder.assert_zero(activity.clone() * is_packed_int.clone() * cube.clone());
}
for i in 0..STATE_WIDTH {
builder.assert_zero(
activity.clone()
* is_packed_int.clone()
* (state_next[i].clone() - expected_packed[i].clone()),
);
}
let (expected_int_ext, int_ext_check, int_ext_cubes) =
apply_internal_plus_ext(&state, &w[0], ark_int_last, &ark, &mat_diag, &cube_regs);
builder.assert_zero(activity.clone() * is_int_ext.clone() * int_ext_check);
for cube in &int_ext_cubes {
builder.assert_zero(activity.clone() * is_int_ext.clone() * cube.clone());
}
for i in 0..STATE_WIDTH {
builder.assert_zero(
activity.clone()
* is_int_ext.clone()
* (state_next[i].clone() - expected_int_ext[i].clone()),
);
}
builder.assert_zero((AB::Expr::ONE - is_packed_int.clone() - is_int_ext) * w[0].clone());
for witness in w.iter().skip(1) {
builder.assert_zero((AB::Expr::ONE - is_packed_int.clone()) * witness.clone());
}
let mut lb =
CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0);
<Self as LookupAir<_>>::eval(self, &mut lb);
}
}
impl<LB> LookupAir<LB> for Poseidon2Air
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 next: [LB::Var; NUM_MAIN_COLS] = next_main(builder.main(), 0);
let periodic = builder.periodic_values();
let is_init_ext: LB::Expr = periodic[PCOL_IS_INIT_EXT].into();
let is_ext: LB::Expr = periodic[PCOL_IS_EXT].into();
let is_packed_int: LB::Expr = periodic[PCOL_IS_PACKED_INT].into();
let is_int_ext: LB::Expr = periodic[PCOL_IS_INT_EXT].into();
let p_last_in_cycle: LB::Expr =
LB::Expr::ONE - is_init_ext.clone() - is_ext - is_packed_int - is_int_ext;
let perm_seq_id: LB::Expr = local[COL_PERM_SEQ_ID].into();
let in_multiplicity: LB::Expr = local[COL_IN_MULTIPLICITY].into();
let out_multiplicity: LB::Expr = local[COL_OUT_MULTIPLICITY].into();
let is_absorb: LB::Expr = local[COL_IS_ABSORB].into();
let is_absorb_next: LB::Expr = next[COL_IS_ABSORB].into();
let state: [LB::Expr; STATE_WIDTH] = array::from_fn(|i| local[COL_STATE_BEGIN + i].into());
let rate0_chunk: [LB::Expr; 4] = array::from_fn(|i| state[i].clone());
let rate1_chunk: [LB::Expr; 4] = array::from_fn(|i| state[4 + i].clone());
let cap_chunk: [LB::Expr; 4] = array::from_fn(|i| state[8 + i].clone());
let digest: [LB::Expr; 4] = array::from_fn(|i| state[i].clone());
let neg_in_mult: LB::Expr = LB::Expr::ZERO - in_multiplicity.clone();
let neg_in_mult_cap: LB::Expr =
(LB::Expr::ZERO - in_multiplicity) * (LB::Expr::ONE - is_absorb);
let neg_out_mult: LB::Expr =
(LB::Expr::ZERO - out_multiplicity) * (LB::Expr::ONE - is_absorb_next);
let interaction_deg = Deg { v: 1, u: 1 };
let row0_batch_deg = Deg { v: 4, u: 3 };
let row15_batch_deg = Deg { v: 4, u: 3 };
let group_deg = Deg { v: 5, u: 4 };
let m_in = is_init_ext.clone() * neg_in_mult;
let m_in_cap = is_init_ext * neg_in_mult_cap;
let m_out = p_last_in_cycle * neg_out_mult;
builder.next_column(
|col| {
col.group(
"p2-col0",
|g| {
g.batch(
"frac",
LB::Expr::ONE,
|b| {
b.insert(
"in_rate0",
m_in.clone(),
Poseidon2InMsg::rate0(perm_seq_id.clone(), rate0_chunk),
interaction_deg,
);
},
row0_batch_deg,
);
},
group_deg,
);
},
group_deg,
);
builder.next_column(
|col| {
col.group(
"p2-col1",
|g| {
g.batch(
"frac",
LB::Expr::ONE,
|b| {
b.insert(
"in_rate1",
m_in,
Poseidon2InMsg::rate1(perm_seq_id.clone(), rate1_chunk),
interaction_deg,
);
b.insert(
"out_rate0",
m_out,
Poseidon2OutMsg { perm_seq_id: perm_seq_id.clone(), digest },
interaction_deg,
);
},
row15_batch_deg,
);
},
group_deg,
);
},
group_deg,
);
builder.next_column(
|col| {
col.group(
"p2-col2",
|g| {
g.batch(
"frac",
LB::Expr::ONE,
|b| {
b.insert(
"in_cap",
m_in_cap,
Poseidon2InMsg::cap(perm_seq_id.clone(), cap_chunk),
interaction_deg,
);
},
row0_batch_deg,
);
},
group_deg,
);
},
group_deg,
);
}
}