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
59#[cfg(feature = "recursive-proofs-experimental")]
60pub mod air_poseidon_mmcs;
61pub mod anonymous_auth;
62pub mod arithmetic;
63pub mod batch_stark_verifier;
64pub mod commitment_verifier;
65pub mod constraint_verifier;
66pub mod credential;
67pub mod fri_verifier;
68pub mod hash_preimage;
69pub mod hash_preimage_nist;
70pub mod identity_proof;
71pub mod merkle_inclusion;
72pub mod opening_verifier;
73pub mod poseidon2_gadget;
74pub mod poseidon_gadget;
75pub mod poseidon_hash;
76pub mod range_proof;
77pub mod recovery_policy;
78pub mod recovery_policy_hybrid;
79pub mod recursive_types;
80pub mod session_key;
81pub mod stark_verifier;
82pub mod state_transition;
83pub mod transaction;
84pub mod unlinkable_membership;
85pub mod unlinkable_membership_baby_bear;
86pub mod verifier_utils;
87pub mod wide_hash;
88pub mod wide_merkle;
89pub mod wide_merkle_path;
90pub mod wide_merkle_path_baby_bear;
91pub mod wide_sponge;
92pub mod wide_sponge_baby_bear;
93
94#[cfg(feature = "recursive-proofs-experimental")]
95pub use air_poseidon_mmcs::{
96 AirPoseidonCompressor,
97 AirPoseidonMmcs,
98 air_poseidon_mmcs_instance,
99};
100pub use anonymous_auth::{
101 AnonymousAuthAir,
102 AnonymousAuthInput,
103};
104pub use arithmetic::ArithmeticAir;
105pub use batch_stark_verifier::{
106 BatchRecursiveStarkVerificationInput,
107 BatchStarkVerifierAir,
108 batch_recursive_verifier_public_values,
109};
110#[cfg(all(feature = "recursive-proofs-experimental", feature = "std"))]
111pub use commitment_verifier::debug_commitment_trace_sanity_check;
112pub use commitment_verifier::{
113 CommitmentVerificationInput,
114 CommitmentVerifierAir,
115};
116pub use constraint_verifier::{
117 ConstraintVerificationInput,
118 ConstraintVerifierAir,
119};
120pub use credential::{
121 CredentialAir,
122 CredentialInput,
123 CredentialSchema,
124};
125pub use fri_verifier::{
126 FriVerificationInput,
127 FriVerifierAir,
128};
129pub use hash_preimage::HashPreimageAir;
130pub use hash_preimage_nist::{
131 HASH_OUTPUT_BYTES,
132 HashPreimageNistAir,
133 HashPreimageNistInput,
134 expected_hash_to_public_values,
135};
136pub use identity_proof::{
137 IdentityProofAir,
138 IdentityProofInput,
139 MlDsaLevel,
140};
141pub use merkle_inclusion::{
142 MerkleHash,
143 MerkleInclusionAir,
144 MerkleProofInput,
145};
146pub use opening_verifier::{
147 OpeningVerificationInput,
148 OpeningVerifierAir,
149};
150pub use poseidon_gadget::PoseidonGadget;
151pub use poseidon_hash::PoseidonHashAir;
152pub use range_proof::RangeProofAir;
153pub use recovery_policy::{
154 RECOVERY_POLICY_AIR_ID,
155 RECOVERY_POLICY_COMMIT_DOMAIN,
156 RECOVERY_PUBLIC_INPUTS_LEN,
157 RECOVERY_VK_COMMIT_DOMAIN,
158 RecoveryPolicyAir,
159 RecoveryPolicyInput,
160 RecoveryPolicyKey,
161 RecoveryPolicyPublicInputs,
162 policy_commitment,
163 shake256_commit,
164 vk_commitment,
165};
166pub use recovery_policy_hybrid::{
167 RECOVERY_HYBRID_POLICY_COMMIT_DOMAIN,
168 RECOVERY_HYBRID_PUBLIC_INPUTS_LEN,
169 RECOVERY_HYBRID_VK_COMMIT_DOMAIN,
170 RECOVERY_POLICY_HYBRID_AIR_ID,
171 RecoveryPolicyHybridAir,
172 RecoveryPolicyHybridInput,
173 RecoveryPolicyHybridPublicInputs,
174 hybrid_policy_commitment,
175};
176pub use recursive_types::{
177 RecursiveStarkInput,
178 SerializedFriRound,
179 SerializedStarkProof,
180};
181pub use session_key::{
182 KdfAlgorithm,
183 KdfParams,
184 OUTPUT_LENGTH_GRANULARITY,
185 SessionKeyDerivationAir,
186 SessionKeyInput,
187 derive_session_keys,
188};
189pub use unlinkable_membership::{
190 CTX_ELEMS,
191 MEMBERSHIP_DOMAIN_STR,
192 MEMBERSHIP_NUM_PUBLIC,
193 MEMBERSHIP_ROW_WIDTH,
194 SECRET_T_ELEMS,
195 UnlinkableMembershipAir,
196 generate_membership_trace,
197 membership_domain,
198 membership_leaf,
199 membership_nullifier,
200 membership_public_values,
201};
202pub use wide_hash::{
203 WIDE_DIGEST_ELEMS,
204 WideDigest,
205 poseidon256_perm_truncated,
206 poseidon256_wide_hash,
207};
208pub use wide_merkle::{
209 NODE_NUM_PERMS,
210 NODE_ROW_WIDTH,
211 WideNodeHashAir,
212 generate_node_trace,
213 node_public_values,
214};
215pub use wide_merkle_path::{
216 PATH_NUM_PUBLIC,
217 PATH_ROW_WIDTH,
218 WideMerklePathAir,
219 generate_path_trace,
220 path_public_values,
221};
222pub use wide_sponge::{
223 constrain_wide_sponge,
224 generate_wide_sponge_cells,
225 wide_sponge_interm_cols,
226 wide_sponge_num_perms,
227};
228pub trait PoseidonCommitmentRoot {
231 fn to_poseidon_root_bytes(&self) -> [u8; recursive_types::COMMITMENT_HASH_SIZE];
232}
233
234#[cfg(feature = "recursive-proofs-experimental")]
235pub use stark_verifier::{
236 MerklePathExtractable,
237 build_recursive_verification_input_from_proof,
238 build_recursive_verification_input_from_proof_with_poseidon,
239};
240pub use stark_verifier::{
241 RecursiveStarkVerificationInput,
242 StarkVerifierAir,
243 build_recursive_verification_input,
244};
245
246pub 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)]
259pub enum AirError {
260 InvalidDimensions {
262 reason: String,
264 },
265
266 ExceedsMaxSize {
268 parameter: String,
270 max: usize,
272 actual: usize,
274 },
275
276 InvalidInput {
278 reason: String,
280 },
281
282 TraceMismatch {
284 expected_width: usize,
286 actual_width: usize,
288 },
289
290 InvalidWitness {
292 constraint: String,
294 },
295
296 InternalError {
298 reason: String,
300 },
301
302 NotSupported {
304 reason: String,
306 },
307
308 MissingFriCommitPhaseOpenings,
310
311 FriRoundCountMismatch,
313}
314
315impl fmt::Display for AirError {
316 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317 match self {
318 AirError::InvalidDimensions { reason } => {
319 write!(f, "Invalid AIR dimensions: {}", reason)
320 }
321 AirError::ExceedsMaxSize {
322 parameter,
323 max,
324 actual,
325 } => {
326 write!(
327 f,
328 "AIR parameter '{}' exceeds maximum: max={}, actual={}",
329 parameter, max, actual
330 )
331 }
332 AirError::InvalidInput { reason } => {
333 write!(f, "Invalid input for trace generation: {}", reason)
334 }
335 AirError::TraceMismatch {
336 expected_width,
337 actual_width,
338 } => {
339 write!(
340 f,
341 "Trace width mismatch: expected {}, got {}",
342 expected_width, actual_width
343 )
344 }
345 AirError::InvalidWitness { constraint } => {
346 write!(
347 f,
348 "Invalid witness: constraint '{}' not satisfied",
349 constraint
350 )
351 }
352 AirError::InternalError { reason } => {
353 write!(f, "Internal AIR error: {}", reason)
354 }
355 AirError::NotSupported { reason } => {
356 write!(f, "Not supported: {}", reason)
357 }
358 AirError::MissingFriCommitPhaseOpenings => {
359 write!(f, "FRI commit-phase openings missing for query index")
360 }
361 AirError::FriRoundCountMismatch => {
362 write!(
363 f,
364 "FRI commit-phase step count does not match number of rounds"
365 )
366 }
367 }
368 }
369}
370
371impl From<AirError> for lib_q_core::Error {
372 fn from(err: AirError) -> Self {
373 lib_q_core::Error::InternalError {
374 operation: "AIR operation".into(),
375 details: err.to_string(),
376 }
377 }
378}
379
380pub trait TraceGenerator<F: Field, I> {
391 fn generate_trace(&self, inputs: &I) -> Result<RowMajorMatrix<F>, AirError>;
409
410 fn public_values(&self, inputs: &I) -> Vec<F> {
424 let _ = inputs;
425 Vec::new()
426 }
427}
428
429pub fn validate_trace_dimensions(width: usize, height: usize) -> Result<(), AirError> {
440 if width == 0 {
441 return Err(AirError::InvalidDimensions {
442 reason: "Trace width must be greater than 0".into(),
443 });
444 }
445
446 if width > MAX_TRACE_WIDTH {
447 return Err(AirError::ExceedsMaxSize {
448 parameter: "width".into(),
449 max: MAX_TRACE_WIDTH,
450 actual: width,
451 });
452 }
453
454 if height == 0 {
455 return Err(AirError::InvalidDimensions {
456 reason: "Trace height must be greater than 0".into(),
457 });
458 }
459
460 if height > MAX_TRACE_HEIGHT {
461 return Err(AirError::ExceedsMaxSize {
462 parameter: "height".into(),
463 max: MAX_TRACE_HEIGHT,
464 actual: height,
465 });
466 }
467
468 if !height.is_power_of_two() {
469 return Err(AirError::InvalidDimensions {
470 reason: "Trace height must be a power of 2".into(),
471 });
472 }
473
474 Ok(())
475}
476
477pub fn next_power_of_two(n: usize) -> usize {
487 if n == 0 {
488 return 1;
489 }
490 n.next_power_of_two()
491}
492
493pub fn poseidon_to_field<F: Field + BasedVectorSpace<Mersenne31>>(pf: &PoseidonField) -> F {
506 let coeffs: &[Mersenne31] = pf.as_basis_coefficients_slice();
507 F::from_basis_coefficients_fn(|i| {
508 if i < coeffs.len() {
509 coeffs[i]
510 } else {
511 <Mersenne31 as PrimeCharacteristicRing>::ZERO
512 }
513 })
514}
515
516pub fn poseidon_slice_to_field<F: Field + BasedVectorSpace<Mersenne31>>(
526 slice: &[PoseidonField],
527) -> Vec<F> {
528 slice.iter().map(poseidon_to_field).collect()
529}
530
531pub fn poseidon_field_to_bytes(hash: &[PoseidonField]) -> Vec<u8> {
544 use lib_q_stark_field::RawDataSerializable;
545 hash.iter().flat_map(|f| (*f).into_bytes()).collect()
547}
548
549#[must_use]
562pub fn merkle_root_to_bytes(root: &PoseidonField) -> [u8; 32] {
563 use lib_q_stark_field::RawDataSerializable;
564 let mut out = [0u8; 32];
565 let bytes: Vec<u8> = (*root).into_bytes().into_iter().collect();
566 let n = core::cmp::min(bytes.len(), 32);
567 out[..n].copy_from_slice(&bytes[..n]);
568 out
569}
570
571pub fn merkle_root_from_bytes(bytes: &[u8]) -> Result<PoseidonField, AirError> {
584 use lib_q_stark_field::extension::Complex;
585 use lib_q_stark_field::integers::QuotientMap;
586 use lib_q_stark_mersenne31::Mersenne31;
587
588 if bytes.len() < 8 {
589 return Err(AirError::InvalidInput {
590 reason: alloc::format!(
591 "Merkle root bytes must have at least 8 bytes, got {}",
592 bytes.len()
593 ),
594 });
595 }
596 let mut real_bytes = [0u8; 4];
597 let mut imag_bytes = [0u8; 4];
598 real_bytes.copy_from_slice(&bytes[0..4]);
599 imag_bytes.copy_from_slice(&bytes[4..8]);
600 let real =
605 Mersenne31::from_canonical_checked(u32::from_le_bytes(real_bytes)).ok_or_else(|| {
606 AirError::InvalidInput {
607 reason: String::from("non-canonical real limb in merkle root (>= 2^31-1)"),
608 }
609 })?;
610 let imag =
611 Mersenne31::from_canonical_checked(u32::from_le_bytes(imag_bytes)).ok_or_else(|| {
612 AirError::InvalidInput {
613 reason: String::from("non-canonical imag limb in merkle root (>= 2^31-1)"),
614 }
615 })?;
616 Ok(Complex::new_complex(real, imag))
617}
618
619pub fn compute_poseidon_row(
624 state: &[PoseidonField],
625 params: &PoseidonParams,
626) -> (Vec<PoseidonField>, Vec<PoseidonField>) {
627 use lib_q_stark_field::extension::Complex;
628 use lib_q_stark_mersenne31::Mersenne31;
629
630 let n = params.state_width;
631 assert!(state.len() >= n, "state must have at least {} elements", n);
632 let zero = Complex::<Mersenne31>::new_complex(Mersenne31::ZERO, Mersenne31::ZERO);
633 let mut intermediates = Vec::new();
634 let mut round_idx = 0usize;
635 let mut s: Vec<PoseidonField> = state[0..n].to_vec();
636 let full_half = params.full_rounds / 2;
637
638 for _ in 0..full_half {
639 let after_arc: Vec<PoseidonField> = (0..n)
640 .map(|i| s[i] + params.round_constants[round_idx + i])
641 .collect();
642 round_idx += n;
643 intermediates.extend(after_arc.iter().cloned());
644 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
645 intermediates.extend(after_sbox.iter().cloned());
646 let mut next_s = alloc::vec![zero; n];
647 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
648 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
649 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
650 }
651 }
652 intermediates.extend(next_s.iter().cloned());
653 s = next_s;
654 }
655 for _ in 0..params.partial_rounds {
656 let after_arc: Vec<PoseidonField> = (0..n)
657 .map(|i| s[i] + params.round_constants[round_idx + i])
658 .collect();
659 round_idx += n;
660 intermediates.extend(after_arc.iter().cloned());
661 let mut after_sbox = alloc::vec![zero; n];
662 after_sbox[0] = sbox(after_arc[0]);
663 after_sbox[1..n].copy_from_slice(&after_arc[1..n]);
664 intermediates.extend(after_sbox.iter().cloned());
665 let mut next_s = alloc::vec![zero; n];
666 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
667 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
668 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
669 }
670 }
671 intermediates.extend(next_s.iter().cloned());
672 s = next_s;
673 }
674 for _ in 0..full_half {
675 let after_arc: Vec<PoseidonField> = (0..n)
676 .map(|i| s[i] + params.round_constants[round_idx + i])
677 .collect();
678 round_idx += n;
679 intermediates.extend(after_arc.iter().cloned());
680 let after_sbox: Vec<PoseidonField> = (0..n).map(|i| sbox(after_arc[i])).collect();
681 intermediates.extend(after_sbox.iter().cloned());
682 let mut next_s = alloc::vec![zero; n];
683 for (i, next_s_i) in next_s.iter_mut().enumerate().take(n) {
684 for (j, &after_sbox_j) in after_sbox.iter().enumerate().take(n) {
685 *next_s_i += params.mds_matrix[i][j] * after_sbox_j;
686 }
687 }
688 intermediates.extend(next_s.iter().cloned());
689 s = next_s;
690 }
691 (s, intermediates)
692}
693
694pub fn bytes_to_poseidon_field(bytes: &[u8]) -> Vec<PoseidonField> {
707 use lib_q_stark_field::extension::Complex;
708 use lib_q_stark_mersenne31::Mersenne31;
709 bytes
710 .iter()
711 .map(|b| Complex::<Mersenne31>::from(Mersenne31::new(*b as u32)))
712 .collect()
713}
714
715pub fn it_bytes_to_public_value<F: Field + BasedVectorSpace<Mersenne31>>(it: &[u8; 16]) -> F {
719 use lib_q_stark_field::extension::Complex;
720 use lib_q_stark_mersenne31::Mersenne31;
721 let mut real_bytes = [0u8; 4];
722 let mut imag_bytes = [0u8; 4];
723 real_bytes.copy_from_slice(&it[0..4]);
724 imag_bytes.copy_from_slice(&it[4..8]);
725 let real = Mersenne31::new(u32::from_le_bytes(real_bytes));
726 let imag = Mersenne31::new(u32::from_le_bytes(imag_bytes));
727 let c = Complex::new_complex(real, imag);
728 poseidon_to_field::<F>(&c)
729}
730
731#[cfg(test)]
732mod tests {
733 use super::*;
734
735 #[test]
736 fn test_validate_trace_dimensions_valid() {
737 assert!(validate_trace_dimensions(8, 16).is_ok());
738 assert!(validate_trace_dimensions(1, 1).is_ok());
739 assert!(validate_trace_dimensions(100, 1024).is_ok());
740 }
741
742 #[test]
743 fn test_validate_trace_dimensions_zero_width() {
744 let result = validate_trace_dimensions(0, 16);
745 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
746 }
747
748 #[test]
749 fn test_validate_trace_dimensions_zero_height() {
750 let result = validate_trace_dimensions(8, 0);
751 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
752 }
753
754 #[test]
755 fn test_validate_trace_dimensions_not_power_of_two() {
756 let result = validate_trace_dimensions(8, 15);
757 assert!(matches!(result, Err(AirError::InvalidDimensions { .. })));
758 }
759
760 #[test]
761 fn test_next_power_of_two() {
762 assert_eq!(next_power_of_two(0), 1);
763 assert_eq!(next_power_of_two(1), 1);
764 assert_eq!(next_power_of_two(2), 2);
765 assert_eq!(next_power_of_two(3), 4);
766 assert_eq!(next_power_of_two(5), 8);
767 assert_eq!(next_power_of_two(16), 16);
768 }
769
770 #[test]
771 fn test_air_error_display() {
772 let err = AirError::InvalidDimensions {
773 reason: "test".into(),
774 };
775 assert!(err.to_string().contains("Invalid AIR dimensions"));
776
777 let err = AirError::ExceedsMaxSize {
778 parameter: "width".into(),
779 max: 100,
780 actual: 200,
781 };
782 assert!(err.to_string().contains("exceeds maximum"));
783 }
784}