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 poseidon2_gadget;
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 unlinkable_membership;
83pub mod unlinkable_membership_baby_bear;
84pub mod verifier_utils;
85pub mod wide_hash;
86pub mod wide_merkle;
87pub mod wide_merkle_path;
88pub mod wide_merkle_path_baby_bear;
89pub mod wide_sponge;
90pub mod wide_sponge_baby_bear;
91
92pub use anonymous_auth::{
93 AnonymousAuthAir,
94 AnonymousAuthInput,
95};
96pub use arithmetic::ArithmeticAir;
97pub use batch_stark_verifier::{
98 BatchRecursiveStarkVerificationInput,
99 BatchStarkVerifierAir,
100 batch_recursive_verifier_public_values,
101};
102#[cfg(all(feature = "recursive-proofs-experimental", feature = "std"))]
103pub use commitment_verifier::debug_commitment_trace_sanity_check;
104pub use commitment_verifier::{
105 CommitmentVerificationInput,
106 CommitmentVerifierAir,
107};
108pub use constraint_verifier::{
109 ConstraintVerificationInput,
110 ConstraintVerifierAir,
111};
112pub use credential::{
113 CredentialAir,
114 CredentialInput,
115 CredentialSchema,
116};
117pub use fri_verifier::{
118 FriVerificationInput,
119 FriVerifierAir,
120};
121pub use hash_preimage::HashPreimageAir;
122pub use hash_preimage_nist::{
123 HASH_OUTPUT_BYTES,
124 HashPreimageNistAir,
125 HashPreimageNistInput,
126 expected_hash_to_public_values,
127};
128pub use identity_proof::{
129 IdentityProofAir,
130 IdentityProofInput,
131 MlDsaLevel,
132};
133pub use merkle_inclusion::{
134 MerkleHash,
135 MerkleInclusionAir,
136 MerkleProofInput,
137};
138pub use opening_verifier::{
139 OpeningVerificationInput,
140 OpeningVerifierAir,
141};
142pub use poseidon_gadget::PoseidonGadget;
143pub use poseidon_hash::PoseidonHashAir;
144pub use range_proof::RangeProofAir;
145pub use recovery_policy::{
146 RECOVERY_POLICY_AIR_ID,
147 RECOVERY_POLICY_COMMIT_DOMAIN,
148 RECOVERY_PUBLIC_INPUTS_LEN,
149 RECOVERY_VK_COMMIT_DOMAIN,
150 RecoveryPolicyAir,
151 RecoveryPolicyInput,
152 RecoveryPolicyKey,
153 RecoveryPolicyPublicInputs,
154 policy_commitment,
155 shake256_commit,
156 vk_commitment,
157};
158pub use recovery_policy_hybrid::{
159 RECOVERY_HYBRID_POLICY_COMMIT_DOMAIN,
160 RECOVERY_HYBRID_PUBLIC_INPUTS_LEN,
161 RECOVERY_HYBRID_VK_COMMIT_DOMAIN,
162 RECOVERY_POLICY_HYBRID_AIR_ID,
163 RecoveryPolicyHybridAir,
164 RecoveryPolicyHybridInput,
165 RecoveryPolicyHybridPublicInputs,
166 hybrid_policy_commitment,
167};
168pub use recursive_types::{
169 RecursiveStarkInput,
170 SerializedFriRound,
171 SerializedStarkProof,
172};
173pub use session_key::{
174 KdfAlgorithm,
175 KdfParams,
176 SessionKeyDerivationAir,
177 SessionKeyInput,
178};
179pub use unlinkable_membership::{
180 CTX_ELEMS,
181 MEMBERSHIP_DOMAIN_STR,
182 MEMBERSHIP_NUM_PUBLIC,
183 MEMBERSHIP_ROW_WIDTH,
184 SECRET_T_ELEMS,
185 UnlinkableMembershipAir,
186 generate_membership_trace,
187 membership_domain,
188 membership_leaf,
189 membership_nullifier,
190 membership_public_values,
191};
192pub use wide_hash::{
193 WIDE_DIGEST_ELEMS,
194 WideDigest,
195 poseidon256_perm_truncated,
196 poseidon256_wide_hash,
197};
198pub use wide_merkle::{
199 NODE_NUM_PERMS,
200 NODE_ROW_WIDTH,
201 WideNodeHashAir,
202 generate_node_trace,
203 node_public_values,
204};
205pub use wide_merkle_path::{
206 PATH_NUM_PUBLIC,
207 PATH_ROW_WIDTH,
208 WideMerklePathAir,
209 generate_path_trace,
210 path_public_values,
211};
212pub use wide_sponge::{
213 constrain_wide_sponge,
214 generate_wide_sponge_cells,
215 wide_sponge_interm_cols,
216 wide_sponge_num_perms,
217};
218pub trait PoseidonCommitmentRoot {
221 fn to_poseidon_root_bytes(&self) -> [u8; recursive_types::COMMITMENT_HASH_SIZE];
222}
223
224#[cfg(feature = "recursive-proofs-experimental")]
225pub use stark_verifier::{
226 MerklePathExtractable,
227 build_recursive_verification_input_from_proof,
228 build_recursive_verification_input_from_proof_with_poseidon,
229};
230pub use stark_verifier::{
231 RecursiveStarkVerificationInput,
232 StarkVerifierAir,
233 build_recursive_verification_input,
234};
235
236pub 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)]
249pub enum AirError {
250 InvalidDimensions {
252 reason: String,
254 },
255
256 ExceedsMaxSize {
258 parameter: String,
260 max: usize,
262 actual: usize,
264 },
265
266 InvalidInput {
268 reason: String,
270 },
271
272 TraceMismatch {
274 expected_width: usize,
276 actual_width: usize,
278 },
279
280 InvalidWitness {
282 constraint: String,
284 },
285
286 InternalError {
288 reason: String,
290 },
291
292 NotSupported {
294 reason: String,
296 },
297
298 MissingFriCommitPhaseOpenings,
300
301 FriRoundCountMismatch,
303}
304
305impl fmt::Display for AirError {
306 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307 match self {
308 AirError::InvalidDimensions { reason } => {
309 write!(f, "Invalid AIR dimensions: {}", reason)
310 }
311 AirError::ExceedsMaxSize {
312 parameter,
313 max,
314 actual,
315 } => {
316 write!(
317 f,
318 "AIR parameter '{}' exceeds maximum: max={}, actual={}",
319 parameter, max, actual
320 )
321 }
322 AirError::InvalidInput { reason } => {
323 write!(f, "Invalid input for trace generation: {}", reason)
324 }
325 AirError::TraceMismatch {
326 expected_width,
327 actual_width,
328 } => {
329 write!(
330 f,
331 "Trace width mismatch: expected {}, got {}",
332 expected_width, actual_width
333 )
334 }
335 AirError::InvalidWitness { constraint } => {
336 write!(
337 f,
338 "Invalid witness: constraint '{}' not satisfied",
339 constraint
340 )
341 }
342 AirError::InternalError { reason } => {
343 write!(f, "Internal AIR error: {}", reason)
344 }
345 AirError::NotSupported { reason } => {
346 write!(f, "Not supported: {}", reason)
347 }
348 AirError::MissingFriCommitPhaseOpenings => {
349 write!(f, "FRI commit-phase openings missing for query index")
350 }
351 AirError::FriRoundCountMismatch => {
352 write!(
353 f,
354 "FRI commit-phase step count does not match number of rounds"
355 )
356 }
357 }
358 }
359}
360
361impl From<AirError> for lib_q_core::Error {
362 fn from(err: AirError) -> Self {
363 lib_q_core::Error::InternalError {
364 operation: "AIR operation".into(),
365 details: err.to_string(),
366 }
367 }
368}
369
370pub trait TraceGenerator<F: Field, I> {
381 fn generate_trace(&self, inputs: &I) -> Result<RowMajorMatrix<F>, AirError>;
399
400 fn public_values(&self, inputs: &I) -> Vec<F> {
414 let _ = inputs;
415 Vec::new()
416 }
417}
418
419pub fn validate_trace_dimensions(width: usize, height: usize) -> Result<(), AirError> {
430 if width == 0 {
431 return Err(AirError::InvalidDimensions {
432 reason: "Trace width must be greater than 0".into(),
433 });
434 }
435
436 if width > MAX_TRACE_WIDTH {
437 return Err(AirError::ExceedsMaxSize {
438 parameter: "width".into(),
439 max: MAX_TRACE_WIDTH,
440 actual: width,
441 });
442 }
443
444 if height == 0 {
445 return Err(AirError::InvalidDimensions {
446 reason: "Trace height must be greater than 0".into(),
447 });
448 }
449
450 if height > MAX_TRACE_HEIGHT {
451 return Err(AirError::ExceedsMaxSize {
452 parameter: "height".into(),
453 max: MAX_TRACE_HEIGHT,
454 actual: height,
455 });
456 }
457
458 if !height.is_power_of_two() {
459 return Err(AirError::InvalidDimensions {
460 reason: "Trace height must be a power of 2".into(),
461 });
462 }
463
464 Ok(())
465}
466
467pub fn next_power_of_two(n: usize) -> usize {
477 if n == 0 {
478 return 1;
479 }
480 n.next_power_of_two()
481}
482
483pub fn poseidon_to_field<F: Field + BasedVectorSpace<Mersenne31>>(pf: &PoseidonField) -> F {
496 let coeffs: &[Mersenne31] = pf.as_basis_coefficients_slice();
497 F::from_basis_coefficients_fn(|i| {
498 if i < coeffs.len() {
499 coeffs[i]
500 } else {
501 <Mersenne31 as PrimeCharacteristicRing>::ZERO
502 }
503 })
504}
505
506pub fn poseidon_slice_to_field<F: Field + BasedVectorSpace<Mersenne31>>(
516 slice: &[PoseidonField],
517) -> Vec<F> {
518 slice.iter().map(poseidon_to_field).collect()
519}
520
521pub fn poseidon_field_to_bytes(hash: &[PoseidonField]) -> Vec<u8> {
534 use lib_q_stark_field::RawDataSerializable;
535 hash.iter().flat_map(|f| (*f).into_bytes()).collect()
537}
538
539#[must_use]
552pub fn merkle_root_to_bytes(root: &PoseidonField) -> [u8; 32] {
553 use lib_q_stark_field::RawDataSerializable;
554 let mut out = [0u8; 32];
555 let bytes: Vec<u8> = (*root).into_bytes().into_iter().collect();
556 let n = core::cmp::min(bytes.len(), 32);
557 out[..n].copy_from_slice(&bytes[..n]);
558 out
559}
560
561pub fn merkle_root_from_bytes(bytes: &[u8]) -> Result<PoseidonField, AirError> {
574 use lib_q_stark_field::extension::Complex;
575 use lib_q_stark_field::integers::QuotientMap;
576 use lib_q_stark_mersenne31::Mersenne31;
577
578 if bytes.len() < 8 {
579 return Err(AirError::InvalidInput {
580 reason: alloc::format!(
581 "Merkle root bytes must have at least 8 bytes, got {}",
582 bytes.len()
583 ),
584 });
585 }
586 let mut real_bytes = [0u8; 4];
587 let mut imag_bytes = [0u8; 4];
588 real_bytes.copy_from_slice(&bytes[0..4]);
589 imag_bytes.copy_from_slice(&bytes[4..8]);
590 let real =
595 Mersenne31::from_canonical_checked(u32::from_le_bytes(real_bytes)).ok_or_else(|| {
596 AirError::InvalidInput {
597 reason: String::from("non-canonical real limb in merkle root (>= 2^31-1)"),
598 }
599 })?;
600 let imag =
601 Mersenne31::from_canonical_checked(u32::from_le_bytes(imag_bytes)).ok_or_else(|| {
602 AirError::InvalidInput {
603 reason: String::from("non-canonical imag limb in merkle root (>= 2^31-1)"),
604 }
605 })?;
606 Ok(Complex::new_complex(real, imag))
607}
608
609pub fn compute_poseidon_row(
614 state: &[PoseidonField],
615 params: &PoseidonParams,
616) -> (Vec<PoseidonField>, Vec<PoseidonField>) {
617 use lib_q_stark_field::extension::Complex;
618 use lib_q_stark_mersenne31::Mersenne31;
619
620 let n = params.state_width;
621 assert!(state.len() >= n, "state must have at least {} elements", n);
622 let zero = Complex::<Mersenne31>::new_complex(Mersenne31::ZERO, Mersenne31::ZERO);
623 let mut intermediates = Vec::new();
624 let mut round_idx = 0usize;
625 let mut s: Vec<PoseidonField> = state[0..n].to_vec();
626 let full_half = params.full_rounds / 2;
627
628 for _ in 0..full_half {
629 let after_arc: Vec<PoseidonField> = (0..n)
630 .map(|i| s[i] + params.round_constants[round_idx + i])
631 .collect();
632 round_idx += n;
633 intermediates.extend(after_arc.iter().cloned());
634 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
635 intermediates.extend(after_sbox.iter().cloned());
636 let mut next_s = alloc::vec![zero; n];
637 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
638 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
639 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
640 }
641 }
642 intermediates.extend(next_s.iter().cloned());
643 s = next_s;
644 }
645 for _ in 0..params.partial_rounds {
646 let after_arc: Vec<PoseidonField> = (0..n)
647 .map(|i| s[i] + params.round_constants[round_idx + i])
648 .collect();
649 round_idx += n;
650 intermediates.extend(after_arc.iter().cloned());
651 let mut after_sbox = alloc::vec![zero; n];
652 after_sbox[0] = sbox(after_arc[0]);
653 after_sbox[1..n].copy_from_slice(&after_arc[1..n]);
654 intermediates.extend(after_sbox.iter().cloned());
655 let mut next_s = alloc::vec![zero; n];
656 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
657 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
658 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
659 }
660 }
661 intermediates.extend(next_s.iter().cloned());
662 s = next_s;
663 }
664 for _ in 0..full_half {
665 let after_arc: Vec<PoseidonField> = (0..n)
666 .map(|i| s[i] + params.round_constants[round_idx + i])
667 .collect();
668 round_idx += n;
669 intermediates.extend(after_arc.iter().cloned());
670 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
671 intermediates.extend(after_sbox.iter().cloned());
672 let mut next_s = alloc::vec![zero; n];
673 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
674 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
675 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
676 }
677 }
678 intermediates.extend(next_s.iter().cloned());
679 s = next_s;
680 }
681 (s, intermediates)
682}
683
684pub fn bytes_to_poseidon_field(bytes: &[u8]) -> Vec<PoseidonField> {
697 use lib_q_stark_field::extension::Complex;
698 use lib_q_stark_mersenne31::Mersenne31;
699 bytes
700 .iter()
701 .map(|b| Complex::<Mersenne31>::from(Mersenne31::new(*b as u32)))
702 .collect()
703}
704
705pub fn it_bytes_to_public_value<F: Field + BasedVectorSpace<Mersenne31>>(it: &[u8; 16]) -> F {
709 use lib_q_stark_field::extension::Complex;
710 use lib_q_stark_mersenne31::Mersenne31;
711 let mut real_bytes = [0u8; 4];
712 let mut imag_bytes = [0u8; 4];
713 real_bytes.copy_from_slice(&it[0..4]);
714 imag_bytes.copy_from_slice(&it[4..8]);
715 let real = Mersenne31::new(u32::from_le_bytes(real_bytes));
716 let imag = Mersenne31::new(u32::from_le_bytes(imag_bytes));
717 let c = Complex::new_complex(real, imag);
718 poseidon_to_field::<F>(&c)
719}
720
721#[cfg(test)]
722mod tests {
723 use super::*;
724
725 #[test]
726 fn test_validate_trace_dimensions_valid() {
727 assert!(validate_trace_dimensions(8, 16).is_ok());
728 assert!(validate_trace_dimensions(1, 1).is_ok());
729 assert!(validate_trace_dimensions(100, 1024).is_ok());
730 }
731
732 #[test]
733 fn test_validate_trace_dimensions_zero_width() {
734 let result = validate_trace_dimensions(0, 16);
735 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
736 }
737
738 #[test]
739 fn test_validate_trace_dimensions_zero_height() {
740 let result = validate_trace_dimensions(8, 0);
741 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
742 }
743
744 #[test]
745 fn test_validate_trace_dimensions_not_power_of_two() {
746 let result = validate_trace_dimensions(8, 15);
747 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
748 }
749
750 #[test]
751 fn test_next_power_of_two() {
752 assert_eq!(next_power_of_two(0), 1);
753 assert_eq!(next_power_of_two(1), 1);
754 assert_eq!(next_power_of_two(2), 2);
755 assert_eq!(next_power_of_two(3), 4);
756 assert_eq!(next_power_of_two(5), 8);
757 assert_eq!(next_power_of_two(16), 16);
758 }
759
760 #[test]
761 fn test_air_error_display() {
762 let err = AirError::InvalidDimensions {
763 reason: "test".into(),
764 };
765 assert!(err.to_string().contains("Invalid AIR dimensions"));
766
767 let err = AirError::ExceedsMaxSize {
768 parameter: "width".into(),
769 max: 100,
770 actual: 200,
771 };
772 assert!(err.to_string().contains("exceeds maximum"));
773 }
774}