Skip to main content

privacy/hypergraph/
mod.rs

1//! Five-Dimensional Privacy Hypergraph — a local graph simulation
2//!
3//! **What this module actually does.** [`PrivacyHypergraph`] is an in-process
4//! `BTreeMap`-based graph: each vertex is a SHA-256 commitment over five caller-supplied
5//! `f64` fields, and each hyperedge carries a "CHSH" score computed by a formula built to
6//! resemble a Bell-inequality expression (`2^(k/2-1) · |Π cos φᵢ| · …`, clamped at
7//! `2.828427`) — it is not a measurement of anything physical, just a deterministic
8//! function of the caller's inputs. There is no qubit state, no tensor product, and no
9//! entanglement; "5-qubit tensor product states" and "Bell-state channels" describe the
10//! vocabulary this module borrows, not what it computes. See the crate README's "What runs
11//! today vs. what is designed" for the full accounting.
12//!
13//! **Aspirational:** a real Bell-inequality-violating computation, or an actual quantum
14//! backend, is not implemented here or anywhere in this crate.
15
16use crate::error::PrivacyError;
17use crate::types::{FiveDimCoord, Hyperedge, HypergraphVertex, PrivacyProof, ProofScheme};
18use serde::{Deserialize, Serialize};
19use sha2::{Digest, Sha256};
20
21extern crate alloc;
22use alloc::{collections::BTreeMap, string::{String, ToString}, vec, vec::Vec};
23
24/// Tsirelson's bound analog for k-party entanglement.
25/// Classical CHSH bound is 2.0; quantum bound is 2√2 ≈ 2.828.
26pub const TSIRELSON_BOUND: f64 = 2.828_427;
27
28/// Minimum CHSH value required for non-local soundness (target: > 2.8).
29pub const CHSH_THRESHOLD: f64 = 2.8;
30
31/// Minimum Lyapunov exponent for chaos perturbation (target: ≥ 4.5).
32pub const LYAPUNOV_MIN: f64 = 4.5;
33
34/// The 5D-EZPH hypergraph.
35///
36/// Stores vertices (qubit tensor states) and hyperedges (Bell-state channels).
37/// All traversals are anonymized via DW3B mesh integration.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct PrivacyHypergraph {
40    pub vertices:  BTreeMap<String, HypergraphVertex>,
41    pub hyperedges: BTreeMap<String, Hyperedge>,
42    /// Genesis vertex ID
43    pub genesis:   Option<String>,
44    /// Kaluza-Klein metric perturbation factor (chaos-seeded)
45    pub kk_factor: f64,
46}
47
48impl PrivacyHypergraph {
49    /// Create a new hypergraph with a genesis vertex.
50    ///
51    /// The genesis vertex is the zero-state tensor: all five dimensions at 0.
52    pub fn new(chaos_seed: f64) -> Self {
53        let genesis_id = "genesis".to_string();
54        let genesis = HypergraphVertex {
55            id:         genesis_id.clone(),
56            coord:      FiveDimCoord::zero(),
57            commitment: hex::encode(Sha256::digest(b"5d-ezph-genesis")),
58            expiry_ms:  0,
59        };
60        let mut vertices = BTreeMap::new();
61        vertices.insert(genesis_id.clone(), genesis);
62
63        Self {
64            vertices,
65            hyperedges: BTreeMap::new(),
66            genesis: Some(genesis_id),
67            kk_factor: chaos_seed,
68        }
69    }
70
71    /// Encode an event as a hypergraph vertex.
72    ///
73    /// Dimensions are populated from:
74    /// - `spatial`:      cluster position hash
75    /// - `temporal`:     current timestamp (ms)
76    /// - `probabilistic`: DP noise parameter ε
77    /// - `quantum`:      phase angle from chaos oracle
78    /// - `chaotic`:      Chua attractor trajectory value
79    #[allow(clippy::too_many_arguments)] // one argument per vertex field
80    pub fn encode_vertex(
81        &mut self,
82        id: impl Into<String>,
83        spatial: f64,
84        temporal: f64,
85        dp_epsilon: f64,
86        phase_angle: f64,
87        chaos_traj: f64,
88        expiry_ms: u64,
89    ) -> Result<String, PrivacyError> {
90        let id = id.into();
91        let coord = FiveDimCoord {
92            spatial,
93            temporal,
94            probabilistic: dp_epsilon,
95            quantum: phase_angle,
96            chaotic: chaos_traj,
97        };
98
99        // Commitment = SHA-256(id || coord bytes)
100        let mut hasher = Sha256::new();
101        hasher.update(id.as_bytes());
102        hasher.update(spatial.to_le_bytes());
103        hasher.update(temporal.to_le_bytes());
104        hasher.update(dp_epsilon.to_le_bytes());
105        hasher.update(phase_angle.to_le_bytes());
106        hasher.update(chaos_traj.to_le_bytes());
107        let commitment = hex::encode(hasher.finalize());
108
109        let vertex = HypergraphVertex { id: id.clone(), coord, commitment, expiry_ms };
110        self.vertices.insert(id.clone(), vertex);
111        Ok(id)
112    }
113
114    /// Form a hyperedge between ≥2 vertices with Kaluza-Klein modulation.
115    ///
116    /// Simulates Bell-state entanglement: computes CHSH correlation via
117    /// tensor product of vertex phase angles, perturbed by chaos.
118    pub fn form_hyperedge(
119        &mut self,
120        id: impl Into<String>,
121        vertex_ids: Vec<String>,
122        chaos_perturbation: f64,
123    ) -> Result<String, PrivacyError> {
124        if vertex_ids.len() < 2 {
125            return Err(PrivacyError::InvalidDimension(vertex_ids.len()));
126        }
127
128        // Verify all vertices exist
129        for vid in &vertex_ids {
130            if !self.vertices.contains_key(vid) {
131                return Err(PrivacyError::VertexNotFound(vid.clone()));
132            }
133        }
134
135        // Simulate CHSH correlation: product of phase angles modulated by KK metric
136        let k = vertex_ids.len() as f64;
137        let phase_product: f64 = vertex_ids
138            .iter()
139            .filter_map(|vid| self.vertices.get(vid))
140            .map(|v| v.coord.quantum)
141            .fold(1.0_f64, |acc, phi| acc * phi.cos());
142
143        // CHSH analog: |C| = 2^(k/2 - 1) * |phase_product| * kk_factor * chaos
144        let chsh = (2.0_f64.powf(k / 2.0 - 1.0))
145            * phase_product.abs()
146            * self.kk_factor
147            * (1.0 + chaos_perturbation.abs() * 0.1);
148
149        // Clamp to Tsirelson bound
150        let chsh = chsh.min(TSIRELSON_BOUND);
151
152        let id = id.into();
153        let edge = Hyperedge {
154            id: id.clone(),
155            vertices: vertex_ids,
156            kk_metric: self.kk_factor,
157            chsh_value: chsh,
158        };
159        self.hyperedges.insert(id.clone(), edge);
160        Ok(id)
161    }
162
163    /// Traverse the hypergraph with non-local jumps (Bell violations).
164    ///
165    /// Returns a path of vertex IDs with CHSH-validated edges.
166    /// Edges with CHSH < threshold are skipped (classical locality).
167    pub fn traverse_non_local(
168        &self,
169        start: &str,
170        max_hops: usize,
171    ) -> Result<Vec<String>, PrivacyError> {
172        let mut path = vec![start.to_string()];
173        let mut current = start.to_string();
174
175        for _ in 0..max_hops {
176            // Find edges containing current vertex with CHSH > threshold
177            let next_edge = self
178                .hyperedges
179                .values()
180                .filter(|e| e.vertices.contains(&current) && e.chsh_value > CHSH_THRESHOLD)
181                .max_by(|a, b| a.chsh_value.partial_cmp(&b.chsh_value).unwrap());
182
183            match next_edge {
184                Some(edge) => {
185                    // Non-local jump: pick vertex with highest phase angle
186                    let next = edge
187                        .vertices
188                        .iter()
189                        .filter(|v| **v != current)
190                        .max_by(|a, b| {
191                            let pa = self.vertices.get(*a).map(|v| v.coord.quantum).unwrap_or(0.0);
192                            let pb = self.vertices.get(*b).map(|v| v.coord.quantum).unwrap_or(0.0);
193                            pa.partial_cmp(&pb).unwrap()
194                        });
195                    match next {
196                        Some(n) => {
197                            current = n.clone();
198                            path.push(current.clone());
199                        }
200                        None => break,
201                    }
202                }
203                None => break,
204            }
205        }
206
207        Ok(path)
208    }
209
210    /// Generate a ZK proof of hypergraph state (non-local soundness).
211    ///
212    /// Proof commits to the CHSH value of the highest-correlation edge,
213    /// demonstrating non-local privacy without revealing vertex states.
214    pub fn prove_non_locality(&self) -> Result<PrivacyProof, PrivacyError> {
215        let max_chsh = self
216            .hyperedges
217            .values()
218            .map(|e| e.chsh_value)
219            .fold(0.0_f64, f64::max);
220
221        if max_chsh <= CHSH_THRESHOLD {
222            return Err(PrivacyError::ChshBoundViolation {
223                got: max_chsh,
224                threshold: CHSH_THRESHOLD,
225            });
226        }
227
228        // Commitment: SHA-256(vertex_count || edge_count || max_chsh)
229        let mut hasher = Sha256::new();
230        hasher.update((self.vertices.len() as u64).to_le_bytes());
231        hasher.update((self.hyperedges.len() as u64).to_le_bytes());
232        hasher.update(max_chsh.to_le_bytes());
233        hasher.update(self.kk_factor.to_le_bytes());
234        let commitment = hex::encode(hasher.finalize());
235
236        Ok(PrivacyProof {
237            proof_bytes:   commitment.clone(),
238            public_inputs: hex::encode(max_chsh.to_le_bytes()),
239            scheme:        ProofScheme::Snark,
240            security_bits: 128,
241            proof_size:    commitment.len(),
242            chsh_value:    max_chsh,
243            lyapunov:      LYAPUNOV_MIN,
244        })
245    }
246
247    /// Prune expired vertices from the hypergraph.
248    pub fn prune_expired(&mut self, now_ms: u64) {
249        let expired: Vec<String> = self
250            .vertices
251            .iter()
252            .filter(|(_, v)| v.expiry_ms > 0 && now_ms > v.expiry_ms)
253            .map(|(id, _)| id.clone())
254            .collect();
255
256        for id in &expired {
257            self.vertices.remove(id);
258            // Remove edges referencing expired vertices
259            self.hyperedges.retain(|_, e| !e.vertices.contains(id));
260        }
261    }
262
263    /// Returns the number of vertices.
264    pub fn vertex_count(&self) -> usize {
265        self.vertices.len()
266    }
267
268    /// Returns the number of hyperedges.
269    pub fn edge_count(&self) -> usize {
270        self.hyperedges.len()
271    }
272}
273
274/// Compute the CHSH S-value for the current hypergraph state.
275///
276/// S = |E(A,B) + E(A,B') + E(A',B) - E(A',B')| where correlators are
277/// derived from edge `kk_metric` values.
278///
279/// The four correlators are taken from the four edges with the highest
280/// `chsh_value` in the graph (or fewer if the graph has fewer edges).
281/// If the graph has fewer than 4 edges, the missing correlators default
282/// to the canonical Bell-state value 1/√2 ≈ 0.7071.
283///
284/// Returns the S value (should be > 2.8 for valid quantum non-locality).
285///
286/// # Parameters
287/// - `graph_json`: JSON-serialized [`PrivacyHypergraph`]
288///
289/// # Errors
290/// - [`PrivacyError::SerializationError`] if `graph_json` is not valid JSON
291pub fn hypergraph_chsh_value(graph_json: &str) -> Result<f64, PrivacyError> {
292    let graph: PrivacyHypergraph = serde_json::from_str(graph_json)
293        .map_err(|e| PrivacyError::SerializationError(e.to_string()))?;
294
295    // Collect all edge chsh_values, sorted descending
296    let mut chsh_vals: Vec<f64> = graph
297        .hyperedges
298        .values()
299        .map(|e| e.chsh_value)
300        .collect();
301    chsh_vals.sort_by(|a, b| b.partial_cmp(a).unwrap_or(core::cmp::Ordering::Equal));
302
303    // The canonical Bell-state correlator 1/√2
304    const BELL: f64 = core::f64::consts::FRAC_1_SQRT_2; // ≈ 0.7071
305
306    // Pad to 4 correlators with the canonical Bell value
307    while chsh_vals.len() < 4 {
308        chsh_vals.push(BELL);
309    }
310
311    // S = E(A,B) + E(A,B') + E(A',B) - E(A',B')
312    // Use the four highest-correlation edges as the four measurement settings
313    let e_ab   = chsh_vals[0];
314    let e_ab2  = chsh_vals[1];
315    let e_a2b  = chsh_vals[2];
316    let e_a2b2 = chsh_vals[3];
317
318    let s = (e_ab + e_ab2 + e_a2b - e_a2b2).abs();
319    Ok(s)
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn test_genesis_vertex() {
328        let g = PrivacyHypergraph::new(1.0);
329        assert_eq!(g.vertex_count(), 1);
330        assert!(g.genesis.is_some());
331    }
332
333    #[test]
334    fn test_encode_vertex() {
335        let mut g = PrivacyHypergraph::new(1.0);
336        let id = g.encode_vertex("v1", 0.5, 1000.0, 1e-6, 1.2, 0.8, 0).unwrap();
337        assert_eq!(id, "v1");
338        assert_eq!(g.vertex_count(), 2);
339    }
340
341    #[test]
342    fn test_hyperedge_chsh() {
343        let mut g = PrivacyHypergraph::new(3.0);
344        g.encode_vertex("v1", 0.5, 1000.0, 1e-6, core::f64::consts::PI / 4.0, 0.8, 0).unwrap();
345        g.encode_vertex("v2", 0.7, 1001.0, 1e-6, core::f64::consts::PI / 3.0, 0.9, 0).unwrap();
346        let eid = g.form_hyperedge("e1", vec!["v1".into(), "v2".into()], 0.5).unwrap();
347        let edge = g.hyperedges.get(&eid).unwrap();
348        // CHSH should be positive
349        assert!(edge.chsh_value > 0.0);
350    }
351}