chie_crypto/
zkproof.rs

1//! Zero-Knowledge Proof Composition Framework
2//!
3//! This module provides a framework for composing multiple zero-knowledge proofs
4//! into complex protocols with AND/OR logic and proof aggregation.
5//!
6//! Perfect for building complex privacy-preserving protocols where you need to prove
7//! multiple statements together (e.g., "I know a secret AND it's in a certain range").
8
9use blake3;
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13/// Errors that can occur in ZK proof operations
14#[derive(Debug, Error, Clone, PartialEq, Eq)]
15pub enum ZkProofError {
16    #[error("Proof verification failed")]
17    VerificationFailed,
18    #[error("Invalid proof composition")]
19    InvalidComposition,
20    #[error("Proof not found")]
21    ProofNotFound,
22    #[error("Invalid proof type")]
23    InvalidProofType,
24}
25
26pub type ZkProofResult<T> = Result<T, ZkProofError>;
27
28/// A zero-knowledge proof with metadata
29#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct ZkProof {
31    /// Unique identifier for this proof type
32    proof_type: String,
33    /// The actual proof data
34    proof_data: Vec<u8>,
35    /// Public inputs/statements
36    public_inputs: Vec<Vec<u8>>,
37    /// Optional metadata
38    metadata: Vec<(String, Vec<u8>)>,
39}
40
41impl ZkProof {
42    /// Create a new ZK proof
43    pub fn new(
44        proof_type: impl Into<String>,
45        proof_data: Vec<u8>,
46        public_inputs: Vec<Vec<u8>>,
47    ) -> Self {
48        Self {
49            proof_type: proof_type.into(),
50            proof_data,
51            public_inputs,
52            metadata: Vec::new(),
53        }
54    }
55
56    /// Add metadata to the proof
57    pub fn with_metadata(mut self, key: impl Into<String>, value: Vec<u8>) -> Self {
58        self.metadata.push((key.into(), value));
59        self
60    }
61
62    /// Get the proof type
63    pub fn proof_type(&self) -> &str {
64        &self.proof_type
65    }
66
67    /// Get the proof data
68    pub fn proof_data(&self) -> &[u8] {
69        &self.proof_data
70    }
71
72    /// Get the public inputs
73    pub fn public_inputs(&self) -> &[Vec<u8>] {
74        &self.public_inputs
75    }
76
77    /// Get metadata by key
78    pub fn get_metadata(&self, key: &str) -> Option<&[u8]> {
79        self.metadata
80            .iter()
81            .find(|(k, _)| k == key)
82            .map(|(_, v)| v.as_slice())
83    }
84
85    /// Compute a hash commitment to this proof
86    pub fn commitment(&self) -> [u8; 32] {
87        let mut hasher = blake3::Hasher::new();
88        hasher.update(b"ZK_PROOF_COMMITMENT:");
89        hasher.update(self.proof_type.as_bytes());
90        hasher.update(&self.proof_data);
91        for input in &self.public_inputs {
92            hasher.update(input);
93        }
94        *hasher.finalize().as_bytes()
95    }
96}
97
98/// Composite proof combining multiple proofs with AND logic
99///
100/// All constituent proofs must verify for the composite to be valid
101#[derive(Clone, Debug, Serialize, Deserialize)]
102pub struct AndProof {
103    proofs: Vec<ZkProof>,
104    /// Optional binding to ensure all proofs relate to the same statement
105    binding: Option<[u8; 32]>,
106}
107
108impl AndProof {
109    /// Create a new AND composition of proofs
110    pub fn new(proofs: Vec<ZkProof>) -> Self {
111        Self {
112            proofs,
113            binding: None,
114        }
115    }
116
117    /// Add a binding value that links all proofs together
118    pub fn with_binding(mut self, binding: [u8; 32]) -> Self {
119        self.binding = Some(binding);
120        self
121    }
122
123    /// Get all constituent proofs
124    pub fn proofs(&self) -> &[ZkProof] {
125        &self.proofs
126    }
127
128    /// Get a proof by type
129    pub fn get_proof(&self, proof_type: &str) -> Option<&ZkProof> {
130        self.proofs.iter().find(|p| p.proof_type() == proof_type)
131    }
132
133    /// Verify the binding if present
134    pub fn verify_binding(&self) -> ZkProofResult<()> {
135        if let Some(binding) = self.binding {
136            // Compute expected binding from all proof commitments
137            let mut hasher = blake3::Hasher::new();
138            hasher.update(b"AND_PROOF_BINDING:");
139            for proof in &self.proofs {
140                hasher.update(&proof.commitment());
141            }
142            let expected = hasher.finalize();
143
144            if expected.as_bytes() != &binding {
145                return Err(ZkProofError::VerificationFailed);
146            }
147        }
148        Ok(())
149    }
150
151    /// Compute the AND composition's commitment
152    pub fn commitment(&self) -> [u8; 32] {
153        let mut hasher = blake3::Hasher::new();
154        hasher.update(b"AND_COMPOSITION:");
155        for proof in &self.proofs {
156            hasher.update(&proof.commitment());
157        }
158        if let Some(binding) = &self.binding {
159            hasher.update(binding);
160        }
161        *hasher.finalize().as_bytes()
162    }
163}
164
165/// Composite proof combining multiple proofs with OR logic
166///
167/// At least one constituent proof must verify for the composite to be valid
168#[derive(Clone, Debug, Serialize, Deserialize)]
169pub struct OrProof {
170    /// The valid proof (only one is revealed)
171    proof: ZkProof,
172    /// Commitments to all possible proofs (for privacy)
173    commitments: Vec<[u8; 32]>,
174    /// Index of the revealed proof
175    revealed_index: usize,
176}
177
178impl OrProof {
179    /// Create a new OR composition (reveals one proof, hides others)
180    pub fn new(all_proofs: Vec<ZkProof>, revealed_index: usize) -> ZkProofResult<Self> {
181        if revealed_index >= all_proofs.len() {
182            return Err(ZkProofError::InvalidComposition);
183        }
184
185        let commitments: Vec<[u8; 32]> = all_proofs.iter().map(|p| p.commitment()).collect();
186
187        Ok(Self {
188            proof: all_proofs[revealed_index].clone(),
189            commitments,
190            revealed_index,
191        })
192    }
193
194    /// Get the revealed proof
195    pub fn proof(&self) -> &ZkProof {
196        &self.proof
197    }
198
199    /// Get all commitments
200    pub fn commitments(&self) -> &[[u8; 32]] {
201        &self.commitments
202    }
203
204    /// Get the revealed index
205    pub fn revealed_index(&self) -> usize {
206        self.revealed_index
207    }
208
209    /// Verify that the revealed proof matches its commitment
210    pub fn verify_commitment(&self) -> ZkProofResult<()> {
211        let commitment = self.proof.commitment();
212        if commitment != self.commitments[self.revealed_index] {
213            return Err(ZkProofError::VerificationFailed);
214        }
215        Ok(())
216    }
217}
218
219/// Builder for creating composite proofs
220pub struct ZkProofBuilder {
221    proofs: Vec<ZkProof>,
222}
223
224impl ZkProofBuilder {
225    /// Create a new proof builder
226    pub fn new() -> Self {
227        Self { proofs: Vec::new() }
228    }
229
230    /// Add a proof to the composition
231    pub fn add_proof(mut self, proof: ZkProof) -> Self {
232        self.proofs.push(proof);
233        self
234    }
235
236    /// Build an AND composition
237    pub fn build_and(self) -> AndProof {
238        AndProof::new(self.proofs)
239    }
240
241    /// Build an AND composition with binding
242    pub fn build_and_with_binding(self, binding: [u8; 32]) -> AndProof {
243        AndProof::new(self.proofs).with_binding(binding)
244    }
245
246    /// Build an OR composition (reveal one proof)
247    pub fn build_or(self, revealed_index: usize) -> ZkProofResult<OrProof> {
248        OrProof::new(self.proofs, revealed_index)
249    }
250}
251
252impl Default for ZkProofBuilder {
253    fn default() -> Self {
254        Self::new()
255    }
256}
257
258/// Trait for types that can be proven in zero-knowledge
259pub trait ZkProvable {
260    /// Generate a zero-knowledge proof
261    fn prove(&self) -> ZkProofResult<ZkProof>;
262
263    /// Verify a zero-knowledge proof
264    fn verify(proof: &ZkProof) -> ZkProofResult<bool>;
265}
266
267/// Helper to create a binding value for AND compositions
268pub fn create_binding(proofs: &[&ZkProof]) -> [u8; 32] {
269    let mut hasher = blake3::Hasher::new();
270    hasher.update(b"AND_PROOF_BINDING:");
271    for proof in proofs {
272        hasher.update(&proof.commitment());
273    }
274    *hasher.finalize().as_bytes()
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    fn create_test_proof(proof_type: &str, data: &[u8]) -> ZkProof {
282        ZkProof::new(proof_type, data.to_vec(), vec![b"public_input".to_vec()])
283    }
284
285    #[test]
286    fn test_zkproof_creation() {
287        let proof = create_test_proof("range", b"proof_data");
288        assert_eq!(proof.proof_type(), "range");
289        assert_eq!(proof.proof_data(), b"proof_data");
290        assert_eq!(proof.public_inputs().len(), 1);
291    }
292
293    #[test]
294    fn test_zkproof_metadata() {
295        let proof = create_test_proof("range", b"proof_data")
296            .with_metadata("key1", b"value1".to_vec())
297            .with_metadata("key2", b"value2".to_vec());
298
299        assert_eq!(proof.get_metadata("key1"), Some(b"value1".as_slice()));
300        assert_eq!(proof.get_metadata("key2"), Some(b"value2".as_slice()));
301        assert_eq!(proof.get_metadata("key3"), None);
302    }
303
304    #[test]
305    fn test_zkproof_commitment() {
306        let proof1 = create_test_proof("range", b"proof_data");
307        let proof2 = create_test_proof("range", b"proof_data");
308        let proof3 = create_test_proof("range", b"different_data");
309
310        // Same data should produce same commitment
311        assert_eq!(proof1.commitment(), proof2.commitment());
312
313        // Different data should produce different commitment
314        assert_ne!(proof1.commitment(), proof3.commitment());
315    }
316
317    #[test]
318    fn test_and_proof_basic() {
319        let proof1 = create_test_proof("range", b"proof1");
320        let proof2 = create_test_proof("membership", b"proof2");
321        let proof3 = create_test_proof("signature", b"proof3");
322
323        let and_proof = AndProof::new(vec![proof1, proof2, proof3]);
324
325        assert_eq!(and_proof.proofs().len(), 3);
326        assert!(and_proof.get_proof("range").is_some());
327        assert!(and_proof.get_proof("membership").is_some());
328        assert!(and_proof.get_proof("signature").is_some());
329        assert!(and_proof.get_proof("nonexistent").is_none());
330    }
331
332    #[test]
333    fn test_and_proof_binding() {
334        let proof1 = create_test_proof("range", b"proof1");
335        let proof2 = create_test_proof("membership", b"proof2");
336
337        // Clone for binding computation since we need to move them into AndProof
338        let binding = create_binding(&[&proof1, &proof2]);
339        let and_proof = AndProof::new(vec![proof1.clone(), proof2.clone()]).with_binding(binding);
340
341        // Correct binding should verify
342        assert!(and_proof.verify_binding().is_ok());
343    }
344
345    #[test]
346    fn test_and_proof_invalid_binding() {
347        let proof1 = create_test_proof("range", b"proof1");
348        let proof2 = create_test_proof("membership", b"proof2");
349
350        // Create with wrong binding
351        let wrong_binding = [0u8; 32];
352        let and_proof = AndProof::new(vec![proof1, proof2]).with_binding(wrong_binding);
353
354        // Wrong binding should fail
355        assert!(and_proof.verify_binding().is_err());
356    }
357
358    #[test]
359    fn test_or_proof_basic() {
360        let proof1 = create_test_proof("option1", b"proof1");
361        let proof2 = create_test_proof("option2", b"proof2");
362        let proof3 = create_test_proof("option3", b"proof3");
363
364        let or_proof = OrProof::new(vec![proof1, proof2.clone(), proof3], 1).unwrap();
365
366        assert_eq!(or_proof.revealed_index(), 1);
367        assert_eq!(or_proof.proof().proof_type(), "option2");
368        assert_eq!(or_proof.commitments().len(), 3);
369    }
370
371    #[test]
372    fn test_or_proof_commitment_verification() {
373        let proof1 = create_test_proof("option1", b"proof1");
374        let proof2 = create_test_proof("option2", b"proof2");
375
376        let or_proof = OrProof::new(vec![proof1, proof2], 0).unwrap();
377
378        // Commitment should verify
379        assert!(or_proof.verify_commitment().is_ok());
380    }
381
382    #[test]
383    fn test_or_proof_invalid_index() {
384        let proof1 = create_test_proof("option1", b"proof1");
385        let proof2 = create_test_proof("option2", b"proof2");
386
387        // Index out of bounds should error
388        let result = OrProof::new(vec![proof1, proof2], 5);
389        assert!(result.is_err());
390    }
391
392    #[test]
393    fn test_proof_builder_and() {
394        let proof1 = create_test_proof("range", b"proof1");
395        let proof2 = create_test_proof("membership", b"proof2");
396
397        let and_proof = ZkProofBuilder::new()
398            .add_proof(proof1)
399            .add_proof(proof2)
400            .build_and();
401
402        assert_eq!(and_proof.proofs().len(), 2);
403    }
404
405    #[test]
406    fn test_proof_builder_and_with_binding() {
407        let proof1 = create_test_proof("range", b"proof1");
408        let proof2 = create_test_proof("membership", b"proof2");
409
410        let binding = create_binding(&[&proof1, &proof2]);
411        let and_proof = ZkProofBuilder::new()
412            .add_proof(proof1.clone())
413            .add_proof(proof2.clone())
414            .build_and_with_binding(binding);
415
416        assert!(and_proof.verify_binding().is_ok());
417    }
418
419    #[test]
420    fn test_proof_builder_or() {
421        let proof1 = create_test_proof("option1", b"proof1");
422        let proof2 = create_test_proof("option2", b"proof2");
423        let proof3 = create_test_proof("option3", b"proof3");
424
425        let or_proof = ZkProofBuilder::new()
426            .add_proof(proof1)
427            .add_proof(proof2)
428            .add_proof(proof3)
429            .build_or(1)
430            .unwrap();
431
432        assert_eq!(or_proof.revealed_index(), 1);
433        assert_eq!(or_proof.proof().proof_type(), "option2");
434    }
435
436    #[test]
437    fn test_serialization() {
438        let proof1 = create_test_proof("range", b"proof1");
439        let proof2 = create_test_proof("membership", b"proof2");
440
441        let and_proof = AndProof::new(vec![proof1, proof2]);
442
443        // Serialize with bincode
444        let serialized = crate::codec::encode(&and_proof).unwrap();
445        let deserialized: AndProof = crate::codec::decode(&serialized).unwrap();
446
447        assert_eq!(deserialized.proofs().len(), 2);
448        assert_eq!(deserialized.commitment(), and_proof.commitment());
449    }
450}