Skip to main content

tibet_cortex_core/
crypto.rs

1use ed25519_dalek::{SigningKey, VerifyingKey, Signer, Verifier, Signature};
2use sha2::{Sha256, Digest};
3use serde::{Serialize, Deserialize};
4use zeroize::{Zeroize, ZeroizeOnDrop};
5
6use crate::error::{CortexError, CortexResult};
7
8/// Ed25519 keypair with zeroize-on-drop for the signing key
9pub struct KeyPair {
10    signing: SigningKey,
11    verifying: VerifyingKey,
12}
13
14impl KeyPair {
15    pub fn generate() -> Self {
16        let mut rng = rand::rngs::OsRng;
17        let signing = SigningKey::generate(&mut rng);
18        let verifying = signing.verifying_key();
19        Self { signing, verifying }
20    }
21
22    pub fn sign(&self, data: &[u8]) -> Vec<u8> {
23        self.signing.sign(data).to_bytes().to_vec()
24    }
25
26    pub fn verify(&self, data: &[u8], signature: &[u8]) -> CortexResult<()> {
27        let sig = Signature::from_slice(signature)
28            .map_err(|e| CortexError::Crypto(e.to_string()))?;
29        self.verifying
30            .verify(data, &sig)
31            .map_err(|_| CortexError::SignatureInvalid)
32    }
33
34    pub fn verifying_key_bytes(&self) -> [u8; 32] {
35        self.verifying.to_bytes()
36    }
37
38    pub fn from_signing_key_bytes(bytes: &[u8; 32]) -> Self {
39        let signing = SigningKey::from_bytes(bytes);
40        let verifying = signing.verifying_key();
41        Self { signing, verifying }
42    }
43}
44
45impl Drop for KeyPair {
46    fn drop(&mut self) {
47        // SigningKey implements Zeroize internally in ed25519-dalek
48    }
49}
50
51/// SHA-256 content hash with display and comparison
52#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
53pub struct ContentHash(pub String);
54
55impl ContentHash {
56    pub fn compute(data: &[u8]) -> Self {
57        let mut hasher = Sha256::new();
58        hasher.update(data);
59        let result = hasher.finalize();
60        Self(format!("sha256:{}", hex::encode(result)))
61    }
62
63    pub fn verify(&self, data: &[u8]) -> bool {
64        let computed = Self::compute(data);
65        computed == *self
66    }
67
68    pub fn as_str(&self) -> &str {
69        &self.0
70    }
71}
72
73impl std::fmt::Display for ContentHash {
74    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75        write!(f, "{}", self.0)
76    }
77}
78
79/// Zeroizing byte buffer — wipes on drop
80#[derive(Zeroize, ZeroizeOnDrop)]
81pub struct SecureBuffer {
82    data: Vec<u8>,
83}
84
85impl SecureBuffer {
86    pub fn new(data: Vec<u8>) -> Self {
87        Self { data }
88    }
89
90    pub fn as_bytes(&self) -> &[u8] {
91        &self.data
92    }
93
94    pub fn len(&self) -> usize {
95        self.data.len()
96    }
97
98    pub fn is_empty(&self) -> bool {
99        self.data.is_empty()
100    }
101}
102
103#[cfg(test)]
104mod tests {
105    use super::*;
106
107    #[test]
108    fn test_keypair_sign_verify() {
109        let kp = KeyPair::generate();
110        let data = b"TIBET Cortex test data";
111        let sig = kp.sign(data);
112        assert!(kp.verify(data, &sig).is_ok());
113    }
114
115    #[test]
116    fn test_tampered_data_fails() {
117        let kp = KeyPair::generate();
118        let data = b"original data";
119        let sig = kp.sign(data);
120        assert!(kp.verify(b"tampered data", &sig).is_err());
121    }
122
123    #[test]
124    fn test_content_hash() {
125        let data = b"hello cortex";
126        let hash = ContentHash::compute(data);
127        assert!(hash.verify(data));
128        assert!(!hash.verify(b"tampered"));
129        assert!(hash.as_str().starts_with("sha256:"));
130    }
131
132    #[test]
133    fn test_secure_buffer_basics() {
134        let buf = SecureBuffer::new(vec![1, 2, 3, 4]);
135        assert_eq!(buf.len(), 4);
136        assert_eq!(buf.as_bytes(), &[1, 2, 3, 4]);
137        assert!(!buf.is_empty());
138        // On drop, data is zeroized
139    }
140}