Skip to main content

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}