extern crate alloc;
use alloc::string::ToString;
use alloc::vec::Vec;
use alloc::{
format,
vec,
};
use lib_q_poseidon::{
Poseidon,
Poseidon128,
PoseidonField,
};
use lib_q_stark_air::{
Air,
AirBuilder,
BaseAir,
WindowAccess,
};
use lib_q_stark_field::{
BasedVectorSpace,
Field,
};
use lib_q_stark_matrix::dense::RowMajorMatrix;
use lib_q_stark_mersenne31::Mersenne31;
use super::{
AirError,
TraceGenerator,
next_power_of_two,
poseidon_to_field,
validate_trace_dimensions,
};
const POSEIDON_128: Poseidon128 = Poseidon128;
pub const MAX_PREIMAGE_SIZE: usize = 64;
#[derive(Debug, Clone)]
pub struct PoseidonHashAir {
max_preimage_size: usize,
}
impl PoseidonHashAir {
pub fn new(max_preimage_size: usize) -> Result<Self, AirError> {
if max_preimage_size == 0 {
return Err(AirError::InvalidDimensions {
reason: "Max preimage size must be greater than 0".to_string(),
});
}
if max_preimage_size > MAX_PREIMAGE_SIZE {
return Err(AirError::ExceedsMaxSize {
parameter: "max_preimage_size".to_string(),
max: MAX_PREIMAGE_SIZE,
actual: max_preimage_size,
});
}
Ok(Self { max_preimage_size })
}
pub fn max_preimage_size(&self) -> usize {
self.max_preimage_size
}
fn trace_width(&self) -> usize {
self.max_preimage_size + 3 + 1
}
}
impl<F: Field + BasedVectorSpace<Mersenne31>> BaseAir<F> for PoseidonHashAir {
fn width(&self) -> usize {
self.trace_width()
}
}
impl<AB: AirBuilder> Air<AB> for PoseidonHashAir
where
AB::F: Field + BasedVectorSpace<Mersenne31>,
{
fn eval(&self, builder: &mut AB) {
let main = builder.main();
let local = main.current_slice();
let _preimage_start = 0;
let _state_start = self.max_preimage_size;
let output_idx = self.max_preimage_size + 3;
let _output = local[output_idx];
}
}
pub type PoseidonHashInput = Vec<PoseidonField>;
impl<F: Field + BasedVectorSpace<Mersenne31>> TraceGenerator<F, PoseidonHashInput>
for PoseidonHashAir
{
fn generate_trace(&self, inputs: &PoseidonHashInput) -> Result<RowMajorMatrix<F>, AirError> {
if inputs.is_empty() {
return Err(AirError::InvalidInput {
reason: "Preimage cannot be empty".to_string(),
});
}
if inputs.len() > self.max_preimage_size {
return Err(AirError::InvalidInput {
reason: format!(
"Preimage size {} exceeds maximum {}",
inputs.len(),
self.max_preimage_size
),
});
}
let width = self.trace_width();
let num_rows = 1;
let num_rows_padded = next_power_of_two(num_rows);
validate_trace_dimensions(width, num_rows_padded)?;
let mut trace_values = vec![F::ZERO; num_rows_padded * width];
for (i, element) in inputs.iter().enumerate() {
trace_values[i] = poseidon_to_field(element);
}
let hash_output = POSEIDON_128.hash(inputs);
if !hash_output.is_empty() {
trace_values[self.max_preimage_size + 3] = poseidon_to_field(&hash_output[0]);
}
Ok(RowMajorMatrix::new(trace_values, width))
}
fn public_values(&self, inputs: &PoseidonHashInput) -> Vec<F> {
let hash_output = POSEIDON_128.hash(inputs);
hash_output.iter().map(poseidon_to_field).collect()
}
}
#[cfg(test)]
mod tests {
use lib_q_stark::check_constraints;
use lib_q_stark_air::BaseAir;
use lib_q_stark_field::PrimeCharacteristicRing;
use lib_q_stark_field::extension::Complex;
use lib_q_stark_matrix::Matrix;
use lib_q_stark_matrix::dense::RowMajorMatrix;
use lib_q_stark_mersenne31::Mersenne31;
use super::*;
type TestField = Complex<Mersenne31>;
#[test]
fn test_poseidon_hash_air_new_valid() {
let air = PoseidonHashAir::new(32);
assert!(air.is_ok());
assert_eq!(air.unwrap().max_preimage_size(), 32);
}
#[test]
fn test_poseidon_hash_air_new_zero_size() {
let result = PoseidonHashAir::new(0);
assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
}
#[test]
fn test_poseidon_hash_air_new_rejects_oversized_input_capacity() {
let result = PoseidonHashAir::new(MAX_PREIMAGE_SIZE + 1);
assert!(matches!(result, Err(AirError::ExceedsMaxSize { .. })));
}
#[test]
fn test_poseidon_hash_air_width() {
let air = PoseidonHashAir::new(16).unwrap();
assert_eq!(BaseAir::<TestField>::width(&air), 20);
}
#[test]
fn test_poseidon_hash_generate_trace_rejects_empty_preimage() {
let air = PoseidonHashAir::new(4).unwrap();
let input: PoseidonHashInput = vec![];
let result: Result<RowMajorMatrix<TestField>, _> = air.generate_trace(&input);
assert!(matches!(result, Err(AirError::InvalidInput { .. })));
}
#[test]
fn test_poseidon_hash_generate_trace_rejects_oversized_preimage() {
let air = PoseidonHashAir::new(2).unwrap();
let input: PoseidonHashInput = vec![TestField::ONE, TestField::ONE, TestField::ONE];
let result: Result<RowMajorMatrix<TestField>, _> = air.generate_trace(&input);
assert!(matches!(result, Err(AirError::InvalidInput { .. })));
}
#[test]
fn test_poseidon_hash_trace_output_matches_public_values() {
let air = PoseidonHashAir::new(4).unwrap();
let input: PoseidonHashInput = vec![TestField::ONE, TestField::ONE];
let trace: RowMajorMatrix<TestField> = air.generate_trace(&input).expect("trace");
let public_values: Vec<TestField> = air.public_values(&input);
assert_eq!(public_values.len(), 1);
let output_col = 4 + 3;
assert_eq!(trace.get(0, output_col), Some(public_values[0]));
}
#[test]
fn test_poseidon_hash_trace_satisfies_constraints() {
let air = PoseidonHashAir::new(4).unwrap();
let input: PoseidonHashInput = vec![TestField::ONE, TestField::ONE];
let trace: RowMajorMatrix<TestField> = air.generate_trace(&input).expect("trace");
let public_values: Vec<TestField> = air.public_values(&input);
check_constraints(&air, &trace, &public_values);
}
}