Skip to main content

lit/core/
objects.rs

1use crate::crypto::signatures::PQSignature;
2use blake3;
3use serde::{Deserialize, Serialize};
4use sha3::{Digest, Sha3_512};
5use std::fmt;
6
7/// Object hash type (SHA3-512 + BLAKE3 composite)
8/// Uses NIST-approved SHA-3 as primary hash with BLAKE3 for quantum resistance
9/// Format: sha3_512(data) || blake3(data) = 128 hex chars (64 + 64)
10#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct ObjectHash(pub String);
12
13impl ObjectHash {
14    /// Create a quantum-resistant hash from bytes
15    /// Combines SHA3-512 (NIST FIPS 202) and BLAKE3 for defense-in-depth
16    pub fn from_bytes(data: &[u8]) -> Self {
17        // SHA3-512 (NIST standard, quantum-resistant hash function)
18        let mut sha3_hasher = Sha3_512::new();
19        sha3_hasher.update(data);
20        let sha3_result = sha3_hasher.finalize();
21
22        // BLAKE3 (additional quantum-resistant security)
23        let blake3_result = blake3::hash(data);
24
25        // Combine both hashes for maximum security
26        let combined = format!(
27            "{}{}",
28            hex::encode(sha3_result),
29            hex::encode(blake3_result.as_bytes())
30        );
31        ObjectHash(combined)
32    }
33
34    /// Get the hash as a string
35    pub fn as_str(&self) -> &str {
36        &self.0
37    }
38
39    /// Create from a hex string
40    pub fn from_hex(hex: String) -> Self {
41        ObjectHash(hex)
42    }
43
44    /// Get short hash (first 16 characters for quantum-resistant display)
45    pub fn short(&self) -> String {
46        self.0.chars().take(16).collect()
47    }
48
49    /// Get hash length in characters
50    pub fn len(&self) -> usize {
51        self.0.len()
52    }
53
54    pub fn is_empty(&self) -> bool {
55        self.0.is_empty()
56    }
57}
58
59impl fmt::Display for ObjectHash {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        write!(f, "{}", self.0)
62    }
63}
64
65/// Lit object types
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub enum Object {
68    /// File content
69    Blob(Blob),
70    /// Directory structure
71    Tree(Tree),
72    /// Commit snapshot
73    Commit(Commit),
74    /// Annotated tag
75    Tag(Tag),
76}
77
78impl Object {
79    /// Get the object type name
80    pub fn type_name(&self) -> &str {
81        match self {
82            Object::Blob(_) => "blob",
83            Object::Tree(_) => "tree",
84            Object::Commit(_) => "commit",
85            Object::Tag(_) => "tag",
86        }
87    }
88
89    /// Serialize the object to bytes
90    pub fn to_bytes(&self) -> Vec<u8> {
91        serde_json::to_vec(self).expect("Failed to serialize object")
92    }
93
94    /// Deserialize from bytes
95    pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
96        serde_json::from_slice(bytes).map_err(|e| format!("Failed to deserialize object: {}", e))
97    }
98
99    /// Calculate the hash of this object
100    pub fn hash(&self) -> ObjectHash {
101        ObjectHash::from_bytes(&self.to_bytes())
102    }
103}
104
105/// Blob - stores file content
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct Blob {
108    pub content: Vec<u8>,
109}
110
111impl Blob {
112    pub fn new(content: Vec<u8>) -> Self {
113        Blob { content }
114    }
115}
116
117/// Tree entry - represents a file or subdirectory in a tree
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct TreeEntry {
120    pub mode: String,
121    pub name: String,
122    pub hash: ObjectHash,
123    pub object_type: String,
124}
125
126/// Tree - represents a directory structure
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct Tree {
129    pub entries: Vec<TreeEntry>,
130}
131
132impl Default for Tree {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138impl Tree {
139    pub fn new() -> Self {
140        Tree {
141            entries: Vec::new(),
142        }
143    }
144
145    pub fn add_entry(&mut self, mode: String, name: String, hash: ObjectHash, object_type: String) {
146        self.entries.push(TreeEntry {
147            mode,
148            name,
149            hash,
150            object_type,
151        });
152    }
153}
154
155/// Commit - represents a snapshot in history with quantum-resistant signatures
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct Commit {
158    pub tree: ObjectHash,
159    pub parents: Vec<ObjectHash>,
160    pub author: String,
161    pub committer: String,
162    pub timestamp: i64,
163    pub message: String,
164    /// Optional post-quantum signature (ML-DSA/Dilithium)
165    /// Provides quantum-resistant verification of commit authenticity
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub pq_signature: Option<PQSignature>,
168    /// Optional metadata (agent annotations, tool context, etc.)
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub metadata: Option<serde_json::Value>,
171}
172
173impl Commit {
174    pub fn new(
175        tree: ObjectHash,
176        parents: Vec<ObjectHash>,
177        author: String,
178        message: String,
179    ) -> Self {
180        let timestamp = chrono::Utc::now().timestamp();
181        Commit {
182            tree,
183            parents,
184            author: author.clone(),
185            committer: author,
186            timestamp,
187            message,
188            pq_signature: None, // Can be added after creation
189            metadata: None,
190        }
191    }
192
193    /// Sign this commit with post-quantum signature
194    pub fn sign(&mut self, keypair: &crate::crypto::signatures::PQKeyPair) {
195        // Serialize commit data (excluding signature)
196        let mut commit_data = self.clone();
197        commit_data.pq_signature = None;
198        let data = serde_json::to_vec(&commit_data).expect("Failed to serialize commit");
199
200        // Generate quantum-resistant signature
201        self.pq_signature = Some(keypair.sign(&data));
202    }
203
204    /// Verify post-quantum signature
205    pub fn verify_signature(&self) -> Result<(), String> {
206        match &self.pq_signature {
207            Some(sig) => {
208                // Reconstruct commit without signature
209                let mut commit_data = self.clone();
210                commit_data.pq_signature = None;
211                let data = serde_json::to_vec(&commit_data)
212                    .map_err(|e| format!("Failed to serialize: {}", e))?;
213
214                sig.verify(&data)
215            }
216            None => Err("Commit is not signed".to_string()),
217        }
218    }
219}
220
221/// Tag - annotated tag object with metadata and optional signature
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct Tag {
224    /// Hash of the tagged object (usually a commit)
225    pub target: ObjectHash,
226    /// Type of the tagged object
227    pub target_type: String,
228    /// Tag name
229    pub tag_name: String,
230    /// Tagger identity
231    pub tagger: String,
232    /// Creation timestamp
233    pub timestamp: i64,
234    /// Tag message
235    pub message: String,
236    /// Optional post-quantum signature
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub pq_signature: Option<PQSignature>,
239    /// Optional metadata (agent annotations, tool context, etc.)
240    #[serde(skip_serializing_if = "Option::is_none")]
241    pub metadata: Option<serde_json::Value>,
242}
243
244impl Tag {
245    pub fn new(
246        target: ObjectHash,
247        target_type: String,
248        tag_name: String,
249        tagger: String,
250        message: String,
251    ) -> Self {
252        Tag {
253            target,
254            target_type,
255            tag_name,
256            tagger,
257            timestamp: chrono::Utc::now().timestamp(),
258            message,
259            pq_signature: None,
260            metadata: None,
261        }
262    }
263
264    /// Sign this tag with post-quantum signature
265    pub fn sign(&mut self, keypair: &crate::crypto::signatures::PQKeyPair) {
266        let mut tag_data = self.clone();
267        tag_data.pq_signature = None;
268        let data = serde_json::to_vec(&tag_data).expect("Failed to serialize tag");
269        self.pq_signature = Some(keypair.sign(&data));
270    }
271
272    /// Verify post-quantum signature
273    pub fn verify_signature(&self) -> Result<(), String> {
274        match &self.pq_signature {
275            Some(sig) => {
276                let mut tag_data = self.clone();
277                tag_data.pq_signature = None;
278                let data = serde_json::to_vec(&tag_data)
279                    .map_err(|e| format!("Failed to serialize: {}", e))?;
280                sig.verify(&data)
281            }
282            None => Err("Tag is not signed".to_string()),
283        }
284    }
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn test_object_hash() {
293        let data = b"test content";
294        let hash = ObjectHash::from_bytes(data);
295        // SHA3-512 (128 hex) + BLAKE3 (64 hex) = 192 hex chars total
296        // Note: BLAKE3 produces 32 bytes = 64 hex chars
297        assert_eq!(hash.as_str().len(), 192); // Composite quantum-resistant hash
298    }
299
300    #[test]
301    fn test_blob() {
302        let content = b"Hello, world!".to_vec();
303        let blob = Blob::new(content.clone());
304        assert_eq!(blob.content, content);
305    }
306
307    #[test]
308    fn test_tree() {
309        let mut tree = Tree::new();
310        let hash = ObjectHash::from_hex("abc123".to_string());
311        tree.add_entry(
312            "100644".to_string(),
313            "file.txt".to_string(),
314            hash,
315            "blob".to_string(),
316        );
317        assert_eq!(tree.entries.len(), 1);
318    }
319}