extern crate alloc;
use alloc::string::{
String,
ToString,
};
use alloc::vec::Vec;
use core::fmt;
use lib_q_poseidon::{
PoseidonField,
PoseidonParams,
sbox,
};
use lib_q_stark_field::{
BasedVectorSpace,
Field,
PrimeCharacteristicRing,
};
use lib_q_stark_matrix::dense::RowMajorMatrix;
use lib_q_stark_mersenne31::Mersenne31;
pub mod anonymous_auth;
pub mod arithmetic;
pub mod batch_stark_verifier;
pub mod commitment_verifier;
pub mod constraint_verifier;
pub mod credential;
pub mod fri_verifier;
pub mod hash_preimage;
pub mod hash_preimage_nist;
pub mod identity_proof;
pub mod merkle_inclusion;
pub mod opening_verifier;
pub mod poseidon_gadget;
pub mod poseidon_hash;
pub mod range_proof;
pub mod recursive_types;
pub mod session_key;
pub mod stark_verifier;
pub mod state_transition;
pub mod transaction;
pub mod verifier_utils;
pub use anonymous_auth::{
AnonymousAuthAir,
AnonymousAuthInput,
};
pub use arithmetic::ArithmeticAir;
pub use batch_stark_verifier::{
BatchRecursiveStarkVerificationInput,
BatchStarkVerifierAir,
batch_recursive_verifier_public_values,
};
#[cfg(all(feature = "recursive-proofs-experimental", feature = "std"))]
pub use commitment_verifier::debug_commitment_trace_sanity_check;
pub use commitment_verifier::{
CommitmentVerificationInput,
CommitmentVerifierAir,
};
pub use constraint_verifier::{
ConstraintVerificationInput,
ConstraintVerifierAir,
};
pub use credential::{
CredentialAir,
CredentialInput,
CredentialSchema,
};
pub use fri_verifier::{
FriVerificationInput,
FriVerifierAir,
};
pub use hash_preimage::HashPreimageAir;
pub use hash_preimage_nist::{
HASH_OUTPUT_BYTES,
HashPreimageNistAir,
HashPreimageNistInput,
expected_hash_to_public_values,
};
pub use identity_proof::{
IdentityProofAir,
IdentityProofInput,
MlDsaLevel,
};
pub use merkle_inclusion::{
MerkleHash,
MerkleInclusionAir,
MerkleProofInput,
};
pub use opening_verifier::{
OpeningVerificationInput,
OpeningVerifierAir,
};
pub use poseidon_gadget::PoseidonGadget;
pub use poseidon_hash::PoseidonHashAir;
pub use range_proof::RangeProofAir;
pub use recursive_types::{
RecursiveStarkInput,
SerializedFriRound,
SerializedStarkProof,
};
pub use session_key::{
KdfAlgorithm,
KdfParams,
SessionKeyDerivationAir,
SessionKeyInput,
};
pub trait PoseidonCommitmentRoot {
fn to_poseidon_root_bytes(&self) -> [u8; recursive_types::COMMITMENT_HASH_SIZE];
}
#[cfg(feature = "recursive-proofs-experimental")]
pub use stark_verifier::{
MerklePathExtractable,
build_recursive_verification_input_from_proof,
build_recursive_verification_input_from_proof_with_poseidon,
};
pub use stark_verifier::{
RecursiveStarkVerificationInput,
StarkVerifierAir,
build_recursive_verification_input,
};
pub const MAX_OPERATIONS: usize = 1 << 20;
pub const MAX_TRACE_WIDTH: usize = 1 << 17;
pub const MAX_TRACE_HEIGHT: usize = 1 << 24;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AirError {
InvalidDimensions {
reason: String,
},
ExceedsMaxSize {
parameter: String,
max: usize,
actual: usize,
},
InvalidInput {
reason: String,
},
TraceMismatch {
expected_width: usize,
actual_width: usize,
},
InvalidWitness {
constraint: String,
},
InternalError {
reason: String,
},
NotSupported {
reason: String,
},
MissingFriCommitPhaseOpenings,
FriRoundCountMismatch,
}
impl fmt::Display for AirError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AirError::InvalidDimensions { reason } => {
write!(f, "Invalid AIR dimensions: {}", reason)
}
AirError::ExceedsMaxSize {
parameter,
max,
actual,
} => {
write!(
f,
"AIR parameter '{}' exceeds maximum: max={}, actual={}",
parameter, max, actual
)
}
AirError::InvalidInput { reason } => {
write!(f, "Invalid input for trace generation: {}", reason)
}
AirError::TraceMismatch {
expected_width,
actual_width,
} => {
write!(
f,
"Trace width mismatch: expected {}, got {}",
expected_width, actual_width
)
}
AirError::InvalidWitness { constraint } => {
write!(
f,
"Invalid witness: constraint '{}' not satisfied",
constraint
)
}
AirError::InternalError { reason } => {
write!(f, "Internal AIR error: {}", reason)
}
AirError::NotSupported { reason } => {
write!(f, "Not supported: {}", reason)
}
AirError::MissingFriCommitPhaseOpenings => {
write!(f, "FRI commit-phase openings missing for query index")
}
AirError::FriRoundCountMismatch => {
write!(
f,
"FRI commit-phase step count does not match number of rounds"
)
}
}
}
}
impl From<AirError> for lib_q_core::Error {
fn from(err: AirError) -> Self {
lib_q_core::Error::InternalError {
operation: "AIR operation".into(),
details: err.to_string(),
}
}
}
pub trait TraceGenerator<F: Field, I> {
fn generate_trace(&self, inputs: &I) -> Result<RowMajorMatrix<F>, AirError>;
fn public_values(&self, inputs: &I) -> Vec<F> {
let _ = inputs;
Vec::new()
}
}
pub fn validate_trace_dimensions(width: usize, height: usize) -> Result<(), AirError> {
if width == 0 {
return Err(AirError::InvalidDimensions {
reason: "Trace width must be greater than 0".into(),
});
}
if width > MAX_TRACE_WIDTH {
return Err(AirError::ExceedsMaxSize {
parameter: "width".into(),
max: MAX_TRACE_WIDTH,
actual: width,
});
}
if height == 0 {
return Err(AirError::InvalidDimensions {
reason: "Trace height must be greater than 0".into(),
});
}
if height > MAX_TRACE_HEIGHT {
return Err(AirError::ExceedsMaxSize {
parameter: "height".into(),
max: MAX_TRACE_HEIGHT,
actual: height,
});
}
if !height.is_power_of_two() {
return Err(AirError::InvalidDimensions {
reason: "Trace height must be a power of 2".into(),
});
}
Ok(())
}
pub fn next_power_of_two(n: usize) -> usize {
if n == 0 {
return 1;
}
n.next_power_of_two()
}
pub fn poseidon_to_field<F: Field + BasedVectorSpace<Mersenne31>>(pf: &PoseidonField) -> F {
let coeffs: &[Mersenne31] = pf.as_basis_coefficients_slice();
F::from_basis_coefficients_fn(|i| {
if i < coeffs.len() {
coeffs[i]
} else {
<Mersenne31 as PrimeCharacteristicRing>::ZERO
}
})
}
pub fn poseidon_slice_to_field<F: Field + BasedVectorSpace<Mersenne31>>(
slice: &[PoseidonField],
) -> Vec<F> {
slice.iter().map(poseidon_to_field).collect()
}
pub fn poseidon_field_to_bytes(hash: &[PoseidonField]) -> Vec<u8> {
use lib_q_stark_field::RawDataSerializable;
hash.iter().flat_map(|f| (*f).into_bytes()).collect()
}
#[must_use]
pub fn merkle_root_to_bytes(root: &PoseidonField) -> [u8; 32] {
use lib_q_stark_field::RawDataSerializable;
let mut out = [0u8; 32];
let bytes: Vec<u8> = (*root).into_bytes().into_iter().collect();
let n = core::cmp::min(bytes.len(), 32);
out[..n].copy_from_slice(&bytes[..n]);
out
}
pub fn merkle_root_from_bytes(bytes: &[u8]) -> Result<PoseidonField, AirError> {
use lib_q_stark_field::extension::Complex;
use lib_q_stark_field::integers::QuotientMap;
use lib_q_stark_mersenne31::Mersenne31;
if bytes.len() < 8 {
return Err(AirError::InvalidInput {
reason: alloc::format!(
"Merkle root bytes must have at least 8 bytes, got {}",
bytes.len()
),
});
}
let mut real_bytes = [0u8; 4];
let mut imag_bytes = [0u8; 4];
real_bytes.copy_from_slice(&bytes[0..4]);
imag_bytes.copy_from_slice(&bytes[4..8]);
let real = Mersenne31::from_int(u32::from_le_bytes(real_bytes));
let imag = Mersenne31::from_int(u32::from_le_bytes(imag_bytes));
Ok(Complex::new_complex(real, imag))
}
pub fn compute_poseidon_row(
state: &[PoseidonField],
params: &PoseidonParams,
) -> (Vec<PoseidonField>, Vec<PoseidonField>) {
use lib_q_stark_field::extension::Complex;
use lib_q_stark_mersenne31::Mersenne31;
let n = params.state_width;
assert!(state.len() >= n, "state must have at least {} elements", n);
let zero = Complex::<Mersenne31>::new_complex(Mersenne31::ZERO, Mersenne31::ZERO);
let mut intermediates = Vec::new();
let mut round_idx = 0usize;
let mut s: Vec<PoseidonField> = state[0..n].to_vec();
let full_half = params.full_rounds / 2;
for _ in 0..full_half {
let after_arc: Vec<PoseidonField> = (0..n)
.map(|i| s[i] + params.round_constants[round_idx + i])
.collect();
round_idx += n;
intermediates.extend(after_arc.iter().cloned());
let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
intermediates.extend(after_sbox.iter().cloned());
let mut next_s = alloc::vec![zero; n];
for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
*next_s_i += params.mds_matrix[i][j] * after_sbox_j;
}
}
intermediates.extend(next_s.iter().cloned());
s = next_s;
}
for _ in 0..params.partial_rounds {
let after_arc: Vec<PoseidonField> = (0..n)
.map(|i| s[i] + params.round_constants[round_idx + i])
.collect();
round_idx += n;
intermediates.extend(after_arc.iter().cloned());
let mut after_sbox = alloc::vec![zero; n];
after_sbox[0] = sbox(after_arc[0]);
after_sbox[1..n].copy_from_slice(&after_arc[1..n]);
intermediates.extend(after_sbox.iter().cloned());
let mut next_s = alloc::vec![zero; n];
for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
*next_s_i += params.mds_matrix[i][j] * after_sbox_j;
}
}
intermediates.extend(next_s.iter().cloned());
s = next_s;
}
for _ in 0..full_half {
let after_arc: Vec<PoseidonField> = (0..n)
.map(|i| s[i] + params.round_constants[round_idx + i])
.collect();
round_idx += n;
intermediates.extend(after_arc.iter().cloned());
let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
intermediates.extend(after_sbox.iter().cloned());
let mut next_s = alloc::vec![zero; n];
for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
*next_s_i += params.mds_matrix[i][j] * after_sbox_j;
}
}
intermediates.extend(next_s.iter().cloned());
s = next_s;
}
(s, intermediates)
}
pub fn bytes_to_poseidon_field(bytes: &[u8]) -> Vec<PoseidonField> {
use lib_q_stark_field::extension::Complex;
use lib_q_stark_mersenne31::Mersenne31;
bytes
.iter()
.map(|b| Complex::<Mersenne31>::from(Mersenne31::new(*b as u32)))
.collect()
}
pub fn it_bytes_to_public_value<F: Field + BasedVectorSpace<Mersenne31>>(it: &[u8; 16]) -> F {
use lib_q_stark_field::extension::Complex;
use lib_q_stark_mersenne31::Mersenne31;
let mut real_bytes = [0u8; 4];
let mut imag_bytes = [0u8; 4];
real_bytes.copy_from_slice(&it[0..4]);
imag_bytes.copy_from_slice(&it[4..8]);
let real = Mersenne31::new(u32::from_le_bytes(real_bytes));
let imag = Mersenne31::new(u32::from_le_bytes(imag_bytes));
let c = Complex::new_complex(real, imag);
poseidon_to_field::<F>(&c)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_trace_dimensions_valid() {
assert!(validate_trace_dimensions(8, 16).is_ok());
assert!(validate_trace_dimensions(1, 1).is_ok());
assert!(validate_trace_dimensions(100, 1024).is_ok());
}
#[test]
fn test_validate_trace_dimensions_zero_width() {
let result = validate_trace_dimensions(0, 16);
assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
}
#[test]
fn test_validate_trace_dimensions_zero_height() {
let result = validate_trace_dimensions(8, 0);
assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
}
#[test]
fn test_validate_trace_dimensions_not_power_of_two() {
let result = validate_trace_dimensions(8, 15);
assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
}
#[test]
fn test_next_power_of_two() {
assert_eq!(next_power_of_two(0), 1);
assert_eq!(next_power_of_two(1), 1);
assert_eq!(next_power_of_two(2), 2);
assert_eq!(next_power_of_two(3), 4);
assert_eq!(next_power_of_two(5), 8);
assert_eq!(next_power_of_two(16), 16);
}
#[test]
fn test_air_error_display() {
let err = AirError::InvalidDimensions {
reason: "test".into(),
};
assert!(err.to_string().contains("Invalid AIR dimensions"));
let err = AirError::ExceedsMaxSize {
parameter: "width".into(),
max: 100,
actual: 200,
};
assert!(err.to_string().contains("exceeds maximum"));
}
}