1extern crate alloc;
39
40use alloc::string::{
41 String,
42 ToString,
43};
44use alloc::vec::Vec;
45use core::fmt;
46
47use lib_q_poseidon::{
48 PoseidonField,
49 PoseidonParams,
50 sbox,
51};
52use lib_q_stark_field::{
53 BasedVectorSpace,
54 Field,
55 PrimeCharacteristicRing,
56};
57use lib_q_stark_matrix::dense::RowMajorMatrix;
58use lib_q_stark_mersenne31::Mersenne31;
59
60pub mod anonymous_auth;
61pub mod arithmetic;
62pub mod batch_stark_verifier;
63pub mod commitment_verifier;
64pub mod constraint_verifier;
65pub mod credential;
66pub mod fri_verifier;
67pub mod hash_preimage;
68pub mod hash_preimage_nist;
69pub mod identity_proof;
70pub mod merkle_inclusion;
71pub mod opening_verifier;
72pub mod poseidon_gadget;
73pub mod poseidon_hash;
74pub mod range_proof;
75pub mod recovery_policy;
76pub mod recovery_policy_hybrid;
77pub mod recursive_types;
78pub mod session_key;
79pub mod stark_verifier;
80pub mod state_transition;
81pub mod transaction;
82pub mod verifier_utils;
83
84pub use anonymous_auth::{
85 AnonymousAuthAir,
86 AnonymousAuthInput,
87};
88pub use arithmetic::ArithmeticAir;
89pub use batch_stark_verifier::{
90 BatchRecursiveStarkVerificationInput,
91 BatchStarkVerifierAir,
92 batch_recursive_verifier_public_values,
93};
94#[cfg(all(feature = "recursive-proofs-experimental", feature = "std"))]
95pub use commitment_verifier::debug_commitment_trace_sanity_check;
96pub use commitment_verifier::{
97 CommitmentVerificationInput,
98 CommitmentVerifierAir,
99};
100pub use constraint_verifier::{
101 ConstraintVerificationInput,
102 ConstraintVerifierAir,
103};
104pub use credential::{
105 CredentialAir,
106 CredentialInput,
107 CredentialSchema,
108};
109pub use fri_verifier::{
110 FriVerificationInput,
111 FriVerifierAir,
112};
113pub use hash_preimage::HashPreimageAir;
114pub use hash_preimage_nist::{
115 HASH_OUTPUT_BYTES,
116 HashPreimageNistAir,
117 HashPreimageNistInput,
118 expected_hash_to_public_values,
119};
120pub use identity_proof::{
121 IdentityProofAir,
122 IdentityProofInput,
123 MlDsaLevel,
124};
125pub use merkle_inclusion::{
126 MerkleHash,
127 MerkleInclusionAir,
128 MerkleProofInput,
129};
130pub use opening_verifier::{
131 OpeningVerificationInput,
132 OpeningVerifierAir,
133};
134pub use poseidon_gadget::PoseidonGadget;
135pub use poseidon_hash::PoseidonHashAir;
136pub use range_proof::RangeProofAir;
137pub use recovery_policy::{
138 RECOVERY_POLICY_AIR_ID,
139 RECOVERY_POLICY_COMMIT_DOMAIN,
140 RECOVERY_PUBLIC_INPUTS_LEN,
141 RECOVERY_VK_COMMIT_DOMAIN,
142 RecoveryPolicyAir,
143 RecoveryPolicyInput,
144 RecoveryPolicyKey,
145 RecoveryPolicyPublicInputs,
146 policy_commitment,
147 shake256_commit,
148 vk_commitment,
149};
150pub use recovery_policy_hybrid::{
151 RECOVERY_HYBRID_POLICY_COMMIT_DOMAIN,
152 RECOVERY_HYBRID_PUBLIC_INPUTS_LEN,
153 RECOVERY_HYBRID_VK_COMMIT_DOMAIN,
154 RECOVERY_POLICY_HYBRID_AIR_ID,
155 RecoveryPolicyHybridAir,
156 RecoveryPolicyHybridInput,
157 RecoveryPolicyHybridPublicInputs,
158 hybrid_policy_commitment,
159};
160pub use recursive_types::{
161 RecursiveStarkInput,
162 SerializedFriRound,
163 SerializedStarkProof,
164};
165pub use session_key::{
166 KdfAlgorithm,
167 KdfParams,
168 SessionKeyDerivationAir,
169 SessionKeyInput,
170};
171pub trait PoseidonCommitmentRoot {
174 fn to_poseidon_root_bytes(&self) -> [u8; recursive_types::COMMITMENT_HASH_SIZE];
175}
176
177#[cfg(feature = "recursive-proofs-experimental")]
178pub use stark_verifier::{
179 MerklePathExtractable,
180 build_recursive_verification_input_from_proof,
181 build_recursive_verification_input_from_proof_with_poseidon,
182};
183pub use stark_verifier::{
184 RecursiveStarkVerificationInput,
185 StarkVerifierAir,
186 build_recursive_verification_input,
187};
188
189pub 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)]
202pub enum AirError {
203 InvalidDimensions {
205 reason: String,
207 },
208
209 ExceedsMaxSize {
211 parameter: String,
213 max: usize,
215 actual: usize,
217 },
218
219 InvalidInput {
221 reason: String,
223 },
224
225 TraceMismatch {
227 expected_width: usize,
229 actual_width: usize,
231 },
232
233 InvalidWitness {
235 constraint: String,
237 },
238
239 InternalError {
241 reason: String,
243 },
244
245 NotSupported {
247 reason: String,
249 },
250
251 MissingFriCommitPhaseOpenings,
253
254 FriRoundCountMismatch,
256}
257
258impl fmt::Display for AirError {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 match self {
261 AirError::InvalidDimensions { reason } => {
262 write!(f, "Invalid AIR dimensions: {}", reason)
263 }
264 AirError::ExceedsMaxSize {
265 parameter,
266 max,
267 actual,
268 } => {
269 write!(
270 f,
271 "AIR parameter '{}' exceeds maximum: max={}, actual={}",
272 parameter, max, actual
273 )
274 }
275 AirError::InvalidInput { reason } => {
276 write!(f, "Invalid input for trace generation: {}", reason)
277 }
278 AirError::TraceMismatch {
279 expected_width,
280 actual_width,
281 } => {
282 write!(
283 f,
284 "Trace width mismatch: expected {}, got {}",
285 expected_width, actual_width
286 )
287 }
288 AirError::InvalidWitness { constraint } => {
289 write!(
290 f,
291 "Invalid witness: constraint '{}' not satisfied",
292 constraint
293 )
294 }
295 AirError::InternalError { reason } => {
296 write!(f, "Internal AIR error: {}", reason)
297 }
298 AirError::NotSupported { reason } => {
299 write!(f, "Not supported: {}", reason)
300 }
301 AirError::MissingFriCommitPhaseOpenings => {
302 write!(f, "FRI commit-phase openings missing for query index")
303 }
304 AirError::FriRoundCountMismatch => {
305 write!(
306 f,
307 "FRI commit-phase step count does not match number of rounds"
308 )
309 }
310 }
311 }
312}
313
314impl From<AirError> for lib_q_core::Error {
315 fn from(err: AirError) -> Self {
316 lib_q_core::Error::InternalError {
317 operation: "AIR operation".into(),
318 details: err.to_string(),
319 }
320 }
321}
322
323pub trait TraceGenerator<F: Field, I> {
334 fn generate_trace(&self, inputs: &I) -> Result<RowMajorMatrix<F>, AirError>;
352
353 fn public_values(&self, inputs: &I) -> Vec<F> {
367 let _ = inputs;
368 Vec::new()
369 }
370}
371
372pub fn validate_trace_dimensions(width: usize, height: usize) -> Result<(), AirError> {
383 if width == 0 {
384 return Err(AirError::InvalidDimensions {
385 reason: "Trace width must be greater than 0".into(),
386 });
387 }
388
389 if width > MAX_TRACE_WIDTH {
390 return Err(AirError::ExceedsMaxSize {
391 parameter: "width".into(),
392 max: MAX_TRACE_WIDTH,
393 actual: width,
394 });
395 }
396
397 if height == 0 {
398 return Err(AirError::InvalidDimensions {
399 reason: "Trace height must be greater than 0".into(),
400 });
401 }
402
403 if height > MAX_TRACE_HEIGHT {
404 return Err(AirError::ExceedsMaxSize {
405 parameter: "height".into(),
406 max: MAX_TRACE_HEIGHT,
407 actual: height,
408 });
409 }
410
411 if !height.is_power_of_two() {
412 return Err(AirError::InvalidDimensions {
413 reason: "Trace height must be a power of 2".into(),
414 });
415 }
416
417 Ok(())
418}
419
420pub fn next_power_of_two(n: usize) -> usize {
430 if n == 0 {
431 return 1;
432 }
433 n.next_power_of_two()
434}
435
436pub fn poseidon_to_field<F: Field + BasedVectorSpace<Mersenne31>>(pf: &PoseidonField) -> F {
449 let coeffs: &[Mersenne31] = pf.as_basis_coefficients_slice();
450 F::from_basis_coefficients_fn(|i| {
451 if i < coeffs.len() {
452 coeffs[i]
453 } else {
454 <Mersenne31 as PrimeCharacteristicRing>::ZERO
455 }
456 })
457}
458
459pub fn poseidon_slice_to_field<F: Field + BasedVectorSpace<Mersenne31>>(
469 slice: &[PoseidonField],
470) -> Vec<F> {
471 slice.iter().map(poseidon_to_field).collect()
472}
473
474pub fn poseidon_field_to_bytes(hash: &[PoseidonField]) -> Vec<u8> {
487 use lib_q_stark_field::RawDataSerializable;
488 hash.iter().flat_map(|f| (*f).into_bytes()).collect()
490}
491
492#[must_use]
505pub fn merkle_root_to_bytes(root: &PoseidonField) -> [u8; 32] {
506 use lib_q_stark_field::RawDataSerializable;
507 let mut out = [0u8; 32];
508 let bytes: Vec<u8> = (*root).into_bytes().into_iter().collect();
509 let n = core::cmp::min(bytes.len(), 32);
510 out[..n].copy_from_slice(&bytes[..n]);
511 out
512}
513
514pub fn merkle_root_from_bytes(bytes: &[u8]) -> Result<PoseidonField, AirError> {
527 use lib_q_stark_field::extension::Complex;
528 use lib_q_stark_field::integers::QuotientMap;
529 use lib_q_stark_mersenne31::Mersenne31;
530
531 if bytes.len() < 8 {
532 return Err(AirError::InvalidInput {
533 reason: alloc::format!(
534 "Merkle root bytes must have at least 8 bytes, got {}",
535 bytes.len()
536 ),
537 });
538 }
539 let mut real_bytes = [0u8; 4];
540 let mut imag_bytes = [0u8; 4];
541 real_bytes.copy_from_slice(&bytes[0..4]);
542 imag_bytes.copy_from_slice(&bytes[4..8]);
543 let real = Mersenne31::from_int(u32::from_le_bytes(real_bytes));
544 let imag = Mersenne31::from_int(u32::from_le_bytes(imag_bytes));
545 Ok(Complex::new_complex(real, imag))
546}
547
548pub fn compute_poseidon_row(
553 state: &[PoseidonField],
554 params: &PoseidonParams,
555) -> (Vec<PoseidonField>, Vec<PoseidonField>) {
556 use lib_q_stark_field::extension::Complex;
557 use lib_q_stark_mersenne31::Mersenne31;
558
559 let n = params.state_width;
560 assert!(state.len() >= n, "state must have at least {} elements", n);
561 let zero = Complex::<Mersenne31>::new_complex(Mersenne31::ZERO, Mersenne31::ZERO);
562 let mut intermediates = Vec::new();
563 let mut round_idx = 0usize;
564 let mut s: Vec<PoseidonField> = state[0..n].to_vec();
565 let full_half = params.full_rounds / 2;
566
567 for _ in 0..full_half {
568 let after_arc: Vec<PoseidonField> = (0..n)
569 .map(|i| s[i] + params.round_constants[round_idx + i])
570 .collect();
571 round_idx += n;
572 intermediates.extend(after_arc.iter().cloned());
573 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
574 intermediates.extend(after_sbox.iter().cloned());
575 let mut next_s = alloc::vec![zero; n];
576 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
577 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
578 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
579 }
580 }
581 intermediates.extend(next_s.iter().cloned());
582 s = next_s;
583 }
584 for _ in 0..params.partial_rounds {
585 let after_arc: Vec<PoseidonField> = (0..n)
586 .map(|i| s[i] + params.round_constants[round_idx + i])
587 .collect();
588 round_idx += n;
589 intermediates.extend(after_arc.iter().cloned());
590 let mut after_sbox = alloc::vec![zero; n];
591 after_sbox[0] = sbox(after_arc[0]);
592 after_sbox[1..n].copy_from_slice(&after_arc[1..n]);
593 intermediates.extend(after_sbox.iter().cloned());
594 let mut next_s = alloc::vec![zero; n];
595 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
596 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
597 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
598 }
599 }
600 intermediates.extend(next_s.iter().cloned());
601 s = next_s;
602 }
603 for _ in 0..full_half {
604 let after_arc: Vec<PoseidonField> = (0..n)
605 .map(|i| s[i] + params.round_constants[round_idx + i])
606 .collect();
607 round_idx += n;
608 intermediates.extend(after_arc.iter().cloned());
609 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
610 intermediates.extend(after_sbox.iter().cloned());
611 let mut next_s = alloc::vec![zero; n];
612 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
613 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
614 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
615 }
616 }
617 intermediates.extend(next_s.iter().cloned());
618 s = next_s;
619 }
620 (s, intermediates)
621}
622
623pub fn bytes_to_poseidon_field(bytes: &[u8]) -> Vec<PoseidonField> {
636 use lib_q_stark_field::extension::Complex;
637 use lib_q_stark_mersenne31::Mersenne31;
638 bytes
639 .iter()
640 .map(|b| Complex::<Mersenne31>::from(Mersenne31::new(*b as u32)))
641 .collect()
642}
643
644pub fn it_bytes_to_public_value<F: Field + BasedVectorSpace<Mersenne31>>(it: &[u8; 16]) -> F {
648 use lib_q_stark_field::extension::Complex;
649 use lib_q_stark_mersenne31::Mersenne31;
650 let mut real_bytes = [0u8; 4];
651 let mut imag_bytes = [0u8; 4];
652 real_bytes.copy_from_slice(&it[0..4]);
653 imag_bytes.copy_from_slice(&it[4..8]);
654 let real = Mersenne31::new(u32::from_le_bytes(real_bytes));
655 let imag = Mersenne31::new(u32::from_le_bytes(imag_bytes));
656 let c = Complex::new_complex(real, imag);
657 poseidon_to_field::<F>(&c)
658}
659
660#[cfg(test)]
661mod tests {
662 use super::*;
663
664 #[test]
665 fn test_validate_trace_dimensions_valid() {
666 assert!(validate_trace_dimensions(8, 16).is_ok());
667 assert!(validate_trace_dimensions(1, 1).is_ok());
668 assert!(validate_trace_dimensions(100, 1024).is_ok());
669 }
670
671 #[test]
672 fn test_validate_trace_dimensions_zero_width() {
673 let result = validate_trace_dimensions(0, 16);
674 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
675 }
676
677 #[test]
678 fn test_validate_trace_dimensions_zero_height() {
679 let result = validate_trace_dimensions(8, 0);
680 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
681 }
682
683 #[test]
684 fn test_validate_trace_dimensions_not_power_of_two() {
685 let result = validate_trace_dimensions(8, 15);
686 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
687 }
688
689 #[test]
690 fn test_next_power_of_two() {
691 assert_eq!(next_power_of_two(0), 1);
692 assert_eq!(next_power_of_two(1), 1);
693 assert_eq!(next_power_of_two(2), 2);
694 assert_eq!(next_power_of_two(3), 4);
695 assert_eq!(next_power_of_two(5), 8);
696 assert_eq!(next_power_of_two(16), 16);
697 }
698
699 #[test]
700 fn test_air_error_display() {
701 let err = AirError::InvalidDimensions {
702 reason: "test".into(),
703 };
704 assert!(err.to_string().contains("Invalid AIR dimensions"));
705
706 let err = AirError::ExceedsMaxSize {
707 parameter: "width".into(),
708 max: 100,
709 actual: 200,
710 };
711 assert!(err.to_string().contains("exceeds maximum"));
712 }
713}