1use serde::{Deserialize, Serialize};
36
37use crate::context::{ContextFact, ContextKey, ContextState};
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Serialize, Deserialize)]
65pub struct LamportClock {
66 time: u64,
67}
68
69impl LamportClock {
70 #[must_use]
72 pub const fn new() -> Self {
73 Self { time: 0 }
74 }
75
76 #[must_use]
78 pub const fn time(&self) -> u64 {
79 self.time
80 }
81
82 pub fn tick(&mut self) -> u64 {
85 self.time += 1;
86 self.time
87 }
88
89 pub fn update(&mut self, received: u64) -> u64 {
93 self.time = self.time.max(received) + 1;
94 self.time
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum ContentHashError {
105 InvalidHex,
107 InvalidLength,
109}
110
111impl std::fmt::Display for ContentHashError {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 Self::InvalidHex => write!(f, "invalid hexadecimal character"),
115 Self::InvalidLength => write!(f, "invalid string length (expected 64 hex chars)"),
116 }
117 }
118}
119
120impl std::error::Error for ContentHashError {}
121
122#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
129pub struct ContentHash(pub [u8; 32]);
130
131impl ContentHash {
132 #[must_use]
134 pub fn compute(content: &str) -> Self {
135 use sha2::{Digest, Sha256};
136 let mut hasher = Sha256::new();
137 hasher.update(content.as_bytes());
138 Self(hasher.finalize().into())
139 }
140
141 #[must_use]
143 pub fn compute_fact(fact: &ContextFact) -> Self {
144 let payload = fact
145 .to_wire()
146 .map(|wire| wire.payload.payload.to_string())
147 .unwrap_or_else(|error| error.to_string());
148 let combined = format!("{:?}|{}|{}", fact.key(), fact.id(), payload);
149 Self::compute(&combined)
150 }
151
152 #[must_use]
154 pub fn combine(left: &Self, right: &Self) -> Self {
155 use sha2::{Digest, Sha256};
156 let mut hasher = Sha256::new();
157 hasher.update(left.0);
158 hasher.update(right.0);
159 Self(hasher.finalize().into())
160 }
161
162 #[must_use]
164 pub fn verify(&self, content: &str) -> bool {
165 *self == Self::compute(content)
166 }
167
168 #[must_use]
170 pub fn to_hex(&self) -> String {
171 self.0.iter().map(|b| format!("{b:02x}")).collect()
172 }
173
174 pub fn from_hex(s: &str) -> Result<Self, ContentHashError> {
180 if s.len() != 64 {
181 return Err(ContentHashError::InvalidLength);
182 }
183 let mut result = [0u8; 32];
184 for (i, chunk) in s.as_bytes().chunks(2).enumerate() {
185 let high = Self::hex_char_to_nibble(chunk[0])?;
186 let low = Self::hex_char_to_nibble(chunk[1])?;
187 result[i] = (high << 4) | low;
188 }
189 Ok(Self(result))
190 }
191
192 fn hex_char_to_nibble(c: u8) -> Result<u8, ContentHashError> {
193 match c {
194 b'0'..=b'9' => Ok(c - b'0'),
195 b'a'..=b'f' => Ok(c - b'a' + 10),
196 b'A'..=b'F' => Ok(c - b'A' + 10),
197 _ => Err(ContentHashError::InvalidHex),
198 }
199 }
200}
201
202impl std::fmt::Display for ContentHash {
203 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
204 write!(f, "{}", &self.to_hex()[..16]) }
206}
207
208#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
220pub struct MerkleRoot(pub ContentHash);
221
222impl MerkleRoot {
223 #[must_use]
229 pub fn compute(hashes: &[ContentHash]) -> Self {
230 if hashes.is_empty() {
231 return Self(ContentHash::compute(""));
232 }
233
234 let mut current_level: Vec<ContentHash> = hashes.to_vec();
235
236 loop {
239 if current_level.len() == 1 {
240 if hashes.len() == 1 {
243 return Self(ContentHash::combine(¤t_level[0], ¤t_level[0]));
244 }
245 return Self(current_level.into_iter().next().unwrap());
246 }
247
248 let mut next_level = Vec::new();
249
250 for chunk in current_level.chunks(2) {
251 let combined = if chunk.len() == 2 {
252 ContentHash::combine(&chunk[0], &chunk[1])
253 } else {
254 ContentHash::combine(&chunk[0], &chunk[0])
256 };
257 next_level.push(combined);
258 }
259
260 current_level = next_level;
261 }
262 }
263
264 #[must_use]
268 pub fn from_context(ctx: &ContextState) -> Self {
269 let mut all_hashes: Vec<ContentHash> = Vec::new();
270 let mut keys: Vec<_> = ctx.all_keys();
271 keys.sort();
272
273 for key in keys {
274 for fact in ctx.get(key) {
275 all_hashes.push(ContentHash::compute_fact(fact));
276 }
277 }
278
279 Self::compute(&all_hashes)
280 }
281
282 #[must_use]
284 pub fn to_hex(&self) -> String {
285 self.0.to_hex()
286 }
287}
288
289#[derive(Debug, Clone, Serialize)]
297pub struct TrackedContext {
298 pub context: ContextState,
300 pub clock: LamportClock,
302 merkle_root: Option<MerkleRoot>,
304 fact_hashes: Vec<(ContextKey, String, ContentHash)>,
306}
307
308impl TrackedContext {
309 #[must_use]
311 pub fn new(context: ContextState) -> Self {
312 let mut tracked = Self {
313 context,
314 clock: LamportClock::new(),
315 merkle_root: None,
316 fact_hashes: Vec::new(),
317 };
318 tracked.recompute_hashes();
319 tracked
320 }
321
322 #[must_use]
324 pub fn empty() -> Self {
325 Self::new(ContextState::new())
326 }
327
328 #[must_use]
330 pub fn clock_time(&self) -> u64 {
331 self.clock.time()
332 }
333
334 pub fn tick(&mut self) -> u64 {
336 self.clock.tick()
337 }
338
339 #[must_use]
341 pub fn merkle_root(&mut self) -> &MerkleRoot {
342 if self.merkle_root.is_none() {
343 self.merkle_root = Some(MerkleRoot::from_context(&self.context));
344 }
345 self.merkle_root.as_ref().unwrap()
346 }
347
348 #[must_use]
352 pub fn verify_integrity(&self) -> bool {
353 for (key, id, expected_hash) in &self.fact_hashes {
354 if let Some(fact) = self
355 .context
356 .get(*key)
357 .iter()
358 .find(|f| f.id().as_str() == id)
359 {
360 if ContentHash::compute_fact(fact) != *expected_hash {
361 return false;
362 }
363 } else {
364 return false; }
366 }
367 true
368 }
369
370 fn recompute_hashes(&mut self) {
372 self.fact_hashes.clear();
373 for key in self.context.all_keys() {
374 for fact in self.context.get(key) {
375 let hash = ContentHash::compute_fact(fact);
376 self.fact_hashes.push((key, fact.id().to_string(), hash));
377 }
378 }
379 self.merkle_root = None; }
381
382 pub(crate) fn add_fact(
388 &mut self,
389 fact: ContextFact,
390 ) -> Result<bool, crate::error::ConvergeError> {
391 let key = fact.key();
392 let id = fact.id().to_string();
393 let hash = ContentHash::compute_fact(&fact);
394
395 let changed = self.context.add_fact(fact)?;
396
397 if changed {
398 self.fact_hashes.push((key, id, hash));
399 self.merkle_root = None; self.clock.tick();
401 }
402
403 Ok(changed)
404 }
405
406 pub fn extract_proof(&mut self) -> IntegrityProof {
408 let merkle_root = self.merkle_root().clone();
409 IntegrityProof {
410 merkle_root,
411 clock_time: self.clock_time(),
412 fact_count: self.fact_hashes.len(),
413 }
414 }
415}
416
417impl Default for TrackedContext {
418 fn default() -> Self {
419 Self::empty()
420 }
421}
422
423#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
434pub struct IntegrityProof {
435 pub merkle_root: MerkleRoot,
437 pub clock_time: u64,
439 pub fact_count: usize,
441}
442
443#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
456 fn lamport_clock_starts_at_zero() {
457 let clock = LamportClock::new();
458 assert_eq!(clock.time(), 0);
459 }
460
461 #[test]
462 fn lamport_clock_tick_increments() {
463 let mut clock = LamportClock::new();
464 assert_eq!(clock.tick(), 1);
465 assert_eq!(clock.tick(), 2);
466 assert_eq!(clock.tick(), 3);
467 }
468
469 #[test]
470 fn lamport_clock_update_takes_max_plus_one() {
471 let mut clock = LamportClock::new();
472 clock.tick(); clock.tick(); assert_eq!(clock.update(5), 6);
477
478 assert_eq!(clock.update(3), 7);
480 }
481
482 #[test]
487
488 fn content_hash_is_deterministic() {
489 let h1 = ContentHash::compute("hello world");
490 let h2 = ContentHash::compute("hello world");
491 assert_eq!(h1, h2);
492 }
493
494 #[test]
495
496 fn content_hash_changes_with_content() {
497 let h1 = ContentHash::compute("hello");
498 let h2 = ContentHash::compute("world");
499 assert_ne!(h1, h2);
500 }
501
502 #[test]
503
504 fn content_hash_verify_works() {
505 let hash = ContentHash::compute("test");
506 assert!(hash.verify("test"));
507 assert!(!hash.verify("modified"));
508 }
509
510 #[test]
511
512 fn content_hash_hex_roundtrip() {
513 let original = ContentHash::compute("test content");
514 let hex = original.to_hex();
515 let restored = ContentHash::from_hex(&hex).unwrap();
516 assert_eq!(original, restored);
517 }
518
519 #[test]
524
525 fn merkle_root_empty_list() {
526 let root = MerkleRoot::compute(&[]);
527 let empty_hash = ContentHash::compute("");
528 assert_eq!(root.0, empty_hash);
529 }
530
531 #[test]
532
533 fn merkle_root_single_element() {
534 let h = ContentHash::compute("only element");
535 let root = MerkleRoot::compute(std::slice::from_ref(&h));
536 let expected = ContentHash::combine(&h, &h);
537 assert_eq!(root.0, expected);
538 }
539
540 #[test]
541
542 fn merkle_root_two_elements() {
543 let h1 = ContentHash::compute("first");
544 let h2 = ContentHash::compute("second");
545 let root = MerkleRoot::compute(&[h1.clone(), h2.clone()]);
546 let expected = ContentHash::combine(&h1, &h2);
547 assert_eq!(root.0, expected);
548 }
549
550 #[test]
551
552 fn merkle_root_is_deterministic() {
553 let hashes = vec![
554 ContentHash::compute("a"),
555 ContentHash::compute("b"),
556 ContentHash::compute("c"),
557 ];
558 let root1 = MerkleRoot::compute(&hashes);
559 let root2 = MerkleRoot::compute(&hashes);
560 assert_eq!(root1, root2);
561 }
562
563 #[test]
564
565 fn merkle_root_changes_with_different_content() {
566 let hashes1 = vec![ContentHash::compute("a"), ContentHash::compute("b")];
567 let hashes2 = vec![ContentHash::compute("a"), ContentHash::compute("c")];
568 let root1 = MerkleRoot::compute(&hashes1);
569 let root2 = MerkleRoot::compute(&hashes2);
570 assert_ne!(root1, root2);
571 }
572
573 #[test]
578 fn tracked_context_starts_empty() {
579 let tracked = TrackedContext::empty();
580 assert_eq!(tracked.clock_time(), 0);
581 assert!(!tracked.context.has(ContextKey::Seeds));
582 }
583
584 #[test]
585 fn tracked_context_ticks_on_add() {
586 let mut tracked = TrackedContext::empty();
587 assert_eq!(tracked.clock_time(), 0);
588
589 tracked
590 .add_fact(crate::context::new_fact(ContextKey::Seeds, "s1", "seed"))
591 .unwrap();
592 assert_eq!(tracked.clock_time(), 1);
593
594 tracked
595 .add_fact(crate::context::new_fact(ContextKey::Seeds, "s2", "seed2"))
596 .unwrap();
597 assert_eq!(tracked.clock_time(), 2);
598 }
599
600 #[test]
601 fn tracked_context_computes_merkle_root() {
602 let mut tracked = TrackedContext::empty();
603 tracked
604 .add_fact(crate::context::new_fact(ContextKey::Seeds, "s1", "first"))
605 .unwrap();
606 tracked
607 .add_fact(crate::context::new_fact(ContextKey::Seeds, "s2", "second"))
608 .unwrap();
609
610 let root1 = tracked.merkle_root().clone();
611
612 tracked
614 .add_fact(crate::context::new_fact(ContextKey::Seeds, "s3", "third"))
615 .unwrap();
616 let root2 = tracked.merkle_root().clone();
617
618 assert_ne!(root1, root2);
619 }
620
621 #[test]
622 fn tracked_context_verifies_integrity() {
623 let mut tracked = TrackedContext::empty();
624 tracked
625 .add_fact(crate::context::new_fact(ContextKey::Seeds, "s1", "test"))
626 .unwrap();
627
628 assert!(tracked.verify_integrity());
629 }
630
631 #[test]
632 fn integrity_proof_serializes() {
633 let mut tracked = TrackedContext::empty();
634 tracked
635 .add_fact(crate::context::new_fact(ContextKey::Seeds, "s1", "test"))
636 .unwrap();
637 let proof = tracked.extract_proof();
638 assert_eq!(proof.clock_time, 1);
639 assert_eq!(proof.fact_count, 1);
640
641 let json = serde_json::to_string(&proof).unwrap();
642 let deser: IntegrityProof = serde_json::from_str(&json).unwrap();
643 assert_eq!(proof, deser);
644 }
645
646 #[test]
647 fn integrity_proof_changes_with_different_facts() {
648 let mut t1 = TrackedContext::empty();
649 t1.add_fact(crate::context::new_fact(ContextKey::Seeds, "s1", "alpha"))
650 .unwrap();
651 let proof1 = t1.extract_proof();
652
653 let mut t2 = TrackedContext::empty();
654 t2.add_fact(crate::context::new_fact(ContextKey::Seeds, "s1", "beta"))
655 .unwrap();
656 let proof2 = t2.extract_proof();
657
658 assert_ne!(proof1.merkle_root, proof2.merkle_root);
659 assert_eq!(proof1.clock_time, proof2.clock_time);
660 assert_eq!(proof1.fact_count, proof2.fact_count);
661 }
662}