pub mod trace;
use alloc::vec::Vec;
use core::array;
use miden_core::{
Felt,
deferred::Tag,
field::{PrimeCharacteristicRing, QuadFelt},
utils::RowMajorMatrix,
};
use miden_lifted_air::{AirBuilder, BaseAir, LiftedAir, LiftedAirBuilder};
use miden_precompiles::Keccak256Precompile;
use crate::{
hash::{chunk::ChunkChainMsg, keccak::sponge::KeccakSpongeMsg, memory64::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::{
binding::BindingMsg,
poseidon2::{Poseidon2InMsg, Poseidon2OutMsg},
},
utils::{current_main, next_main},
};
pub const COL_ACT: usize = 0;
pub const COL_SPONGE_SEQ_ID_HEAD: usize = 1;
pub const COL_N_SPONGE_PERMS: usize = 2;
pub const COL_CHUNK_SEQ_ID_HEAD: usize = 3;
pub const COL_N_CHUNKS: usize = 4;
pub const COL_PERM_SEQ_ID_CHUNKS: usize = 5;
pub const COL_LEN_BYTES: usize = 6;
pub const COL_PERM_SEQ_ID_DIGEST_CHUNKS: usize = 7;
pub const COL_PERM_SEQ_ID_KECCAK: usize = 8;
pub const COL_D_BEGIN: usize = 9;
pub const NUM_D: usize = 8;
pub const COL_D_END: usize = COL_D_BEGIN + NUM_D;
pub const NUM_HASH: usize = 4;
pub const COL_H_INPUT_CHUNKS_BEGIN: usize = COL_D_END;
pub const COL_H_INPUT_CHUNKS_END: usize = COL_H_INPUT_CHUNKS_BEGIN + NUM_HASH;
pub const COL_H_DIGEST_CHUNKS_BEGIN: usize = COL_H_INPUT_CHUNKS_END;
pub const COL_H_DIGEST_CHUNKS_END: usize = COL_H_DIGEST_CHUNKS_BEGIN + NUM_HASH;
pub const COL_H_KECCAK_BEGIN: usize = COL_H_DIGEST_CHUNKS_END;
pub const COL_H_KECCAK_END: usize = COL_H_KECCAK_BEGIN + NUM_HASH;
pub const COL_OUT_MULT: usize = COL_H_KECCAK_END;
pub const NUM_MAIN_COLS: usize = COL_OUT_MULT + 1;
pub const NUM_AUX_COLS: usize = 9;
const COLUMN_SHAPE: [usize; NUM_AUX_COLS] = [1, 2, 1, 2, 2, 2, 2, 2, 2];
#[derive(Debug, Default, Clone, Copy)]
pub struct KeccakNodeAir;
impl BaseAir<Felt> for KeccakNodeAir {
fn width(&self) -> usize {
NUM_MAIN_COLS
}
fn num_public_values(&self) -> usize {
NUM_PUBLIC_VALUES
}
}
impl LiftedAir<Felt, QuadFelt> for KeccakNodeAir {
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 act: AB::Expr = local[COL_ACT].into();
let act_next: AB::Expr = next[COL_ACT].into();
let out_mult: AB::Expr = local[COL_OUT_MULT].into();
let sponge_seq_id_head: AB::Expr = local[COL_SPONGE_SEQ_ID_HEAD].into();
let sponge_seq_id_head_next: AB::Expr = next[COL_SPONGE_SEQ_ID_HEAD].into();
let n_sponge_perms: AB::Expr = local[COL_N_SPONGE_PERMS].into();
let chunk_seq_id_head: AB::Expr = local[COL_CHUNK_SEQ_ID_HEAD].into();
let chunk_seq_id_head_next: AB::Expr = next[COL_CHUNK_SEQ_ID_HEAD].into();
let n_chunks: AB::Expr = local[COL_N_CHUNKS].into();
let _ = next[COL_PERM_SEQ_ID_CHUNKS];
builder.when_first_row().assert_zero(sponge_seq_id_head.clone());
builder.when_first_row().assert_zero(chunk_seq_id_head.clone());
builder.assert_bool(local[COL_ACT]);
builder
.when_transition()
.assert_zero((AB::Expr::ONE - act.clone()) * act_next.clone());
builder.assert_zero((AB::Expr::ONE - act) * out_mult);
builder.when_transition().assert_zero(
act_next.clone()
* (sponge_seq_id_head_next
- sponge_seq_id_head
- AB::Expr::from(Felt::from(32u8)) * n_sponge_perms),
);
builder
.when_transition()
.assert_zero(act_next * (chunk_seq_id_head_next - chunk_seq_id_head - n_chunks));
let mut lb =
CyclicConstraintLookupBuilder::new(builder, self, self.preprocessed_width() > 0);
<Self as LookupAir<_>>::eval(self, &mut lb);
}
}
impl<LB> LookupAir<LB> for KeccakNodeAir
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 act: LB::Expr = local[COL_ACT].into();
let sponge_seq_id_head: LB::Expr = local[COL_SPONGE_SEQ_ID_HEAD].into();
let n_sponge_perms: LB::Expr = local[COL_N_SPONGE_PERMS].into();
let chunk_seq_id_head: LB::Expr = local[COL_CHUNK_SEQ_ID_HEAD].into();
let n_chunks: LB::Expr = local[COL_N_CHUNKS].into();
let perm_seq_id_chunks: LB::Expr = local[COL_PERM_SEQ_ID_CHUNKS].into();
let len_bytes: LB::Expr = local[COL_LEN_BYTES].into();
let perm_seq_id_digest_chunks: LB::Expr = local[COL_PERM_SEQ_ID_DIGEST_CHUNKS].into();
let perm_seq_id_keccak: LB::Expr = local[COL_PERM_SEQ_ID_KECCAK].into();
let d: [LB::Expr; NUM_D] = array::from_fn(|i| local[COL_D_BEGIN + i].into());
let h_input_chunks: [LB::Expr; NUM_HASH] =
array::from_fn(|i| local[COL_H_INPUT_CHUNKS_BEGIN + i].into());
let h_digest_chunks: [LB::Expr; NUM_HASH] =
array::from_fn(|i| local[COL_H_DIGEST_CHUNKS_BEGIN + i].into());
let h_keccak: [LB::Expr; NUM_HASH] =
array::from_fn(|i| local[COL_H_KECCAK_BEGIN + i].into());
let neg_act: LB::Expr = LB::Expr::ZERO - act.clone();
let pos_act: LB::Expr = act.clone();
let pos_act_x2: LB::Expr = LB::Expr::from(Felt::from(2u8)) * act;
let out_mult: LB::Expr = local[COL_OUT_MULT].into();
let neg_out_mult: LB::Expr = LB::Expr::ZERO - out_mult;
let chunk_ptr_head: LB::Expr = LB::Expr::from(Felt::from(4u8)) * chunk_seq_id_head.clone();
let perm_seq_id_chunks_tail: LB::Expr =
perm_seq_id_chunks.clone() + n_chunks - LB::Expr::ONE;
let digest_addr_base: LB::Expr = LB::Expr::from(Felt::from(100u8)) * sponge_seq_id_head
+ LB::Expr::from(Felt::from(3200u32)) * n_sponge_perms
- LB::Expr::from(Felt::from(128u8));
let cap_digest_chunks = Tag::CHUNKS.as_word().map(LB::Expr::from);
let cap_keccak = [
LB::Expr::from(Keccak256Precompile::id()),
LB::Expr::from(Felt::from_u32(Keccak256Precompile::ASSERT_TAG_ID)),
len_bytes.clone(),
LB::Expr::ZERO,
];
let d_rate0 = [d[0].clone(), d[1].clone(), d[2].clone(), d[3].clone()];
let d_rate1 = [d[4].clone(), d[5].clone(), d[6].clone(), d[7].clone()];
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,
"handshake-and-chunks-digest",
provides_deg,
(
"ks-request",
neg_act.clone(),
KeccakSpongeMsg {
sponge_seq_id: local[COL_SPONGE_SEQ_ID_HEAD].into(),
chunk_ptr: chunk_ptr_head,
len_bytes: len_bytes.clone(),
},
interaction_deg
),
);
frac_col!(
builder,
"handshake-and-chunks-digest",
pair_deg,
(
"binding-truth",
neg_out_mult,
BindingMsg::truth(h_keccak.clone()),
interaction_deg
),
(
"chunk-chain",
pos_act.clone(),
ChunkChainMsg {
chunk_seq_id_head: chunk_seq_id_head.clone(),
perm_seq_id_head: perm_seq_id_chunks,
},
interaction_deg
),
);
frac_col!(
builder,
"handshake-and-chunks-digest",
provides_deg,
(
"p2out-h-input-chunks",
pos_act.clone(),
Poseidon2OutMsg {
perm_seq_id: perm_seq_id_chunks_tail,
digest: h_input_chunks.clone(),
},
interaction_deg
),
);
let addr_lane =
|j: u8| -> LB::Expr { digest_addr_base.clone() + LB::Expr::from(Felt::from(j)) };
frac_col!(
builder,
"memory64-d-limbs",
pair_deg,
(
"d-lane-0",
pos_act_x2.clone(),
Memory64Msg {
addr: addr_lane(0),
lo: d[0].clone(),
hi: d[1].clone()
},
interaction_deg
),
(
"d-lane-1",
pos_act_x2.clone(),
Memory64Msg {
addr: addr_lane(1),
lo: d[2].clone(),
hi: d[3].clone()
},
interaction_deg
),
);
frac_col!(
builder,
"memory64-d-limbs",
pair_deg,
(
"d-lane-2",
pos_act_x2.clone(),
Memory64Msg {
addr: addr_lane(2),
lo: d[4].clone(),
hi: d[5].clone()
},
interaction_deg
),
(
"d-lane-3",
pos_act_x2,
Memory64Msg {
addr: addr_lane(3),
lo: d[6].clone(),
hi: d[7].clone()
},
interaction_deg
),
);
frac_col!(
builder,
"digest-chunks-p2",
pair_deg,
(
"p2in-rate0",
pos_act.clone(),
Poseidon2InMsg::rate0(perm_seq_id_digest_chunks.clone(), d_rate0),
interaction_deg
),
(
"p2in-rate1",
pos_act.clone(),
Poseidon2InMsg::rate1(perm_seq_id_digest_chunks.clone(), d_rate1),
interaction_deg
),
);
frac_col!(
builder,
"digest-chunks-p2",
pair_deg,
(
"p2in-cap",
pos_act.clone(),
Poseidon2InMsg::cap(perm_seq_id_digest_chunks.clone(), cap_digest_chunks),
interaction_deg
),
(
"p2out-h-digest-chunks",
pos_act.clone(),
Poseidon2OutMsg {
perm_seq_id: perm_seq_id_digest_chunks,
digest: h_digest_chunks.clone(),
},
interaction_deg
),
);
frac_col!(
builder,
"keccak-p2",
pair_deg,
(
"p2in-rate0",
pos_act.clone(),
Poseidon2InMsg::rate0(perm_seq_id_keccak.clone(), h_input_chunks),
interaction_deg
),
(
"p2in-rate1",
pos_act.clone(),
Poseidon2InMsg::rate1(perm_seq_id_keccak.clone(), h_digest_chunks),
interaction_deg
),
);
frac_col!(
builder,
"keccak-p2",
pair_deg,
(
"p2in-cap",
pos_act.clone(),
Poseidon2InMsg::cap(perm_seq_id_keccak.clone(), cap_keccak),
interaction_deg
),
(
"p2out-h-keccak",
pos_act,
Poseidon2OutMsg {
perm_seq_id: perm_seq_id_keccak,
digest: h_keccak
},
interaction_deg
),
);
}
}