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