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