cdx_core/document/provenance.rs
1use crate::Result;
2
3use super::Document;
4
5impl Document {
6 /// Generate a block index for this document.
7 ///
8 /// The block index contains hashes for all content blocks and the Merkle root.
9 /// This is used for generating proofs and verifying content integrity.
10 ///
11 /// # Errors
12 ///
13 /// Returns an error if the content has no blocks.
14 pub fn block_index(&self) -> Result<crate::provenance::BlockIndex> {
15 crate::provenance::BlockIndex::from_content(&self.content, self.manifest.hash_algorithm)
16 }
17
18 /// Generate a Merkle proof for a specific block by index.
19 ///
20 /// The proof can be used to verify that a block is part of this document
21 /// without revealing the entire document content.
22 ///
23 /// # Arguments
24 ///
25 /// * `block_index` - The zero-based index of the block to prove
26 ///
27 /// # Errors
28 ///
29 /// Returns an error if:
30 /// - The content has no blocks
31 /// - The block index is out of bounds
32 pub fn prove_block(&self, block_index: usize) -> Result<crate::provenance::BlockProof> {
33 let index = self.block_index()?;
34 let hashes: Vec<_> = index.hashes().into_iter().cloned().collect();
35 let tree =
36 crate::provenance::MerkleTree::from_hashes(&hashes, self.manifest.hash_algorithm)?;
37 tree.prove(block_index)
38 }
39
40 /// Generate a Merkle proof for a block by its ID.
41 ///
42 /// # Arguments
43 ///
44 /// * `block_id` - The ID of the block to prove
45 ///
46 /// # Errors
47 ///
48 /// Returns an error if:
49 /// - The content has no blocks
50 /// - No block with the given ID exists
51 pub fn prove_block_by_id(&self, block_id: &str) -> Result<crate::provenance::BlockProof> {
52 let index = self.block_index()?;
53 let entry = index
54 .find_block(block_id)
55 .ok_or_else(|| crate::Error::ValidationFailed {
56 reason: format!("block with ID '{block_id}' not found"),
57 })?;
58 self.prove_block(entry.index)
59 }
60
61 /// Verify a block proof against this document.
62 ///
63 /// # Arguments
64 ///
65 /// * `proof` - The proof to verify
66 /// * `block_hash` - The hash of the block being verified
67 ///
68 /// # Returns
69 ///
70 /// `true` if the proof is valid and the block is part of this document.
71 #[must_use]
72 pub fn verify_proof(
73 &self,
74 proof: &crate::provenance::BlockProof,
75 block_hash: &crate::DocumentId,
76 ) -> bool {
77 // First verify the proof is internally consistent
78 if !proof.verify(block_hash) {
79 return false;
80 }
81
82 // Then verify the root matches this document's Merkle root
83 if let Ok(index) = self.block_index() {
84 proof.root_hash == *index.merkle_root()
85 } else {
86 false
87 }
88 }
89
90 /// Get the Merkle root hash for this document's content.
91 ///
92 /// # Errors
93 ///
94 /// Returns an error if the content has no blocks.
95 pub fn merkle_root(&self) -> Result<crate::DocumentId> {
96 let index = self.block_index()?;
97 Ok(index.merkle_root().clone())
98 }
99
100 /// Create a provenance record for this document.
101 ///
102 /// # Errors
103 ///
104 /// Returns an error if computing the document ID or Merkle root fails.
105 pub fn provenance_record(&self) -> Result<crate::provenance::ProvenanceRecord> {
106 let doc_id = if self.manifest.id.is_pending() {
107 self.compute_id()?
108 } else {
109 self.manifest.id.clone()
110 };
111
112 let index = self.block_index()?;
113 let merkle = crate::provenance::MerkleInfo::new(
114 index.merkle_root().clone(),
115 index.block_count(),
116 self.manifest.hash_algorithm,
117 );
118
119 let mut record = crate::provenance::ProvenanceRecord::new(doc_id, merkle);
120
121 // Add lineage if present
122 if let Some(ref lineage) = self.manifest.lineage {
123 record = record.with_lineage(lineage.clone());
124 }
125
126 Ok(record)
127 }
128}