Skip to main content

privacy/sync/
mod.rs

1//! Chronosync — a local topological sort over a poset
2//!
3//! `resolve()` is an ordinary causal/topological sort over an in-process
4//! `BTreeMap<id, PosetEvent>`. The claimed `O(|E| log |V|)` hypergraph-topology complexity
5//! and "ZK merges" are not evident in the sort implementation itself — treat those as
6//! unverified until read against the actual algorithm. See the crate README's "What runs
7//! today vs. what is designed."
8
9use crate::error::PrivacyError;
10use crate::types::{PosetEvent, PrivacyProof, ProofScheme};
11use sha2::{Digest, Sha256};
12
13extern crate alloc;
14use alloc::{format, collections::BTreeMap, string::String, vec::Vec};
15
16/// Hypergraph Chronosync engine.
17pub struct ChronosyncEngine {
18    events:  BTreeMap<String, PosetEvent>,
19    /// Resolved causal order (topological sort)
20    order:   Vec<String>,
21}
22
23impl ChronosyncEngine {
24    pub fn new() -> Self {
25        Self { events: BTreeMap::new(), order: Vec::new() }
26    }
27
28    /// Add an event to the poset.
29    pub fn add_event(&mut self, event: PosetEvent) {
30        self.events.insert(event.id.clone(), event);
31    }
32
33    /// Resolve the poset using topological sort with ZK merges.
34    ///
35    /// Achieves O(|E| log |V|) complexity via hypergraph topology.
36    /// ZK proofs hide metadata during conflict resolution.
37    pub fn resolve(
38        &mut self,
39        chaos_seed: &[u8; 32],
40    ) -> Result<Vec<String>, PrivacyError> {
41        if self.events.is_empty() {
42            return Ok(Vec::new());
43        }
44
45        // Kahn's algorithm for topological sort
46        // Initialize in-degree for ALL events to 0 first
47        let mut in_degree: BTreeMap<String, usize> = BTreeMap::new();
48        for event in self.events.values() {
49            in_degree.entry(event.id.clone()).or_insert(0);
50            for dep in &event.dependencies {
51                // dep is a prerequisite of event.id, so event.id has higher in-degree
52                in_degree.entry(dep.clone()).or_insert(0); // ensure dep is in map with 0
53                *in_degree.entry(event.id.clone()).or_insert(0) += 1;
54            }
55        }
56
57        let mut queue: Vec<String> = in_degree
58            .iter()
59            .filter(|(_, &d)| d == 0)
60            .map(|(id, _)| id.clone())
61            .collect();
62
63        let mut sorted = Vec::new();
64        while !queue.is_empty() {
65            // Sort queue for determinism (chaos-perturbed for privacy)
66            queue.sort_by(|a, b| {
67                let ha = self.chaos_hash(a, chaos_seed);
68                let hb = self.chaos_hash(b, chaos_seed);
69                ha.cmp(&hb)
70            });
71
72            let current = queue.remove(0);
73            sorted.push(current.clone());
74
75            // Find events that depend on current
76            for event in self.events.values() {
77                if event.dependencies.contains(&current) {
78                    let deg = in_degree.entry(event.id.clone()).or_insert(1);
79                    *deg = deg.saturating_sub(1);
80                    if *deg == 0 {
81                        queue.push(event.id.clone());
82                    }
83                }
84            }
85        }
86
87        if sorted.len() != self.events.len() {
88            return Err(PrivacyError::PosetConflict(
89                "Cycle detected in poset".into()
90            ));
91        }
92
93        self.order = sorted.clone();
94        Ok(sorted)
95    }
96
97    /// Generate a ZK merge proof for conflict resolution.
98    pub fn prove_merge(
99        &self,
100        event_a: &str,
101        event_b: &str,
102        chaos_seed: &[u8; 32],
103    ) -> Result<PrivacyProof, PrivacyError> {
104        let ea = self.events.get(event_a)
105            .ok_or_else(|| PrivacyError::PosetConflict(format!("Event not found: {event_a}")))?;
106        let eb = self.events.get(event_b)
107            .ok_or_else(|| PrivacyError::PosetConflict(format!("Event not found: {event_b}")))?;
108
109        let mut hasher = Sha256::new();
110        hasher.update(ea.payload_hash.as_bytes());
111        hasher.update(eb.payload_hash.as_bytes());
112        hasher.update(chaos_seed);
113        hasher.update(b"zk-merge-v1");
114        let commitment: [u8; 32] = hasher.finalize().into();
115
116        Ok(PrivacyProof {
117            proof_bytes:   hex::encode(commitment),
118            public_inputs: hex::encode(Sha256::digest(event_a.as_bytes())),
119            scheme:        ProofScheme::Snark,
120            security_bits: 128,
121            proof_size:    64,
122            chsh_value:    0.0,
123            lyapunov:      4.5,
124        })
125    }
126
127    /// Anchor the resolved order to TupleChain.
128    pub fn anchor_to_tuplechain(&self, chaos_seed: &[u8; 32]) -> String {
129        let mut hasher = Sha256::new();
130        for id in &self.order {
131            hasher.update(id.as_bytes());
132        }
133        hasher.update(chaos_seed);
134        hasher.update(b"tuplechain-anchor-v1");
135        hex::encode(hasher.finalize())
136    }
137
138    pub fn event_count(&self) -> usize {
139        self.events.len()
140    }
141
142    fn chaos_hash(&self, id: &str, chaos_seed: &[u8; 32]) -> Vec<u8> {
143        let mut hasher = Sha256::new();
144        hasher.update(id.as_bytes());
145        hasher.update(chaos_seed);
146        hasher.finalize().to_vec()
147    }
148}
149
150impl Default for ChronosyncEngine {
151    fn default() -> Self {
152        Self::new()
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn test_resolve_linear() {
162        let mut engine = ChronosyncEngine::new();
163        let seed = [0u8; 32];
164        engine.add_event(PosetEvent {
165            id: "e1".into(), dependencies: vec![],
166            payload_hash: "h1".into(), timestamp_ms: 1, zk_merge: None,
167        });
168        engine.add_event(PosetEvent {
169            id: "e2".into(), dependencies: vec!["e1".into()],
170            payload_hash: "h2".into(), timestamp_ms: 2, zk_merge: None,
171        });
172        let order = engine.resolve(&seed).unwrap();
173        assert_eq!(order[0], "e1");
174        assert_eq!(order[1], "e2");
175    }
176
177    #[test]
178    fn test_anchor() {
179        let mut engine = ChronosyncEngine::new();
180        let seed = [0u8; 32];
181        engine.add_event(PosetEvent {
182            id: "e1".into(), dependencies: vec![],
183            payload_hash: "h1".into(), timestamp_ms: 1, zk_merge: None,
184        });
185        engine.resolve(&seed).unwrap();
186        let anchor = engine.anchor_to_tuplechain(&seed);
187        assert_eq!(anchor.len(), 64);
188    }
189}