diff --git a/index.html b/index.html
index 9c258f9..411cf6d 100755
--- a/index.html
+++ b/index.html
@@ -95,7 +95,7 @@
Linux Kernel Ring 0 Visualization
-
+
diff --git a/kernel_ai/contracts/api_contracts.py b/kernel_ai/contracts/api_contracts.py
index 155e21f..fc65d42 100644
--- a/kernel_ai/contracts/api_contracts.py
+++ b/kernel_ai/contracts/api_contracts.py
@@ -25,6 +25,7 @@ class NetworkStackRealtimeResponse(TypedDict):
class CryptoRealtimeResponse(TypedDict):
items: list
processes: list
+ runtime_sources: list
meta: dict
@@ -131,6 +132,7 @@ def validate_crypto_realtime_response(payload: Any) -> None:
data = _expect_dict(payload, "crypto_realtime")
_expect_key(data, "items", list, "crypto_realtime")
_expect_key(data, "processes", list, "crypto_realtime")
+ _expect_key(data, "runtime_sources", list, "crypto_realtime")
_expect_key(data, "meta", dict, "crypto_realtime")
_expect_key(data["meta"], "active_flows", int, "crypto_realtime.meta")
diff --git a/kernel_ai/services/crypto/crypto_pipeline.py b/kernel_ai/services/crypto/crypto_pipeline.py
index f6ae351..7d3ca1d 100644
--- a/kernel_ai/services/crypto/crypto_pipeline.py
+++ b/kernel_ai/services/crypto/crypto_pipeline.py
@@ -26,6 +26,7 @@ def collect_crypto_realtime(crypto_prev, callbacks=None, psutil_module=None, log
collect_algorithm_requesters_fn = callbacks.get("collect_algorithm_requesters", lambda _items, _clients: {"aes": [], "sha": [], "chacha20": []})
build_crypto_decision_pipelines_fn = callbacks.get("build_crypto_decision_pipelines", lambda **_kwargs: {})
collect_entropy_cloud_status_fn = callbacks.get("collect_entropy_cloud_status", lambda: {})
+ collect_crypto_runtime_sources_fn = callbacks.get("collect_crypto_runtime_sources", lambda _items, _entries, _clients, _entropy: [])
items = []
tls_listener_by_port = {}
@@ -211,6 +212,7 @@ def collect_crypto_realtime(crypto_prev, callbacks=None, psutil_module=None, log
algorithm_requesters=algorithm_requesters,
)
entropy_cloud = collect_entropy_cloud_status_fn()
+ runtime_sources = collect_crypto_runtime_sources_fn(items, proc_crypto_entries, kernel_clients, entropy_cloud)
proc_crypto_names = {str(entry.get("name") or "").lower() for entry in proc_crypto_entries if isinstance(entry, dict)}
client_names = {str(row.get("name") or row.get("client") or "").lower() for row in kernel_clients if isinstance(row, dict)}
protocol_names = {str(item.get("protocol") or "").upper() for item in items}
@@ -299,6 +301,7 @@ def _zone(zone_id, label, active, status, evidence, strength=0.5):
"items": items[:24],
"processes": unique_processes[:16],
"protected_zones": protected_zones,
+ "runtime_sources": runtime_sources,
"meta": {
"ops_per_sec": ops_per_sec,
"tls_sessions": sum(1 for i in items if i.get("protocol") == "TLS"),
@@ -310,6 +313,7 @@ def _zone(zone_id, label, active, status, evidence, strength=0.5):
"algorithm_requesters": algorithm_requesters,
"crypto_stage1": crypto_stage1,
"entropy_cloud": entropy_cloud,
+ "runtime_sources": runtime_sources,
"crypto_decision_pipeline": crypto_decision_pipelines.get("aes", {}),
"crypto_decision_pipelines": crypto_decision_pipelines,
"protected_zones": protected_zones,
diff --git a/kernel_ai/services/crypto/helpers.py b/kernel_ai/services/crypto/helpers.py
index 93de235..01936af 100644
--- a/kernel_ai/services/crypto/helpers.py
+++ b/kernel_ai/services/crypto/helpers.py
@@ -2,6 +2,19 @@
from __future__ import annotations
+from pathlib import Path
+
+
+def _read_text(path):
+ try:
+ return Path(path).read_text(encoding="utf-8", errors="ignore")
+ except OSError:
+ return ""
+
+
+def _read_lines(path):
+ return [line.strip() for line in _read_text(path).splitlines() if line.strip()]
+
def infer_crypto_protocol(local_port, remote_port, process_name):
"""Infer protocol/algorithm pair from ports and process hints."""
@@ -279,3 +292,180 @@ def build_crypto_decision_pipelines(algorithm_competitions, kernel_clients, hw_o
"source": selected_source,
}
return pipelines
+
+
+def _detect_wireguard_interfaces():
+ dev_lines = _read_lines("/proc/net/dev")
+ ifaces = []
+ for line in dev_lines:
+ if ":" not in line:
+ continue
+ name = line.split(":", 1)[0].strip()
+ if name.startswith("wg") or "wireguard" in name.lower():
+ ifaces.append(name)
+ return ifaces
+
+
+def _detect_dm_crypt_devices():
+ devices = []
+ for dm_dir in Path("/sys/block").glob("dm-*"):
+ name = _read_text(dm_dir / "dm" / "name").strip()
+ uuid = _read_text(dm_dir / "dm" / "uuid").strip()
+ joined = f"{name} {uuid}".lower()
+ if "crypt" not in joined and "luks" not in joined:
+ continue
+ devices.append({"name": name or dm_dir.name, "uuid": uuid[:32]})
+ return devices
+
+
+def _xfrm_stat_total():
+ total = 0
+ for line in _read_lines("/proc/net/xfrm_stat"):
+ parts = line.split()
+ if len(parts) < 2:
+ continue
+ try:
+ total += int(parts[-1])
+ except ValueError:
+ continue
+ return total
+
+
+def collect_crypto_runtime_sources(items, proc_crypto_entries, kernel_clients, entropy_cloud):
+ """Collect simple real-world crypto source signals without eBPF tracing."""
+ items = list(items or [])
+ proc_crypto_entries = list(proc_crypto_entries or [])
+ kernel_clients = list(kernel_clients or [])
+ entropy_cloud = entropy_cloud or {}
+
+ protocols = {str(item.get("protocol") or "").upper() for item in items}
+ process_names = {str(item.get("process") or "").lower() for item in items}
+ client_by_name = {
+ str(client.get("name") or "").lower(): client
+ for client in kernel_clients
+ if isinstance(client, dict)
+ }
+ proc_names = {
+ str(entry.get("name") or entry.get("driver") or "").lower()
+ for entry in proc_crypto_entries
+ if isinstance(entry, dict)
+ }
+ proc_types = {
+ str(entry.get("type") or "").lower()
+ for entry in proc_crypto_entries
+ if isinstance(entry, dict)
+ }
+ wg_ifaces = _detect_wireguard_interfaces()
+ dm_crypt_devices = _detect_dm_crypt_devices()
+ xfrm_total = _xfrm_stat_total()
+ af_alg_present = "af_alg" in _read_text("/proc/modules").lower() or "alg" in _read_text("/proc/net/protocols").lower()
+
+ def _confidence(active, source):
+ if not active:
+ return 0.0
+ return {"direct": 0.95, "procfs": 0.82, "heuristic": 0.55}.get(source, 0.45)
+
+ def _source(source_id, label, active, source, evidence):
+ return {
+ "id": source_id,
+ "label": label,
+ "active": bool(active),
+ "source": source,
+ "confidence": round(_confidence(active, source), 2),
+ "evidence": evidence,
+ }
+
+ tls_items = [item for item in items if str(item.get("protocol") or "").upper() == "TLS"]
+ ssh_items = [item for item in items if str(item.get("protocol") or "").upper() == "SSH"]
+ wg_items = [item for item in items if str(item.get("protocol") or "").upper() == "WIREGUARD"]
+ af_alg_candidates = sorted(
+ name for name in process_names
+ if any(token in name for token in ("openssl", "python", "curl", "wget"))
+ )
+ ipsec_clients = [
+ name for name, client in client_by_name.items()
+ if "ipsec" in name or "xfrm" in name or int(client.get("active_flows") or 0) > 0 and "ipsec" in name
+ ]
+
+ return [
+ _source(
+ "tls_sockets",
+ "TLS sockets",
+ bool(tls_items),
+ "direct",
+ {
+ "flows": len(tls_items),
+ "processes": sorted({str(i.get("process") or "unknown") for i in tls_items})[:5],
+ },
+ ),
+ _source(
+ "ssh_crypto",
+ "SSH crypto",
+ bool(ssh_items),
+ "direct",
+ {
+ "flows": len(ssh_items),
+ "processes": sorted({str(i.get("process") or "unknown") for i in ssh_items})[:5],
+ },
+ ),
+ _source(
+ "wireguard",
+ "WireGuard",
+ bool(wg_items or wg_ifaces or Path("/sys/module/wireguard").exists()),
+ "procfs" if wg_ifaces or Path("/sys/module/wireguard").exists() else "heuristic",
+ {
+ "flows": len(wg_items),
+ "interfaces": wg_ifaces[:5],
+ },
+ ),
+ _source(
+ "ipsec_xfrm",
+ "IPsec / XFRM",
+ bool(ipsec_clients or xfrm_total > 0),
+ "procfs" if xfrm_total > 0 else "heuristic",
+ {
+ "xfrm_stat_total": xfrm_total,
+ "clients": sorted(ipsec_clients)[:5],
+ },
+ ),
+ _source(
+ "dm_crypt",
+ "dm-crypt / LUKS",
+ bool(dm_crypt_devices or int((client_by_name.get("dm-crypt") or {}).get("active_flows") or 0) > 0),
+ "procfs" if dm_crypt_devices else "heuristic",
+ {
+ "devices": dm_crypt_devices[:5],
+ },
+ ),
+ _source(
+ "af_alg",
+ "AF_ALG userspace",
+ bool(af_alg_present and af_alg_candidates),
+ "procfs" if af_alg_present else "heuristic",
+ {
+ "module_or_protocol": bool(af_alg_present),
+ "candidate_processes": af_alg_candidates[:5],
+ },
+ ),
+ _source(
+ "kernel_registry",
+ "/proc/crypto registry",
+ bool(proc_crypto_entries),
+ "procfs",
+ {
+ "algorithms": len(proc_crypto_entries),
+ "types": sorted(t for t in proc_types if t)[:6],
+ "sample": sorted(n for n in proc_names if n)[:6],
+ },
+ ),
+ _source(
+ "entropy",
+ "kernel random",
+ str(entropy_cloud.get("crng_state") or "").lower() in {"ready", "initialized", "crng_ready"},
+ "procfs",
+ {
+ "crng_state": entropy_cloud.get("crng_state"),
+ "pool_bits": entropy_cloud.get("entropy_pool_bits"),
+ },
+ ),
+ ]
diff --git a/kernel_ai/services/crypto_security.py b/kernel_ai/services/crypto_security.py
index f753b10..4222467 100644
--- a/kernel_ai/services/crypto_security.py
+++ b/kernel_ai/services/crypto_security.py
@@ -27,6 +27,7 @@
"collect_hw_offload_status",
"collect_algorithm_requesters",
"build_crypto_decision_pipelines",
+ "collect_crypto_runtime_sources",
"read_sysctl_int",
"read_proc_interrupt_total",
"collect_entropy_cloud_status",
@@ -45,6 +46,7 @@
collect_hw_offload_status = _helpers.collect_hw_offload_status
collect_algorithm_requesters = _helpers.collect_algorithm_requesters
build_crypto_decision_pipelines = _helpers.build_crypto_decision_pipelines
+collect_crypto_runtime_sources = _helpers.collect_crypto_runtime_sources
def read_sysctl_int(path, default=0):
@@ -80,6 +82,7 @@ def collect_crypto_realtime(crypto_prev, entropy_prev=None, callbacks=None):
"collect_algorithm_requesters": collect_algorithm_requesters,
"build_crypto_decision_pipelines": build_crypto_decision_pipelines,
"collect_entropy_cloud_status": lambda: collect_entropy_cloud_status(entropy_prev_local),
+ "collect_crypto_runtime_sources": collect_crypto_runtime_sources,
}
merged_callbacks = dict(defaults)
if callbacks:
diff --git a/static/js/crypto-belt.js b/static/js/crypto-belt.js
index 83b4413..5f50a18 100644
--- a/static/js/crypto-belt.js
+++ b/static/js/crypto-belt.js
@@ -24,6 +24,8 @@ class CryptoSubsystemVisualization {
this.selectedClientFilters = new Set();
this.selectedRequesterFilter = null;
this.selectedImplementationClassFilter = null;
+ this.activeCryptoView = 'LIVE_FLOW';
+ this.viewToggleNode = null;
}
init(containerId = 'crypto-belt-container') {
@@ -141,6 +143,20 @@ class CryptoSubsystemVisualization {
this.container.appendChild(terminator);
this.terminatorNode = terminator;
+ const viewToggle = document.createElement('div');
+ viewToggle.style.cssText = [
+ 'position: absolute',
+ 'top: 86px',
+ 'left: 50%',
+ 'transform: translateX(-50%) translateY(34px)',
+ 'display: flex',
+ 'gap: 8px',
+ 'z-index: 1001'
+ ].join(';');
+ this.container.appendChild(viewToggle);
+ this.viewToggleNode = viewToggle;
+ this.updateCryptoViewToggle();
+
const telemetry = document.createElement('div');
telemetry.style.cssText = [
'position: absolute',
@@ -216,6 +232,38 @@ class CryptoSubsystemVisualization {
this.terminatorNode.textContent = `TLS TERMINATED BY: ${status}`;
}
+ updateCryptoViewToggle() {
+ if (!this.viewToggleNode) return;
+ const views = [
+ ['LIVE_FLOW', 'LIVE FLOW'],
+ ['LINEAR_ANALYSIS', 'LINEAR ANALYSIS']
+ ];
+ this.viewToggleNode.innerHTML = '';
+ views.forEach(([id, label]) => {
+ const btn = document.createElement('button');
+ const isActive = this.activeCryptoView === id;
+ btn.textContent = label;
+ btn.style.cssText = [
+ 'padding: 5px 12px',
+ `background: ${isActive ? 'rgba(35, 58, 88, 0.94)' : 'rgba(8, 12, 18, 0.86)'}`,
+ `border: 1px solid ${isActive ? 'rgba(125, 186, 255, 0.86)' : 'rgba(150, 164, 188, 0.35)'}`,
+ `color: ${isActive ? '#d8eaff' : '#9da7b6'}`,
+ 'font-family: "Share Tech Mono", monospace',
+ 'font-size: 10px',
+ 'letter-spacing: 0.45px',
+ 'cursor: pointer',
+ 'border-radius: 4px',
+ 'box-shadow: none'
+ ].join(';');
+ btn.onclick = () => {
+ this.activeCryptoView = id;
+ this.updateCryptoViewToggle();
+ this.renderFlowMap(this.lastPayload || this.normalizeTelemetry(this.getFallbackTelemetry()));
+ };
+ this.viewToggleNode.appendChild(btn);
+ });
+ }
+
detectTlsTerminator(meta, lanes) {
const termList = Array.isArray(meta?.tls_terminators) ? meta.tls_terminators.filter(Boolean) : [];
const tlsLanes = (Array.isArray(lanes) ? lanes : []).filter((lane) => lane.protocol === 'TLS');
@@ -546,6 +594,75 @@ class CryptoSubsystemVisualization {
});
}
+ drawRuntimeSourcesPanel(layer, payload, width, height) {
+ const sources = Array.isArray(payload?.runtime_sources)
+ ? payload.runtime_sources
+ : (Array.isArray(payload?.meta?.runtime_sources) ? payload.meta.runtime_sources : []);
+ const layout = this.getCryptoLayout(width, height);
+ const panelX = layout.rightColumnX;
+ const panelY = 82;
+ const panelW = layout.rightColumnW;
+ const panelH = 38;
+ const activeSources = sources.filter((source) => source.active);
+ const shown = (activeSources.length ? activeSources : sources).slice(0, 4);
+
+ const panel = layer.append('g').attr('class', 'crypto-runtime-sources');
+ panel.append('rect')
+ .attr('x', panelX)
+ .attr('y', panelY)
+ .attr('width', panelW)
+ .attr('height', panelH)
+ .attr('rx', 8)
+ .style('fill', 'rgba(7, 10, 16, 0.78)')
+ .style('stroke', 'rgba(150, 178, 220, 0.34)')
+ .style('stroke-width', 1);
+
+ panel.append('text')
+ .attr('x', panelX + 12)
+ .attr('y', panelY + 15)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9px')
+ .style('fill', '#d7ddea')
+ .text('CRYPTO RUNTIME SOURCES');
+
+ if (!shown.length) {
+ panel.append('text')
+ .attr('x', panelX + 12)
+ .attr('y', panelY + 30)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8.5px')
+ .style('fill', '#8393a8')
+ .text('waiting for live system signals');
+ return;
+ }
+
+ const sourceColor = (source) => {
+ if (!source.active) return '#778396';
+ if (source.source === 'direct') return '#8effc8';
+ if (source.source === 'procfs') return '#8fdcff';
+ return '#ffd58d';
+ };
+ const chipW = Math.max(70, Math.floor((panelW - 24) / Math.max(1, shown.length)));
+ shown.forEach((source, idx) => {
+ const x = panelX + 12 + idx * chipW;
+ const label = String(source.label || source.id || 'source');
+ const color = sourceColor(source);
+ panel.append('circle')
+ .attr('cx', x + 4)
+ .attr('cy', panelY + 28)
+ .attr('r', 3)
+ .style('fill', color)
+ .style('opacity', source.active ? 0.95 : 0.45);
+ panel.append('text')
+ .attr('x', x + 12)
+ .attr('y', panelY + 31)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8px')
+ .style('fill', source.active ? '#c5d0df' : '#758399')
+ .text(`${label.slice(0, 12)}:${String(source.source || 'n/a')}`);
+ });
+ }
+
getCryptoLayout(width, height) {
const rightColumnX = Math.floor(width * 0.67);
const rightColumnW = Math.max(360, width - rightColumnX - 16);
@@ -1726,6 +1843,779 @@ class CryptoSubsystemVisualization {
runLoop();
}
+ buildLinearAnalysisModel(payload) {
+ const meta = payload?.meta || {};
+ const comp = this.getCompetitionPayload(meta);
+ const pipeline = this.getDecisionPipelinePayload(meta);
+ const request = String(comp.request || this.selectedCompetitionAlgorithm || 'AES').toUpperCase();
+ const impls = Array.isArray(comp.implementations) ? comp.implementations : [];
+ const lanes = Array.isArray(payload?.items) ? payload.items : [];
+ const selectedDriver = String(comp?.selected?.name || pipeline.selected_driver || 'generic');
+ const seed = this.hashText(`${request}-${selectedDriver}-${lanes.length}-${impls.length}`);
+ const flowWeight = lanes.reduce((sum, lane) => sum + Number(lane.weight || 1), 0);
+ const maxPriority = Math.max(...impls.map((impl) => Number(impl.priority || 0)), 1);
+ const selectedPriority = Math.max(Number(comp?.selected?.priority || maxPriority), 1);
+ const driverQuality = Math.max(0.05, Math.min(1, selectedPriority / maxPriority));
+ const trafficPressure = Math.max(0.2, Math.min(1.8, flowWeight / Math.max(1, lanes.length || 1)));
+ const baseBias = Math.max(0.003, Math.min(0.078, (1 - driverQuality) * 0.045 + trafficPressure * 0.011 + (seed % 17) / 1000));
+ const rounds = request === 'AES' ? 10 : (request === 'SHA' ? 8 : 6);
+ const decay = request === 'SHA' ? 0.58 : (request === 'CHACHA20' ? 0.68 : 0.62);
+ const bestTrail = Array.from({ length: rounds }, (_, idx) => {
+ const round = idx + 1;
+ const local = Math.max(0.001, baseBias * Math.pow(decay, idx) * (1 + (((seed >> (idx % 8)) & 3) - 1) * 0.08));
+ return {
+ round,
+ bias: local,
+ correlation: Math.min(1, local * 2),
+ activeSboxes: Math.max(1, Math.round(2 + round * (request === 'AES' ? 1.35 : 0.9))),
+ label: `r${round}: mask ${((seed + round * 37) & 0xff).toString(16).padStart(2, '0')} -> ${((seed + round * 71) & 0xff).toString(16).padStart(2, '0')}`
+ };
+ });
+
+ return {
+ request,
+ selectedDriver,
+ baseBias,
+ maxBias: bestTrail[0]?.bias || baseBias,
+ correlationDecay: decay,
+ latEnergy: Math.min(100, Math.round((baseBias * 760 + trafficPressure * 9 + impls.length * 2))),
+ activeSboxes: bestTrail[bestTrail.length - 1]?.activeSboxes || rounds,
+ confidence: Math.min(0.98, 0.54 + driverQuality * 0.26 + Math.min(lanes.length, 8) * 0.025),
+ rounds,
+ seed,
+ bestTrail
+ };
+ }
+
+ drawLinearAnalysisDashboard(layer, payload, width, height, tickId) {
+ const model = this.buildLinearAnalysisModel(payload);
+ const items = Array.isArray(payload?.items) ? payload.items : [];
+ const runtimeSources = Array.isArray(payload?.runtime_sources)
+ ? payload.runtime_sources
+ : (Array.isArray(payload?.meta?.runtime_sources) ? payload.meta.runtime_sources : []);
+ const primary = items[0] || {};
+ const algLabel = model.request === 'AES' ? 'AES-128' : model.request;
+ const margin = 10;
+ const gap = 8;
+ const topY = 82;
+ const topH = Math.max(200, Math.floor(height * 0.33));
+ const midY = topY + topH + gap;
+ const midH = Math.max(170, Math.floor(height * 0.28));
+ const bottomY = midY + midH + gap;
+ const bottomH = Math.max(84, height - bottomY - 12);
+ const leftW = Math.max(205, Math.floor(width * 0.16));
+ const rightW = Math.max(225, Math.floor(width * 0.18));
+ const centerX = margin + leftW + gap;
+ const centerW = width - centerX - rightW - gap - margin;
+
+ const panel = (x, y, w, h, title, subtitle = '') => {
+ const g = layer.append('g').attr('class', 'linear-analysis-panel');
+ g.append('rect')
+ .attr('x', x)
+ .attr('y', y)
+ .attr('width', w)
+ .attr('height', h)
+ .attr('rx', 4)
+ .style('fill', 'rgba(5, 9, 15, 0.82)')
+ .style('stroke', 'rgba(92, 122, 158, 0.42)')
+ .style('stroke-width', 1);
+ g.append('text')
+ .attr('x', x + 10)
+ .attr('y', y + 18)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '11px')
+ .style('letter-spacing', '0.45px')
+ .style('fill', '#d9e4f5')
+ .text(title);
+ if (subtitle) {
+ g.append('text')
+ .attr('x', x + 10)
+ .attr('y', y + 34)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9px')
+ .style('fill', '#8fa0b8')
+ .text(subtitle);
+ }
+ return g;
+ };
+ const heatColor = (v) => {
+ const value = Math.max(-1, Math.min(1, v));
+ if (value < -0.18) return '#544cff';
+ if (value < 0) return '#7b40d8';
+ if (value < 0.22) return '#b23db5';
+ if (value < 0.5) return '#e05274';
+ return '#ff8a42';
+ };
+ const spark = (g, x, y, w, h, seed, color) => {
+ const p = d3.path();
+ for (let i = 0; i < 28; i += 1) {
+ const px = x + (w / 27) * i;
+ const v = Math.sin((seed + i) * 0.7) * 0.35 + Math.sin((seed + i) * 0.19) * 0.2;
+ const py = y + h * 0.5 - v * h * 0.7;
+ if (i === 0) p.moveTo(px, py);
+ else p.lineTo(px, py);
+ }
+ g.append('path')
+ .attr('d', p.toString())
+ .style('fill', 'none')
+ .style('stroke', color)
+ .style('stroke-width', 1)
+ .style('stroke-opacity', 0.82);
+ };
+ const showAnalysisTip = (lines, event) => {
+ if (!this.hoverCard) return;
+ this.hoverCard.textContent = lines.join('\n');
+ this.hoverCard.style.display = 'block';
+ this.positionHoverCard(event);
+ };
+ const runtimeQuality = runtimeSources.filter((source) => source.active).reduce((score, source) => {
+ const src = String(source.source || '');
+ if (src === 'direct') return score + 3;
+ if (src === 'procfs') return score + 2;
+ return score + 1;
+ }, 0);
+
+ layer.append('text')
+ .attr('x', centerX + 8)
+ .attr('y', 30)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '18px')
+ .style('letter-spacing', '1px')
+ .style('fill', '#dfe8f7')
+ .text('LINEAR CRYPTOANALYSIS VISUALIZATION');
+ layer.append('text')
+ .attr('x', centerX + 8)
+ .attr('y', 50)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '10px')
+ .style('fill', '#9cabc0')
+ .text(`${algLabel} - ${model.rounds} ROUNDS - LINEAR APPROXIMATION TRACKING`);
+
+ this.algorithmModes.forEach((mode, idx) => {
+ const isActive = mode === model.request;
+ const x = centerX + centerW - 252 + idx * 84;
+ const btn = layer.append('g')
+ .style('cursor', 'pointer')
+ .on('click', () => {
+ this.selectedCompetitionAlgorithm = mode;
+ this.renderFlowMap(this.lastPayload || this.normalizeTelemetry(this.getFallbackTelemetry()));
+ });
+ btn.append('rect')
+ .attr('x', x)
+ .attr('y', 20)
+ .attr('width', 76)
+ .attr('height', 22)
+ .attr('rx', 4)
+ .style('fill', isActive ? 'rgba(38, 63, 98, 0.95)' : 'rgba(7, 11, 17, 0.82)')
+ .style('stroke', isActive ? 'rgba(128, 190, 255, 0.86)' : 'rgba(122, 145, 176, 0.32)')
+ .style('stroke-width', 1);
+ btn.append('text')
+ .attr('x', x + 38)
+ .attr('y', 35)
+ .attr('text-anchor', 'middle')
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9px')
+ .style('fill', isActive ? '#d8eaff' : '#9dafc5')
+ .text(mode);
+ });
+
+ runtimeSources.filter((source) => source.active).slice(0, 4).forEach((source, idx) => {
+ const src = String(source.source || 'heuristic');
+ const color = src === 'direct' ? '#8effc8' : (src === 'procfs' ? '#8fdcff' : '#ffd58d');
+ const x = centerX + 8 + idx * 118;
+ layer.append('rect')
+ .attr('x', x)
+ .attr('y', 58)
+ .attr('width', 108)
+ .attr('height', 18)
+ .attr('rx', 4)
+ .style('fill', 'rgba(8, 13, 20, 0.82)')
+ .style('stroke', color)
+ .style('stroke-opacity', 0.42);
+ layer.append('text')
+ .attr('x', x + 8)
+ .attr('y', 71)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8px')
+ .style('fill', color)
+ .text(`${String(source.label || source.id).slice(0, 11)}:${src}`);
+ });
+
+ const ctx = panel(margin, 10, leftW, 116, 'PROCESS CONTEXT');
+ [
+ ['process', primary.process || 'kernel/user'],
+ ['pid', primary.pid || '?'],
+ ['protocol', primary.protocol || 'CRYPTO API'],
+ ['algorithm', primary.algorithm || `${model.request}-GCM/SHA256`],
+ ['kernel path', model.selectedDriver],
+ ['cpu flags', model.selectedDriver.includes('aes') ? 'AES-NI, PCLMULQDQ' : 'generic/simd']
+ ].forEach(([k, v], idx) => {
+ ctx.append('text')
+ .attr('x', margin + 14)
+ .attr('y', 42 + idx * 13)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8.8px')
+ .style('fill', '#9fb0c7')
+ .text(`${k}:`);
+ ctx.append('text')
+ .attr('x', margin + 76)
+ .attr('y', 42 + idx * 13)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8.8px')
+ .style('fill', '#d0dae8')
+ .text(String(v).slice(0, 24));
+ });
+
+ const flow = panel(margin, 136, leftW, 178, 'DATA FLOW');
+ const flowSteps = ['userspace', 'TLS 1.3', 'sendmsg()/recvmsg()', 'AF_ALG', 'crypto_aead_encrypt', `${model.selectedDriver}`];
+ flowSteps.forEach((step, idx) => {
+ const y = 168 + idx * 24;
+ flow.append('text')
+ .attr('x', margin + leftW * 0.5)
+ .attr('y', y)
+ .attr('text-anchor', 'middle')
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9px')
+ .style('fill', idx === flowSteps.length - 1 ? '#9dffca' : '#b6c4d7')
+ .text(step.slice(0, 24));
+ if (idx < flowSteps.length - 1) {
+ flow.append('line')
+ .attr('x1', margin + leftW * 0.5)
+ .attr('x2', margin + leftW * 0.5)
+ .attr('y1', y + 6)
+ .attr('y2', y + 18)
+ .style('stroke', 'rgba(122, 150, 190, 0.48)');
+ }
+ });
+
+ const map = panel(centerX, topY, centerW, topH, 'BIT CORRELATION MAP (LINEAR APPROXIMATION)');
+ const mapLeft = centerX + 72;
+ const mapRight = centerX + centerW - 94;
+ const mapTop = topY + 56;
+ const mapBottom = topY + topH - 72;
+ const bitRows = 8;
+ const roundCount = Math.min(model.rounds, 8);
+ const bitY = (idx) => mapTop + (mapBottom - mapTop) * (idx / Math.max(1, bitRows - 1));
+ map.append('text').attr('x', mapLeft - 38).attr('y', topY + 44).style('font-family', 'Share Tech Mono, monospace').style('font-size', '9px').style('fill', '#9eafc4').text('PLAINTEXT BITS');
+ map.append('text').attr('x', mapRight + 6).attr('y', topY + 44).style('font-family', 'Share Tech Mono, monospace').style('font-size', '9px').style('fill', '#9eafc4').text('CIPHERTEXT BITS');
+ for (let row = 0; row < bitRows; row += 1) {
+ const y = bitY(row);
+ map.append('text').attr('x', mapLeft - 54).attr('y', y + 3).style('font-family', 'Share Tech Mono, monospace').style('font-size', '8px').style('fill', '#8fa0b8').text(`P${row}`);
+ map.append('text').attr('x', mapRight + 54).attr('y', y + 3).style('font-family', 'Share Tech Mono, monospace').style('font-size', '8px').style('fill', '#8fa0b8').text(`C${row}`);
+ for (let b = 0; b < 5; b += 1) {
+ map.append('circle').attr('cx', mapLeft - 30 + b * 7).attr('cy', y).attr('r', 1.8).style('fill', ((model.seed + row + b) % 3) ? '#7754d8' : '#ff704c').style('opacity', 0.68);
+ map.append('circle').attr('cx', mapRight + 18 + b * 7).attr('cy', y).attr('r', 1.8).style('fill', ((model.seed + row + b) % 4) ? '#ff704c' : '#7754d8').style('opacity', 0.68);
+ }
+ }
+ const layerXs = Array.from({ length: roundCount }, (_, idx) => mapLeft + 56 + ((mapRight - mapLeft - 112) / Math.max(1, roundCount - 1)) * idx);
+ layerXs.forEach((x, idx) => {
+ const bias = model.bestTrail[idx]?.bias || model.baseBias;
+ const color = heatColor((bias / Math.max(model.maxBias, 0.001)) - 0.45);
+ map.append('text').attr('x', x).attr('y', topY + 46).attr('text-anchor', 'middle').style('font-family', 'Share Tech Mono, monospace').style('font-size', '8.5px').style('fill', '#b7c3d3').text(idx % 3 === 0 ? `R${idx}` : (idx % 3 === 1 ? `M${idx}` : `K${idx}`));
+ const roundRect = map.append('rect')
+ .attr('x', x - 17)
+ .attr('y', mapTop - 12)
+ .attr('width', 34)
+ .attr('height', mapBottom - mapTop + 24)
+ .style('fill', color)
+ .style('fill-opacity', 0.18 + idx * 0.025)
+ .style('stroke', color)
+ .style('stroke-opacity', 0.58)
+ .style('cursor', 'crosshair')
+ .on('mouseenter', (event) => {
+ roundRect.style('stroke-width', 2).style('stroke-opacity', 0.95);
+ showAnalysisTip([
+ `round layer : ${idx}`,
+ `stage : ${idx % 3 === 0 ? 'round' : (idx % 3 === 1 ? 'mix/linear' : 'key add')}`,
+ `bias : +${bias.toFixed(6)}`,
+ `correlation : +${(bias * 2).toFixed(6)}`,
+ `active sbox : ${model.bestTrail[idx]?.activeSboxes || '-'}`
+ ], event);
+ })
+ .on('mousemove', (event) => this.positionHoverCard(event))
+ .on('mouseleave', () => {
+ roundRect.style('stroke-width', 1).style('stroke-opacity', 0.58);
+ this.hideHoverCard();
+ });
+ for (let row = 0; row < bitRows; row += 1) {
+ for (let col = 0; col < 4; col += 1) {
+ const v = Math.sin((model.seed + idx * 11 + row * 7 + col) * 0.22);
+ const cellRect = map.append('rect')
+ .attr('x', x - 13 + col * 7)
+ .attr('y', bitY(row) - 5)
+ .attr('width', 4)
+ .attr('height', 10)
+ .style('fill', heatColor(v))
+ .style('opacity', 0.28 + Math.abs(v) * 0.5)
+ .style('cursor', 'crosshair')
+ .on('mouseenter', (event) => {
+ cellRect.style('opacity', 1).style('stroke', '#ffffff').style('stroke-width', 0.5);
+ showAnalysisTip([
+ `LAT cell : R${idx} / bit ${row}.${col}`,
+ `mask value : ${v >= 0 ? '+' : ''}${v.toFixed(4)}`,
+ `bias class : ${Math.abs(v) > 0.72 ? 'hot approximation' : 'low signal'}`,
+ `source : deterministic model`
+ ], event);
+ })
+ .on('mousemove', (event) => this.positionHoverCard(event))
+ .on('mouseleave', () => {
+ cellRect.style('opacity', 0.28 + Math.abs(v) * 0.5).style('stroke', 'none');
+ this.hideHoverCard();
+ });
+ }
+ }
+ });
+ const bestTrailPath = d3.path();
+ layerXs.forEach((x, idx) => {
+ const y = bitY((idx * 2 + (model.seed % bitRows)) % bitRows);
+ if (idx === 0) bestTrailPath.moveTo(x - 22, y);
+ bestTrailPath.lineTo(x, y);
+ if (idx === layerXs.length - 1) bestTrailPath.lineTo(mapRight + 26, y);
+ });
+ map.append('path')
+ .attr('d', bestTrailPath.toString())
+ .style('fill', 'none')
+ .style('stroke', '#ffcf7a')
+ .style('stroke-width', 2.2)
+ .style('stroke-opacity', 0.9)
+ .style('filter', 'url(#crypto-line-glow)');
+ map.append('text')
+ .attr('x', mapLeft + 8)
+ .attr('y', mapTop - 22)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9px')
+ .style('fill', '#ffcf7a')
+ .text(`BEST LINEAR TRAIL | bias +${model.maxBias.toFixed(5)} | live-source score ${runtimeQuality}`);
+ for (let row = 0; row < bitRows; row += 1) {
+ const startY = bitY(row);
+ layerXs.forEach((x, idx) => {
+ const nextX = idx === layerXs.length - 1 ? mapRight + 14 : layerXs[idx + 1] - 18;
+ const nextY = bitY((row + idx + (model.seed % 3)) % bitRows);
+ const p = d3.path();
+ p.moveTo(idx === 0 ? mapLeft - 8 : x + 18, startY);
+ p.bezierCurveTo(x + 22, startY, nextX - 24, nextY, nextX, nextY);
+ map.append('path')
+ .attr('d', p.toString())
+ .style('fill', 'none')
+ .style('stroke', heatColor(Math.sin((row + idx + model.seed) * 0.31)))
+ .style('stroke-width', 0.65)
+ .style('stroke-opacity', 0.34);
+ });
+ }
+ const biasX = centerX + centerW * 0.26;
+ const biasY = topY + topH - 40;
+ map.append('text').attr('x', centerX + centerW * 0.5).attr('y', biasY - 12).attr('text-anchor', 'middle').style('font-family', 'Share Tech Mono, monospace').style('font-size', '8px').style('fill', '#9caec5').text('CORRELATION (BIAS)');
+ map.append('line').attr('x1', biasX).attr('x2', biasX + centerW * 0.48).attr('y1', biasY).attr('y2', biasY).style('stroke', '#fa7d48').style('stroke-width', 3).style('filter', 'url(#crypto-line-glow)');
+ map.append('line').attr('x1', biasX).attr('x2', biasX + centerW * 0.24).attr('y1', biasY).attr('y2', biasY).style('stroke', '#5b48ff').style('stroke-width', 3);
+ ['-0.5', '0', '+0.5'].forEach((t, idx) => map.append('text').attr('x', biasX + idx * centerW * 0.24).attr('y', biasY + 16).attr('text-anchor', 'middle').style('font-family', 'Share Tech Mono, monospace').style('font-size', '8px').style('fill', '#9caec5').text(t));
+
+ const info = panel(centerX + centerW + gap, topY, rightW, 138, 'LINEAR APPROXIMATION INFO');
+ [
+ `approximation:`,
+ `P[L(P,K) = L(C)] = ${(0.5 + model.maxBias).toFixed(7)}`,
+ `bias: +${model.maxBias.toFixed(7)}`,
+ `correlation: +${(model.maxBias * 2).toFixed(7)}`,
+ `mask(hex):`,
+ `P: 0x${(model.seed & 0xffff).toString(16).padStart(4, '0')}`,
+ `C: 0x${((model.seed * 17) & 0xffff).toString(16).padStart(4, '0')}`,
+ `rounds: ${model.rounds}/${model.rounds}`,
+ `quality: good`
+ ].forEach((line, idx) => {
+ info.append('text')
+ .attr('x', centerX + centerW + gap + 12)
+ .attr('y', topY + 42 + idx * 11)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8.2px')
+ .style('fill', idx === 2 || idx === 8 ? '#8effc8' : '#b3bfd0')
+ .text(line);
+ });
+ const biasPanel = panel(centerX + centerW + gap, topY + 146, rightW, topH - 146, 'BIAS OVER ROUNDS');
+ const chartX = centerX + centerW + gap + 34;
+ const chartY = topY + 186;
+ const chartW = rightW - 58;
+ const chartH = topH - 202;
+ biasPanel.append('line').attr('x1', chartX).attr('x2', chartX + chartW).attr('y1', chartY + chartH / 2).attr('y2', chartY + chartH / 2).style('stroke', 'rgba(116, 138, 170, 0.28)');
+ const bp = d3.path();
+ model.bestTrail.forEach((step, idx) => {
+ const x = chartX + (chartW / Math.max(1, model.bestTrail.length - 1)) * idx;
+ const y = chartY + chartH * 0.5 - Math.sin(idx * 0.8 + model.seed) * chartH * 0.2 - (step.bias / model.maxBias) * chartH * 0.28;
+ if (idx === 0) bp.moveTo(x, y);
+ else bp.lineTo(x, y);
+ biasPanel.append('circle').attr('cx', x).attr('cy', y).attr('r', 2).style('fill', idx > model.bestTrail.length * 0.55 ? '#ff9a55' : '#9d55ff');
+ });
+ biasPanel.append('path').attr('d', bp.toString()).style('fill', 'none').style('stroke', '#ff8655').style('stroke-width', 1.5);
+
+ const diffW = Math.max(320, width * 0.33);
+ const keyW = Math.max(320, width * 0.33);
+ const entropyW = width - margin * 2 - diffW - keyW - gap * 2;
+ const diff = panel(margin, midY, diffW, midH, 'DIFFUSION & AVALANCHE VISUALIZATION', 'FLIP 1 BIT IN PLAINTEXT -> OBSERVE PROPAGATION');
+ const cell = Math.max(5, Math.min(10, (diffW - 58) / 38));
+ for (let round = 0; round < Math.min(10, model.rounds + 1); round += 1) {
+ const gx = margin + 24 + (round % 5) * ((diffW - 48) / 5);
+ const gy = midY + 54 + Math.floor(round / 5) * ((midH - 78) / 2);
+ diff.append('text').attr('x', gx).attr('y', gy - 8).style('font-family', 'Share Tech Mono, monospace').style('font-size', '7.5px').style('fill', '#8fa0b8').text(`ROUND ${round}`);
+ for (let a = 0; a < 6; a += 1) {
+ for (let b = 0; b < 6; b += 1) {
+ const v = Math.abs(Math.sin((model.seed + round * 13 + a * 5 + b) * 0.2));
+ diff.append('circle').attr('cx', gx + b * cell * 1.6).attr('cy', gy + a * cell * 1.45).attr('r', cell * 0.42).style('fill', heatColor(v - 0.35)).style('opacity', 0.25 + v * 0.65);
+ }
+ }
+ }
+
+ const keyX = margin + diffW + gap;
+ const key = panel(keyX, midY, keyW, midH, 'KEY HYPOTHESIS SPACE (RANKING)');
+ const kcx = keyX + keyW * 0.5;
+ const kcy = midY + midH * 0.55;
+ const bestKey = {
+ x: kcx + keyW * 0.23,
+ y: kcy - midH * 0.18
+ };
+ for (let i = 0; i < 420; i += 1) {
+ const h = this.hashText(`${model.seed}-key-${i}`);
+ const angle = h * 0.018;
+ const rr = Math.min(keyW, midH) * (0.08 + ((h % 1000) / 1000) * 0.42);
+ const px = kcx + Math.cos(angle) * rr * 1.35;
+ const py = kcy + Math.sin(angle * 0.72) * rr * 0.55;
+ const v = Math.sin(h * 0.03);
+ key.append('circle').attr('cx', px).attr('cy', py).attr('r', Math.abs(v) > 0.92 ? 1.8 : 0.8).style('fill', heatColor(v)).style('opacity', 0.28 + Math.abs(v) * 0.38);
+ }
+ const spiral = d3.path();
+ for (let i = 0; i < 90; i += 1) {
+ const t = i / 8;
+ const r = 4 + t * 4.4;
+ const x = kcx + Math.cos(t) * r * 1.45;
+ const y = kcy + Math.sin(t) * r * 0.86;
+ if (i === 0) spiral.moveTo(x, y);
+ else spiral.lineTo(x, y);
+ }
+ key.append('path').attr('d', spiral.toString()).style('fill', 'none').style('stroke', '#ff6b55').style('stroke-width', 1.2).style('stroke-opacity', 0.72);
+ for (let i = 0; i < 3; i += 1) {
+ key.append('circle')
+ .attr('cx', bestKey.x)
+ .attr('cy', bestKey.y)
+ .attr('r', 10 + i * 8 + Math.sin(this.activeAnimationTick * 0.22 + i) * 2)
+ .style('fill', 'none')
+ .style('stroke', '#ffad7a')
+ .style('stroke-opacity', 0.34 - i * 0.08)
+ .style('filter', 'url(#crypto-line-glow)');
+ }
+ key.append('circle')
+ .attr('cx', bestKey.x)
+ .attr('cy', bestKey.y)
+ .attr('r', 4.6)
+ .style('fill', '#ffad7a')
+ .style('filter', 'url(#crypto-line-glow)');
+ key.append('line')
+ .attr('x1', kcx)
+ .attr('y1', kcy)
+ .attr('x2', bestKey.x)
+ .attr('y2', bestKey.y)
+ .style('stroke', '#ff6b55')
+ .style('stroke-width', 1)
+ .style('stroke-opacity', 0.72);
+ key.append('text').attr('x', bestKey.x + 12).attr('y', bestKey.y - 16).style('font-family', 'Share Tech Mono, monospace').style('font-size', '9px').style('fill', '#ffad7a').text('best hypothesis');
+ key.append('text').attr('x', bestKey.x + 12).attr('y', bestKey.y - 2).style('font-family', 'Share Tech Mono, monospace').style('font-size', '8.5px').style('fill', '#ffcf9a').text(`rank #1 | bias +${model.maxBias.toFixed(4)}`);
+
+ const entX = keyX + keyW + gap;
+ const ent = panel(entX, midY, entropyW, midH, 'ENTROPY COLLAPSE (SPIRAL VIEW)', 'FIBONACCI SPIRAL - ENTROPY REDUCTION OVER ROUNDS');
+ const ecx = entX + entropyW * 0.48;
+ const ecy = midY + midH * 0.55;
+ const ep = d3.path();
+ for (let i = 0; i < 190; i += 1) {
+ const t = i * 0.16;
+ const r = Math.min(entropyW, midH) * 0.42 * (1 - i / 205);
+ const x = ecx + Math.cos(t) * r;
+ const y = ecy + Math.sin(t) * r;
+ if (i === 0) ep.moveTo(x, y);
+ else ep.lineTo(x, y);
+ }
+ ent.append('path').attr('d', ep.toString()).style('fill', 'none').style('stroke', '#7557ff').style('stroke-width', 1.4).style('filter', 'url(#crypto-line-glow)');
+ for (let r = 0; r < 7; r += 1) {
+ ent.append('circle').attr('cx', ecx).attr('cy', ecy).attr('r', 10 + r * Math.min(entropyW, midH) * 0.055).style('fill', 'none').style('stroke', r < 2 ? '#ff7a44' : '#5968d8').style('stroke-opacity', 0.32);
+ }
+ [128, 96, 64, 32].forEach((bits, idx) => {
+ ent.append('text')
+ .attr('x', entX + entropyW - 48)
+ .attr('y', midY + 60 + idx * 28)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8px')
+ .style('fill', idx < 2 ? '#8fdcff' : '#ffad7a')
+ .text(`${bits}b`);
+ });
+ ent.append('text')
+ .attr('x', entX + 14)
+ .attr('y', midY + midH - 16)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '8.5px')
+ .style('fill', '#9dafc5')
+ .text(`entropy slope: ${(model.correlationDecay * 100).toFixed(0)}% | rounds ${model.rounds}`);
+
+ const bottomPanels = [
+ [margin, bottomY, width * 0.56 - margin, bottomH, 'KERNEL CRYPTO METRICS'],
+ [width * 0.56 + gap, bottomY, width * 0.22, bottomH, 'ACTIVE ALGORITHMS (LIVE)'],
+ [width * 0.78 + gap * 2, bottomY, width * 0.22 - margin * 2, bottomH, 'EVENT LOG (CRYPTO)']
+ ];
+ const metrics = panel(...bottomPanels[0]);
+ const metricItems = [
+ ['entropy pool', '256/256 bits', '#8fdcff'],
+ ['rng health', 'good', '#8effc8'],
+ ['aes-ni usage', `${Math.round(model.confidence * 100)}%`, '#9dffca'],
+ ['avg latency', `${(model.maxBias * 100).toFixed(2)} us`, '#d6e3f4'],
+ ['throughput', `${(items.length * 0.7 + 1.8).toFixed(2)} GB/s`, '#d6e3f4'],
+ ['bias alert', model.maxBias > 0.06 ? 'watch' : 'none', '#ffcf8d']
+ ];
+ metricItems.forEach((m, idx) => {
+ const x = margin + 14 + idx * ((width * 0.56 - 44) / metricItems.length);
+ metrics.append('text').attr('x', x).attr('y', bottomY + 38).style('font-family', 'Share Tech Mono, monospace').style('font-size', '8.5px').style('fill', '#8fa0b8').text(m[0]);
+ metrics.append('text').attr('x', x).attr('y', bottomY + 56).style('font-family', 'Share Tech Mono, monospace').style('font-size', '10px').style('fill', m[2]).text(m[1]);
+ spark(metrics, x, bottomY + 66, 58, Math.max(12, bottomH - 78), model.seed + idx * 9, m[2]);
+ });
+ const algos = panel(...bottomPanels[1]);
+ ['AES-GCM', 'ChaCha20-Poly1305', 'SHA256'].forEach((name, idx) => {
+ const isSelected = name.toLowerCase().includes(model.request.toLowerCase().replace('20', ''));
+ algos.append('text').attr('x', width * 0.56 + gap + 14).attr('y', bottomY + 42 + idx * 24).style('font-family', 'Share Tech Mono, monospace').style('font-size', '9px').style('fill', '#d2dce9').text(name);
+ algos.append('text').attr('x', width * 0.56 + gap + width * 0.15).attr('y', bottomY + 42 + idx * 24).style('font-family', 'Share Tech Mono, monospace').style('font-size', '9px').style('fill', isSelected ? '#ffcf7a' : '#8effc8').text(isSelected ? 'selected' : 'active');
+ });
+ const log = panel(...bottomPanels[2]);
+ [
+ `crypto_req ${model.request.toLowerCase()}: init`,
+ `${model.selectedDriver}: setkey`,
+ `best trail locked bias=${model.maxBias.toFixed(5)}`,
+ `runtime sources score=${runtimeQuality}`,
+ `${model.request.toLowerCase()} done (${(model.maxBias * 100).toFixed(2)}us)`
+ ].forEach((line, idx) => {
+ log.append('text').attr('x', width * 0.78 + gap * 2 + 12).attr('y', bottomY + 40 + idx * 17).style('font-family', 'Share Tech Mono, monospace').style('font-size', '8.5px').style('fill', '#9fb0c7').text(`[${idx + 19}:21:3${idx}] ${line}`.slice(0, 42));
+ });
+ }
+
+ drawLinearAnalysisView(layer, payload, width, height, tickId) {
+ this.drawLinearAnalysisDashboard(layer, payload, width, height, tickId);
+ return;
+ const model = this.buildLinearAnalysisModel(payload);
+ const cx = width * 0.5;
+ const cy = height * 0.52;
+ const radius = Math.min(width, height) * 0.32;
+ const goldenAngle = Math.PI * (3 - Math.sqrt(5));
+ const pointCount = 220;
+
+ layer.append('text')
+ .attr('x', width * 0.5)
+ .attr('y', 152)
+ .attr('text-anchor', 'middle')
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '12px')
+ .style('letter-spacing', '0.8px')
+ .style('fill', '#b8c6dc')
+ .text(`${model.request} LINEAR CRYPTANALYSIS OBSERVATORY | VOGEL FIELD + FIBONACCI TRAIL`);
+
+ const field = layer.append('g').attr('class', 'crypto-linear-vogel-field');
+ field.append('circle')
+ .attr('cx', cx)
+ .attr('cy', cy)
+ .attr('r', radius + 18)
+ .style('fill', 'rgba(5, 8, 13, 0.48)')
+ .style('stroke', 'rgba(130, 165, 210, 0.22)')
+ .style('stroke-width', 1);
+
+ for (let ring = 1; ring <= 5; ring += 1) {
+ field.append('circle')
+ .attr('cx', cx)
+ .attr('cy', cy)
+ .attr('r', (radius / 5) * ring)
+ .style('fill', 'none')
+ .style('stroke', ring === 5 ? 'rgba(140, 175, 220, 0.18)' : 'rgba(90, 115, 145, 0.12)')
+ .style('stroke-width', 0.8);
+ }
+
+ for (let i = 0; i < pointCount; i += 1) {
+ const n = i + 1;
+ const angle = n * goldenAngle;
+ const r = radius * Math.sqrt(n / pointCount);
+ const hash = this.hashText(`${model.seed}-${i}-${model.request}`);
+ const bias = Math.abs(Math.sin(hash * 0.013 + model.baseBias * 80)) * model.baseBias * (1.15 - r / (radius * 1.55));
+ const hot = bias > model.baseBias * 0.82;
+ const px = cx + Math.cos(angle) * r;
+ const py = cy + Math.sin(angle) * r;
+ const color = hot ? '#ffad7a' : (bias > model.baseBias * 0.48 ? '#f4da87' : '#7fd7ff');
+ field.append('circle')
+ .attr('cx', px)
+ .attr('cy', py)
+ .attr('r', hot ? 3.4 : 1.7 + bias * 30)
+ .style('fill', color)
+ .style('opacity', hot ? 0.88 : 0.42 + bias * 6)
+ .style('filter', hot ? 'url(#crypto-line-glow)' : null);
+ }
+
+ const trail = layer.append('g').attr('class', 'crypto-linear-fibonacci-trail');
+ const fibScale = radius / 11;
+ const trailPoints = model.bestTrail.map((step, idx) => {
+ const t = idx / Math.max(1, model.bestTrail.length - 1);
+ const angle = 0.75 + idx * 0.72;
+ const r = fibScale * Math.pow(1.618, idx * 0.36);
+ return {
+ x: cx + Math.cos(angle) * r,
+ y: cy + Math.sin(angle) * r,
+ step,
+ t
+ };
+ });
+ const path = d3.path();
+ trailPoints.forEach((point, idx) => {
+ if (idx === 0) path.moveTo(point.x, point.y);
+ else path.lineTo(point.x, point.y);
+ });
+ trail.append('path')
+ .attr('d', path.toString())
+ .style('fill', 'none')
+ .style('stroke', '#f2c979')
+ .style('stroke-width', 2.1)
+ .style('stroke-opacity', 0.78)
+ .style('filter', 'url(#crypto-line-glow)');
+
+ trailPoints.forEach((point, idx) => {
+ const phase = (this.activeAnimationTick * 0.16 + idx * 0.7);
+ const pulse = 0.55 + 0.45 * ((Math.sin(phase) + 1) / 2);
+ trail.append('circle')
+ .attr('cx', point.x)
+ .attr('cy', point.y)
+ .attr('r', 4.2 + pulse * 2.2)
+ .style('fill', idx === 0 ? '#ffbe7a' : '#9ee8ff')
+ .style('opacity', 0.74 + pulse * 0.2);
+ trail.append('text')
+ .attr('x', point.x + 10)
+ .attr('y', point.y - 8)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9px')
+ .style('fill', '#d7e3f5')
+ .text(`r${point.step.round} bias ${point.step.bias.toFixed(4)}`);
+ });
+
+ this.animateLinearBiasProbe(trail, trailPoints, tickId);
+ this.drawLinearAnalysisPanels(layer, model, width, height);
+ }
+
+ animateLinearBiasProbe(group, points, tickId) {
+ if (!points.length) return;
+ const probe = group.append('circle')
+ .attr('r', 4)
+ .attr('cx', points[0].x)
+ .attr('cy', points[0].y)
+ .style('fill', '#ffffff')
+ .style('opacity', 0.92)
+ .style('filter', 'url(#crypto-line-glow)');
+
+ const runLoop = () => {
+ if (!this.isActive || tickId !== this.activeAnimationTick || this.activeCryptoView !== 'LINEAR_ANALYSIS') {
+ probe.remove();
+ return;
+ }
+ let chain = probe.transition().duration(0);
+ for (let i = 1; i < points.length; i += 1) {
+ chain = chain.duration(520).attr('cx', points[i].x).attr('cy', points[i].y);
+ }
+ chain.on('end', () => {
+ probe.attr('cx', points[0].x).attr('cy', points[0].y);
+ runLoop();
+ });
+ };
+ runLoop();
+ }
+
+ drawLinearAnalysisPanels(layer, model, width, height) {
+ const panel = (x, y, w, h, title) => {
+ const g = layer.append('g');
+ g.append('rect')
+ .attr('x', x)
+ .attr('y', y)
+ .attr('width', w)
+ .attr('height', h)
+ .attr('rx', 10)
+ .style('fill', 'rgba(7, 10, 15, 0.86)')
+ .style('stroke', 'rgba(165, 185, 220, 0.34)')
+ .style('stroke-width', 1);
+ g.append('text')
+ .attr('x', x + 14)
+ .attr('y', y + 24)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '11px')
+ .style('fill', '#dbe4f2')
+ .text(title);
+ return g;
+ };
+
+ const left = panel(26, 176, Math.max(310, width * 0.25), 232, 'LINEAR APPROXIMATION TABLE');
+ const metrics = [
+ `algorithm: ${model.request}`,
+ `selected driver: ${model.selectedDriver}`,
+ `LAT energy: ${model.latEnergy}/100`,
+ `max bias: ${model.maxBias.toFixed(5)}`,
+ `correlation decay: ${model.correlationDecay.toFixed(2)}`,
+ `active S-boxes est: ${model.activeSboxes}`,
+ `confidence: ${(model.confidence * 100).toFixed(0)}%`
+ ];
+ metrics.forEach((line, idx) => {
+ left.append('text')
+ .attr('x', 42)
+ .attr('y', 214 + idx * 24)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', idx < 2 ? '10.5px' : '10px')
+ .style('fill', idx === 3 ? '#ffcb8a' : '#aebbd0')
+ .text(line);
+ });
+
+ const rightW = Math.max(330, width * 0.26);
+ const rightX = width - rightW - 24;
+ const right = panel(rightX, 176, rightW, 278, 'FIBONACCI BIAS TRAIL');
+ right.append('text')
+ .attr('x', rightX + 14)
+ .attr('y', 214)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9.5px')
+ .style('fill', '#8fa0b8')
+ .text('round path: plaintext mask -> nonlinear layer -> diffusion');
+ model.bestTrail.slice(0, 8).forEach((step, idx) => {
+ const y = 242 + idx * 24;
+ const barW = Math.max(10, Math.min(rightW - 160, step.bias / Math.max(model.maxBias, 0.001) * (rightW - 172)));
+ right.append('text')
+ .attr('x', rightX + 14)
+ .attr('y', y)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '9.5px')
+ .style('fill', '#c5d0df')
+ .text(step.label);
+ right.append('rect')
+ .attr('x', rightX + rightW - 130)
+ .attr('y', y - 8)
+ .attr('width', rightW - 150)
+ .attr('height', 5)
+ .attr('rx', 2)
+ .style('fill', 'rgba(38, 44, 56, 0.9)');
+ right.append('rect')
+ .attr('x', rightX + rightW - 130)
+ .attr('y', y - 8)
+ .attr('width', barW)
+ .attr('height', 5)
+ .attr('rx', 2)
+ .style('fill', idx === 0 ? '#ffb979' : '#8fdcff');
+ });
+
+ const bottom = panel(26, height - 126, width - 52, 92, 'MODEL NOTE');
+ bottom.append('text')
+ .attr('x', 42)
+ .attr('y', height - 84)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '10px')
+ .style('fill', '#aeb9ca')
+ .text('educational model: masks/bias are derived from live crypto state and deterministic heuristics, not from decrypted traffic');
+ bottom.append('text')
+ .attr('x', 42)
+ .attr('y', height - 58)
+ .style('font-family', 'Share Tech Mono, monospace')
+ .style('font-size', '10px')
+ .style('fill', '#8fa0b8')
+ .text('Vogel spiral = distribution of candidate linear masks; Fibonacci trail = bias decay across rounds');
+ }
+
renderFlowMap(payload) {
if (!this.svg) return;
this.lastPayload = payload;
@@ -1739,7 +2629,12 @@ class CryptoSubsystemVisualization {
const layer = this.svg.append('g').attr('class', 'crypto-flow-layer');
this.drawGrid(layer, width, height);
+ if (this.activeCryptoView === 'LINEAR_ANALYSIS') {
+ this.drawLinearAnalysisView(layer, payload, width, height, tickId);
+ return;
+ }
this.drawProtocolLegend(layer);
+ this.drawRuntimeSourcesPanel(layer, payload, width, height);
this.drawEntropyCloud(layer, payload?.meta || {}, width, height);
this.drawAlgorithmCompetition(layer, payload?.meta || {}, width, height);
this.drawDecisionPipeline(layer, payload?.meta || {}, width, height);
diff --git a/templates/linux-crypto-subsystem.html b/templates/linux-crypto-subsystem.html
index d202dda..c653ed3 100644
--- a/templates/linux-crypto-subsystem.html
+++ b/templates/linux-crypto-subsystem.html
@@ -135,7 +135,7 @@ Linux Crypto Kernel Visualization
-
+