Skip to main content

immutable_logging/
chain.rs

1//! Log Chain - Append-only linked list with hash chaining
2
3use crate::log_entry::LogEntry;
4use crate::error::LogError;
5use serde::{Deserialize, Serialize};
6
7/// Genesis hash (initial chain hash)
8pub const GENESIS_HASH: &str = "0000000000000000000000000000000000000000000000000000000000000000";
9
10/// Chain proof for verification
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct ChainProof {
13    pub entry_id: String,
14    pub entry_hash: String,
15    pub path: Vec<ChainProofStep>,
16}
17
18/// Single step in chain proof
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ChainProofStep {
21    pub previous_hash: String,
22}
23
24/// Log chain state
25pub struct LogChain {
26    entries: Vec<LogEntry>,
27    current_hash: String,
28    entry_index: std::collections::HashMap<String, usize>,
29}
30
31impl LogChain {
32    /// Create new chain with genesis block
33    pub fn new() -> Self {
34        LogChain {
35            entries: Vec::new(),
36            current_hash: GENESIS_HASH.to_string(),
37            entry_index: std::collections::HashMap::new(),
38        }
39    }
40    
41    /// Append entry to chain
42    pub async fn append(&mut self, mut entry: LogEntry) -> Result<LogEntry, LogError> {
43        // Update previous hash
44        entry.update_previous_hash(&self.current_hash);
45        
46        // Compute new hash
47        let new_hash = entry.compute_hash(&self.current_hash);
48        
49        // Store entry
50        let index = self.entries.len();
51        self.entry_index.insert(entry.entry_id.clone(), index);
52        self.entries.push(entry.clone());
53        
54        // Update current hash
55        self.current_hash = new_hash;
56        
57        Ok(entry)
58    }
59    
60    /// Verify chain integrity
61    pub fn verify(&self) -> bool {
62        if self.entries.is_empty() {
63            return true;
64        }
65        
66        let mut previous_hash = GENESIS_HASH.to_string();
67        
68        for entry in &self.entries {
69            // Verify previous hash
70            if entry.integrity.previous_entry_hash != previous_hash {
71                return false;
72            }
73            
74            // Compute expected hash
75            let computed = entry.compute_hash(&previous_hash);
76            if computed != self.get_entry_hash(&entry.entry_id) {
77                return false;
78            }
79            
80            previous_hash = computed;
81        }
82        
83        true
84    }
85    
86    /// Get hash for entry
87    fn get_entry_hash(&self, entry_id: &str) -> String {
88        // This would be stored in a real implementation
89        // For now, compute on demand
90        let index = self.entry_index.get(entry_id).copied();
91        
92        if let Some(idx) = index {
93            let entry = &self.entries[idx];
94            entry.compute_hash(&entry.integrity.previous_entry_hash)
95        } else {
96            String::new()
97        }
98    }
99    
100    /// Generate proof for entry
101    pub fn generate_proof(&self, entry_id: &str) -> Option<ChainProof> {
102        let index = self.entry_index.get(entry_id)?;
103        
104        if *index >= self.entries.len() {
105            return None;
106        }
107        
108        let entry = &self.entries[*index];
109        let entry_hash = entry.compute_hash(&entry.integrity.previous_entry_hash);
110        
111        let mut path = Vec::new();
112        
113        // Build proof path
114        for i in 0..=*index {
115            if i == 0 {
116                path.push(ChainProofStep {
117                    previous_hash: GENESIS_HASH.to_string(),
118                });
119            } else {
120                let prev_entry = &self.entries[i - 1];
121                let hash = prev_entry.compute_hash(&prev_entry.integrity.previous_entry_hash);
122                path.push(ChainProofStep {
123                    previous_hash: hash,
124                });
125            }
126        }
127        
128        Some(ChainProof {
129            entry_id: entry_id.to_string(),
130            entry_hash,
131            path,
132        })
133    }
134    
135    /// Get entry count
136    pub fn len(&self) -> usize {
137        self.entries.len()
138    }
139    
140    /// Check if empty
141    pub fn is_empty(&self) -> bool {
142        self.entries.is_empty()
143    }
144    
145    /// Get current hash
146    pub fn current_hash(&self) -> &str {
147        &self.current_hash
148    }
149}
150
151impl Default for LogChain {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157/// Verify chain proof
158pub fn verify_chain_proof(proof: &ChainProof) -> bool {
159    // Simplified verification
160    !proof.entry_hash.is_empty() && !proof.path.is_empty()
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::log_entry::EventType;
167    
168    #[test]
169    fn test_genesis_hash() {
170        assert_eq!(GENESIS_HASH.len(), 64);
171    }
172    
173    #[tokio::test]
174    async fn test_append_entry() {
175        let mut chain = LogChain::new();
176        
177        let mut entry = LogEntry::new(
178            EventType::AccountQuery,
179            "AGENT_001".to_string(),
180            "DGFiP".to_string(),
181        );
182        entry.update_previous_hash(GENESIS_HASH);
183        
184        let result = chain.append(entry).await;
185        assert!(result.is_ok());
186        assert!(chain.verify());
187    }
188    
189    #[tokio::test]
190    async fn test_chain_proof() {
191        let mut chain = LogChain::new();
192        
193        let entry = LogEntry::new(
194            EventType::AuthSuccess,
195            "AGENT_001".to_string(),
196            "DGFiP".to_string(),
197        );
198        
199        let entry = chain.append(entry).await.unwrap();
200        let proof = chain.generate_proof(&entry.entry_id);
201        
202        assert!(proof.is_some());
203    }
204}