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 OUTPUT_LENGTH_GRANULARITY,
177 SessionKeyDerivationAir,
178 SessionKeyInput,
179 derive_session_keys,
180};
181pub use unlinkable_membership::{
182 CTX_ELEMS,
183 MEMBERSHIP_DOMAIN_STR,
184 MEMBERSHIP_NUM_PUBLIC,
185 MEMBERSHIP_ROW_WIDTH,
186 SECRET_T_ELEMS,
187 UnlinkableMembershipAir,
188 generate_membership_trace,
189 membership_domain,
190 membership_leaf,
191 membership_nullifier,
192 membership_public_values,
193};
194pub use wide_hash::{
195 WIDE_DIGEST_ELEMS,
196 WideDigest,
197 poseidon256_perm_truncated,
198 poseidon256_wide_hash,
199};
200pub use wide_merkle::{
201 NODE_NUM_PERMS,
202 NODE_ROW_WIDTH,
203 WideNodeHashAir,
204 generate_node_trace,
205 node_public_values,
206};
207pub use wide_merkle_path::{
208 PATH_NUM_PUBLIC,
209 PATH_ROW_WIDTH,
210 WideMerklePathAir,
211 generate_path_trace,
212 path_public_values,
213};
214pub use wide_sponge::{
215 constrain_wide_sponge,
216 generate_wide_sponge_cells,
217 wide_sponge_interm_cols,
218 wide_sponge_num_perms,
219};
220pub trait PoseidonCommitmentRoot {
223 fn to_poseidon_root_bytes(&self) -> [u8; recursive_types::COMMITMENT_HASH_SIZE];
224}
225
226#[cfg(feature = "recursive-proofs-experimental")]
227pub use stark_verifier::{
228 MerklePathExtractable,
229 build_recursive_verification_input_from_proof,
230 build_recursive_verification_input_from_proof_with_poseidon,
231};
232pub use stark_verifier::{
233 RecursiveStarkVerificationInput,
234 StarkVerifierAir,
235 build_recursive_verification_input,
236};
237
238pub 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)]
251pub enum AirError {
252 InvalidDimensions {
254 reason: String,
256 },
257
258 ExceedsMaxSize {
260 parameter: String,
262 max: usize,
264 actual: usize,
266 },
267
268 InvalidInput {
270 reason: String,
272 },
273
274 TraceMismatch {
276 expected_width: usize,
278 actual_width: usize,
280 },
281
282 InvalidWitness {
284 constraint: String,
286 },
287
288 InternalError {
290 reason: String,
292 },
293
294 NotSupported {
296 reason: String,
298 },
299
300 MissingFriCommitPhaseOpenings,
302
303 FriRoundCountMismatch,
305}
306
307impl fmt::Display for AirError {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 match self {
310 AirError::InvalidDimensions { reason } => {
311 write!(f, "Invalid AIR dimensions: {}", reason)
312 }
313 AirError::ExceedsMaxSize {
314 parameter,
315 max,
316 actual,
317 } => {
318 write!(
319 f,
320 "AIR parameter '{}' exceeds maximum: max={}, actual={}",
321 parameter, max, actual
322 )
323 }
324 AirError::InvalidInput { reason } => {
325 write!(f, "Invalid input for trace generation: {}", reason)
326 }
327 AirError::TraceMismatch {
328 expected_width,
329 actual_width,
330 } => {
331 write!(
332 f,
333 "Trace width mismatch: expected {}, got {}",
334 expected_width, actual_width
335 )
336 }
337 AirError::InvalidWitness { constraint } => {
338 write!(
339 f,
340 "Invalid witness: constraint '{}' not satisfied",
341 constraint
342 )
343 }
344 AirError::InternalError { reason } => {
345 write!(f, "Internal AIR error: {}", reason)
346 }
347 AirError::NotSupported { reason } => {
348 write!(f, "Not supported: {}", reason)
349 }
350 AirError::MissingFriCommitPhaseOpenings => {
351 write!(f, "FRI commit-phase openings missing for query index")
352 }
353 AirError::FriRoundCountMismatch => {
354 write!(
355 f,
356 "FRI commit-phase step count does not match number of rounds"
357 )
358 }
359 }
360 }
361}
362
363impl From<AirError> for lib_q_core::Error {
364 fn from(err: AirError) -> Self {
365 lib_q_core::Error::InternalError {
366 operation: "AIR operation".into(),
367 details: err.to_string(),
368 }
369 }
370}
371
372pub trait TraceGenerator<F: Field, I> {
383 fn generate_trace(&self, inputs: &I) -> Result<RowMajorMatrix<F>, AirError>;
401
402 fn public_values(&self, inputs: &I) -> Vec<F> {
416 let _ = inputs;
417 Vec::new()
418 }
419}
420
421pub fn validate_trace_dimensions(width: usize, height: usize) -> Result<(), AirError> {
432 if width == 0 {
433 return Err(AirError::InvalidDimensions {
434 reason: "Trace width must be greater than 0".into(),
435 });
436 }
437
438 if width > MAX_TRACE_WIDTH {
439 return Err(AirError::ExceedsMaxSize {
440 parameter: "width".into(),
441 max: MAX_TRACE_WIDTH,
442 actual: width,
443 });
444 }
445
446 if height == 0 {
447 return Err(AirError::InvalidDimensions {
448 reason: "Trace height must be greater than 0".into(),
449 });
450 }
451
452 if height > MAX_TRACE_HEIGHT {
453 return Err(AirError::ExceedsMaxSize {
454 parameter: "height".into(),
455 max: MAX_TRACE_HEIGHT,
456 actual: height,
457 });
458 }
459
460 if !height.is_power_of_two() {
461 return Err(AirError::InvalidDimensions {
462 reason: "Trace height must be a power of 2".into(),
463 });
464 }
465
466 Ok(())
467}
468
469pub fn next_power_of_two(n: usize) -> usize {
479 if n == 0 {
480 return 1;
481 }
482 n.next_power_of_two()
483}
484
485pub fn poseidon_to_field<F: Field + BasedVectorSpace<Mersenne31>>(pf: &PoseidonField) -> F {
498 let coeffs: &[Mersenne31] = pf.as_basis_coefficients_slice();
499 F::from_basis_coefficients_fn(|i| {
500 if i < coeffs.len() {
501 coeffs[i]
502 } else {
503 <Mersenne31 as PrimeCharacteristicRing>::ZERO
504 }
505 })
506}
507
508pub fn poseidon_slice_to_field<F: Field + BasedVectorSpace<Mersenne31>>(
518 slice: &[PoseidonField],
519) -> Vec<F> {
520 slice.iter().map(poseidon_to_field).collect()
521}
522
523pub fn poseidon_field_to_bytes(hash: &[PoseidonField]) -> Vec<u8> {
536 use lib_q_stark_field::RawDataSerializable;
537 hash.iter().flat_map(|f| (*f).into_bytes()).collect()
539}
540
541#[must_use]
554pub fn merkle_root_to_bytes(root: &PoseidonField) -> [u8; 32] {
555 use lib_q_stark_field::RawDataSerializable;
556 let mut out = [0u8; 32];
557 let bytes: Vec<u8> = (*root).into_bytes().into_iter().collect();
558 let n = core::cmp::min(bytes.len(), 32);
559 out[..n].copy_from_slice(&bytes[..n]);
560 out
561}
562
563pub fn merkle_root_from_bytes(bytes: &[u8]) -> Result<PoseidonField, AirError> {
576 use lib_q_stark_field::extension::Complex;
577 use lib_q_stark_field::integers::QuotientMap;
578 use lib_q_stark_mersenne31::Mersenne31;
579
580 if bytes.len() < 8 {
581 return Err(AirError::InvalidInput {
582 reason: alloc::format!(
583 "Merkle root bytes must have at least 8 bytes, got {}",
584 bytes.len()
585 ),
586 });
587 }
588 let mut real_bytes = [0u8; 4];
589 let mut imag_bytes = [0u8; 4];
590 real_bytes.copy_from_slice(&bytes[0..4]);
591 imag_bytes.copy_from_slice(&bytes[4..8]);
592 let real =
597 Mersenne31::from_canonical_checked(u32::from_le_bytes(real_bytes)).ok_or_else(|| {
598 AirError::InvalidInput {
599 reason: String::from("non-canonical real limb in merkle root (>= 2^31-1)"),
600 }
601 })?;
602 let imag =
603 Mersenne31::from_canonical_checked(u32::from_le_bytes(imag_bytes)).ok_or_else(|| {
604 AirError::InvalidInput {
605 reason: String::from("non-canonical imag limb in merkle root (>= 2^31-1)"),
606 }
607 })?;
608 Ok(Complex::new_complex(real, imag))
609}
610
611pub fn compute_poseidon_row(
616 state: &[PoseidonField],
617 params: &PoseidonParams,
618) -> (Vec<PoseidonField>, Vec<PoseidonField>) {
619 use lib_q_stark_field::extension::Complex;
620 use lib_q_stark_mersenne31::Mersenne31;
621
622 let n = params.state_width;
623 assert!(state.len() >= n, "state must have at least {} elements", n);
624 let zero = Complex::<Mersenne31>::new_complex(Mersenne31::ZERO, Mersenne31::ZERO);
625 let mut intermediates = Vec::new();
626 let mut round_idx = 0usize;
627 let mut s: Vec<PoseidonField> = state[0..n].to_vec();
628 let full_half = params.full_rounds / 2;
629
630 for _ in 0..full_half {
631 let after_arc: Vec<PoseidonField> = (0..n)
632 .map(|i| s[i] + params.round_constants[round_idx + i])
633 .collect();
634 round_idx += n;
635 intermediates.extend(after_arc.iter().cloned());
636 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
637 intermediates.extend(after_sbox.iter().cloned());
638 let mut next_s = alloc::vec![zero; n];
639 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
640 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
641 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
642 }
643 }
644 intermediates.extend(next_s.iter().cloned());
645 s = next_s;
646 }
647 for _ in 0..params.partial_rounds {
648 let after_arc: Vec<PoseidonField> = (0..n)
649 .map(|i| s[i] + params.round_constants[round_idx + i])
650 .collect();
651 round_idx += n;
652 intermediates.extend(after_arc.iter().cloned());
653 let mut after_sbox = alloc::vec![zero; n];
654 after_sbox[0] = sbox(after_arc[0]);
655 after_sbox[1..n].copy_from_slice(&after_arc[1..n]);
656 intermediates.extend(after_sbox.iter().cloned());
657 let mut next_s = alloc::vec![zero; n];
658 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
659 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
660 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
661 }
662 }
663 intermediates.extend(next_s.iter().cloned());
664 s = next_s;
665 }
666 for _ in 0..full_half {
667 let after_arc: Vec<PoseidonField> = (0..n)
668 .map(|i| s[i] + params.round_constants[round_idx + i])
669 .collect();
670 round_idx += n;
671 intermediates.extend(after_arc.iter().cloned());
672 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
673 intermediates.extend(after_sbox.iter().cloned());
674 let mut next_s = alloc::vec![zero; n];
675 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
676 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
677 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
678 }
679 }
680 intermediates.extend(next_s.iter().cloned());
681 s = next_s;
682 }
683 (s, intermediates)
684}
685
686pub fn bytes_to_poseidon_field(bytes: &[u8]) -> Vec<PoseidonField> {
699 use lib_q_stark_field::extension::Complex;
700 use lib_q_stark_mersenne31::Mersenne31;
701 bytes
702 .iter()
703 .map(|b| Complex::<Mersenne31>::from(Mersenne31::new(*b as u32)))
704 .collect()
705}
706
707pub fn it_bytes_to_public_value<F: Field + BasedVectorSpace<Mersenne31>>(it: &[u8; 16]) -> F {
711 use lib_q_stark_field::extension::Complex;
712 use lib_q_stark_mersenne31::Mersenne31;
713 let mut real_bytes = [0u8; 4];
714 let mut imag_bytes = [0u8; 4];
715 real_bytes.copy_from_slice(&it[0..4]);
716 imag_bytes.copy_from_slice(&it[4..8]);
717 let real = Mersenne31::new(u32::from_le_bytes(real_bytes));
718 let imag = Mersenne31::new(u32::from_le_bytes(imag_bytes));
719 let c = Complex::new_complex(real, imag);
720 poseidon_to_field::<F>(&c)
721}
722
723#[cfg(test)]
724mod tests {
725 use super::*;
726
727 #[test]
728 fn test_validate_trace_dimensions_valid() {
729 assert!(validate_trace_dimensions(8, 16).is_ok());
730 assert!(validate_trace_dimensions(1, 1).is_ok());
731 assert!(validate_trace_dimensions(100, 1024).is_ok());
732 }
733
734 #[test]
735 fn test_validate_trace_dimensions_zero_width() {
736 let result = validate_trace_dimensions(0, 16);
737 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
738 }
739
740 #[test]
741 fn test_validate_trace_dimensions_zero_height() {
742 let result = validate_trace_dimensions(8, 0);
743 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
744 }
745
746 #[test]
747 fn test_validate_trace_dimensions_not_power_of_two() {
748 let result = validate_trace_dimensions(8, 15);
749 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
750 }
751
752 #[test]
753 fn test_next_power_of_two() {
754 assert_eq!(next_power_of_two(0), 1);
755 assert_eq!(next_power_of_two(1), 1);
756 assert_eq!(next_power_of_two(2), 2);
757 assert_eq!(next_power_of_two(3), 4);
758 assert_eq!(next_power_of_two(5), 8);
759 assert_eq!(next_power_of_two(16), 16);
760 }
761
762 #[test]
763 fn test_air_error_display() {
764 let err = AirError::InvalidDimensions {
765 reason: "test".into(),
766 };
767 assert!(err.to_string().contains("Invalid AIR dimensions"));
768
769 let err = AirError::ExceedsMaxSize {
770 parameter: "width".into(),
771 max: 100,
772 actual: 200,
773 };
774 assert!(err.to_string().contains("exceeds maximum"));
775 }
776}