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