mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
feat(quintuplet): 5 watchdog consensus engine — full implementation
3 domain expert agents built the quintuplet consensus system:
AGENT 1 — DistributedSystemsExpert: quintuplet_consensus.py
- ByzantineConsensus: 5-way agreement, 2-fault tolerance
- Checkpoint: immutable receipt with SHA-256 hash linking
- DAG: directed acyclic graph with topological sort
- Fault classification: CRASH, BIT_FLIP, DETERMINISM_FAILURE,
STEALTH_FAULT, GÖDEL_BOUNDARY, FAMM_CORRUPTION
- Demo: all 6 fault types correctly detected, 4/5 clique found
AGENT 2 — ManifoldVerifier: manifold_verifier.py
- Fisher-Rao metric: g_ij = δ_ij/p_i + 1/p_8 on Δ₇
- Fisher distance: Bhattacharyya angle arccos(Σ√(pᵢqᵢ))
- Geodesic verification: coplanarity test on S⁷
- Stealth fault detection: DAG-agree + manifold-diverge
- Demo: 4 honest pass, 1 byzantine detected, stealth caught
AGENT 3 — LatticeImplementer: silversight_lattice.py
- SilverSightLattice: main engine integrating all components
- FAMMBank: delay-line memory with LWMA-1 guidance
- PhiCorkscrew: per-watchdog geodesic walk
- FisherGeometry: S⁷ ↔ Δ₇ conversions
- Demo: 10 iterations, 90% consensus, 9 checkpoints,
chain verified, FAMM stabilized
Architecture (ℒ Lattice emulation):
5 watchdogs = miners doing Φ-corkscrew PoW
DAG checkpoints = blocks in chain
FAMM guidance = per-iteration difficulty adjustment
Fisher-Chentsov = post-quantum lattice metric
4/5 consensus = 2-fault Byzantine tolerance
Perpetual emission = never stops computing
Refs: SILVERSIGHT_LATTICE.md (architecture),
EXPERIMENT_RADIAL_SELF_FIND.md (experiment),
PHI_CORKSCREW_PERFECT_RECOVERY.md (encoding)
This commit is contained in:
parent
f7f3969a11
commit
b9161a8ea5
3 changed files with 2991 additions and 0 deletions
846
python/manifold_verifier.py
Normal file
846
python/manifold_verifier.py
Normal file
|
|
@ -0,0 +1,846 @@
|
||||||
|
"""
|
||||||
|
MANIFOLD VERIFICATION — SilverSight Lattice Quintuplet Watchdog
|
||||||
|
================================================================
|
||||||
|
|
||||||
|
Verifies that 5 watchdog processes running Φ-corkscrew computation on
|
||||||
|
the Fisher manifold S⁷ are actually on the SAME GEODESIC after reaching
|
||||||
|
Byzantine consensus on a checkpoint.
|
||||||
|
|
||||||
|
Mathematical Framework
|
||||||
|
----------------------
|
||||||
|
- State space: Δ₇ (7-simplex of 8 Hachimoji probabilities)
|
||||||
|
p ∈ Δ₇ ⟺ p_i ≥ 0, Σᵢ pᵢ = 1 for i ∈ {0, ..., 7}
|
||||||
|
|
||||||
|
- Fisher-Rao metric on Δ₇:
|
||||||
|
gᵢⱼ(p) = δᵢⱼ/pᵢ + 1/p₇ for i,j ∈ {0, ..., 6}
|
||||||
|
(using indices 0..6 with p₇ = 1 − Σᵢ₌₀⁶ pᵢ as the dependent coordinate)
|
||||||
|
|
||||||
|
- √p-coordinate embedding (Chentsov):
|
||||||
|
xᵢ = √pᵢ for i ∈ {0, ..., 7}
|
||||||
|
This maps Δ₇ → S⁷₊ (positive octant of the 7-sphere)
|
||||||
|
The Fisher metric becomes the ROUND metric on S⁷.
|
||||||
|
|
||||||
|
- Geodesics: great circles on S⁷ (intersections of S⁷ with 2-planes
|
||||||
|
through the origin).
|
||||||
|
|
||||||
|
- Bhattacharyya / Fisher distance:
|
||||||
|
d(p, q) = arccos( Σᵢ₌₀⁷ √(pᵢ qᵢ) )
|
||||||
|
This is the great-circle arc length on S⁷ in √p coordinates.
|
||||||
|
|
||||||
|
Core Insight for Verification
|
||||||
|
-----------------------------
|
||||||
|
Processes can agree on a checkpoint (similar DAGs) while being on
|
||||||
|
different geodesics. This is the STEALTH FAULT — the most dangerous
|
||||||
|
failure mode. To detect it, we verify that all agreeing processes lie
|
||||||
|
on the same great circle of S⁷.
|
||||||
|
|
||||||
|
A set of points {x⁽ᵏ⁾} on S⁷ lies on a single great circle iff all
|
||||||
|
x⁽ᵏ⁾ are contained in a common 2-plane through the origin, i.e.
|
||||||
|
rank[x⁽⁰⁾ | x⁽¹⁾ | ... ] ≤ 2.
|
||||||
|
Equivalently: every x⁽ᵏ⁾ is orthogonal to the (ℓ-2)-dimensional normal
|
||||||
|
space of the plane spanned by the first two independent vectors.
|
||||||
|
|
||||||
|
Author: Lattice Watchdog Geometric Verification Module
|
||||||
|
License: Internal — SilverSight Consensus Stack
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"fisher_rao_metric",
|
||||||
|
"fisher_distance",
|
||||||
|
"geodesic_point",
|
||||||
|
"verify_same_geodesic",
|
||||||
|
"verify_geodesic_consistency",
|
||||||
|
"detect_stealth_fault",
|
||||||
|
"is_valid_probability",
|
||||||
|
"to_sqrt_sphere",
|
||||||
|
"from_sqrt_sphere",
|
||||||
|
"great_circle_plane",
|
||||||
|
"angular_deviation_from_plane",
|
||||||
|
]
|
||||||
|
|
||||||
|
import math
|
||||||
|
from typing import List, Tuple
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Constants
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
DIM_SIMPLEX: int = 7 # 7-simplex Δ₇
|
||||||
|
N_PROBS: int = 8 # 8 probabilities p₀ … p₇
|
||||||
|
PHI: float = (1.0 + math.sqrt(5.0)) / 2.0
|
||||||
|
GOLDEN_ANGLE_RAD: float = 2.0 * math.pi / (PHI ** 2)
|
||||||
|
EPS: float = 1e-12 # numerical zero
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 1. Probability / Manifold utilities
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def is_valid_probability(p: np.ndarray) -> bool:
|
||||||
|
"""Check whether *p* is a point on Δ₇ (non-negative, sums to 1)."""
|
||||||
|
p = np.asarray(p, dtype=float)
|
||||||
|
if p.shape != (N_PROBS,):
|
||||||
|
return False
|
||||||
|
if np.any(p < -EPS):
|
||||||
|
return False
|
||||||
|
if abs(p.sum() - 1.0) > 1e-9:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def to_sqrt_sphere(p: np.ndarray) -> np.ndarray:
|
||||||
|
"""Embed Δ₇ → S⁷ via the Chentsov map xᵢ = √pᵢ.
|
||||||
|
|
||||||
|
Returns a unit vector in ℝ⁸ because Σᵢ (√pᵢ)² = Σᵢ pᵢ = 1.
|
||||||
|
"""
|
||||||
|
p = np.asarray(p, dtype=float)
|
||||||
|
assert is_valid_probability(p), "Input must lie on Δ₇"
|
||||||
|
x = np.sqrt(np.maximum(p, 0.0))
|
||||||
|
# Renormalise to protect against drift
|
||||||
|
norm = np.linalg.norm(x)
|
||||||
|
if norm > EPS:
|
||||||
|
x = x / norm
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def from_sqrt_sphere(x: np.ndarray) -> np.ndarray:
|
||||||
|
"""Inverse Chentsov map: S⁷ ∩ ℝ₊⁸ → Δ₇ via pᵢ = xᵢ²."""
|
||||||
|
x = np.asarray(x, dtype=float)
|
||||||
|
assert x.shape == (N_PROBS,), f"Expected shape {(N_PROBS,)}, got {x.shape}"
|
||||||
|
p = x * x
|
||||||
|
s = p.sum()
|
||||||
|
if s > EPS:
|
||||||
|
p = p / s
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 2. Fisher-Rao geometry
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def fisher_rao_metric(p: np.ndarray) -> np.ndarray:
|
||||||
|
"""Compute the Fisher-Rao metric matrix at point *p* on Δ₇.
|
||||||
|
|
||||||
|
Using the independent coordinates {p₀, …, p₆} with
|
||||||
|
p₇ = 1 − Σᵢ₌₀⁶ pᵢ, the metric components are
|
||||||
|
|
||||||
|
gᵢⱼ = δᵢⱼ / pᵢ + 1 / p₇ for i, j ∈ {0, …, 6}
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
p : np.ndarray, shape (8,)
|
||||||
|
A point on the 7-simplex (non-negative, sum = 1).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
g : np.ndarray, shape (7, 7)
|
||||||
|
The Fisher information matrix in the {p₀,…,p₆} coordinate basis.
|
||||||
|
"""
|
||||||
|
p = np.asarray(p, dtype=float)
|
||||||
|
assert is_valid_probability(p), "fisher_rao_metric: p must be on Δ₇"
|
||||||
|
|
||||||
|
p7 = p[-1] # dependent coordinate
|
||||||
|
if p7 < EPS:
|
||||||
|
raise ValueError("fisher_rao_metric: p₇ is too close to zero — metric singular")
|
||||||
|
if np.any(p[:DIM_SIMPLEX] < EPS):
|
||||||
|
raise ValueError("fisher_rao_metric: some pᵢ too close to zero — metric singular")
|
||||||
|
|
||||||
|
# g_ij = δ_ij / p_i + 1 / p_7
|
||||||
|
g = np.eye(DIM_SIMPLEX, dtype=float) / p[:DIM_SIMPLEX][:, None]
|
||||||
|
g += 1.0 / p7
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def fisher_distance(p: np.ndarray, q: np.ndarray) -> float:
|
||||||
|
"""Fisher-Rao distance between two points on Δ₇.
|
||||||
|
|
||||||
|
In √p coordinates this is the great-circle arc length on S⁷:
|
||||||
|
|
||||||
|
d(p, q) = arccos( Σᵢ₌₀⁷ √(pᵢ qᵢ) )
|
||||||
|
|
||||||
|
The result lies in [0, π]. For nearby points this is ≈ the chord
|
||||||
|
length; for antipodal points on the sphere it equals π.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
p, q : np.ndarray, shape (8,)
|
||||||
|
Two points on Δ₇.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
The Fisher-Rao / Bhattacharyya angle between *p* and *q*.
|
||||||
|
"""
|
||||||
|
p = np.asarray(p, dtype=float)
|
||||||
|
q = np.asarray(q, dtype=float)
|
||||||
|
assert is_valid_probability(p), "fisher_distance: p must be on Δ₇"
|
||||||
|
assert is_valid_probability(q), "fisher_distance: q must be on Δ₇"
|
||||||
|
|
||||||
|
# Bhattacharyya coefficient: BC = Σ √(pᵢ qᵢ)
|
||||||
|
# This is exactly the Euclidean inner product of √p and √q on S⁷.
|
||||||
|
bc = np.sum(np.sqrt(np.maximum(p * q, 0.0)))
|
||||||
|
|
||||||
|
# Numerical guard: clamp to [-1, 1] before arccos
|
||||||
|
bc = float(np.clip(bc, -1.0, 1.0))
|
||||||
|
return math.acos(bc)
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 3. Geodesic operations on S⁷
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def geodesic_point(p: np.ndarray, d: np.ndarray, t: float) -> np.ndarray:
|
||||||
|
"""Point at Fisher distance *t* along the geodesic from *p* in direction *d*.
|
||||||
|
|
||||||
|
On S⁷ in √p coordinates geodesics are great circles, so we use the
|
||||||
|
standard spherical exponential map:
|
||||||
|
|
||||||
|
γ(t) = cos(t) · x + sin(t) · v̂
|
||||||
|
|
||||||
|
where x = √p (unit vector on S⁷)
|
||||||
|
and v̂ = projₓ(d) / |projₓ(d)| (tangent direction, normalised).
|
||||||
|
|
||||||
|
The result is projected back from √p coordinates to Δ₇ via pᵢ = xᵢ².
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
p : np.ndarray, shape (8,)
|
||||||
|
Starting point on Δ₇.
|
||||||
|
d : np.ndarray, shape (8,)
|
||||||
|
Direction vector in the tangent space at *p* (in √p coordinates).
|
||||||
|
This need NOT be normalised or exactly tangent — it is projected.
|
||||||
|
t : float
|
||||||
|
Distance along the geodesic (same units as fisher_distance).
|
||||||
|
Use small *t* for local steps.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
q : np.ndarray, shape (8,)
|
||||||
|
The point on Δ₇ reached after walking distance *t*.
|
||||||
|
"""
|
||||||
|
p = np.asarray(p, dtype=float)
|
||||||
|
d = np.asarray(d, dtype=float)
|
||||||
|
assert is_valid_probability(p), "geodesic_point: p must be on Δ₇"
|
||||||
|
assert d.shape == (N_PROBS,), f"geodesic_point: d must have shape {(N_PROBS,)}"
|
||||||
|
|
||||||
|
x = to_sqrt_sphere(p) # unit vector on S⁷
|
||||||
|
|
||||||
|
# Project *d* onto the tangent space TₓS⁷: remove radial component
|
||||||
|
d_dot_x = float(d @ x)
|
||||||
|
v = d - d_dot_x * x # now orthogonal to x
|
||||||
|
v_norm = np.linalg.norm(v)
|
||||||
|
|
||||||
|
if v_norm < EPS:
|
||||||
|
# Direction is radial — stay at the same point
|
||||||
|
return p.copy()
|
||||||
|
|
||||||
|
v_hat = v / v_norm # unit tangent vector
|
||||||
|
|
||||||
|
# Great circle formula on S⁷
|
||||||
|
gamma_sqrt = math.cos(t) * x + math.sin(t) * v_hat
|
||||||
|
|
||||||
|
# Renormalise (protect against drift)
|
||||||
|
gamma_sqrt_norm = np.linalg.norm(gamma_sqrt)
|
||||||
|
if gamma_sqrt_norm > EPS:
|
||||||
|
gamma_sqrt = gamma_sqrt / gamma_sqrt_norm
|
||||||
|
|
||||||
|
# Map back to Δ₇
|
||||||
|
q = from_sqrt_sphere(gamma_sqrt)
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
def great_circle_plane(x0: np.ndarray, x1: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||||||
|
"""Compute the 2-plane in ℝ⁸ that contains the great-circle geodesic
|
||||||
|
through two points *x0*, *x1* on S⁷.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
u, v : np.ndarray, shape (8,)
|
||||||
|
Orthonormal basis vectors spanning the geodesic plane.
|
||||||
|
normal : np.ndarray, shape (8, 6)
|
||||||
|
Matrix whose columns form an orthonormal basis of the normal
|
||||||
|
space to the geodesic plane (so that a point *y* lies in the
|
||||||
|
plane iff y @ normal ≈ 0).
|
||||||
|
|
||||||
|
Raises
|
||||||
|
------
|
||||||
|
ValueError
|
||||||
|
If *x0* and *x1* are antipodal (lie on opposite poles) or
|
||||||
|
collinear, in which case the geodesic is not unique.
|
||||||
|
"""
|
||||||
|
x0 = np.asarray(x0, dtype=float)
|
||||||
|
x1 = np.asarray(x1, dtype=float)
|
||||||
|
|
||||||
|
# Normalise
|
||||||
|
x0 = x0 / (np.linalg.norm(x0) + EPS)
|
||||||
|
x1 = x1 / (np.linalg.norm(x1) + EPS)
|
||||||
|
|
||||||
|
# Check for antipodal / collinear
|
||||||
|
inner = float(x0 @ x1)
|
||||||
|
if abs(abs(inner) - 1.0) < 1e-6:
|
||||||
|
raise ValueError(
|
||||||
|
"great_circle_plane: points are (anti)collinear — "
|
||||||
|
"geodesic plane is not unique"
|
||||||
|
)
|
||||||
|
|
||||||
|
# First basis vector: x0
|
||||||
|
u = x0.copy()
|
||||||
|
|
||||||
|
# Second basis vector: Gram-Schmidt orthonormalise (x1 - proj_u(x1))
|
||||||
|
v_raw = x1 - inner * u
|
||||||
|
v_norm = np.linalg.norm(v_raw)
|
||||||
|
if v_norm < EPS:
|
||||||
|
raise ValueError("great_circle_plane: degenerate points")
|
||||||
|
v = v_raw / v_norm
|
||||||
|
|
||||||
|
# Normal space: orthonormal complement of span{u, v} in ℝ⁸
|
||||||
|
basis = np.eye(N_PROBS, dtype=float)
|
||||||
|
# Use QR decomposition on [u, v, e₀, e₁, …] and extract Q[:, 2:]
|
||||||
|
M = np.column_stack([u, v, basis])
|
||||||
|
Q, _ = np.linalg.qr(M)
|
||||||
|
normal = Q[:, 2:] # shape (8, 6)
|
||||||
|
|
||||||
|
return u, v, normal
|
||||||
|
|
||||||
|
|
||||||
|
def angular_deviation_from_plane(
|
||||||
|
x: np.ndarray,
|
||||||
|
normal: np.ndarray,
|
||||||
|
) -> float:
|
||||||
|
"""Compute the angular deviation (in radians) of a point *x* on S⁷
|
||||||
|
from the 2-plane defined by *normal* (columns = orthonormal basis
|
||||||
|
of the normal space).
|
||||||
|
|
||||||
|
A point lies exactly in the plane iff the deviation is 0.
|
||||||
|
"""
|
||||||
|
x = np.asarray(x, dtype=float)
|
||||||
|
x = x / (np.linalg.norm(x) + EPS)
|
||||||
|
|
||||||
|
# Components in the normal directions
|
||||||
|
normal_comps = x @ normal # shape (6,)
|
||||||
|
sin_theta = np.linalg.norm(normal_comps)
|
||||||
|
sin_theta = float(np.clip(sin_theta, 0.0, 1.0))
|
||||||
|
return math.asin(sin_theta)
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 4. Core verification routines
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def verify_same_geodesic(
|
||||||
|
positions: List[np.ndarray],
|
||||||
|
threshold: float = 1e-4,
|
||||||
|
check_coincidence: bool = True,
|
||||||
|
) -> Tuple[bool, dict]:
|
||||||
|
"""Verify that all *positions* lie on the same geodesic of S⁷.
|
||||||
|
|
||||||
|
Algorithm
|
||||||
|
---------
|
||||||
|
1. Convert every position to √p coordinates (points on S⁷).
|
||||||
|
2. Find the pair of points with maximal angular separation and use
|
||||||
|
them to build the geodesic plane (numerically stable).
|
||||||
|
3. Measure the angular deviation of every point from that plane.
|
||||||
|
4. (Optional) Compute pairwise Fisher distances — this checks that
|
||||||
|
processes are not just on the same geodesic but at the same
|
||||||
|
checkpoint position.
|
||||||
|
5. PASS iff (a) all angular deviations < *threshold*, AND
|
||||||
|
(b) if *check_coincidence*: all pairwise Fisher
|
||||||
|
distances < *threshold*.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
positions : list of np.ndarray, each shape (8,)
|
||||||
|
Points on Δ₇ from the consensus clique (typically 4–5 points).
|
||||||
|
threshold : float
|
||||||
|
Maximum allowed deviation (radians for angle; also used for
|
||||||
|
Fisher distance when *check_coincidence* is True).
|
||||||
|
Default 1e-4 is ~0.006°.
|
||||||
|
check_coincidence : bool
|
||||||
|
If True (default), also require that all points are close to
|
||||||
|
each other in Fisher distance — this is the right mode when
|
||||||
|
the consensus clique should be at the SAME checkpoint.
|
||||||
|
Set to False when checking coplanarity of points that may be
|
||||||
|
legitimately spread along the same geodesic.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
ok : bool
|
||||||
|
True if all processes are on the same geodesic (and optionally
|
||||||
|
at the same position), False otherwise.
|
||||||
|
report : dict
|
||||||
|
Detailed measurements: angular deviations, pairwise distances,
|
||||||
|
and human-readable diagnostics.
|
||||||
|
"""
|
||||||
|
if len(positions) < 2:
|
||||||
|
return False, {
|
||||||
|
"error": "Need at least 2 points to define a geodesic",
|
||||||
|
"n_points": len(positions),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate and convert to √p coordinates
|
||||||
|
sqrt_pts = []
|
||||||
|
for idx, p in enumerate(positions):
|
||||||
|
if not is_valid_probability(np.asarray(p)):
|
||||||
|
return False, {"error": f"positions[{idx}] is not on Δ₇", "point": p}
|
||||||
|
sqrt_pts.append(to_sqrt_sphere(p))
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4a. Angular-deviation test (great-circle coplanarity)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
angular_devs = []
|
||||||
|
plane_info = {}
|
||||||
|
angular_ok: bool | None = None
|
||||||
|
max_angular_dev = math.inf
|
||||||
|
|
||||||
|
# Find the pair of points with maximal angular separation to define
|
||||||
|
# the plane — this is numerically more stable than using adjacent
|
||||||
|
# points that may be nearly collinear.
|
||||||
|
n_pts = len(sqrt_pts)
|
||||||
|
best_pair = (0, min(1, n_pts - 1))
|
||||||
|
best_sep = -1.0
|
||||||
|
for i in range(n_pts):
|
||||||
|
for j in range(i + 1, n_pts):
|
||||||
|
sep = float(abs(sqrt_pts[i] @ sqrt_pts[j]))
|
||||||
|
# We want points that are well-separated (inner product small)
|
||||||
|
# but not antipodal (inner product ≈ -1).
|
||||||
|
score = 1.0 - abs(sep) # 0 = collinear, 1 = orthogonal
|
||||||
|
if score > best_sep and score > 1e-3:
|
||||||
|
best_sep = score
|
||||||
|
best_pair = (i, j)
|
||||||
|
|
||||||
|
try:
|
||||||
|
u, v, normal = great_circle_plane(sqrt_pts[best_pair[0]], sqrt_pts[best_pair[1]])
|
||||||
|
plane_info = {
|
||||||
|
"basis_u": u.tolist(),
|
||||||
|
"basis_v": v.tolist(),
|
||||||
|
"plane_from_pair": best_pair,
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, x in enumerate(sqrt_pts):
|
||||||
|
dev = angular_deviation_from_plane(x, normal)
|
||||||
|
angular_devs.append({
|
||||||
|
"index": idx,
|
||||||
|
"deviation_rad": float(dev),
|
||||||
|
"deviation_deg": float(math.degrees(dev)),
|
||||||
|
})
|
||||||
|
|
||||||
|
max_angular_dev = max(d["deviation_rad"] for d in angular_devs)
|
||||||
|
angular_ok = max_angular_dev < threshold
|
||||||
|
|
||||||
|
except ValueError as exc:
|
||||||
|
# Points are collinear / antipodal — fall back to distance-only
|
||||||
|
angular_ok = None
|
||||||
|
max_angular_dev = math.inf
|
||||||
|
plane_info = {"error": str(exc)}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4b. Pairwise Fisher-distance test (redundant safety net)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
n = len(positions)
|
||||||
|
pairwise = []
|
||||||
|
max_fisher_dist = 0.0
|
||||||
|
max_pair = (0, 0)
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
for j in range(i + 1, n):
|
||||||
|
d = fisher_distance(positions[i], positions[j])
|
||||||
|
pairwise.append({
|
||||||
|
"i": i,
|
||||||
|
"j": j,
|
||||||
|
"fisher_distance": float(d),
|
||||||
|
})
|
||||||
|
if d > max_fisher_dist:
|
||||||
|
max_fisher_dist = d
|
||||||
|
max_pair = (i, j)
|
||||||
|
|
||||||
|
distance_ok = max_fisher_dist < threshold
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 4c. Decide
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
if angular_ok is not None:
|
||||||
|
ok = angular_ok
|
||||||
|
if check_coincidence:
|
||||||
|
ok = ok and distance_ok
|
||||||
|
else:
|
||||||
|
# Degraded mode: angular test failed, fall back to distance
|
||||||
|
ok = distance_ok if check_coincidence else False
|
||||||
|
|
||||||
|
report = {
|
||||||
|
"verdict": "PASS" if ok else "FAIL",
|
||||||
|
"n_points": n,
|
||||||
|
"threshold": threshold,
|
||||||
|
"angular_test": {
|
||||||
|
"performed": angular_ok is not None,
|
||||||
|
"max_deviation_rad": float(max_angular_dev),
|
||||||
|
"max_deviation_deg": float(math.degrees(max_angular_dev)),
|
||||||
|
"details": angular_devs,
|
||||||
|
},
|
||||||
|
"distance_test": {
|
||||||
|
"max_fisher_distance": float(max_fisher_dist),
|
||||||
|
"max_pair": max_pair,
|
||||||
|
"pairwise": pairwise,
|
||||||
|
},
|
||||||
|
"plane": plane_info,
|
||||||
|
"same_geodesic": ok,
|
||||||
|
}
|
||||||
|
return ok, report
|
||||||
|
|
||||||
|
|
||||||
|
def verify_geodesic_consistency(
|
||||||
|
chain_0: List[np.ndarray],
|
||||||
|
chain_i: List[np.ndarray],
|
||||||
|
tolerance: float = 0.05,
|
||||||
|
) -> Tuple[bool, float]:
|
||||||
|
"""Verify that process *i* followed the same geodesic as process 0.
|
||||||
|
|
||||||
|
We compare the **step sizes** (Fisher distances between consecutive
|
||||||
|
checkpoints). If both processes traverse the same geodesic with the
|
||||||
|
same speed profile, the step-size sequences must match up to a
|
||||||
|
common scaling factor (clock drift).
|
||||||
|
|
||||||
|
Algorithm
|
||||||
|
---------
|
||||||
|
1. Compute step-size arrays:
|
||||||
|
s_0[k] = d(chain_0[k], chain_0[k+1])
|
||||||
|
s_i[k] = d(chain_i[k], chain_i[k+1])
|
||||||
|
2. Find the optimal ratio α that minimises |s_i − α·s_0|₂.
|
||||||
|
(This accounts for a constant clock-speed difference.)
|
||||||
|
3. Compute the maximum relative deviation after scaling.
|
||||||
|
4. PASS if max deviation < *tolerance* (default 5 %).
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
chain_0 : list of np.ndarray
|
||||||
|
Reference checkpoints from the leader process.
|
||||||
|
chain_i : list of np.ndarray
|
||||||
|
Checkpoints from process *i*.
|
||||||
|
tolerance : float
|
||||||
|
Maximum allowed relative deviation (default 5 % to account for
|
||||||
|
clock jitter and floating-point noise).
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
ok : bool
|
||||||
|
True if the step-size profiles are consistent.
|
||||||
|
max_deviation : float
|
||||||
|
Maximum relative deviation between scaled step sequences.
|
||||||
|
"""
|
||||||
|
if len(chain_0) < 2 or len(chain_i) < 2:
|
||||||
|
return False, math.inf
|
||||||
|
|
||||||
|
# Compute step sizes
|
||||||
|
s_0 = np.array([fisher_distance(chain_0[k], chain_0[k + 1])
|
||||||
|
for k in range(len(chain_0) - 1)])
|
||||||
|
s_i = np.array([fisher_distance(chain_i[k], chain_i[k + 1])
|
||||||
|
for k in range(len(chain_i) - 1)])
|
||||||
|
|
||||||
|
min_len = min(len(s_0), len(s_i))
|
||||||
|
s_0 = s_0[:min_len]
|
||||||
|
s_i = s_i[:min_len]
|
||||||
|
|
||||||
|
# Filter out zero steps (shouldn't happen in normal operation)
|
||||||
|
mask = (s_0 > EPS) & (s_i > EPS)
|
||||||
|
if not np.any(mask):
|
||||||
|
return False, math.inf
|
||||||
|
|
||||||
|
s_0_f = s_0[mask]
|
||||||
|
s_i_f = s_i[mask]
|
||||||
|
|
||||||
|
# Optimal scale factor: minimise |s_i − α·s_0|² → α = (s_i·s_0)/(s_0·s_0)
|
||||||
|
alpha = float(s_i_f @ s_0_f) / float(s_0_f @ s_0_f)
|
||||||
|
if alpha < EPS or not np.isfinite(alpha):
|
||||||
|
return False, math.inf
|
||||||
|
|
||||||
|
# Relative deviation after scaling
|
||||||
|
rel_dev = np.abs(s_i_f - alpha * s_0_f) / (alpha * s_0_f + EPS)
|
||||||
|
max_deviation = float(np.max(rel_dev))
|
||||||
|
|
||||||
|
ok = max_deviation < tolerance
|
||||||
|
return ok, max_deviation
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 5. Stealth-fault detection
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def detect_stealth_fault(
|
||||||
|
proposals: List[dict],
|
||||||
|
clique: List[int],
|
||||||
|
) -> Tuple[bool, dict]:
|
||||||
|
"""Detect the stealth fault: DAGs agree but manifold positions diverge.
|
||||||
|
|
||||||
|
After Byzantine consensus, the processes in *clique* have all
|
||||||
|
committed to the same checkpoint DAG. This routine extracts their
|
||||||
|
actual manifold positions from the *proposals* and verifies that
|
||||||
|
they are on the same geodesic.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
proposals : list of dict
|
||||||
|
One dict per process. Each dict must contain a key
|
||||||
|
``'manifold_position'`` whose value is an np.ndarray of shape
|
||||||
|
(8,) on Δ₇, and optionally a key ``'dag_hash'`` for
|
||||||
|
cross-checking.
|
||||||
|
clique : list of int
|
||||||
|
Process indices that formed the consensus clique.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
stealth_detected : bool
|
||||||
|
True if the DAGs agree BUT the positions are NOT all on the
|
||||||
|
same geodesic (this is the attack / fault).
|
||||||
|
details : dict
|
||||||
|
Full diagnostic report.
|
||||||
|
"""
|
||||||
|
if len(clique) < 2:
|
||||||
|
return False, {"error": "Clique too small for geodesic check", "clique": clique}
|
||||||
|
|
||||||
|
# Extract positions of clique members
|
||||||
|
positions = []
|
||||||
|
missing = []
|
||||||
|
for pid in clique:
|
||||||
|
if pid < 0 or pid >= len(proposals):
|
||||||
|
missing.append(pid)
|
||||||
|
continue
|
||||||
|
prop = proposals[pid]
|
||||||
|
if "manifold_position" not in prop:
|
||||||
|
missing.append(pid)
|
||||||
|
continue
|
||||||
|
positions.append(np.asarray(prop["manifold_position"], dtype=float))
|
||||||
|
|
||||||
|
if missing:
|
||||||
|
return True, {
|
||||||
|
"stealth_detected": True,
|
||||||
|
"reason": "missing_positions",
|
||||||
|
"missing_processes": missing,
|
||||||
|
"clique": clique,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check DAG agreement (basic sanity)
|
||||||
|
dag_hashes = []
|
||||||
|
for pid in clique:
|
||||||
|
h = proposals[pid].get("dag_hash")
|
||||||
|
if h is not None:
|
||||||
|
dag_hashes.append(h)
|
||||||
|
|
||||||
|
dags_agree = len(set(dag_hashes)) <= 1 if dag_hashes else True
|
||||||
|
|
||||||
|
# Now the crucial test: are they on the same geodesic AND at the
|
||||||
|
# same checkpoint position? Use a slightly relaxed threshold for
|
||||||
|
# stealth detection because real processes have small clock jitter.
|
||||||
|
geodesic_ok, geo_report = verify_same_geodesic(
|
||||||
|
positions, threshold=1e-3, check_coincidence=True
|
||||||
|
)
|
||||||
|
|
||||||
|
stealth_detected = dags_agree and (not geodesic_ok)
|
||||||
|
|
||||||
|
details = {
|
||||||
|
"stealth_detected": stealth_detected,
|
||||||
|
"dags_agree": dags_agree,
|
||||||
|
"n_clique": len(clique),
|
||||||
|
"n_positions": len(positions),
|
||||||
|
"geodesic_report": geo_report,
|
||||||
|
}
|
||||||
|
|
||||||
|
if stealth_detected:
|
||||||
|
details["severity"] = "CRITICAL"
|
||||||
|
details["description"] = (
|
||||||
|
"STEALTH FAULT: Byzantine clique agreed on DAG but processes "
|
||||||
|
"are on DIFFERENT GEODESICS of S⁷. This is a manifold-level "
|
||||||
|
"equivocation attack — the processes appear consistent from "
|
||||||
|
"the consensus layer but diverge in the physical state space."
|
||||||
|
)
|
||||||
|
|
||||||
|
return stealth_detected, details
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 6. Helpers for unit tests / demos
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
def random_point_on_simplex(rng: np.random.Generator | None = None) -> np.ndarray:
|
||||||
|
"""Generate a uniformly random point on Δ₇ (Dirichlet(1,…,1))."""
|
||||||
|
rng = rng or np.random.default_rng()
|
||||||
|
p = rng.random(N_PROBS)
|
||||||
|
p = p / p.sum()
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
|
def random_tangent_direction(x: np.ndarray, rng: np.random.Generator | None = None) -> np.ndarray:
|
||||||
|
"""Generate a random unit vector in the tangent space TₓS⁷."""
|
||||||
|
rng = rng or np.random.default_rng()
|
||||||
|
v = rng.standard_normal(N_PROBS)
|
||||||
|
v = v - (v @ x) * x # project onto tangent space
|
||||||
|
v_norm = np.linalg.norm(v)
|
||||||
|
if v_norm < EPS:
|
||||||
|
return random_tangent_direction(x, rng)
|
||||||
|
return v / v_norm
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# 7. Demonstration
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=" * 70)
|
||||||
|
print("MANIFOLD VERIFICATION — SilverSight Lattice Quintuplet Watchdog")
|
||||||
|
print("Fisher manifold S⁷ | Φ-corkscrew consensus verification")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
rng = np.random.default_rng(42)
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7.1 Create a common geodesic (shared by 4 honest processes)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
print("\n--- 1. Creating 4 honest processes on the SAME GEODESIC ---")
|
||||||
|
|
||||||
|
# Random starting point on Δ₇
|
||||||
|
p0 = random_point_on_simplex(rng)
|
||||||
|
x0 = to_sqrt_sphere(p0)
|
||||||
|
|
||||||
|
# Random tangent direction → defines a unique great circle
|
||||||
|
d_hat = random_tangent_direction(x0, rng)
|
||||||
|
|
||||||
|
# Walk 5 checkpoints along the great circle
|
||||||
|
N_CHECKPOINTS = 5
|
||||||
|
STEP_SIZE = 0.1 # Fisher distance per step
|
||||||
|
JITTER = 1e-5 # tiny clock jitter (small enough for clean tests)
|
||||||
|
|
||||||
|
honest_chains: List[List[np.ndarray]] = [[] for _ in range(4)]
|
||||||
|
|
||||||
|
for proc in range(4):
|
||||||
|
for k in range(N_CHECKPOINTS):
|
||||||
|
t = k * STEP_SIZE + rng.normal(0, JITTER)
|
||||||
|
# Great circle: γ(t) = cos(t)·x0 + sin(t)·d̂
|
||||||
|
x_t = math.cos(t) * x0 + math.sin(t) * d_hat
|
||||||
|
x_t = x_t / (np.linalg.norm(x_t) + EPS)
|
||||||
|
p_t = from_sqrt_sphere(x_t)
|
||||||
|
honest_chains[proc].append(p_t)
|
||||||
|
|
||||||
|
# Print first and last checkpoint for this process
|
||||||
|
p_first = honest_chains[proc][0]
|
||||||
|
p_last = honest_chains[proc][-1]
|
||||||
|
print(f" Process {proc}: d(p₀, p₄) = {fisher_distance(p_first, p_last):.6f}")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7.2 Verify all 4 honest processes are on the same geodesic
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
print("\n--- 2. Verifying SAME GEODESIC for honest processes ---")
|
||||||
|
|
||||||
|
latest_positions = [chain[-1] for chain in honest_chains]
|
||||||
|
ok, report = verify_same_geodesic(
|
||||||
|
latest_positions, threshold=1e-3, check_coincidence=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f" Result: {'PASS ✓' if ok else 'FAIL ✗'}")
|
||||||
|
print(f" Max angular deviation: {report['angular_test']['max_deviation_deg']:.6f}°")
|
||||||
|
print(f" Max Fisher distance: {report['distance_test']['max_fisher_distance']:.8f}")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7.3 Verify geodesic consistency (step-size profiles match)
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
print("\n--- 3. Verifying GEODESIC CONSISTENCY (step-size profiles) ---")
|
||||||
|
|
||||||
|
for proc in range(1, 4):
|
||||||
|
ok_dev, max_dev = verify_geodesic_consistency(honest_chains[0], honest_chains[proc])
|
||||||
|
status = "PASS ✓" if ok_dev else "FAIL ✗"
|
||||||
|
print(f" Process {proc} vs 0: max_dev = {max_dev:.6f} ({status})")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7.4 Introduce a BYZANTINE process on a DIFFERENT GEODESIC
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
print("\n--- 4. Creating 1 BYZANTINE process on a DIFFERENT GEODESIC ---")
|
||||||
|
|
||||||
|
# Same starting point, but DIFFERENT tangent direction
|
||||||
|
d_byz = random_tangent_direction(x0, rng)
|
||||||
|
# Ensure it's actually different (not close to d_hat)
|
||||||
|
dot = abs(float(d_byz @ d_hat))
|
||||||
|
while dot > 0.3:
|
||||||
|
d_byz = random_tangent_direction(x0, rng)
|
||||||
|
dot = abs(float(d_byz @ d_hat))
|
||||||
|
|
||||||
|
byz_chain: List[np.ndarray] = []
|
||||||
|
for k in range(N_CHECKPOINTS):
|
||||||
|
t = k * STEP_SIZE + rng.normal(0, JITTER)
|
||||||
|
x_t = math.cos(t) * x0 + math.sin(t) * d_byz
|
||||||
|
x_t = x_t / (np.linalg.norm(x_t) + EPS)
|
||||||
|
byz_chain.append(from_sqrt_sphere(x_t))
|
||||||
|
|
||||||
|
p_byz_first = byz_chain[0]
|
||||||
|
p_byz_last = byz_chain[-1]
|
||||||
|
print(f" Byzantine: d(p₀, p₄) = {fisher_distance(p_byz_first, p_byz_last):.6f}")
|
||||||
|
print(f" Direction dot-product |honest · byz|: {dot:.4f} (low = different geodesic)")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7.5 Verify the mixed group (4 honest + 1 byzantine) FAILS
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
print("\n--- 5. Verifying mixed group (4 honest + 1 byzantine) ---")
|
||||||
|
|
||||||
|
mixed_positions = latest_positions + [byz_chain[-1]]
|
||||||
|
ok_mix, report_mix = verify_same_geodesic(
|
||||||
|
mixed_positions, threshold=1e-3, check_coincidence=True
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f" Result: {'PASS ✓' if ok_mix else 'FAIL ✗'}")
|
||||||
|
print(f" Max angular deviation: {report_mix['angular_test']['max_deviation_deg']:.6f}°")
|
||||||
|
print(f" Max Fisher distance: {report_mix['distance_test']['max_fisher_distance']:.8f}")
|
||||||
|
|
||||||
|
if not ok_mix:
|
||||||
|
print(" → Byzantine process detected via manifold divergence!")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7.6 Detect STEALTH FAULT: DAGs agree but positions diverge
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
print("\n--- 6. STEALTH FAULT detection ---")
|
||||||
|
|
||||||
|
# Build mock proposals: all processes claim the same DAG hash
|
||||||
|
dag_hash = "checkpoint_42_deadbeef"
|
||||||
|
proposals = []
|
||||||
|
for proc in range(4):
|
||||||
|
proposals.append({
|
||||||
|
"dag_hash": dag_hash,
|
||||||
|
"manifold_position": honest_chains[proc][-1],
|
||||||
|
})
|
||||||
|
# Byzantine process claims the SAME DAG but is on a different geodesic
|
||||||
|
proposals.append({
|
||||||
|
"dag_hash": dag_hash,
|
||||||
|
"manifold_position": byz_chain[-1],
|
||||||
|
})
|
||||||
|
|
||||||
|
clique_all = [0, 1, 2, 3, 4] # all 5 appeared to agree
|
||||||
|
stealth_detected, details = detect_stealth_fault(proposals, clique_all)
|
||||||
|
|
||||||
|
print(f" DAGs agree: {details['dags_agree']}")
|
||||||
|
print(f" Same geodesic: {details['geodesic_report']['same_geodesic']}")
|
||||||
|
print(f" STEALTH DETECTED: {stealth_detected}")
|
||||||
|
if stealth_detected:
|
||||||
|
print(f" SEVERITY: {details['severity']}")
|
||||||
|
print(f" Description: {details['description']}")
|
||||||
|
|
||||||
|
# Now test the honest-only clique (should NOT detect stealth)
|
||||||
|
print("\n --- Honest-only clique (control) ---")
|
||||||
|
clique_honest = [0, 1, 2, 3]
|
||||||
|
stealth_h, details_h = detect_stealth_fault(proposals, clique_honest)
|
||||||
|
print(f" STEALTH DETECTED: {stealth_h} (expected False)")
|
||||||
|
if not stealth_h:
|
||||||
|
print(" → Control passed: honest clique correctly cleared.")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# 7.7 Summary
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("SUMMARY")
|
||||||
|
print("=" * 70)
|
||||||
|
honest_geo, _ = verify_same_geodesic(
|
||||||
|
latest_positions, threshold=1e-3, check_coincidence=True
|
||||||
|
)
|
||||||
|
print(f" Honest clique same-geodesic test: {'PASS ✓' if honest_geo else 'FAIL ✗'}")
|
||||||
|
print(f" Mixed clique same-geodesic test: {'FAIL ✓ (Byzantine caught)' if not ok_mix else 'PASS ✗'}")
|
||||||
|
print(f" Stealth fault on 5-process clique: DETECTED = {stealth_detected}")
|
||||||
|
print(f" Stealth fault on honest clique: DETECTED = {stealth_h} (should be False)")
|
||||||
|
print("=" * 70)
|
||||||
1029
python/quintuplet_consensus.py
Normal file
1029
python/quintuplet_consensus.py
Normal file
File diff suppressed because it is too large
Load diff
1116
python/silversight_lattice.py
Normal file
1116
python/silversight_lattice.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue