Skip to main content

archimedes_kernel/persistence/
mod.rs

1use std::fs;
2use std::io;
3use std::path::Path;
4
5use ed25519_dalek::{Signature, Signer, SigningKey, VerifyingKey};
6use serde::de::DeserializeOwned;
7use serde::{Deserialize, Serialize};
8
9use crate::{Reality, RealitySnapshot};
10
11#[derive(Debug)]
12pub enum PersistenceError {
13    Io(io::Error),
14    Serialization(bincode::Error),
15    IntegrityFailure(&'static str),
16    Signature(&'static str),
17}
18
19impl std::fmt::Display for PersistenceError {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            PersistenceError::Io(e) => write!(f, "IO error: {}", e),
23            PersistenceError::Serialization(e) => write!(f, "Serialization error: {}", e),
24            PersistenceError::IntegrityFailure(msg) => write!(f, "Integrity failure: {}", msg),
25            PersistenceError::Signature(msg) => write!(f, "Signature failure: {}", msg),
26        }
27    }
28}
29
30impl std::error::Error for PersistenceError {
31    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
32        match self {
33            PersistenceError::Io(e) => Some(e),
34            PersistenceError::Serialization(e) => Some(e),
35            PersistenceError::IntegrityFailure(_) => None,
36            PersistenceError::Signature(_) => None,
37        }
38    }
39}
40
41fn write_bincode<T: Serialize>(path: &Path, value: &T) -> Result<(), PersistenceError> {
42    let bytes = bincode::serialize(value).map_err(PersistenceError::Serialization)?;
43    fs::write(path, bytes).map_err(PersistenceError::Io)
44}
45
46fn read_bincode<T: DeserializeOwned>(path: &Path) -> Result<T, PersistenceError> {
47    let bytes = fs::read(path).map_err(PersistenceError::Io)?;
48    bincode::deserialize(&bytes).map_err(PersistenceError::Serialization)
49}
50
51pub fn save_reality(reality: &Reality, path: &Path) -> Result<(), PersistenceError> {
52    write_bincode(path, reality)
53}
54
55pub fn load_reality(path: &Path) -> Result<Reality, PersistenceError> {
56    let reality: Reality = read_bincode(path)?;
57    if !reality.memory_integrity() {
58        return Err(PersistenceError::IntegrityFailure(
59            "movement memory integrity failed",
60        ));
61    }
62    if reality.drift_check().hidden_drift_required {
63        return Err(PersistenceError::IntegrityFailure("hidden drift detected"));
64    }
65    Ok(reality)
66}
67
68pub fn save_snapshot(snapshot: &RealitySnapshot, path: &Path) -> Result<(), PersistenceError> {
69    write_bincode(path, snapshot)
70}
71
72pub fn load_snapshot(path: &Path) -> Result<RealitySnapshot, PersistenceError> {
73    read_bincode(path)
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub struct SignedReality {
78    pub reality: Reality,
79    pub public_key: [u8; 32],
80    pub signature: Vec<u8>,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84pub struct SignedSnapshot {
85    pub snapshot: RealitySnapshot,
86    pub public_key: [u8; 32],
87    pub signature: Vec<u8>,
88}
89
90fn sign_bytes(message: &[u8], signing_key: &SigningKey) -> ([u8; 32], [u8; 64]) {
91    let signature: Signature = signing_key.sign(message);
92    (signing_key.verifying_key().to_bytes(), signature.to_bytes())
93}
94
95fn verify_bytes(
96    message: &[u8],
97    public_key: &[u8; 32],
98    signature: &[u8],
99) -> Result<(), PersistenceError> {
100    let sig_bytes: [u8; 64] = signature
101        .try_into()
102        .map_err(|_| PersistenceError::Signature("invalid signature length"))?;
103    let verifying_key = VerifyingKey::from_bytes(public_key)
104        .map_err(|_| PersistenceError::Signature("invalid public key"))?;
105    let signature = Signature::from_bytes(&sig_bytes);
106    verifying_key
107        .verify_strict(message, &signature)
108        .map_err(|_| PersistenceError::Signature("invalid signature"))
109}
110
111pub fn sign_reality(
112    reality: &Reality,
113    signing_key: &SigningKey,
114) -> Result<SignedReality, PersistenceError> {
115    let message = bincode::serialize(reality).map_err(PersistenceError::Serialization)?;
116    let (public_key, signature) = sign_bytes(&message, signing_key);
117    Ok(SignedReality {
118        reality: reality.clone(),
119        public_key,
120        signature: signature.to_vec(),
121    })
122}
123
124pub fn save_signed_reality(signed: &SignedReality, path: &Path) -> Result<(), PersistenceError> {
125    write_bincode(path, signed)
126}
127
128pub fn load_signed_reality(
129    path: &Path,
130    expected_public_key: &[u8; 32],
131) -> Result<SignedReality, PersistenceError> {
132    let signed: SignedReality = read_bincode(path)?;
133    let message = bincode::serialize(&signed.reality).map_err(PersistenceError::Serialization)?;
134    verify_bytes(&message, expected_public_key, &signed.signature)?;
135    if !signed.reality.memory_integrity() {
136        return Err(PersistenceError::IntegrityFailure(
137            "movement memory integrity failed",
138        ));
139    }
140    if signed.reality.drift_check().hidden_drift_required {
141        return Err(PersistenceError::IntegrityFailure("hidden drift detected"));
142    }
143    Ok(signed)
144}
145
146pub fn sign_snapshot(
147    snapshot: &RealitySnapshot,
148    signing_key: &SigningKey,
149) -> Result<SignedSnapshot, PersistenceError> {
150    let message = bincode::serialize(snapshot).map_err(PersistenceError::Serialization)?;
151    let (public_key, signature) = sign_bytes(&message, signing_key);
152    Ok(SignedSnapshot {
153        snapshot: snapshot.clone(),
154        public_key,
155        signature: signature.to_vec(),
156    })
157}
158
159pub fn save_signed_snapshot(signed: &SignedSnapshot, path: &Path) -> Result<(), PersistenceError> {
160    write_bincode(path, signed)
161}
162
163pub fn load_signed_snapshot(
164    path: &Path,
165    expected_public_key: &[u8; 32],
166) -> Result<SignedSnapshot, PersistenceError> {
167    let signed: SignedSnapshot = read_bincode(path)?;
168    let message = bincode::serialize(&signed.snapshot).map_err(PersistenceError::Serialization)?;
169    verify_bytes(&message, expected_public_key, &signed.signature)?;
170    Ok(signed)
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::{perform_movement_sequence, Boundary, Event, Identity, Law, State};
177    use rand::rngs::OsRng;
178
179    fn test_reality() -> Reality {
180        Reality::new(
181            Identity("persist-test".to_string()),
182            Boundary {
183                allowed_values: vec![
184                    "before".to_string(),
185                    "after".to_string(),
186                    "done".to_string(),
187                ],
188            },
189            Law {
190                allowed_transitions: vec![
191                    ("before".to_string(), "after".to_string()),
192                    ("after".to_string(), "done".to_string()),
193                ],
194            },
195            State {
196                field: "before".to_string(),
197            },
198        )
199    }
200
201    #[test]
202    fn reality_roundtrip_preserves_integrity() {
203        let mut reality = test_reality();
204        let events = vec![
205            Event {
206                proposed_field: "after".to_string(),
207            },
208            Event {
209                proposed_field: "done".to_string(),
210            },
211        ];
212        let _ = perform_movement_sequence(&mut reality, events).unwrap();
213
214        let path = std::env::temp_dir().join("archimedes-test-reality.bin");
215        save_reality(&reality, &path).unwrap();
216        let loaded = load_reality(&path).unwrap();
217        assert_eq!(reality, loaded);
218        assert!(loaded.memory_integrity());
219        assert!(!loaded.drift_check().hidden_drift_required);
220        let _ = std::fs::remove_file(&path);
221    }
222
223    #[test]
224    fn snapshot_roundtrip_matches_original() {
225        let reality = test_reality();
226        let snapshot = reality.snapshot();
227        let path = std::env::temp_dir().join("archimedes-test-snapshot.bin");
228        save_snapshot(&snapshot, &path).unwrap();
229        let loaded = load_snapshot(&path).unwrap();
230        assert_eq!(snapshot, loaded);
231        let _ = std::fs::remove_file(&path);
232    }
233
234    #[test]
235    fn tampered_reality_file_is_rejected() {
236        let mut reality = test_reality();
237        let event = Event {
238            proposed_field: "after".to_string(),
239        };
240        let _ = perform_movement_sequence(&mut reality, vec![event]).unwrap();
241
242        let path = std::env::temp_dir().join("archimedes-test-tampered.bin");
243        save_reality(&reality, &path).unwrap();
244
245        let mut bytes = std::fs::read(&path).unwrap();
246        let mid = bytes.len() / 2;
247        bytes[mid] ^= 0xFF;
248        std::fs::write(&path, &bytes).unwrap();
249
250        let result = load_reality(&path);
251        assert!(result.is_err());
252        let _ = std::fs::remove_file(&path);
253    }
254
255    #[test]
256    fn signed_reality_roundtrip_works() {
257        let mut reality = test_reality();
258        let event = Event {
259            proposed_field: "after".to_string(),
260        };
261        let _ = perform_movement_sequence(&mut reality, vec![event]).unwrap();
262
263        let mut csprng = OsRng;
264        let signing_key = SigningKey::generate(&mut csprng);
265        let public_key = signing_key.verifying_key().to_bytes();
266
267        let signed = sign_reality(&reality, &signing_key).unwrap();
268        let path = std::env::temp_dir().join("archimedes-test-signed-reality.bin");
269        save_signed_reality(&signed, &path).unwrap();
270
271        let loaded = load_signed_reality(&path, &public_key).unwrap();
272        assert_eq!(loaded.reality, reality);
273        assert_eq!(loaded.public_key, public_key);
274        let _ = std::fs::remove_file(&path);
275    }
276
277    #[test]
278    fn signed_reality_rejects_wrong_key() {
279        let mut reality = test_reality();
280        let event = Event {
281            proposed_field: "after".to_string(),
282        };
283        let _ = perform_movement_sequence(&mut reality, vec![event]).unwrap();
284
285        let mut csprng = OsRng;
286        let signing_key = SigningKey::generate(&mut csprng);
287        let wrong_key = SigningKey::generate(&mut csprng).verifying_key().to_bytes();
288
289        let signed = sign_reality(&reality, &signing_key).unwrap();
290        let path = std::env::temp_dir().join("archimedes-test-signed-wrong.bin");
291        save_signed_reality(&signed, &path).unwrap();
292
293        let result = load_signed_reality(&path, &wrong_key);
294        assert!(result.is_err());
295        let _ = std::fs::remove_file(&path);
296    }
297
298    #[test]
299    fn signed_snapshot_roundtrip_works() {
300        let reality = test_reality();
301        let snapshot = reality.snapshot();
302
303        let mut csprng = OsRng;
304        let signing_key = SigningKey::generate(&mut csprng);
305        let public_key = signing_key.verifying_key().to_bytes();
306
307        let signed = sign_snapshot(&snapshot, &signing_key).unwrap();
308        let path = std::env::temp_dir().join("archimedes-test-signed-snapshot.bin");
309        save_signed_snapshot(&signed, &path).unwrap();
310
311        let loaded = load_signed_snapshot(&path, &public_key).unwrap();
312        assert_eq!(loaded.snapshot, snapshot);
313        let _ = std::fs::remove_file(&path);
314    }
315}