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    pub fn prove_secret_value_nist(
427        &mut self,
428        secret_value: &[u8],
429        _public_statement: &[u8],
430    ) -> Result<ZkpProof> {
431        use crate::air::{
432            HASH_OUTPUT_BYTES,
433            HashPreimageNistAir,
434            TraceGenerator,
435        };
436        use crate::stark::{
437            StarkProver,
438            default_config,
439        };
440
441        if secret_value.is_empty() {
442            return Err(lib_q_core::Error::InvalidState {
443                operation: "prove_secret_value_nist".to_string(),
444                reason: "Secret value cannot be empty".to_string(),
445            });
446        }
447        if secret_value.len() > air::hash_preimage_nist::MAX_PREIMAGE_SIZE {
448            return Err(lib_q_core::Error::InvalidState {
449                operation: "prove_secret_value_nist".to_string(),
450                reason: "Secret value exceeds maximum size".to_string(),
451            });
452        }
453
454        let air = HashPreimageNistAir::new();
455        let input = secret_value.to_vec();
456        let trace: RowMajorMatrix<ZkpField> =
457            air.generate_trace(&input)
458                .map_err(|e| lib_q_core::Error::InternalError {
459                    operation: "prove_secret_value_nist".to_string(),
460                    details: e.to_string(),
461                })?;
462
463        let public_values: Vec<ZkpField> = air.public_values(&input);
464        let config = default_config();
465        let prover = StarkProver::new(config);
466
467        let proof = prover.prove(&air, trace, &public_values).map_err(|e| {
468            lib_q_core::Error::InternalError {
469                operation: "STARK proof generation (NIST)".to_string(),
470                details: e.to_string(),
471            }
472        })?;
473
474        let metadata = ProofMetadata::HashPreimageNist {
475            output_size: HASH_OUTPUT_BYTES as u16,
476        };
477        ZkpProof::from_stark_proof(&proof, metadata)
478    }
479
480    /// Prove a computation using a circuit
481    ///
482    /// This generates a STARK proof that the prover knows witness values that
483    /// satisfy all constraints in the arithmetic circuit.
484    ///
485    /// # Arguments
486    ///
487    /// * `circuit` - The arithmetic circuit defining the computation
488    /// * `witness` - The witness values (private inputs)
489    /// * `public` - The public input values
490    ///
491    /// # Returns
492    ///
493    /// A zero-knowledge proof of computation correctness
494    ///
495    /// # Example
496    ///
497    /// ```rust,ignore
498    /// use lib_q_zkp::{ZkpProver, circuit::CircuitBuilder};
499    /// use lib_q_stark_field::extension::Complex;
500    /// use lib_q_stark_mersenne31::Mersenne31;
501    ///
502    /// type Val = Complex<Mersenne31>;
503    ///
504    /// // Build a circuit: prove knowledge of a, b such that a * b = public_output
505    /// let mut builder = CircuitBuilder::<Val>::new(2, 1);
506    /// let a = builder.wire(0);
507    /// let b = builder.wire(1);
508    /// let output = builder.wire(2);
509    /// let product = builder.mul(a, b);
510    /// builder.assert_eq(product, output);
511    /// let circuit = builder.build();
512    ///
513    /// // Generate proof
514    /// let witness = vec![Val::from(3u32), Val::from(4u32)];
515    /// let public = vec![Val::from(12u32)];
516    ///
517    /// let mut prover = ZkpProver::new();
518    /// let proof = prover.prove_computation(&circuit, &witness, &public)?;
519    /// ```
520    pub fn prove_computation(
521        &mut self,
522        circuit: &circuit::ArithmeticCircuit<ZkpField>,
523        witness: &[ZkpField],
524        public: &[ZkpField],
525    ) -> Result<ZkpProof> {
526        use crate::circuit::CircuitAir;
527        use crate::stark::{
528            StarkProver,
529            default_config,
530        };
531
532        // Create the circuit AIR
533        let air = CircuitAir::new(circuit.clone());
534
535        // Generate trace from witness and public values
536        let trace = air.generate_trace(witness, public)?;
537
538        // Create prover with default config
539        let config = default_config();
540        let prover = StarkProver::new(config);
541
542        // Generate STARK proof
543        let proof =
544            prover
545                .prove(&air, trace, public)
546                .map_err(|e| lib_q_core::Error::InternalError {
547                    operation: "STARK proof generation".to_string(),
548                    details: e.to_string(),
549                })?;
550
551        // Store circuit parameters in proof metadata
552        let metadata = ProofMetadata::Circuit {
553            num_witnesses: witness.len().min(u32::MAX as usize) as u32,
554            num_public: public.len().min(u32::MAX as usize) as u32,
555        };
556
557        // Serialize into ZkpProof
558        ZkpProof::from_stark_proof(&proof, metadata)
559    }
560}
561
562#[cfg(not(feature = "zkp"))]
563impl ZkpProver {
564    /// Create a new ZKP prover
565    pub fn new() -> Self {
566        Self {}
567    }
568
569    /// Prove knowledge of a secret value without revealing it
570    pub fn prove_secret_value(
571        &mut self,
572        _secret_value: &[u8],
573        _public_statement: &[u8],
574    ) -> Result<ZkpProof> {
575        Err(lib_q_core::Error::NotImplemented {
576            feature: "ZKP feature not enabled".to_string(),
577        })
578    }
579
580    /// Prove knowledge of a secret value (NIST variant)
581    pub fn prove_secret_value_nist(
582        &mut self,
583        _secret_value: &[u8],
584        _public_statement: &[u8],
585    ) -> Result<ZkpProof> {
586        Err(lib_q_core::Error::NotImplemented {
587            feature: "ZKP feature not enabled".to_string(),
588        })
589    }
590}
591
592/// Crate-private helper for NIST secret value verification. Used by both
593/// `ZkpVerifier::verify` and `ZkpVerifier::verify_secret_value_nist`.
594#[cfg(feature = "zkp")]
595fn verify_secret_value_nist_impl(proof: &ZkpProof, expected_hash: &[u8]) -> Result<bool> {
596    use crate::air::{
597        HashPreimageNistAir,
598        expected_hash_to_public_values,
599    };
600    use crate::stark::{
601        StarkVerifier,
602        default_config,
603    };
604
605    if proof.proof_type != ProofType::Stark {
606        return Ok(false);
607    }
608    if proof.data.is_empty() {
609        return Ok(false);
610    }
611
612    let ProofMetadata::HashPreimageNist { .. } = &proof.metadata else {
613        return Ok(false);
614    };
615
616    let air = HashPreimageNistAir::new();
617    let public_values = expected_hash_to_public_values::<ZkpField>(expected_hash);
618
619    let stark_proof = match proof.to_stark_proof() {
620        Ok(p) => p,
621        Err(_) => return Ok(false),
622    };
623
624    let config = default_config();
625    let verifier = StarkVerifier::new(config);
626
627    match verifier.verify(&air, &stark_proof, &public_values) {
628        Ok(()) => Ok(true),
629        Err(_) => Ok(false),
630    }
631}
632
633#[cfg(feature = "zkp")]
634impl ZkpVerifier {
635    /// Create a new ZKP verifier
636    pub fn new() -> Self {
637        Self {}
638    }
639
640    /// Verify a zero-knowledge proof of secret value knowledge
641    ///
642    /// This verifies a proof generated by `ZkpProver::prove_secret_value`. The
643    /// expected hash is the **Poseidon** commitment (same encoding as used in proving).
644    /// For NIST proofs use [`verify_secret_value_nist`](ZkpVerifier::verify_secret_value_nist).
645    ///
646    /// # Arguments
647    ///
648    /// * `proof` - The proof to verify
649    /// * `public_hash` - The expected Poseidon hash output (bytes encoding used by the prover)
650    ///
651    /// # Returns
652    ///
653    /// `Ok(true)` if the proof is valid, `Ok(false)` or `Err` otherwise
654    pub fn verify_secret_value(&self, proof: &ZkpProof, public_hash: &[u8]) -> Result<bool> {
655        use lib_q_poseidon::{
656            Poseidon,
657            Poseidon128,
658        };
659
660        use crate::air::{
661            HashPreimageAir,
662            bytes_to_poseidon_field,
663            poseidon_slice_to_field,
664        };
665        use crate::stark::{
666            StarkVerifier,
667            default_config,
668        };
669
670        if proof.proof_type != ProofType::Stark {
671            return Ok(false);
672        }
673
674        if proof.data.is_empty() {
675            return Ok(false);
676        }
677
678        // Proof must contain output size metadata
679        let ProofMetadata::HashPreimage { output_size } = &proof.metadata else {
680            // Missing metadata - proof is invalid
681            return Ok(false);
682        };
683
684        // Create the same AIR used for proving (output_size in metadata retained for compatibility)
685        let _ = output_size;
686        let air = HashPreimageAir::new();
687
688        // Convert expected hash bytes to Poseidon field elements, then hash to get public values
689        // The public values are the Poseidon hash output (field elements), matching what
690        // HashPreimageAir::public_values() returns during proving
691        let expected_field_elements = bytes_to_poseidon_field(public_hash);
692        let poseidon_hash = Poseidon128.hash(&expected_field_elements);
693
694        // Convert Poseidon hash output to public values (field elements)
695        let public_values = poseidon_slice_to_field(&poseidon_hash);
696
697        // Deserialize the STARK proof
698        let stark_proof = proof.to_stark_proof()?;
699
700        // Create verifier with default config
701        let config = default_config();
702        let verifier = StarkVerifier::new(config);
703
704        // Verify the proof
705        match verifier.verify(&air, &stark_proof, &public_values) {
706            Ok(()) => Ok(true),
707            Err(_) => Ok(false),
708        }
709    }
710
711    /// Verify a NIST (cSHAKE256) secret value proof
712    ///
713    /// Verifies a proof from [`prove_secret_value_nist`](ZkpProver::prove_secret_value_nist).
714    /// `expected_hash` is the raw 32-byte cSHAKE256 output (same as used in proving).
715    pub fn verify_secret_value_nist(&self, proof: &ZkpProof, expected_hash: &[u8]) -> Result<bool> {
716        verify_secret_value_nist_impl(proof, expected_hash)
717    }
718
719    /// Verify a zero-knowledge proof of computation
720    ///
721    /// This verifies a proof generated by `ZkpProver::prove_computation`.
722    ///
723    /// # Arguments
724    ///
725    /// * `proof` - The proof to verify
726    /// * `circuit` - The arithmetic circuit that was proven
727    /// * `public` - The public input values
728    ///
729    /// # Returns
730    ///
731    /// `Ok(true)` if the proof is valid, `Ok(false)` or `Err` otherwise
732    pub fn verify_computation(
733        &self,
734        proof: &ZkpProof,
735        circuit: &circuit::ArithmeticCircuit<ZkpField>,
736        public: &[ZkpField],
737    ) -> Result<bool> {
738        use crate::circuit::CircuitAir;
739        use crate::stark::{
740            StarkVerifier,
741            default_config,
742        };
743
744        if proof.proof_type != ProofType::Stark {
745            return Ok(false);
746        }
747
748        if proof.data.is_empty() {
749            return Ok(false);
750        }
751
752        // Create the same AIR used for proving
753        let air = CircuitAir::new(circuit.clone());
754
755        // Deserialize the STARK proof
756        let stark_proof = proof.to_stark_proof()?;
757
758        // Create verifier with default config
759        let config = default_config();
760        let verifier = StarkVerifier::new(config);
761
762        // Verify the proof
763        match verifier.verify(&air, &stark_proof, public) {
764            Ok(()) => Ok(true),
765            Err(_) => Ok(false),
766        }
767    }
768
769    /// Verify a zero-knowledge proof.
770    ///
771    /// Performs full cryptographic (STARK) verification for proof types whose public
772    /// inputs are fully described by a byte slice:
773    ///
774    /// - `ProofMetadata::HashPreimage`: `public_statement` is the expected hash output
775    ///   (same semantics as `verify_secret_value`).
776    /// - `ProofMetadata::HashPreimageNist`: `public_statement` is the expected cSHAKE256
777    ///   hash output (same semantics as `verify_secret_value_nist`).
778    /// - `ProofMetadata::MerkleInclusion`: `public_statement` is the expected Merkle
779    ///   root hash (same semantics as `api::verify_membership`).
780    ///
781    /// Returns `Ok(false)` for `Circuit`, `Credential`, `Identity`, and `None`
782    /// metadata variants. Those proof types require a type-specific verifier that accepts
783    /// the additional inputs needed to reconstruct verification state.
784    ///
785    /// `batch_verify` delegates to this method, so the same rules apply in bulk.
786    pub fn verify(&self, proof: ZkpProof, public_statement: &[u8]) -> Result<bool> {
787        if proof.proof_type != ProofType::Stark {
788            return Ok(false);
789        }
790        if proof.data.is_empty() {
791            return Ok(false);
792        }
793        match &proof.metadata {
794            ProofMetadata::HashPreimage { .. } => {
795                self.verify_secret_value(&proof, public_statement)
796            }
797            ProofMetadata::HashPreimageNist { .. } => {
798                verify_secret_value_nist_impl(&proof, public_statement)
799            }
800            ProofMetadata::MerkleInclusion { .. } => verify_membership(&proof, public_statement),
801            _ => Ok(false),
802        }
803    }
804
805    /// Batch verify multiple proofs
806    ///
807    /// # Arguments
808    ///
809    /// * `proofs` - The proofs to verify
810    /// * `publics` - The public statements for each proof
811    ///
812    /// # Returns
813    ///
814    /// `true` if all proofs are valid, `false` otherwise
815    pub fn batch_verify(&self, proofs: &[ZkpProof], publics: &[&[u8]]) -> Result<bool> {
816        if proofs.len() != publics.len() {
817            return Err(lib_q_core::Error::InvalidState {
818                operation: "batch_verify".to_string(),
819                reason: "Number of proofs must match number of public statements".to_string(),
820            });
821        }
822
823        for (proof, public) in proofs.iter().zip(publics.iter()) {
824            match self.verify(proof.clone(), public) {
825                Ok(true) => continue,
826                Ok(false) => return Ok(false),
827                Err(e) => return Err(e),
828            }
829        }
830
831        Ok(true)
832    }
833}
834
835#[cfg(not(feature = "zkp"))]
836impl ZkpVerifier {
837    /// Create a new ZKP verifier
838    pub fn new() -> Self {
839        Self {}
840    }
841
842    /// Verify a zero-knowledge proof
843    pub fn verify(&self, _proof: ZkpProof, _public_statement: &[u8]) -> Result<bool> {
844        Err(lib_q_core::Error::NotImplemented {
845            feature: "ZKP feature not enabled".to_string(),
846        })
847    }
848
849    /// Verify a NIST secret value proof
850    pub fn verify_secret_value_nist(
851        &self,
852        _proof: &ZkpProof,
853        _expected_hash: &[u8],
854    ) -> Result<bool> {
855        Err(lib_q_core::Error::NotImplemented {
856            feature: "ZKP feature not enabled".to_string(),
857        })
858    }
859
860    /// Batch verify multiple proofs
861    pub fn batch_verify(&self, _proofs: &[ZkpProof], _publics: &[&[u8]]) -> Result<bool> {
862        Err(lib_q_core::Error::NotImplemented {
863            feature: "ZKP feature not enabled".to_string(),
864        })
865    }
866}
867
868impl Default for ZkpProver {
869    fn default() -> Self {
870        Self::new()
871    }
872}
873
874impl Default for ZkpVerifier {
875    fn default() -> Self {
876        Self::new()
877    }
878}
879
880/// Get available ZKP algorithms (STARK when zkp feature is enabled).
881pub fn available_algorithms() -> Vec<&'static str> {
882    let algorithms = vec![
883        #[cfg(feature = "zkp")]
884        "stark",
885    ];
886
887    algorithms
888}
889
890/// Create a ZKP instance by algorithm name
891pub fn create_zkp(algorithm: &str) -> Result<Box<dyn core::any::Any>> {
892    match algorithm {
893        #[cfg(feature = "zkp")]
894        "stark" => Ok(Box::new(ZkpProver::new())),
895
896        _ => Err(lib_q_core::Error::InvalidAlgorithm {
897            algorithm: "Unknown ZKP algorithm",
898        }),
899    }
900}
901
902#[cfg(test)]
903mod tests {
904    use super::*;
905
906    #[test]
907    fn test_zkp_prover_creation() {
908        let _prover = ZkpProver::new();
909        // Just check that creation doesn't panic
910    }
911
912    #[test]
913    fn test_zkp_verifier_creation() {
914        let _verifier = ZkpVerifier::new();
915        // Just check that creation doesn't panic
916    }
917
918    #[test]
919    fn test_zkp_proof_creation() {
920        let mut prover = ZkpProver::new();
921        let secret_value = b"secret_value";
922        let public_statement = b"public_statement";
923
924        // Now that prove_secret_value is implemented, it should succeed
925        let result = prover.prove_secret_value(secret_value, public_statement);
926        // The proof generation should succeed (though it may take some time)
927        assert!(
928            result.is_ok(),
929            "Proof generation should succeed: {:?}",
930            result.err()
931        );
932    }
933
934    #[cfg(feature = "zkp")]
935    #[test]
936    fn test_nist_secret_value_round_trip() {
937        use digest::{
938            ExtendableOutput,
939            Update,
940        };
941        use lib_q_sha3::CShake256;
942
943        use crate::air::hash_preimage_nist::CSHAKE_DOMAIN;
944
945        let secret = b"nist_secret_value";
946        let mut prover = ZkpProver::new();
947        let proof = prover
948            .prove_secret_value_nist(secret, b"")
949            .expect("prove_secret_value_nist");
950        let mut expected_hash = [0u8; 32];
951        {
952            let mut hasher = CShake256::new_with_function_name(&[], CSHAKE_DOMAIN);
953            hasher.update(secret);
954            hasher.finalize_xof_into(&mut expected_hash);
955        }
956        let verifier = ZkpVerifier::new();
957        assert!(
958            verifier
959                .verify_secret_value_nist(&proof, &expected_hash)
960                .unwrap(),
961            "NIST proof should verify with correct expected hash"
962        );
963        // Generic verify() must dispatch to verify_secret_value_nist for HashPreimageNist
964        assert!(
965            verifier.verify(proof.clone(), &expected_hash).unwrap(),
966            "verify() must accept NIST proof with correct expected hash"
967        );
968    }
969
970    #[cfg(feature = "zkp")]
971    #[test]
972    fn test_nist_proof_rejected_by_poseidon_verifier() {
973        use digest::{
974            ExtendableOutput,
975            Update,
976        };
977        use lib_q_sha3::CShake256;
978
979        use crate::air::hash_preimage_nist::CSHAKE_DOMAIN;
980
981        let secret = b"nist_only";
982        let mut prover = ZkpProver::new();
983        let proof = prover
984            .prove_secret_value_nist(secret, b"")
985            .expect("NIST prove");
986        let mut expected_hash = [0u8; 32];
987        let mut hasher = CShake256::new_with_function_name(&[], CSHAKE_DOMAIN);
988        hasher.update(secret);
989        hasher.finalize_xof_into(&mut expected_hash);
990        let verifier = ZkpVerifier::new();
991        assert!(
992            !verifier
993                .verify_secret_value(&proof, &expected_hash)
994                .unwrap(),
995            "NIST proof must not be accepted by Poseidon verifier"
996        );
997    }
998
999    #[cfg(feature = "zkp")]
1000    #[test]
1001    fn test_poseidon_proof_rejected_by_nist_verifier() {
1002        let secret = b"poseidon_only";
1003        let mut prover = ZkpProver::new();
1004        let proof = prover
1005            .prove_secret_value(secret, b"")
1006            .expect("Poseidon prove");
1007        let verifier = ZkpVerifier::new();
1008        assert!(
1009            !verifier
1010                .verify_secret_value_nist(&proof, &[0u8; 32])
1011                .unwrap(),
1012            "Poseidon proof must not be accepted by NIST verifier"
1013        );
1014    }
1015
1016    #[cfg(feature = "zkp")]
1017    #[test]
1018    fn test_verify_rejects_unknown_metadata() {
1019        use lib_q_stark_field::PrimeCharacteristicRing;
1020        use lib_q_stark_mersenne31::Mersenne31;
1021
1022        use crate::air::{
1023            ArithmeticAir,
1024            TraceGenerator,
1025        };
1026        use crate::stark::{
1027            StarkProver,
1028            default_config,
1029        };
1030
1031        let air = ArithmeticAir::new(1).expect("ArithmeticAir");
1032        let one = <ZkpField as PrimeCharacteristicRing>::ONE;
1033        let seven = ZkpField::from(Mersenne31::new(7));
1034        let input = alloc::vec![(one, seven)];
1035        let trace = air.generate_trace(&input).expect("trace generation");
1036        let public_values = air.public_values(&input);
1037        let proof_inner = StarkProver::new(default_config())
1038            .prove(&air, trace, &public_values)
1039            .expect("prove");
1040        let proof_bytes = postcard::to_allocvec(&proof_inner).expect("serialize STARK proof");
1041
1042        let proof = ZkpProof {
1043            data: proof_bytes,
1044            proof_type: ProofType::Stark,
1045            security_level: 1,
1046            metadata: ProofMetadata::None,
1047        };
1048
1049        let verifier = ZkpVerifier::new();
1050        assert_eq!(
1051            verifier.verify(proof, b"public_statement").unwrap(),
1052            false,
1053            "ProofMetadata::None must return false -- use a type-specific verifier"
1054        );
1055    }
1056
1057    #[test]
1058    fn test_batch_verify_mismatched_lengths() {
1059        let verifier = ZkpVerifier::new();
1060        let proofs = vec![ZkpProof {
1061            data: vec![0u8; 64],
1062            proof_type: ProofType::Stark,
1063            security_level: 1,
1064            metadata: ProofMetadata::None,
1065        }];
1066        let publics: &[&[u8]] = &[b"public1" as &[u8], b"public2" as &[u8]];
1067
1068        let result = verifier.batch_verify(&proofs, publics);
1069        assert!(result.is_err());
1070        if let Err(lib_q_core::Error::InvalidState { .. }) = result {
1071            // Expected
1072        } else {
1073            panic!("Expected InvalidState error");
1074        }
1075    }
1076
1077    #[cfg(feature = "zkp")]
1078    #[test]
1079    fn test_proof_metadata_merkle() {
1080        let metadata = ProofMetadata::MerkleInclusion { tree_depth: 8 };
1081        let proof = ZkpProof {
1082            data: vec![0u8; 64],
1083            proof_type: ProofType::Stark,
1084            security_level: 1,
1085            metadata,
1086        };
1087        assert_eq!(proof.merkle_tree_depth(), Some(8));
1088    }
1089
1090    #[cfg(feature = "zkp")]
1091    #[test]
1092    fn test_proof_metadata_none() {
1093        let proof = ZkpProof {
1094            data: vec![0u8; 64],
1095            proof_type: ProofType::Stark,
1096            security_level: 1,
1097            metadata: ProofMetadata::None,
1098        };
1099        assert_eq!(proof.merkle_tree_depth(), None);
1100    }
1101
1102    #[test]
1103    fn test_available_algorithms() {
1104        let algorithms = available_algorithms();
1105        #[cfg(feature = "zkp")]
1106        assert!(!algorithms.is_empty(), "zkp feature enables STARK");
1107        #[cfg(not(feature = "zkp"))]
1108        let _ = algorithms;
1109    }
1110
1111    #[cfg(feature = "zkp")]
1112    #[test]
1113    fn test_create_zkp() {
1114        let algorithms = available_algorithms();
1115        assert!(!algorithms.is_empty());
1116        let algorithm = algorithms[0];
1117        assert!(create_zkp(algorithm).is_ok());
1118    }
1119
1120    #[cfg(feature = "zkp")]
1121    #[test]
1122    fn test_verify_rejects_forged_proof_with_hash_preimage_metadata() {
1123        let proof = ZkpProof {
1124            data: alloc::vec![
1125                0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD,
1126                0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF,
1127                0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA,
1128                0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD,
1129                0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF, 0xFF, 0xAA, 0xDE, 0xAD, 0xBE, 0xEF,
1130            ],
1131            proof_type: ProofType::Stark,
1132            security_level: 1,
1133            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1134        };
1135        let verifier = ZkpVerifier::new();
1136        let result = verifier.verify(proof, b"expected_hash");
1137        assert!(
1138            matches!(result, Ok(false) | Err(_)),
1139            "forged HashPreimage proof must not return Ok(true)"
1140        );
1141    }
1142
1143    #[cfg(feature = "zkp")]
1144    #[test]
1145    fn test_verify_rejects_forged_proof_with_merkle_metadata() {
1146        let proof = ZkpProof {
1147            data: alloc::vec![
1148                0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE,
1149                0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE,
1150                0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE,
1151                0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE,
1152                0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE, 0xCA, 0xFE, 0xBA, 0xBE,
1153            ],
1154            proof_type: ProofType::Stark,
1155            security_level: 1,
1156            metadata: ProofMetadata::MerkleInclusion { tree_depth: 4 },
1157        };
1158        let verifier = ZkpVerifier::new();
1159        let result = verifier.verify(proof, b"wrong_root");
1160        assert!(
1161            matches!(result, Ok(false) | Err(_)),
1162            "forged MerkleInclusion proof must not return Ok(true)"
1163        );
1164    }
1165
1166    #[cfg(feature = "zkp")]
1167    #[test]
1168    fn test_verify_rejects_circuit_metadata_proof() {
1169        let proof = ZkpProof {
1170            data: alloc::vec![0u8; 64],
1171            proof_type: ProofType::Stark,
1172            security_level: 1,
1173            metadata: ProofMetadata::Circuit {
1174                num_witnesses: 2,
1175                num_public: 1,
1176            },
1177        };
1178        let verifier = ZkpVerifier::new();
1179        assert_eq!(
1180            verifier.verify(proof, b"anything").unwrap(),
1181            false,
1182            "Circuit proofs must be rejected by generic verify; use verify_computation"
1183        );
1184    }
1185
1186    #[cfg(feature = "zkp")]
1187    #[test]
1188    fn test_verify_rejects_credential_metadata_proof() {
1189        let proof = ZkpProof {
1190            data: alloc::vec![0u8; 64],
1191            proof_type: ProofType::Stark,
1192            security_level: 1,
1193            metadata: ProofMetadata::Credential {
1194                attribute_sizes: alloc::vec![8, 4],
1195                reveal_mask: alloc::vec![true, false],
1196            },
1197        };
1198        let verifier = ZkpVerifier::new();
1199        assert_eq!(
1200            verifier.verify(proof, b"anything").unwrap(),
1201            false,
1202            "Credential proofs must be rejected by generic verify; use ip::verify_credential_proof"
1203        );
1204    }
1205
1206    #[cfg(feature = "zkp")]
1207    #[test]
1208    fn test_verify_rejects_identity_metadata_proof() {
1209        let proof = ZkpProof {
1210            data: alloc::vec![0u8; 64],
1211            proof_type: ProofType::Stark,
1212            security_level: 1,
1213            metadata: ProofMetadata::Identity { dsa_level: 65 },
1214        };
1215        let verifier = ZkpVerifier::new();
1216        assert_eq!(
1217            verifier.verify(proof, b"anything").unwrap(),
1218            false,
1219            "Identity proofs must be rejected by generic verify; use ip::verify_it_ownership"
1220        );
1221    }
1222
1223    #[cfg(feature = "zkp")]
1224    #[test]
1225    fn test_verify_empty_data_is_rejected() {
1226        let proof = ZkpProof {
1227            data: alloc::vec![],
1228            proof_type: ProofType::Stark,
1229            security_level: 1,
1230            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1231        };
1232        let verifier = ZkpVerifier::new();
1233        assert_eq!(
1234            verifier.verify(proof, b"anything").unwrap(),
1235            false,
1236            "empty proof data must be rejected regardless of metadata"
1237        );
1238    }
1239
1240    #[cfg(feature = "zkp")]
1241    #[test]
1242    fn test_proof_type_only_stark_exists() {
1243        let _stark = ProofType::Stark;
1244        // ProofType::Snark and ProofType::Bulletproof have been removed.
1245        // Only NIST-approved post-quantum proof systems are supported.
1246    }
1247
1248    #[cfg(feature = "zkp")]
1249    #[test]
1250    fn test_batch_verify_rejects_forged_hash_preimage_proof() {
1251        let forged = ZkpProof {
1252            data: alloc::vec![0xFF; 64],
1253            proof_type: ProofType::Stark,
1254            security_level: 1,
1255            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1256        };
1257        let verifier = ZkpVerifier::new();
1258        let proofs = alloc::vec![forged];
1259        let publics: &[&[u8]] = &[b"anything"];
1260        let result = verifier.batch_verify(&proofs, publics);
1261        assert!(
1262            matches!(result, Ok(false) | Err(_)),
1263            "batch_verify must not accept a forged proof"
1264        );
1265    }
1266
1267    #[cfg(feature = "zkp")]
1268    #[test]
1269    fn test_prove_secret_value_rejects_empty_and_oversized_input() {
1270        let mut prover = ZkpProver::new();
1271        let empty = prover.prove_secret_value(b"", b"");
1272        assert!(empty.is_err());
1273
1274        let oversized = vec![0u8; air::hash_preimage::MAX_PREIMAGE_SIZE + 1];
1275        let too_large = prover.prove_secret_value(&oversized, b"");
1276        assert!(too_large.is_err());
1277    }
1278
1279    #[cfg(feature = "zkp")]
1280    #[test]
1281    fn test_prove_secret_value_nist_rejects_empty_and_oversized_input() {
1282        let mut prover = ZkpProver::new();
1283        let empty = prover.prove_secret_value_nist(b"", b"");
1284        assert!(empty.is_err());
1285
1286        let oversized = vec![0u8; air::hash_preimage_nist::MAX_PREIMAGE_SIZE + 1];
1287        let too_large = prover.prove_secret_value_nist(&oversized, b"");
1288        assert!(too_large.is_err());
1289    }
1290
1291    #[cfg(feature = "zkp")]
1292    #[test]
1293    fn test_verify_secret_value_rejects_invalid_proof_shape_inputs() {
1294        let verifier = ZkpVerifier::new();
1295        let non_stark = ZkpProof {
1296            data: vec![1u8; 16],
1297            proof_type: ProofType::Stark,
1298            security_level: 1,
1299            metadata: ProofMetadata::HashPreimageNist { output_size: 32 },
1300        };
1301        assert!(!verifier.verify_secret_value(&non_stark, b"hash").unwrap());
1302
1303        let empty = ZkpProof {
1304            data: vec![],
1305            proof_type: ProofType::Stark,
1306            security_level: 1,
1307            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1308        };
1309        assert!(!verifier.verify_secret_value(&empty, b"hash").unwrap());
1310    }
1311
1312    #[cfg(feature = "zkp")]
1313    #[test]
1314    fn test_verify_secret_value_nist_rejects_wrong_metadata_and_bad_bytes() {
1315        let verifier = ZkpVerifier::new();
1316        let wrong_meta = ZkpProof {
1317            data: vec![1u8; 16],
1318            proof_type: ProofType::Stark,
1319            security_level: 1,
1320            metadata: ProofMetadata::HashPreimage { output_size: 1 },
1321        };
1322        assert!(
1323            !verifier
1324                .verify_secret_value_nist(&wrong_meta, &[0u8; 32])
1325                .unwrap()
1326        );
1327
1328        let malformed_stark = ZkpProof {
1329            data: vec![0xAA; 16],
1330            proof_type: ProofType::Stark,
1331            security_level: 1,
1332            metadata: ProofMetadata::HashPreimageNist { output_size: 32 },
1333        };
1334        assert!(
1335            !verifier
1336                .verify_secret_value_nist(&malformed_stark, &[0u8; 32])
1337                .unwrap()
1338        );
1339    }
1340
1341    #[cfg(feature = "zkp")]
1342    #[test]
1343    fn test_verify_computation_rejects_empty_or_non_stark_data() {
1344        use crate::circuit::CircuitBuilder;
1345
1346        let verifier = ZkpVerifier::new();
1347        let circuit = CircuitBuilder::<ZkpField>::new(1, 0).build();
1348
1349        let empty = ZkpProof {
1350            data: vec![],
1351            proof_type: ProofType::Stark,
1352            security_level: 1,
1353            metadata: ProofMetadata::Circuit {
1354                num_witnesses: 1,
1355                num_public: 0,
1356            },
1357        };
1358        assert!(!verifier.verify_computation(&empty, &circuit, &[]).unwrap());
1359    }
1360}