Skip to main content

lib_q_zkp/
lib.rs

1//! lib-Q ZKP - Post-quantum Zero-Knowledge Proofs
2//!
3//! This crate provides implementations of post-quantum zero-knowledge proofs.
4//!
5//! # zk-STARK Implementation
6//!
7//! This crate provides a high-level API for creating and verifying zk-STARK proofs.
8//! The underlying implementation is based on Plonky3, adapted for lib-Q's post-quantum
9//! security requirements using SHAKE256.
10//!
11//! ## Field Configuration
12//!
13//! The implementation uses **`Complex<Mersenne31>`** as the base field, which provides:
14//! - **TWO_ADICITY = 32**: Sufficient for FRI protocol and efficient FFT operations
15//! - **Post-quantum security**: All operations use NIST-approved primitives
16//! - **Efficient arithmetic**: Optimized field operations for STARK proofs
17//!
18//! ## Example Usage
19//!
20//! ```rust,ignore
21//! use lib_q_zkp::stark::{StarkProver, StarkVerifier, default_config};
22//! use lib_q_stark_field::extension::Complex;
23//! use lib_q_stark_mersenne31::Mersenne31;
24//!
25//! type Val = Complex<Mersenne31>;
26//!
27//! // Create prover and verifier with default configuration
28//! let config = default_config();
29//! let prover = StarkProver::new(config.clone());
30//! let verifier = StarkVerifier::new(config);
31//!
32//! // Generate proof (requires AIR implementation)
33//! // let proof = prover.prove(&air, trace, &public_values);
34//!
35//! // Verify proof
36//! // verifier.verify(&air, &proof, &public_values)?;
37//! ```
38//!
39//! ## Testing
40//!
41//! - **Recursive aggregation**: The test `test_recursive_verifier_trace_satisfies_constraints_then_prove_verify` (in `aggregation_tests`) runs the full prove → aggregate → verify pipeline. It is slow in dev (unoptimized); run with `--release` for completion in a few minutes. CI runs this test in release with a 15-minute timeout.
42//! - **Merkle tree builder**: `tests/merkle_tree_builder_tests.rs` uses [`stark::fast_proof_config`] for prove/verify round-trips (fast FRI); wrong-root and cross-tree rejection use [`stark::default_config`] because minimal FRI is not sound for those negatives.
43//! - **Merkle tree certificates**: Create/use and security checks (wrong root, wrong depth, cross-tree) are covered by `tests/merkle_certificate_tests.rs`. Additional Merkle and group-membership tests live in `air_integration` and `ip_soundness_tests`.
44
45#![cfg_attr(not(feature = "std"), no_std)]
46// Bounds on generic type parameters in aliases are not enforced by the type checker (Rust RFC
47// follow-up); we still document constraints via `C: StarkGenericConfig` / `F: Field` on aliases.
48#![allow(type_alias_bounds)]
49#![deny(unsafe_code)]
50#![deny(unused_qualifications)]
51#![allow(clippy::bool_assert_comparison)]
52#![allow(clippy::clone_on_copy)]
53#![allow(clippy::collapsible_if)]
54#![allow(clippy::get_first)]
55#![allow(clippy::iter_cloned_collect)]
56#![allow(clippy::manual_is_multiple_of)]
57#![allow(clippy::too_many_arguments)]
58#![allow(clippy::unnecessary_lazy_evaluations)]
59
60#[cfg(feature = "alloc")]
61extern crate alloc;
62
63// Re-export core types for public use
64#[cfg(feature = "alloc")]
65use alloc::boxed::Box;
66#[cfg(feature = "alloc")]
67use alloc::string::ToString;
68#[cfg(feature = "alloc")]
69use alloc::vec;
70#[cfg(feature = "alloc")]
71use alloc::vec::Vec;
72
73pub use lib_q_core::Result;
74/// Plonky3-derived components (Keccak AIR, lookup, batch STARK, etc.).
75///
76/// Enabled when any of `plonky` or the granular `plonky-*` features is on (each pulls in
77/// `lib-q-plonky` with the corresponding sub-features; `plonky` enables the full set).
78#[cfg(any(
79    feature = "plonky",
80    feature = "plonky-keccak-air",
81    feature = "plonky-lookup",
82    feature = "plonky-uni-stark",
83    feature = "plonky-batch-stark",
84))]
85pub use lib_q_plonky as plonky;
86
87/// zk-STARK implementation
88#[cfg(feature = "zkp")]
89pub mod stark;
90
91/// Circuit builder for arithmetic constraints
92#[cfg(feature = "zkp")]
93pub mod circuit;
94
95/// AIR implementations for common proof types
96#[cfg(feature = "zkp")]
97pub mod air;
98
99/// Proof aggregation for combining multiple proofs
100#[cfg(feature = "zkp")]
101pub mod aggregation;
102
103/// IP (Identity Protocol) integration
104#[cfg(feature = "zkp")]
105pub mod ip;
106
107/// Poseidon Merkle tree builder (compatible with MerkleInclusionAir)
108#[cfg(feature = "zkp")]
109pub mod merkle;
110
111/// High-level lib q API
112#[cfg(feature = "zkp")]
113pub mod api;
114
115#[cfg(feature = "zkp")]
116pub mod wire;
117
118#[cfg(feature = "zkp")]
119pub use api::{
120    MerklePath,
121    build_merkle_tree,
122    prove_membership,
123    prove_membership_with_config,
124    prove_preimage,
125    prove_preimage_nist,
126    verify_membership,
127    verify_membership_with_config,
128    verify_membership_with_depth,
129    verify_membership_with_depth_and_config,
130    verify_preimage,
131    verify_preimage_nist,
132};
133
134#[cfg(feature = "wasm")]
135mod wasm;
136
137#[cfg(feature = "zkp")]
138pub use lib_q_stark::{
139    Proof as StarkProof,
140    StarkConfig,
141    StarkGenericConfig,
142    check_constraints,
143    prove,
144    verify,
145};
146#[cfg(feature = "zkp")]
147pub use lib_q_stark_air::Air;
148#[cfg(feature = "zkp")]
149use lib_q_stark_field::extension::Complex;
150#[cfg(feature = "zkp")]
151use lib_q_stark_matrix::dense::RowMajorMatrix;
152#[cfg(feature = "zkp")]
153use lib_q_stark_mersenne31::Mersenne31;
154#[cfg(feature = "zkp")]
155pub use merkle::PoseidonMerkleTree;
156#[cfg(feature = "zkp")]
157use serde::{
158    Deserialize,
159    Serialize,
160};
161
162#[cfg(feature = "zkp")]
163#[allow(unused_imports)]
164use crate::air::TraceGenerator;
165
166/// The field type used for ZKP operations
167///
168/// Uses `Complex<Mersenne31>` which provides TWO_ADICITY = 32, sufficient for
169/// FRI protocol and efficient FFT operations.
170#[cfg(feature = "zkp")]
171pub type ZkpField = Complex<Mersenne31>;
172
173/// Metadata specific to different proof types
174///
175/// This enum stores proof-specific parameters that are required for verification.
176/// The metadata is serialized alongside the proof to make proofs self-describing.
177///
178/// All proofs must include appropriate metadata for their proof type.
179#[derive(Debug, Clone, PartialEq, Eq, Default)]
180#[cfg_attr(feature = "zkp", derive(Serialize, Deserialize))]
181pub enum ProofMetadata {
182    /// No metadata (default variant, but proofs should use specific metadata types)
183    #[default]
184    None,
185    /// Merkle tree inclusion proof metadata
186    MerkleInclusion {
187        /// Depth of the Merkle tree (required for AIR reconstruction)
188        tree_depth: u8,
189    },
190    /// Hash preimage proof metadata (Poseidon-128)
191    HashPreimage {
192        /// Output size in bytes
193        output_size: u16,
194    },
195    /// NIST hash preimage proof metadata (cSHAKE256)
196    HashPreimageNist {
197        /// Output size in bytes (e.g. 32 for cSHAKE256)
198        output_size: u16,
199    },
200    /// Circuit computation proof metadata
201    Circuit {
202        /// Number of witness values
203        num_witnesses: u32,
204        /// Number of public values
205        num_public: u32,
206    },
207    /// Credential proof metadata for selective disclosure
208    Credential {
209        /// Serialized credential schema (attribute sizes in bytes)
210        attribute_sizes: Vec<u16>,
211        /// Reveal mask (which attributes are revealed: true = revealed, false = hidden)
212        reveal_mask: Vec<bool>,
213    },
214    /// Identity Token ownership proof metadata
215    Identity {
216        /// ML-DSA security level: 44, 65, or 87
217        dsa_level: u8,
218    },
219    /// Recovery policy threshold proof metadata
220    RecoveryPolicy {
221        /// Number of keys in policy
222        key_count: u32,
223        /// Circuit air id
224        air_id: u8,
225    },
226}
227
228/// A zero-knowledge proof
229#[derive(Debug, Clone)]
230#[cfg_attr(feature = "zkp", derive(serde::Serialize, serde::Deserialize))]
231pub struct ZkpProof {
232    /// The proof data (serialized STARK proof)
233    pub data: Vec<u8>,
234    /// The proof type
235    pub proof_type: ProofType,
236    /// Security level
237    pub security_level: u32,
238    /// Proof-specific metadata (required for verification)
239    pub metadata: ProofMetadata,
240}
241
242#[cfg(feature = "zkp")]
243impl ZkpProof {
244    /// Serialize a STARK proof into a ZkpProof with metadata
245    ///
246    /// All proofs must include metadata for proper verification.
247    /// This method is used internally by the high-level API functions.
248    pub fn from_stark_proof<C: StarkGenericConfig>(
249        proof: &StarkProof<C>,
250        metadata: ProofMetadata,
251    ) -> Result<Self>
252    where
253        StarkProof<C>: Serialize,
254    {
255        let data = postcard::to_allocvec(proof).map_err(|_| lib_q_core::Error::InternalError {
256            operation: "ZKP proof serialization".to_string(),
257            details: "Failed to serialize STARK proof".to_string(),
258        })?;
259        Ok(Self {
260            data,
261            proof_type: ProofType::Stark,
262            security_level: 1,
263            metadata,
264        })
265    }
266
267    /// Deserialize a ZkpProof into a STARK proof
268    pub fn to_stark_proof<C: StarkGenericConfig>(&self) -> Result<StarkProof<C>>
269    where
270        StarkProof<C>: for<'de> Deserialize<'de>,
271    {
272        postcard::from_bytes(&self.data).map_err(|_| lib_q_core::Error::InternalError {
273            operation: "ZKP proof deserialization".to_string(),
274            details: "Failed to deserialize STARK proof".to_string(),
275        })
276    }
277
278    /// Get the tree depth from Merkle inclusion proof metadata
279    ///
280    /// Returns `Some(depth)` if this is a Merkle inclusion proof with metadata,
281    /// `None` otherwise.
282    pub fn merkle_tree_depth(&self) -> Option<u8> {
283        match &self.metadata {
284            ProofMetadata::MerkleInclusion { tree_depth } => Some(*tree_depth),
285            _ => None,
286        }
287    }
288}
289
290/// Types of zero-knowledge proofs supported by lib-Q
291///
292/// Only NIST-approved post-quantum proof systems are included.
293/// Classical schemes (SNARKs, Bulletproofs) are intentionally excluded.
294#[derive(Debug, Clone, PartialEq, Eq)]
295#[cfg_attr(feature = "zkp", derive(serde::Serialize, serde::Deserialize))]
296pub enum ProofType {
297    /// zk-STARK proof (transparent, post-quantum secure)
298    Stark,
299}
300
301/// Prover for creating zero-knowledge proofs
302#[cfg(feature = "zkp")]
303pub struct ZkpProver {
304    // Using default config for now - can be extended to support custom configs
305}
306
307#[cfg(not(feature = "zkp"))]
308pub struct ZkpProver;
309
310/// Verifier for verifying zero-knowledge proofs
311#[cfg(feature = "zkp")]
312pub struct ZkpVerifier {
313    // Using default config for now - can be extended to support custom configs
314}
315
316#[cfg(not(feature = "zkp"))]
317pub struct ZkpVerifier;
318
319#[cfg(feature = "zkp")]
320impl ZkpProver {
321    /// Create a new ZKP prover
322    pub fn new() -> Self {
323        Self {}
324    }
325
326    /// Prove knowledge of a secret value without revealing it
327    ///
328    /// This generates a STARK proof that the prover knows a preimage `secret_value`
329    /// whose **Poseidon-128** hash equals the public commitment. The proof uses
330    /// Poseidon for constraint encoding (industry-standard for STARKs; e.g. StarkWare,
331    /// RISC Zero, Succinct). For a NIST-only hash, use [`prove_secret_value_nist`](ZkpProver::prove_secret_value_nist).
332    ///
333    /// # Arguments
334    ///
335    /// * `secret_value` - The secret preimage to prove knowledge of
336    /// * `public_statement` - Additional public data (currently unused; reserved for future use)
337    ///
338    /// # Returns
339    ///
340    /// A zero-knowledge proof that can be verified without revealing the secret
341    ///
342    /// # Example
343    ///
344    /// ```rust,ignore
345    /// use lib_q_zkp::{ZkpProver, ZkpVerifier};
346    ///
347    /// let mut prover = ZkpProver::new();
348    /// let secret = b"my secret password";
349    /// let public = b"challenge";
350    ///
351    /// let proof = prover.prove_secret_value(secret, public)?;
352    /// ```
353    pub fn prove_secret_value(
354        &mut self,
355        secret_value: &[u8],
356        _public_statement: &[u8],
357    ) -> Result<ZkpProof> {
358        use crate::air::{
359            HashPreimageAir,
360            TraceGenerator,
361        };
362        use crate::stark::{
363            StarkProver,
364            default_config,
365        };
366
367        // Validate input size
368        if secret_value.is_empty() {
369            return Err(lib_q_core::Error::InvalidState {
370                operation: "prove_secret_value".to_string(),
371                reason: "Secret value cannot be empty".to_string(),
372            });
373        }
374
375        if secret_value.len() > air::hash_preimage::MAX_PREIMAGE_SIZE {
376            return Err(lib_q_core::Error::InvalidState {
377                operation: "prove_secret_value".to_string(),
378                reason: "Secret value exceeds maximum size".to_string(),
379            });
380        }
381
382        // Create the hash preimage AIR
383        let air = HashPreimageAir::new();
384
385        // Generate trace from the secret preimage
386        let input = secret_value.to_vec();
387        let trace: RowMajorMatrix<ZkpField> =
388            air.generate_trace(&input)
389                .map_err(|e| lib_q_core::Error::InternalError {
390                    operation: "prove_secret_value".to_string(),
391                    details: e.to_string(),
392                })?;
393
394        // Get public values (the hash output)
395        let public_values: Vec<ZkpField> = air.public_values(&input);
396
397        // Create prover with default config
398        let config = default_config();
399        let prover = StarkProver::new(config);
400
401        // Generate STARK proof
402        let proof = prover.prove(&air, trace, &public_values).map_err(|e| {
403            lib_q_core::Error::InternalError {
404                operation: "STARK proof generation".to_string(),
405                details: e.to_string(),
406            }
407        })?;
408
409        // Store output size in proof metadata
410        let metadata = ProofMetadata::HashPreimage { output_size: 1u16 };
411
412        // Serialize into ZkpProof
413        ZkpProof::from_stark_proof(&proof, metadata)
414    }
415
416    /// Prove knowledge of a secret value using NIST cSHAKE256 (100% NIST compliance)
417    ///
418    /// Same semantics as [`prove_secret_value`](ZkpProver::prove_secret_value) but uses
419    /// cSHAKE256 with domain `b"HashPreimageNistAir"` for the commitment. Use this when
420    /// NIST-only hashes are required; prover cost is higher than Poseidon-based proofs.
421    ///
422    /// # Arguments
423    ///
424    /// * `secret_value` - The secret preimage to prove knowledge of
425    /// * `_public_statement` - Reserved for future use
426    ///
427    /// # Status
428    ///
429    /// NOT IMPLEMENTED. The underlying [`HashPreimageNistAir`](crate::air::HashPreimageNistAir)
430    /// does not yet encode Keccak-f / cSHAKE256 constraints, so a generated proof would not
431    /// soundly bind the secret to the public hash. This function therefore returns
432    /// [`Error::NotImplemented`](lib_q_core::Error::NotImplemented) until the constraints exist.
433    pub fn prove_secret_value_nist(
434        &mut self,
435        secret_value: &[u8],
436        public_statement: &[u8],
437    ) -> Result<ZkpProof> {
438        // SOUNDNESS GATE: cSHAKE256/Keccak-f AIR constraints are not implemented yet (see
439        // `crate::air::hash_preimage_nist`). A proof from the current AIR would not bind the
440        // secret to the public hash, so we refuse rather than emit a proof that proves nothing.
441        // Once real Keccak-f constraints exist, replace this with the trace-generation and
442        // STARK-proving flow used by `prove_secret_value`.
443        let _ = (secret_value, public_statement);
444        Err(lib_q_core::Error::NotImplemented {
445            feature: "NIST (cSHAKE256) preimage proofs: Keccak-f AIR constraints not implemented"
446                .to_string(),
447        })
448    }
449
450    /// Prove a computation using a circuit
451    ///
452    /// This generates a STARK proof that the prover knows witness values that
453    /// satisfy all constraints in the arithmetic circuit.
454    ///
455    /// # Arguments
456    ///
457    /// * `circuit` - The arithmetic circuit defining the computation
458    /// * `witness` - The witness values (private inputs)
459    /// * `public` - The public input values
460    ///
461    /// # Returns
462    ///
463    /// A zero-knowledge proof of computation correctness
464    ///
465    /// # Example
466    ///
467    /// ```rust,ignore
468    /// use lib_q_zkp::{ZkpProver, circuit::CircuitBuilder};
469    /// use lib_q_stark_field::extension::Complex;
470    /// use lib_q_stark_mersenne31::Mersenne31;
471    ///
472    /// type Val = Complex<Mersenne31>;
473    ///
474    /// // Build a circuit: prove knowledge of a, b such that a * b = public_output
475    /// let mut builder = CircuitBuilder::<Val>::new(2, 1);
476    /// let a = builder.wire(0);
477    /// let b = builder.wire(1);
478    /// let output = builder.wire(2);
479    /// let product = builder.mul(a, b);
480    /// builder.assert_eq(product, output);
481    /// let circuit = builder.build();
482    ///
483    /// // Generate proof
484    /// let witness = vec![Val::from(3u32), Val::from(4u32)];
485    /// let public = vec![Val::from(12u32)];
486    ///
487    /// let mut prover = ZkpProver::new();
488    /// let proof = prover.prove_computation(&circuit, &witness, &public)?;
489    /// ```
490    pub fn prove_computation(
491        &mut self,
492        circuit: &circuit::ArithmeticCircuit<ZkpField>,
493        witness: &[ZkpField],
494        public: &[ZkpField],
495    ) -> Result<ZkpProof> {
496        use crate::circuit::CircuitAir;
497        use crate::stark::{
498            StarkProver,
499            default_config,
500        };
501
502        // Create the circuit AIR
503        let air = CircuitAir::new(circuit.clone());
504
505        // Generate trace from witness and public values
506        let trace = air.generate_trace(witness, public)?;
507
508        // Create prover with default config
509        let config = default_config();
510        let prover = StarkProver::new(config);
511
512        // Generate STARK proof
513        let proof =
514            prover
515                .prove(&air, trace, public)
516                .map_err(|e| lib_q_core::Error::InternalError {
517                    operation: "STARK proof generation".to_string(),
518                    details: e.to_string(),
519                })?;
520
521        // Store circuit parameters in proof metadata
522        let metadata = ProofMetadata::Circuit {
523            num_witnesses: witness.len().min(u32::MAX as usize) as u32,
524            num_public: public.len().min(u32::MAX as usize) as u32,
525        };
526
527        // Serialize into ZkpProof
528        ZkpProof::from_stark_proof(&proof, metadata)
529    }
530}
531
532#[cfg(not(feature = "zkp"))]
533impl ZkpProver {
534    /// Create a new ZKP prover
535    pub fn new() -> Self {
536        Self {}
537    }
538
539    /// Prove knowledge of a secret value without revealing it
540    pub fn prove_secret_value(
541        &mut self,
542        _secret_value: &[u8],
543        _public_statement: &[u8],
544    ) -> Result<ZkpProof> {
545        Err(lib_q_core::Error::NotImplemented {
546            feature: "ZKP feature not enabled".to_string(),
547        })
548    }
549
550    /// Prove knowledge of a secret value (NIST variant)
551    pub fn prove_secret_value_nist(
552        &mut self,
553        _secret_value: &[u8],
554        _public_statement: &[u8],
555    ) -> Result<ZkpProof> {
556        Err(lib_q_core::Error::NotImplemented {
557            feature: "ZKP feature not enabled".to_string(),
558        })
559    }
560}
561
562/// Crate-private helper for NIST secret value verification. Used by both
563/// `ZkpVerifier::verify` and `ZkpVerifier::verify_secret_value_nist`.
564#[cfg(feature = "zkp")]
565fn verify_secret_value_nist_impl(proof: &ZkpProof, expected_hash: &[u8]) -> Result<bool> {
566    if proof.proof_type != ProofType::Stark {
567        return Ok(false);
568    }
569    if proof.data.is_empty() {
570        return Ok(false);
571    }
572
573    let ProofMetadata::HashPreimageNist { .. } = &proof.metadata else {
574        return Ok(false);
575    };
576
577    // SOUNDNESS GATE: the NIST AIR has no Keccak-f constraints yet (see
578    // `crate::air::hash_preimage_nist`), so a "valid" proof would prove nothing. Refuse to
579    // verify NIST proofs rather than accept them. Once the constraints exist, restore the
580    // StarkVerifier flow against `expected_hash_to_public_values(expected_hash)`.
581    let _ = expected_hash;
582    Err(lib_q_core::Error::NotImplemented {
583        feature: "NIST (cSHAKE256) preimage proofs: Keccak-f AIR constraints not implemented"
584            .to_string(),
585    })
586}
587
588#[cfg(feature = "zkp")]
589impl ZkpVerifier {
590    /// Create a new ZKP verifier
591    pub fn new() -> Self {
592        Self {}
593    }
594
595    /// Verify a zero-knowledge proof of secret value (preimage) knowledge
596    ///
597    /// This verifies a proof generated by [`ZkpProver::prove_secret_value`]. The verifier
598    /// recomputes the public Poseidon commitment from the **preimage**, so the caller passes
599    /// the same secret preimage that was given to the prover (NOT the hash output). The proof
600    /// then attests that the prover knew a preimage hashing to that commitment.
601    /// For NIST proofs use [`verify_secret_value_nist`](ZkpVerifier::verify_secret_value_nist).
602    ///
603    /// # Arguments
604    ///
605    /// * `proof` - The proof to verify
606    /// * `preimage` - The secret preimage (same bytes passed to `prove_secret_value`); the
607    ///   verifier hashes it with Poseidon-128 to reconstruct the public commitment
608    ///
609    /// # Returns
610    ///
611    /// `Ok(true)` if the proof is valid, `Ok(false)` or `Err` otherwise
612    pub fn verify_secret_value(&self, proof: &ZkpProof, preimage: &[u8]) -> Result<bool> {
613        use crate::air::{
614            HashPreimageAir,
615            TraceGenerator,
616        };
617        use crate::stark::{
618            StarkVerifier,
619            default_config,
620        };
621
622        if proof.proof_type != ProofType::Stark {
623            return Ok(false);
624        }
625
626        if proof.data.is_empty() {
627            return Ok(false);
628        }
629
630        // Proof must contain output size metadata
631        let ProofMetadata::HashPreimage { output_size } = &proof.metadata else {
632            // Missing metadata - proof is invalid
633            return Ok(false);
634        };
635
636        // Create the same AIR used for proving (output_size in metadata retained for compatibility)
637        let _ = output_size;
638        let air = HashPreimageAir::new();
639
640        // Reconstruct the public commitment exactly as the prover did, from the preimage.
641        // Using the AIR's own `public_values` guarantees the same padding/encoding rules.
642        let public_values: Vec<ZkpField> = air.public_values(&preimage.to_vec());
643
644        // Deserialize the STARK proof
645        let stark_proof = proof.to_stark_proof()?;
646
647        // Create verifier with default config
648        let config = default_config();
649        let verifier = StarkVerifier::new(config);
650
651        // Verify the proof
652        match verifier.verify(&air, &stark_proof, &public_values) {
653            Ok(()) => Ok(true),
654            Err(_) => Ok(false),
655        }
656    }
657
658    /// Verify a NIST (cSHAKE256) secret value proof
659    ///
660    /// Verifies a proof from [`prove_secret_value_nist`](ZkpProver::prove_secret_value_nist).
661    /// `expected_hash` is the raw 32-byte cSHAKE256 output (same as used in proving).
662    pub fn verify_secret_value_nist(&self, proof: &ZkpProof, expected_hash: &[u8]) -> Result<bool> {
663        verify_secret_value_nist_impl(proof, expected_hash)
664    }
665
666    /// Verify a zero-knowledge proof of computation
667    ///
668    /// This verifies a proof generated by `ZkpProver::prove_computation`.
669    ///
670    /// # Arguments
671    ///
672    /// * `proof` - The proof to verify
673    /// * `circuit` - The arithmetic circuit that was proven
674    /// * `public` - The public input values
675    ///
676    /// # Returns
677    ///
678    /// `Ok(true)` if the proof is valid, `Ok(false)` or `Err` otherwise
679    pub fn verify_computation(
680        &self,
681        proof: &ZkpProof,
682        circuit: &circuit::ArithmeticCircuit<ZkpField>,
683        public: &[ZkpField],
684    ) -> Result<bool> {
685        use crate::circuit::CircuitAir;
686        use crate::stark::{
687            StarkVerifier,
688            default_config,
689        };
690
691        if proof.proof_type != ProofType::Stark {
692            return Ok(false);
693        }
694
695        if proof.data.is_empty() {
696            return Ok(false);
697        }
698
699        // Create the same AIR used for proving
700        let air = CircuitAir::new(circuit.clone());
701
702        // Deserialize the STARK proof
703        let stark_proof = proof.to_stark_proof()?;
704
705        // Create verifier with default config
706        let config = default_config();
707        let verifier = StarkVerifier::new(config);
708
709        // Verify the proof
710        match verifier.verify(&air, &stark_proof, public) {
711            Ok(()) => Ok(true),
712            Err(_) => Ok(false),
713        }
714    }
715
716    /// Verify a zero-knowledge proof.
717    ///
718    /// Performs full cryptographic (STARK) verification for proof types whose public
719    /// inputs are fully described by a byte slice:
720    ///
721    /// - `ProofMetadata::HashPreimage`: `public_statement` is the expected hash output
722    ///   (same semantics as `verify_secret_value`).
723    /// - `ProofMetadata::HashPreimageNist`: `public_statement` is the expected cSHAKE256
724    ///   hash output (same semantics as `verify_secret_value_nist`).
725    /// - `ProofMetadata::MerkleInclusion`: `public_statement` is the expected Merkle
726    ///   root hash (same semantics as `api::verify_membership`).
727    ///
728    /// Returns `Ok(false)` for `Circuit`, `Credential`, `Identity`, and `None`
729    /// metadata variants. Those proof types require a type-specific verifier that accepts
730    /// the additional inputs needed to reconstruct verification state.
731    ///
732    /// `batch_verify` delegates to this method, so the same rules apply in bulk.
733    pub fn verify(&self, proof: ZkpProof, public_statement: &[u8]) -> Result<bool> {
734        if proof.proof_type != ProofType::Stark {
735            return Ok(false);
736        }
737        if proof.data.is_empty() {
738            return Ok(false);
739        }
740        match &proof.metadata {
741            ProofMetadata::HashPreimage { .. } => {
742                self.verify_secret_value(&proof, public_statement)
743            }
744            ProofMetadata::HashPreimageNist { .. } => {
745                verify_secret_value_nist_impl(&proof, public_statement)
746            }
747            ProofMetadata::MerkleInclusion { .. } => verify_membership(&proof, public_statement),
748            _ => Ok(false),
749        }
750    }
751
752    /// Batch verify multiple proofs
753    ///
754    /// # Arguments
755    ///
756    /// * `proofs` - The proofs to verify
757    /// * `publics` - The public statements for each proof
758    ///
759    /// # Returns
760    ///
761    /// `true` if all proofs are valid, `false` otherwise
762    pub fn batch_verify(&self, proofs: &[ZkpProof], publics: &[&[u8]]) -> Result<bool> {
763        if proofs.len() != publics.len() {
764            return Err(lib_q_core::Error::InvalidState {
765                operation: "batch_verify".to_string(),
766                reason: "Number of proofs must match number of public statements".to_string(),
767            });
768        }
769
770        for (proof, public) in proofs.iter().zip(publics.iter()) {
771            match self.verify(proof.clone(), public) {
772                Ok(true) => continue,
773                Ok(false) => return Ok(false),
774                Err(e) => return Err(e),
775            }
776        }
777
778        Ok(true)
779    }
780}
781
782#[cfg(not(feature = "zkp"))]
783impl ZkpVerifier {
784    /// Create a new ZKP verifier
785    pub fn new() -> Self {
786        Self {}
787    }
788
789    /// Verify a zero-knowledge proof
790    pub fn verify(&self, _proof: ZkpProof, _public_statement: &[u8]) -> Result<bool> {
791        Err(lib_q_core::Error::NotImplemented {
792            feature: "ZKP feature not enabled".to_string(),
793        })
794    }
795
796    /// Verify a NIST secret value proof
797    pub fn verify_secret_value_nist(
798        &self,
799        _proof: &ZkpProof,
800        _expected_hash: &[u8],
801    ) -> Result<bool> {
802        Err(lib_q_core::Error::NotImplemented {
803            feature: "ZKP feature not enabled".to_string(),
804        })
805    }
806
807    /// Batch verify multiple proofs
808    pub fn batch_verify(&self, _proofs: &[ZkpProof], _publics: &[&[u8]]) -> Result<bool> {
809        Err(lib_q_core::Error::NotImplemented {
810            feature: "ZKP feature not enabled".to_string(),
811        })
812    }
813}
814
815impl Default for ZkpProver {
816    fn default() -> Self {
817        Self::new()
818    }
819}
820
821impl Default for ZkpVerifier {
822    fn default() -> Self {
823        Self::new()
824    }
825}
826
827/// Get available ZKP algorithms (STARK when zkp feature is enabled).
828pub fn available_algorithms() -> Vec<&'static str> {
829    let algorithms = vec![
830        #[cfg(feature = "zkp")]
831        "stark",
832    ];
833
834    algorithms
835}
836
837/// Create a ZKP instance by algorithm name
838pub fn create_zkp(algorithm: &str) -> Result<Box<dyn core::any::Any>> {
839    match algorithm {
840        #[cfg(feature = "zkp")]
841        "stark" => Ok(Box::new(ZkpProver::new())),
842
843        _ => Err(lib_q_core::Error::InvalidAlgorithm {
844            algorithm: "Unknown ZKP algorithm",
845        }),
846    }
847}
848
849#[cfg(test)]
850mod tests {
851    use super::*;
852
853    #[test]
854    fn test_zkp_prover_creation() {
855        let _prover = ZkpProver::new();
856        // Just check that creation doesn't panic
857    }
858
859    #[test]
860    fn test_zkp_verifier_creation() {
861        let _verifier = ZkpVerifier::new();
862        // Just check that creation doesn't panic
863    }
864
865    #[test]
866    fn test_zkp_proof_creation() {
867        let mut prover = ZkpProver::new();
868        let secret_value = b"secret_value";
869        let public_statement = b"public_statement";
870
871        // Now that prove_secret_value is implemented, it should succeed
872        let result = prover.prove_secret_value(secret_value, public_statement);
873        // The proof generation should succeed (though it may take some time)
874        assert!(
875            result.is_ok(),
876            "Proof generation should succeed: {:?}",
877            result.err()
878        );
879    }
880
881    #[cfg(feature = "zkp")]
882    #[test]
883    fn test_nist_secret_value_not_implemented() {
884        // The NIST AIR has no Keccak-f constraints, so prove/verify must refuse rather
885        // than emit/accept an unsound proof.
886        let secret = b"nist_secret_value";
887        let mut prover = ZkpProver::new();
888        assert!(
889            matches!(
890                prover.prove_secret_value_nist(secret, b""),
891                Err(lib_q_core::Error::NotImplemented { .. })
892            ),
893            "NIST prove must return NotImplemented"
894        );
895
896        let verifier = ZkpVerifier::new();
897        let mut dummy = ZkpProof {
898            data: alloc::vec![1u8; 8],
899            proof_type: ProofType::Stark,
900            security_level: 1,
901            metadata: ProofMetadata::HashPreimageNist { output_size: 32 },
902        };
903        assert!(
904            matches!(
905                verifier.verify_secret_value_nist(&dummy, &[0u8; 32]),
906                Err(lib_q_core::Error::NotImplemented { .. })
907            ),
908            "NIST verify must return NotImplemented"
909        );
910        // Generic verify() dispatches to the NIST impl, which must also refuse.
911        dummy.data = alloc::vec![1u8; 8];
912        assert!(
913            matches!(
914                verifier.verify(dummy, &[0u8; 32]),
915                Err(lib_q_core::Error::NotImplemented { .. })
916            ),
917            "verify() must return NotImplemented for NIST proofs"
918        );
919    }
920
921    #[cfg(feature = "zkp")]
922    #[test]
923    fn test_poseidon_proof_rejected_by_nist_verifier() {
924        let secret = b"poseidon_only";
925        let mut prover = ZkpProver::new();
926        let proof = prover
927            .prove_secret_value(secret, b"")
928            .expect("Poseidon prove");
929        let verifier = ZkpVerifier::new();
930        assert!(
931            !verifier
932                .verify_secret_value_nist(&proof, &[0u8; 32])
933                .unwrap(),
934            "Poseidon proof must not be accepted by NIST verifier"
935        );
936    }
937
938    #[cfg(feature = "zkp")]
939    #[test]
940    fn test_verify_rejects_unknown_metadata() {
941        use lib_q_stark_field::PrimeCharacteristicRing;
942        use lib_q_stark_mersenne31::Mersenne31;
943
944        use crate::air::{
945            ArithmeticAir,
946            TraceGenerator,
947        };
948        use crate::stark::{
949            StarkProver,
950            default_config,
951        };
952
953        let air = ArithmeticAir::new(1).expect("ArithmeticAir");
954        let one = <ZkpField as PrimeCharacteristicRing>::ONE;
955        let seven = ZkpField::from(Mersenne31::new(7));
956        let input = alloc::vec![(one, seven)];
957        let trace = air.generate_trace(&input).expect("trace generation");
958        let public_values = air.public_values(&input);
959        let proof_inner = StarkProver::new(default_config())
960            .prove(&air, trace, &public_values)
961            .expect("prove");
962        let proof_bytes = postcard::to_allocvec(&proof_inner).expect("serialize STARK proof");
963
964        let proof = ZkpProof {
965            data: proof_bytes,
966            proof_type: ProofType::Stark,
967            security_level: 1,
968            metadata: ProofMetadata::None,
969        };
970
971        let verifier = ZkpVerifier::new();
972        assert_eq!(
973            verifier.verify(proof, b"public_statement").unwrap(),
974            false,
975            "ProofMetadata::None must return false -- use a type-specific verifier"
976        );
977    }
978
979    #[test]
980    fn test_batch_verify_mismatched_lengths() {
981        let verifier = ZkpVerifier::new();
982        let proofs = vec![ZkpProof {
983            data: vec![0u8; 64],
984            proof_type: ProofType::Stark,
985            security_level: 1,
986            metadata: ProofMetadata::None,
987        }];
988        let publics: &[&[u8]] = &[b"public1" as &[u8], b"public2" as &[u8]];
989
990        let result = verifier.batch_verify(&proofs, publics);
991        assert!(result.is_err());
992        if let Err(lib_q_core::Error::InvalidState { .. }) = result {
993            // Expected
994        } else {
995            panic!("Expected InvalidState error");
996        }
997    }
998
999    #[cfg(feature = "zkp")]
1000    #[test]
1001    fn test_proof_metadata_merkle() {
1002        let metadata = ProofMetadata::MerkleInclusion { tree_depth: 8 };
1003        let proof = ZkpProof {
1004            data: vec![0u8; 64],
1005            proof_type: ProofType::Stark,
1006            security_level: 1,
1007            metadata,
1008        };
1009        assert_eq!(proof.merkle_tree_depth(), Some(8));
1010    }
1011
1012    #[cfg(feature = "zkp")]
1013    #[test]
1014    fn test_proof_metadata_none() {
1015        let proof = ZkpProof {
1016            data: vec![0u8; 64],
1017            proof_type: ProofType::Stark,
1018            security_level: 1,
1019            metadata: ProofMetadata::None,
1020        };
1021        assert_eq!(proof.merkle_tree_depth(), None);
1022    }
1023
1024    #[test]
1025    fn test_available_algorithms() {
1026        let algorithms = available_algorithms();
1027        #[cfg(feature = "zkp")]
1028        assert!(!algorithms.is_empty(), "zkp feature enables STARK");
1029        #[cfg(not(feature = "zkp"))]
1030        let _ = algorithms;
1031    }
1032
1033    #[cfg(feature = "zkp")]
1034    #[test]
1035    fn test_create_zkp() {
1036        let algorithms = available_algorithms();
1037        assert!(!algorithms.is_empty());
1038        let algorithm = algorithms[0];
1039        assert!(create_zkp(algorithm).is_ok());
1040    }
1041
1042    #[cfg(feature = "zkp")]
1043    #[test]
1044    fn test_verify_rejects_forged_proof_with_hash_preimage_metadata() {
1045        let proof = ZkpProof {
1046            data: alloc::vec![
1047                0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD,
1048                0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF,
1049                0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA,
1050                0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD,
1051                0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF,
1052            ],
1053            proof_type: ProofType::Stark,
1054            security_level: 1,
1055            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1056        };
1057        let verifier = ZkpVerifier::new();
1058        let result = verifier.verify(proof, b"expected_hash");
1059        assert!(
1060            matches!(result, Ok(false) | Err(_)),
1061            "forged HashPreimage proof must not return Ok(true)"
1062        );
1063    }
1064
1065    #[cfg(feature = "zkp")]
1066    #[test]
1067    fn test_verify_rejects_forged_proof_with_merkle_metadata() {
1068        let proof = ZkpProof {
1069            data: alloc::vec![
1070                0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE,
1071                0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE,
1072                0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE,
1073                0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE,
1074                0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE,
1075            ],
1076            proof_type: ProofType::Stark,
1077            security_level: 1,
1078            metadata: ProofMetadata::MerkleInclusion { tree_depth: 4 },
1079        };
1080        let verifier = ZkpVerifier::new();
1081        let result = verifier.verify(proof, b"wrong_root");
1082        assert!(
1083            matches!(result, Ok(false) | Err(_)),
1084            "forged MerkleInclusion proof must not return Ok(true)"
1085        );
1086    }
1087
1088    #[cfg(feature = "zkp")]
1089    #[test]
1090    fn test_verify_rejects_circuit_metadata_proof() {
1091        let proof = ZkpProof {
1092            data: alloc::vec![0u8; 64],
1093            proof_type: ProofType::Stark,
1094            security_level: 1,
1095            metadata: ProofMetadata::Circuit {
1096                num_witnesses: 2,
1097                num_public: 1,
1098            },
1099        };
1100        let verifier = ZkpVerifier::new();
1101        assert_eq!(
1102            verifier.verify(proof, b"anything").unwrap(),
1103            false,
1104            "Circuit proofs must be rejected by generic verify; use verify_computation"
1105        );
1106    }
1107
1108    #[cfg(feature = "zkp")]
1109    #[test]
1110    fn test_verify_rejects_credential_metadata_proof() {
1111        let proof = ZkpProof {
1112            data: alloc::vec![0u8; 64],
1113            proof_type: ProofType::Stark,
1114            security_level: 1,
1115            metadata: ProofMetadata::Credential {
1116                attribute_sizes: alloc::vec![8, 4],
1117                reveal_mask: alloc::vec![true, false],
1118            },
1119        };
1120        let verifier = ZkpVerifier::new();
1121        assert_eq!(
1122            verifier.verify(proof, b"anything").unwrap(),
1123            false,
1124            "Credential proofs must be rejected by generic verify; use ip::verify_credential_proof"
1125        );
1126    }
1127
1128    #[cfg(feature = "zkp")]
1129    #[test]
1130    fn test_verify_rejects_identity_metadata_proof() {
1131        let proof = ZkpProof {
1132            data: alloc::vec![0u8; 64],
1133            proof_type: ProofType::Stark,
1134            security_level: 1,
1135            metadata: ProofMetadata::Identity { dsa_level: 65 },
1136        };
1137        let verifier = ZkpVerifier::new();
1138        assert_eq!(
1139            verifier.verify(proof, b"anything").unwrap(),
1140            false,
1141            "Identity proofs must be rejected by generic verify; use ip::verify_it_ownership"
1142        );
1143    }
1144
1145    #[cfg(feature = "zkp")]
1146    #[test]
1147    fn test_verify_empty_data_is_rejected() {
1148        let proof = ZkpProof {
1149            data: alloc::vec![],
1150            proof_type: ProofType::Stark,
1151            security_level: 1,
1152            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1153        };
1154        let verifier = ZkpVerifier::new();
1155        assert_eq!(
1156            verifier.verify(proof, b"anything").unwrap(),
1157            false,
1158            "empty proof data must be rejected regardless of metadata"
1159        );
1160    }
1161
1162    #[cfg(feature = "zkp")]
1163    #[test]
1164    fn test_proof_type_only_stark_exists() {
1165        let _stark = ProofType::Stark;
1166        // ProofType::Snark and ProofType::Bulletproof have been removed.
1167        // Only NIST-approved post-quantum proof systems are supported.
1168    }
1169
1170    #[cfg(feature = "zkp")]
1171    #[test]
1172    fn test_batch_verify_rejects_forged_hash_preimage_proof() {
1173        let forged = ZkpProof {
1174            data: alloc::vec![0xFF; 64],
1175            proof_type: ProofType::Stark,
1176            security_level: 1,
1177            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1178        };
1179        let verifier = ZkpVerifier::new();
1180        let proofs = alloc::vec![forged];
1181        let publics: &[&[u8]] = &[b"anything"];
1182        let result = verifier.batch_verify(&proofs, publics);
1183        assert!(
1184            matches!(result, Ok(false) | Err(_)),
1185            "batch_verify must not accept a forged proof"
1186        );
1187    }
1188
1189    #[cfg(feature = "zkp")]
1190    #[test]
1191    fn test_prove_secret_value_rejects_empty_and_oversized_input() {
1192        let mut prover = ZkpProver::new();
1193        let empty = prover.prove_secret_value(b"", b"");
1194        assert!(empty.is_err());
1195
1196        let oversized = vec![0u8; air::hash_preimage::MAX_PREIMAGE_SIZE + 1];
1197        let too_large = prover.prove_secret_value(&oversized, b"");
1198        assert!(too_large.is_err());
1199    }
1200
1201    #[cfg(feature = "zkp")]
1202    #[test]
1203    fn test_prove_secret_value_nist_rejects_empty_and_oversized_input() {
1204        let mut prover = ZkpProver::new();
1205        let empty = prover.prove_secret_value_nist(b"", b"");
1206        assert!(empty.is_err());
1207
1208        let oversized = vec![0u8; air::hash_preimage_nist::MAX_PREIMAGE_SIZE + 1];
1209        let too_large = prover.prove_secret_value_nist(&oversized, b"");
1210        assert!(too_large.is_err());
1211    }
1212
1213    #[cfg(feature = "zkp")]
1214    #[test]
1215    fn test_verify_secret_value_rejects_invalid_proof_shape_inputs() {
1216        let verifier = ZkpVerifier::new();
1217        let non_stark = ZkpProof {
1218            data: vec![1u8; 16],
1219            proof_type: ProofType::Stark,
1220            security_level: 1,
1221            metadata: ProofMetadata::HashPreimageNist { output_size: 32 },
1222        };
1223        assert!(!verifier.verify_secret_value(&non_stark, b"hash").unwrap());
1224
1225        let empty = ZkpProof {
1226            data: vec![],
1227            proof_type: ProofType::Stark,
1228            security_level: 1,
1229            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1230        };
1231        assert!(!verifier.verify_secret_value(&empty, b"hash").unwrap());
1232    }
1233
1234    #[cfg(feature = "zkp")]
1235    #[test]
1236    fn test_verify_secret_value_nist_rejects_wrong_metadata_and_bad_bytes() {
1237        let verifier = ZkpVerifier::new();
1238        let wrong_meta = ZkpProof {
1239            data: vec![1u8; 16],
1240            proof_type: ProofType::Stark,
1241            security_level: 1,
1242            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1243        };
1244        assert!(
1245            !verifier
1246                .verify_secret_value_nist(&wrong_meta, &[0u8; 32])
1247                .unwrap()
1248        );
1249
1250        // A proof carrying NIST metadata reaches the soundness gate, which refuses to
1251        // verify (NIST constraints not implemented) rather than returning a boolean.
1252        let nist_meta = ZkpProof {
1253            data: vec![0xAA; 16],
1254            proof_type: ProofType::Stark,
1255            security_level: 1,
1256            metadata: ProofMetadata::HashPreimageNist { output_size: 32 },
1257        };
1258        assert!(matches!(
1259            verifier.verify_secret_value_nist(&nist_meta, &[0u8; 32]),
1260            Err(lib_q_core::Error::NotImplemented { .. })
1261        ));
1262    }
1263
1264    #[cfg(feature = "zkp")]
1265    #[test]
1266    fn test_verify_computation_rejects_empty_or_non_stark_data() {
1267        use crate::circuit::CircuitBuilder;
1268
1269        let verifier = ZkpVerifier::new();
1270        let circuit = CircuitBuilder::<ZkpField>::new(1, 0).build();
1271
1272        let empty = ZkpProof {
1273            data: vec![],
1274            proof_type: ProofType::Stark,
1275            security_level: 1,
1276            metadata: ProofMetadata::Circuit {
1277                num_witnesses: 1,
1278                num_public: 0,
1279            },
1280        };
1281        assert!(!verifier.verify_computation(&empty, &circuit, &[]).unwrap());
1282    }
1283}