Skip to main content

cdx_core/provenance/
proof.rs

1//! Block-level proofs for selective disclosure.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{DocumentId, HashAlgorithm, Hasher};
6
7/// A Merkle proof for a specific block.
8///
9/// This proof allows verification that a block is part of a document
10/// without revealing the entire document content.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct BlockProof {
14    /// Index of the block in the document.
15    pub index: usize,
16
17    /// Path from leaf to root: (`sibling_hash`, `is_right_sibling`).
18    pub path: Vec<(DocumentId, bool)>,
19
20    /// Expected root hash.
21    pub root_hash: DocumentId,
22
23    /// Hash algorithm used.
24    pub algorithm: HashAlgorithm,
25}
26
27impl BlockProof {
28    /// Verify that a block hash is part of the document.
29    ///
30    /// # Arguments
31    ///
32    /// * `block_hash` - The hash of the block to verify
33    ///
34    /// # Returns
35    ///
36    /// `true` if the proof is valid, `false` otherwise.
37    #[must_use]
38    pub fn verify(&self, block_hash: &DocumentId) -> bool {
39        let mut current = block_hash.clone();
40
41        for (sibling, is_right) in &self.path {
42            let combined = if *is_right {
43                // Sibling is on right, we are on left
44                format!("{}{}", current.hex_digest(), sibling.hex_digest())
45            } else {
46                // Sibling is on left, we are on right
47                format!("{}{}", sibling.hex_digest(), current.hex_digest())
48            };
49
50            current = Hasher::hash(self.algorithm, combined.as_bytes());
51        }
52
53        current == self.root_hash
54    }
55
56    /// Create a verification result with details.
57    #[must_use]
58    pub fn verify_detailed(&self, block_hash: &DocumentId) -> ProofVerification {
59        let valid = self.verify(block_hash);
60        ProofVerification {
61            valid,
62            index: self.index,
63            root_hash: self.root_hash.clone(),
64            error: if valid {
65                None
66            } else {
67                Some("Computed root hash does not match expected".to_string())
68            },
69        }
70    }
71}
72
73/// Result of proof verification.
74#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct ProofVerification {
76    /// Whether the proof is valid.
77    pub valid: bool,
78
79    /// Block index that was verified.
80    pub index: usize,
81
82    /// Expected root hash.
83    pub root_hash: DocumentId,
84
85    /// Error message if verification failed.
86    pub error: Option<String>,
87}
88
89impl ProofVerification {
90    /// Check if verification passed.
91    #[must_use]
92    pub fn is_valid(&self) -> bool {
93        self.valid
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::provenance::MerkleTree;
101
102    #[test]
103    fn test_proof_verification() {
104        let items = vec!["block0", "block1", "block2", "block3"];
105        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
106
107        // Generate proof for block 1
108        let proof = tree.prove(1).unwrap();
109
110        // Compute hash of block 1
111        let block_hash = Hasher::hash(HashAlgorithm::Sha256, b"block1");
112
113        // Verify
114        assert!(proof.verify(&block_hash));
115    }
116
117    #[test]
118    fn test_proof_fails_wrong_block() {
119        let items = vec!["block0", "block1", "block2", "block3"];
120        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
121
122        let proof = tree.prove(1).unwrap();
123
124        // Try to verify with wrong block hash
125        let wrong_hash = Hasher::hash(HashAlgorithm::Sha256, b"wrong_block");
126        assert!(!proof.verify(&wrong_hash));
127    }
128
129    #[test]
130    fn test_proof_verification_detailed() {
131        let items = vec!["a", "b", "c", "d"];
132        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
133
134        let proof = tree.prove(2).unwrap();
135        let block_hash = Hasher::hash(HashAlgorithm::Sha256, b"c");
136
137        let result = proof.verify_detailed(&block_hash);
138        assert!(result.is_valid());
139        assert_eq!(result.index, 2);
140        assert!(result.error.is_none());
141    }
142
143    #[test]
144    fn test_proof_serialization() {
145        let items = vec!["x", "y"];
146        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
147
148        let proof = tree.prove(0).unwrap();
149
150        let json = serde_json::to_string_pretty(&proof).unwrap();
151        assert!(json.contains("\"index\": 0"));
152
153        let deserialized: BlockProof = serde_json::from_str(&json).unwrap();
154        assert_eq!(deserialized.index, proof.index);
155    }
156
157    #[test]
158    fn test_all_blocks_verifiable() {
159        let items: Vec<&str> = vec!["0", "1", "2", "3", "4", "5", "6", "7"];
160        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
161
162        for (i, item) in items.iter().enumerate() {
163            let proof = tree.prove(i).unwrap();
164            let block_hash = Hasher::hash(HashAlgorithm::Sha256, item.as_bytes());
165            assert!(proof.verify(&block_hash), "Proof failed for block {i}");
166        }
167    }
168
169    #[test]
170    fn test_proof_verification_detailed_failure() {
171        let items = vec!["a", "b", "c", "d"];
172        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
173
174        let proof = tree.prove(2).unwrap();
175        let wrong_hash = Hasher::hash(HashAlgorithm::Sha256, b"wrong");
176
177        let result = proof.verify_detailed(&wrong_hash);
178        assert!(!result.is_valid());
179        assert!(!result.valid);
180        assert_eq!(result.index, 2);
181        assert!(result.error.is_some());
182        assert!(result.error.unwrap().contains("does not match"));
183    }
184
185    #[test]
186    fn test_block_proof_fields() {
187        let items = vec!["x", "y", "z"];
188        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
189
190        let proof = tree.prove(1).unwrap();
191
192        assert_eq!(proof.index, 1);
193        assert_eq!(proof.algorithm, HashAlgorithm::Sha256);
194        assert_eq!(proof.root_hash, *tree.root_hash());
195        assert!(!proof.path.is_empty());
196    }
197
198    #[test]
199    fn test_single_item_proof() {
200        let items = vec!["only"];
201        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
202
203        let proof = tree.prove(0).unwrap();
204        let block_hash = Hasher::hash(HashAlgorithm::Sha256, b"only");
205
206        // Single item tree should verify
207        assert!(proof.verify(&block_hash));
208    }
209
210    #[test]
211    fn test_proof_verification_struct() {
212        let verification = ProofVerification {
213            valid: true,
214            index: 5,
215            root_hash: Hasher::hash(HashAlgorithm::Sha256, b"root"),
216            error: None,
217        };
218
219        assert!(verification.is_valid());
220        assert_eq!(verification.index, 5);
221        assert!(verification.error.is_none());
222    }
223
224    #[test]
225    fn test_proof_path_direction() {
226        // Create a simple 4-item tree
227        let items = vec!["a", "b", "c", "d"];
228        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
229
230        // Proof for index 0 (leftmost)
231        let proof0 = tree.prove(0).unwrap();
232        // Proof for index 3 (rightmost)
233        let proof3 = tree.prove(3).unwrap();
234
235        // Both proofs should have same depth
236        assert_eq!(proof0.path.len(), proof3.path.len());
237
238        // Verify both work
239        let hash0 = Hasher::hash(HashAlgorithm::Sha256, b"a");
240        let hash3 = Hasher::hash(HashAlgorithm::Sha256, b"d");
241        assert!(proof0.verify(&hash0));
242        assert!(proof3.verify(&hash3));
243    }
244
245    #[test]
246    fn test_proof_power_of_two_tree() {
247        // Power-of-two trees work with standard proof generation
248        let items: Vec<&str> = vec!["a", "b", "c", "d"];
249        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
250
251        // All items should be verifiable
252        for (i, item) in items.iter().enumerate() {
253            let proof = tree.prove(i).unwrap();
254            let hash = Hasher::hash(HashAlgorithm::Sha256, item.as_bytes());
255            assert!(proof.verify(&hash), "Failed for index {i}");
256        }
257    }
258
259    #[test]
260    fn test_proof_sixteen_items() {
261        // Larger power-of-two tree
262        let items: Vec<String> = (0..16).map(|i| format!("item{i}")).collect();
263        let tree = MerkleTree::from_items(&items, HashAlgorithm::Sha256).unwrap();
264
265        for (i, item) in items.iter().enumerate() {
266            let proof = tree.prove(i).unwrap();
267            let hash = Hasher::hash(HashAlgorithm::Sha256, item.as_bytes());
268            assert!(proof.verify(&hash), "Failed for index {i}");
269        }
270    }
271}