yakmesh 2.8.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +637 -0
- package/CONTRIBUTING.md +42 -0
- package/Caddyfile +77 -0
- package/README.md +119 -29
- package/adapters/adapter-mlv-bible/README.md +124 -0
- package/adapters/adapter-mlv-bible/index.js +400 -0
- package/adapters/chat-mod-adapter.js +532 -0
- package/adapters/content-adapter.js +273 -0
- package/content/api.js +50 -41
- package/content/index.js +2 -2
- package/content/store.js +355 -173
- package/dashboard/index.html +19 -3
- package/database/replication.js +117 -37
- package/docs/CRYPTO-AGILITY.md +204 -0
- package/docs/MTLS-RESEARCH.md +367 -0
- package/docs/NAMCHE-SPEC.md +681 -0
- package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
- package/docs/PRECISION-DISCLOSURE.md +96 -0
- package/docs/README.md +76 -0
- package/docs/ROADMAP-2.4.0.md +447 -0
- package/docs/ROADMAP-2.5.0.md +244 -0
- package/docs/SECURITY-AUDIT-REPORT.md +306 -0
- package/docs/SST-INTEGRATION.md +712 -0
- package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
- package/docs/TERNARY-AUDIT-REPORT.md +247 -0
- package/docs/TME-FAQ.md +221 -0
- package/docs/WHITEPAPER.md +623 -0
- package/docs/adapters.html +1001 -0
- package/docs/advanced-systems.html +1045 -0
- package/docs/annex.html +1046 -0
- package/docs/api.html +970 -0
- package/docs/business/response-templates.md +160 -0
- package/docs/c2c.html +1225 -0
- package/docs/cli.html +1332 -0
- package/docs/configuration.html +1248 -0
- package/docs/darshan.html +1085 -0
- package/docs/dharma.html +966 -0
- package/docs/docs-bundle.html +1075 -0
- package/docs/docs.css +3120 -0
- package/docs/docs.js +556 -0
- package/docs/doko.html +969 -0
- package/docs/geo-proof.html +858 -0
- package/docs/getting-started.html +840 -0
- package/docs/gumba-tutorial.html +1144 -0
- package/docs/gumba.html +1098 -0
- package/docs/index.html +914 -0
- package/docs/jhilke.html +1312 -0
- package/docs/karma.html +1100 -0
- package/docs/katha.html +1037 -0
- package/docs/lama.html +978 -0
- package/docs/mandala.html +1067 -0
- package/docs/mani.html +964 -0
- package/docs/mantra.html +967 -0
- package/docs/mesh.html +1409 -0
- package/docs/nakpak.html +869 -0
- package/docs/namche.html +928 -0
- package/docs/nav-order.json +53 -0
- package/docs/prahari.html +1043 -0
- package/docs/prism-bash.min.js +1 -0
- package/docs/prism-javascript.min.js +1 -0
- package/docs/prism-json.min.js +1 -0
- package/docs/prism-tomorrow.min.css +1 -0
- package/docs/prism.min.js +1 -0
- package/docs/privacy.html +699 -0
- package/docs/quick-reference.html +1181 -0
- package/docs/sakshi.html +1402 -0
- package/docs/sandboxing.md +386 -0
- package/docs/seva.html +911 -0
- package/docs/sherpa.html +871 -0
- package/docs/studio.html +860 -0
- package/docs/stupa.html +995 -0
- package/docs/tailwind.min.css +2 -0
- package/docs/tattva.html +1332 -0
- package/docs/terms.html +686 -0
- package/docs/time-server-deployment.md +166 -0
- package/docs/time-sources.html +1392 -0
- package/docs/tivra.html +1127 -0
- package/docs/trademark-policy.html +686 -0
- package/docs/tribhuj.html +1183 -0
- package/docs/trust-security.html +1029 -0
- package/docs/tutorials/backup-recovery.html +654 -0
- package/docs/tutorials/dashboard.html +604 -0
- package/docs/tutorials/domain-setup.html +605 -0
- package/docs/tutorials/host-website.html +456 -0
- package/docs/tutorials/mesh-network.html +505 -0
- package/docs/tutorials/mobile-access.html +445 -0
- package/docs/tutorials/privacy.html +467 -0
- package/docs/tutorials/raspberry-pi.html +600 -0
- package/docs/tutorials/security-basics.html +539 -0
- package/docs/tutorials/share-files.html +431 -0
- package/docs/tutorials/troubleshooting.html +637 -0
- package/docs/tutorials/trust-karma.html +419 -0
- package/docs/tutorials/yak-protocol.html +456 -0
- package/docs/tutorials.html +1034 -0
- package/docs/vani.html +1270 -0
- package/docs/webserver.html +809 -0
- package/docs/yak-protocol.html +940 -0
- package/docs/yak-timeserver-design.md +475 -0
- package/docs/yakapp.html +1015 -0
- package/docs/ypc27.html +1069 -0
- package/docs/yurt.html +1344 -0
- package/embedded-docs/bundle.js +334 -74
- package/gossip/protocol.js +247 -27
- package/identity/key-resolver.js +262 -0
- package/identity/machine-seed.js +632 -0
- package/identity/node-key.js +669 -368
- package/identity/tribhuj-ratchet.js +506 -0
- package/knowledge-base.js +37 -8
- package/launcher/yakmesh.bat +62 -0
- package/launcher/yakmesh.sh +70 -0
- package/mesh/annex.js +462 -108
- package/mesh/beacon-broadcast.js +113 -1
- package/mesh/darshan.js +1718 -0
- package/mesh/gumba.js +1567 -0
- package/mesh/jhilke.js +651 -0
- package/mesh/katha.js +1012 -0
- package/mesh/nakpak-routing.js +8 -5
- package/mesh/network.js +724 -34
- package/mesh/pulse-sync.js +4 -1
- package/mesh/rate-limiter.js +127 -15
- package/mesh/seva.js +526 -0
- package/mesh/sherpa-discovery.js +89 -8
- package/mesh/sybil-defense.js +19 -5
- package/mesh/temporal-encoder.js +4 -3
- package/mesh/vani.js +1364 -0
- package/mesh/yurt.js +1340 -0
- package/models/entropy-sentinel.onnx +0 -0
- package/models/karma-trust.onnx +0 -0
- package/models/manifest.json +43 -0
- package/models/sakshi-anomaly.onnx +0 -0
- package/oracle/code-proof-protocol.js +7 -6
- package/oracle/codebase-lock.js +257 -28
- package/oracle/index.js +74 -15
- package/oracle/ma902-snmp.js +678 -0
- package/oracle/module-sealer.js +5 -3
- package/oracle/network-identity.js +16 -0
- package/oracle/packet-checksum.js +201 -0
- package/oracle/sst.js +579 -0
- package/oracle/ternary-144t.js +714 -0
- package/oracle/ternary-ml.js +481 -0
- package/oracle/time-api.js +239 -0
- package/oracle/time-source.js +137 -47
- package/oracle/validation-oracle-hardened.js +1111 -1071
- package/oracle/validation-oracle.js +4 -2
- package/oracle/ypc27.js +211 -0
- package/package.json +20 -3
- package/protocol/yak-handler.js +35 -9
- package/protocol/yak-protocol.js +28 -13
- package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
- package/reference/cpp/yakmesh_ypc27.cpp +179 -0
- package/sbom.json +87 -0
- package/scripts/security-audit.mjs +264 -0
- package/scripts/update-docs-nav.js +194 -0
- package/scripts/update-docs-sidebar.cjs +164 -0
- package/security/crypto-config.js +4 -3
- package/security/dharma-moderation.js +517 -0
- package/security/doko-identity.js +193 -143
- package/security/domain-consensus.js +86 -85
- package/security/fs-hardening.js +620 -0
- package/security/hardware-attestation.js +5 -3
- package/security/hybrid-trust.js +227 -87
- package/security/karma-rate-limiter.js +692 -0
- package/security/khata-protocol.js +22 -21
- package/security/khata-trust-integration.js +277 -150
- package/security/memory-safety.js +635 -0
- package/security/mesh-auth.js +11 -10
- package/security/mesh-revocation.js +373 -5
- package/security/namche-gateway.js +298 -69
- package/security/sakshi.js +460 -3
- package/security/sangha.js +770 -0
- package/security/secure-config.js +473 -0
- package/security/silicon-parity.js +13 -10
- package/security/steadywatch.js +1142 -0
- package/security/strike-system.js +32 -3
- package/security/temporal-signing.js +488 -0
- package/security/trit-commitment.js +464 -0
- package/server/crypto/annex.js +247 -0
- package/server/darshan-api.js +343 -0
- package/server/index.js +3259 -362
- package/server/komm-api.js +668 -0
- package/utils/accel.js +2273 -0
- package/utils/ternary-id.js +79 -0
- package/utils/verify-worker.js +57 -0
- package/webserver/index.js +95 -5
- package/assets/yakmesh-logo.png +0 -0
- package/assets/yakmesh-logo.svg +0 -80
- package/assets/yakmesh-logo2.png +0 -0
- package/assets/yakmesh-logo2sm.png +0 -0
- package/assets/ymsm.png +0 -0
- package/website/assets/silhouettes/adapters.svg +0 -107
- package/website/assets/silhouettes/api-endpoints.svg +0 -115
- package/website/assets/silhouettes/atomic-clock.svg +0 -83
- package/website/assets/silhouettes/base-camp.svg +0 -81
- package/website/assets/silhouettes/bridge.svg +0 -69
- package/website/assets/silhouettes/docs-bundle.svg +0 -113
- package/website/assets/silhouettes/doko-basket.svg +0 -70
- package/website/assets/silhouettes/fortress.svg +0 -93
- package/website/assets/silhouettes/gateway.svg +0 -54
- package/website/assets/silhouettes/gears.svg +0 -93
- package/website/assets/silhouettes/globe-satellite.svg +0 -67
- package/website/assets/silhouettes/karma-wheel.svg +0 -137
- package/website/assets/silhouettes/lama-council.svg +0 -141
- package/website/assets/silhouettes/mandala-network.svg +0 -169
- package/website/assets/silhouettes/mani-stones.svg +0 -149
- package/website/assets/silhouettes/mantra-wheel.svg +0 -116
- package/website/assets/silhouettes/mesh-nodes.svg +0 -113
- package/website/assets/silhouettes/nakpak.svg +0 -56
- package/website/assets/silhouettes/peak-lightning.svg +0 -73
- package/website/assets/silhouettes/sherpa.svg +0 -69
- package/website/assets/silhouettes/stupa-tower.svg +0 -119
- package/website/assets/silhouettes/tattva-eye.svg +0 -78
- package/website/assets/silhouettes/terminal.svg +0 -74
- package/website/assets/silhouettes/webserver.svg +0 -145
- package/website/assets/silhouettes/yak.svg +0 -78
- package/website/assets/yakmesh-logo.png +0 -0
- package/website/assets/yakmesh-logo.webp +0 -0
- package/website/assets/yakmesh-logo128x140.webp +0 -0
- package/website/assets/yakmesh-logo2.png +0 -0
- package/website/assets/yakmesh-logo2.svg +0 -51
- package/website/assets/yakmesh-logo40x44.webp +0 -0
- package/website/assets/yakmesh.gif +0 -0
- package/website/assets/yakmesh.ico +0 -0
- package/website/assets/yakmesh.jpg +0 -0
- package/website/assets/yakmesh.pdf +0 -0
- package/website/assets/yakmesh.png +0 -0
- package/website/assets/yakmesh.svg +0 -70
- package/website/assets/yakmesh128.webp +0 -0
- package/website/assets/yakmesh32.png +0 -0
- package/website/assets/yakmesh32.svg +0 -65
- package/website/assets/yakmesh32o.ico +0 -2
- package/website/assets/yakmesh32o.svg +0 -65
- package/website/assets/yakmesh32o.svgz +0 -0
|
@@ -0,0 +1,1142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STEADYWATCH — Quantum-Hardware-Validated Entropy Seed Module
|
|
3
|
+
*
|
|
4
|
+
* Loads Hurwitz quaternion satellite seeds (256-bit each) generated on
|
|
5
|
+
* IBM ibm_marrakesh (156-qubit Heron r2) via STEADYWATCH circuits.
|
|
6
|
+
*
|
|
7
|
+
* Math: Satellites = 24 * (p + 1) for prime p
|
|
8
|
+
* p=5 -> 144 nodes
|
|
9
|
+
* p=13 -> 336 nodes
|
|
10
|
+
* p=17 -> 432 nodes
|
|
11
|
+
*
|
|
12
|
+
* Integration: XOR quantum seed with local CSPRNG output to create
|
|
13
|
+
* hybrid entropy for ML-KEM-768 keygen. Two-source extractor pattern
|
|
14
|
+
* ensures keys remain secure even if one entropy source is compromised.
|
|
15
|
+
*
|
|
16
|
+
* @module security/steadywatch
|
|
17
|
+
* @license MIT
|
|
18
|
+
* @copyright 2026 YAKMESH Contributors
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { randomBytes } from 'crypto';
|
|
22
|
+
import { readFileSync, existsSync, writeFileSync } from 'fs';
|
|
23
|
+
import { join, dirname } from 'path';
|
|
24
|
+
import { fileURLToPath } from 'url';
|
|
25
|
+
import { createLogger } from '../utils/logger.js';
|
|
26
|
+
import { sha3_256, sha3_256hex } from '../utils/accel.js';
|
|
27
|
+
|
|
28
|
+
// ═══ TRIBHUJ — Balanced ternary quality verdicts ═══
|
|
29
|
+
import { Trit, TritArray, POSITIVE, NEUTRAL, NEGATIVE, TritState } from '../oracle/tribhuj.js';
|
|
30
|
+
|
|
31
|
+
// ═══ SST — Synergy Sequence Theory for family grouping & Fibonacci rotation ═══
|
|
32
|
+
import {
|
|
33
|
+
digitalRoot, getFamily, getFamilyOf, toFamilyTrit,
|
|
34
|
+
fibonacciRoot, fibonacciFamily, fibonacciFamilyTrit,
|
|
35
|
+
SSTFamily, FAMILY_TO_TRIT, FIBONACCI_CYCLE_24,
|
|
36
|
+
} from '../oracle/sst.js';
|
|
37
|
+
|
|
38
|
+
const log = createLogger('security:steadywatch');
|
|
39
|
+
|
|
40
|
+
// ===================================================================
|
|
41
|
+
// CONSTANTS
|
|
42
|
+
// ===================================================================
|
|
43
|
+
|
|
44
|
+
/** Hurwitz quaternion satellite count per prime */
|
|
45
|
+
const SATELLITE_COUNTS = Object.freeze({
|
|
46
|
+
5: 144, // 24 * (5 + 1) — validated Feb 1 2026
|
|
47
|
+
13: 336, // 24 * (13 + 1) — future batch
|
|
48
|
+
17: 432, // 24 * (17 + 1) — future batch
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** Expected seed size in bytes */
|
|
52
|
+
const SEED_BYTES = 32; // 256-bit per satellite seed
|
|
53
|
+
/** Required output for ML-KEM-768 keygen */
|
|
54
|
+
const KEYGEN_SEED_BYTES = 64;
|
|
55
|
+
/** Minimum acceptable min-entropy ratio (bits of entropy / total bits) */
|
|
56
|
+
const MIN_ENTROPY_RATIO = 0.75;
|
|
57
|
+
|
|
58
|
+
// ═══ TERNARY-144 CONSTANTS ═══
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 6-trit address space for satellite indexing.
|
|
62
|
+
* 144 in balanced ternary = "1TT100" (6 trits).
|
|
63
|
+
* 3^5 = 243 > 144, so 5 trits suffice, but 6 trits are used for
|
|
64
|
+
* balanced addressing that encodes the satellite's numeric position.
|
|
65
|
+
*/
|
|
66
|
+
const TRIT_ADDRESS_LENGTH = 6;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* SST family distribution for 144 satellites.
|
|
70
|
+
* Indices are classified by their digital root's SST family:
|
|
71
|
+
* Family A (1,4,7): ~48 satellites — Physical Negative (descending)
|
|
72
|
+
* Family B (2,5,8): ~48 satellites — Physical Positive (ascending)
|
|
73
|
+
* Family C (3,6,9): ~48 satellites — Governing Source (singularity)
|
|
74
|
+
*
|
|
75
|
+
* DR(144) = 9 → Family C → The 144 constellation itself is a NEUTRAL singularity.
|
|
76
|
+
*/
|
|
77
|
+
const FAMILY_GROUPS = Object.freeze({
|
|
78
|
+
[SSTFamily.A]: [], // Populated at load time
|
|
79
|
+
[SSTFamily.B]: [],
|
|
80
|
+
[SSTFamily.C]: [],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// ===================================================================
|
|
84
|
+
// TELEMETRY
|
|
85
|
+
// ===================================================================
|
|
86
|
+
|
|
87
|
+
const telemetry = {
|
|
88
|
+
seedsLoaded: 0,
|
|
89
|
+
hybridSeeds: 0,
|
|
90
|
+
rotations: 0,
|
|
91
|
+
biasRejections: 0,
|
|
92
|
+
sentinelChecks: 0,
|
|
93
|
+
sentinelPasses: 0,
|
|
94
|
+
sentinelFails: 0,
|
|
95
|
+
lastSeedIndex: -1,
|
|
96
|
+
// Ternary-144 telemetry
|
|
97
|
+
familySelections: { A: 0, B: 0, C: 0 },
|
|
98
|
+
fibonacciCyclePosition: 0,
|
|
99
|
+
ternaryQualityVerdicts: { positive: 0, neutral: 0, negative: 0 },
|
|
100
|
+
tritAddressLookups: 0,
|
|
101
|
+
batchConsensusRuns: 0,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
// ===================================================================
|
|
105
|
+
// SEED STORE
|
|
106
|
+
// ===================================================================
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* STEADYWATCH Seed Store.
|
|
110
|
+
* Manages the 144+ satellite seeds and provides hybrid entropy generation.
|
|
111
|
+
*/
|
|
112
|
+
class SteadywatchSeedStore {
|
|
113
|
+
constructor() {
|
|
114
|
+
/** @type {Uint8Array[]} Array of 256-bit satellite seeds */
|
|
115
|
+
this.seeds = [];
|
|
116
|
+
/** Current rotation index */
|
|
117
|
+
this.rotationIndex = 0;
|
|
118
|
+
/** Source prime used to generate this seed set */
|
|
119
|
+
this.prime = 0;
|
|
120
|
+
/** IBM Quantum job metadata */
|
|
121
|
+
this.metadata = null;
|
|
122
|
+
/** Whether seeds are loaded and validated */
|
|
123
|
+
this.initialized = false;
|
|
124
|
+
/** Node-specific seed assignment (index into seeds array) */
|
|
125
|
+
this.nodeAssignment = -1;
|
|
126
|
+
/** Entropy quality scores per seed (from Sentinel) */
|
|
127
|
+
this.qualityScores = new Map();
|
|
128
|
+
|
|
129
|
+
// ═══ TERNARY-144 STATE ═══
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* SST family groups — indices organized by digital root family.
|
|
133
|
+
* @type {{ A: number[], B: number[], C: number[] }}
|
|
134
|
+
*/
|
|
135
|
+
this.familyGroups = { A: [], B: [], C: [] };
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 6-trit balanced ternary address for each satellite.
|
|
139
|
+
* @type {Map<number, TritArray>} index → TritArray("1TT100"-range)
|
|
140
|
+
*/
|
|
141
|
+
this.tritAddresses = new Map();
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reverse lookup: trit string → seed index.
|
|
145
|
+
* @type {Map<string, number>}
|
|
146
|
+
*/
|
|
147
|
+
this.tritIndex = new Map();
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Ternary quality verdicts per seed.
|
|
151
|
+
* POSITIVE = excellent entropy, NEUTRAL = acceptable, NEGATIVE = biased/rejected.
|
|
152
|
+
* @type {Map<number, Trit>}
|
|
153
|
+
*/
|
|
154
|
+
this.qualityVerdicts = new Map();
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Fibonacci cycle position for rotation.
|
|
158
|
+
* Advances through the 24-position cycle, selecting seeds family-aware.
|
|
159
|
+
*/
|
|
160
|
+
this.fibCyclePos = 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Load seeds from a JSON file.
|
|
165
|
+
* Expected format:
|
|
166
|
+
* {
|
|
167
|
+
* "prime": 5,
|
|
168
|
+
* "satelliteCount": 144,
|
|
169
|
+
* "backend": "ibm_marrakesh",
|
|
170
|
+
* "validated": "2026-02-01",
|
|
171
|
+
* "seeds": ["hex-encoded-32-bytes", ...]
|
|
172
|
+
* }
|
|
173
|
+
*
|
|
174
|
+
* @param {string} seedFilePath — path to seeds JSON
|
|
175
|
+
* @returns {boolean} — true if loaded and validated
|
|
176
|
+
*/
|
|
177
|
+
load(seedFilePath) {
|
|
178
|
+
try {
|
|
179
|
+
if (!existsSync(seedFilePath)) {
|
|
180
|
+
log.warn('STEADYWATCH seed file not found:', seedFilePath);
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const raw = readFileSync(seedFilePath, 'utf-8');
|
|
185
|
+
const data = JSON.parse(raw);
|
|
186
|
+
|
|
187
|
+
// Validate structure
|
|
188
|
+
if (!data.seeds || !Array.isArray(data.seeds)) {
|
|
189
|
+
log.error('Invalid seed file: missing seeds array');
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.prime = data.prime || 5;
|
|
194
|
+
const expectedCount = SATELLITE_COUNTS[this.prime];
|
|
195
|
+
if (expectedCount && data.seeds.length !== expectedCount) {
|
|
196
|
+
log.warn('Seed count mismatch: expected ' + expectedCount + ' for p=' + this.prime + ', got ' + data.seeds.length);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Parse and validate each seed
|
|
200
|
+
this.seeds = [];
|
|
201
|
+
const fingerprints = new Set();
|
|
202
|
+
|
|
203
|
+
for (let i = 0; i < data.seeds.length; i++) {
|
|
204
|
+
const hex = data.seeds[i];
|
|
205
|
+
if (hex.length !== SEED_BYTES * 2) {
|
|
206
|
+
log.warn('Seed ' + i + ': invalid length ' + hex.length + ', expected ' + (SEED_BYTES * 2));
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const seedBytes = hexToUint8(hex);
|
|
211
|
+
|
|
212
|
+
// Check uniqueness via SHA3 fingerprint
|
|
213
|
+
const fp = sha3_256hex(seedBytes);
|
|
214
|
+
if (fingerprints.has(fp)) {
|
|
215
|
+
log.warn('Seed ' + i + ': duplicate detected (fingerprint collision)');
|
|
216
|
+
telemetry.biasRejections++;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
fingerprints.add(fp);
|
|
220
|
+
|
|
221
|
+
// Bias check: ensure reasonable byte distribution
|
|
222
|
+
if (!this._checkBias(seedBytes, i)) {
|
|
223
|
+
telemetry.biasRejections++;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
this.seeds.push(seedBytes);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.metadata = {
|
|
231
|
+
prime: this.prime,
|
|
232
|
+
backend: data.backend || 'unknown',
|
|
233
|
+
validated: data.validated || 'unknown',
|
|
234
|
+
jobIds: data.jobIds || [],
|
|
235
|
+
loadedAt: new Date().toISOString(),
|
|
236
|
+
seedCount: this.seeds.length,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
telemetry.seedsLoaded = this.seeds.length;
|
|
240
|
+
this.initialized = this.seeds.length > 0;
|
|
241
|
+
|
|
242
|
+
// ═══ Build ternary-144 structures ═══
|
|
243
|
+
if (this.initialized) {
|
|
244
|
+
this._buildTernaryStructures();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
log.info('STEADYWATCH loaded: ' + this.seeds.length + ' satellite seeds (p=' + this.prime + ', backend=' + this.metadata.backend + ')');
|
|
248
|
+
return this.initialized;
|
|
249
|
+
|
|
250
|
+
} catch (err) {
|
|
251
|
+
log.error('Failed to load STEADYWATCH seeds:', err.message);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Generate initial seeds from quantum circuit parameters.
|
|
258
|
+
* Used when no seed file exists — creates deterministic seeds from
|
|
259
|
+
* Hurwitz quaternion phase rotations.
|
|
260
|
+
*
|
|
261
|
+
* theta_i = 2*pi*i / N where N = 24(p+1)
|
|
262
|
+
* seed_i = SHA3-256(quaternion_coordinate || circuit_measurement)
|
|
263
|
+
*
|
|
264
|
+
* NOTE: This is a simulation. Real seeds come from IBM Quantum hardware.
|
|
265
|
+
* Use this only for development/testing.
|
|
266
|
+
*
|
|
267
|
+
* @param {number} prime — Hurwitz prime (5, 13, or 17)
|
|
268
|
+
* @param {string} [outputPath] — save generated seeds to file
|
|
269
|
+
* @returns {boolean}
|
|
270
|
+
*/
|
|
271
|
+
generateTestSeeds(prime = 5, outputPath = null) {
|
|
272
|
+
const count = SATELLITE_COUNTS[prime];
|
|
273
|
+
if (!count) {
|
|
274
|
+
log.error('Invalid prime ' + prime + '. Use 5, 13, or 17.');
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
log.info('Generating ' + count + ' test seeds for p=' + prime + ' (SIMULATION)');
|
|
279
|
+
|
|
280
|
+
const seeds = [];
|
|
281
|
+
for (let i = 0; i < count; i++) {
|
|
282
|
+
// Hurwitz quaternion phase rotation
|
|
283
|
+
const theta = (2 * Math.PI * i) / count;
|
|
284
|
+
|
|
285
|
+
// Simulate quaternion coordinate: q = cos(theta) + sin(theta)*i + ...
|
|
286
|
+
// In real STEADYWATCH, this parameterizes an 8-qubit circuit
|
|
287
|
+
const qReal = Math.cos(theta);
|
|
288
|
+
const qImag = Math.sin(theta);
|
|
289
|
+
|
|
290
|
+
// Create deterministic but unique input for SHA3
|
|
291
|
+
const input = Buffer.alloc(48);
|
|
292
|
+
input.writeDoubleBE(qReal, 0);
|
|
293
|
+
input.writeDoubleBE(qImag, 8);
|
|
294
|
+
input.writeUInt32BE(prime, 16);
|
|
295
|
+
input.writeUInt32BE(i, 20);
|
|
296
|
+
input.writeDoubleBE(theta, 24);
|
|
297
|
+
// Add satellite-specific label
|
|
298
|
+
const label = 'STEADYWATCH-SAT-' + prime + '-' + i;
|
|
299
|
+
Buffer.from(label).copy(input, 32);
|
|
300
|
+
|
|
301
|
+
// SHA3-256 of quaternion parameters -> 256-bit seed
|
|
302
|
+
const seed = sha3_256(input);
|
|
303
|
+
seeds.push(Buffer.from(seed).toString('hex'));
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const data = {
|
|
307
|
+
prime,
|
|
308
|
+
satelliteCount: count,
|
|
309
|
+
backend: 'simulation',
|
|
310
|
+
validated: new Date().toISOString().split('T')[0],
|
|
311
|
+
note: 'TEST SEEDS — generated from Hurwitz quaternion simulation, not IBM Quantum hardware',
|
|
312
|
+
seeds,
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
if (outputPath) {
|
|
316
|
+
writeFileSync(outputPath, JSON.stringify(data, null, 2));
|
|
317
|
+
log.info('Test seeds written to ' + outputPath);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Load seeds in-memory
|
|
321
|
+
this.seeds = seeds.map(h => hexToUint8(h));
|
|
322
|
+
this.prime = prime;
|
|
323
|
+
this.metadata = {
|
|
324
|
+
prime,
|
|
325
|
+
backend: 'simulation',
|
|
326
|
+
validated: data.validated,
|
|
327
|
+
seedCount: count,
|
|
328
|
+
loadedAt: new Date().toISOString(),
|
|
329
|
+
};
|
|
330
|
+
this.initialized = true;
|
|
331
|
+
telemetry.seedsLoaded = count;
|
|
332
|
+
|
|
333
|
+
// Build ternary-144 structures (family groups, trit addresses)
|
|
334
|
+
if (this.initialized) {
|
|
335
|
+
this._buildTernaryStructures();
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Assign this node a specific satellite seed index.
|
|
343
|
+
* In production: provisioned by DOKO identity ceremony.
|
|
344
|
+
*
|
|
345
|
+
* @param {number} index — satellite index [0, seedCount)
|
|
346
|
+
*/
|
|
347
|
+
assignNode(index) {
|
|
348
|
+
if (index < 0 || index >= this.seeds.length) {
|
|
349
|
+
log.error('Invalid node assignment: ' + index + ' (have ' + this.seeds.length + ' seeds)');
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
this.nodeAssignment = index;
|
|
353
|
+
this.rotationIndex = index;
|
|
354
|
+
log.info('Node assigned to satellite ' + index + ' (seed fingerprint: ' + sha3_256hex(this.seeds[index]).slice(0, 16) + '...)');
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Get a hybrid seed suitable for ML-KEM-768 keygen.
|
|
359
|
+
*
|
|
360
|
+
* Algorithm:
|
|
361
|
+
* 1. Select satellite seed (rotation or assigned)
|
|
362
|
+
* 2. Generate 64 bytes from OS CSPRNG
|
|
363
|
+
* 3. Expand satellite seed to 64 bytes: SHA3-256(seed || "EXPAND-0") || SHA3-256(seed || "EXPAND-1")
|
|
364
|
+
* 4. XOR: hybridSeed = expandedSatellite XOR csprng
|
|
365
|
+
* 5. Return 64-byte hybrid seed for ml_kem768.keygen()
|
|
366
|
+
*
|
|
367
|
+
* Security property: Even if one source is fully compromised (weak VM CSPRNG
|
|
368
|
+
* or faulty quantum hardware), the other source ensures the hybrid seed
|
|
369
|
+
* retains at least 256 bits of entropy.
|
|
370
|
+
*
|
|
371
|
+
* @returns {Uint8Array} — 64-byte hybrid seed
|
|
372
|
+
*/
|
|
373
|
+
/**
|
|
374
|
+
* Get a hybrid seed suitable for ML-KEM-768 keygen.
|
|
375
|
+
*
|
|
376
|
+
* Algorithm (Ternary-144 enhanced):
|
|
377
|
+
* 1. Advance Fibonacci 24-cycle position
|
|
378
|
+
* 2. Determine SST family for this position
|
|
379
|
+
* 3. Select satellite from matching family group (family-aware rotation)
|
|
380
|
+
* 4. Generate 64 bytes from OS CSPRNG
|
|
381
|
+
* 5. Expand satellite seed to 64 bytes: SHA3-256(seed || "EXPAND-0") || SHA3-256(seed || "EXPAND-1")
|
|
382
|
+
* 6. XOR: hybridSeed = expandedSatellite XOR csprng
|
|
383
|
+
* 7. Return 64-byte hybrid seed for ml_kem768.keygen()
|
|
384
|
+
*
|
|
385
|
+
* @returns {Uint8Array} — 64-byte hybrid seed
|
|
386
|
+
*/
|
|
387
|
+
getHybridSeed() {
|
|
388
|
+
if (!this.initialized || this.seeds.length === 0) {
|
|
389
|
+
log.trace('STEADYWATCH not initialized, falling back to pure CSPRNG');
|
|
390
|
+
return randomBytes(KEYGEN_SEED_BYTES);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ═══ TERNARY-144: Fibonacci-cycle family-aware seed selection ═══
|
|
394
|
+
let seedIndex;
|
|
395
|
+
|
|
396
|
+
if (this.nodeAssignment >= 0) {
|
|
397
|
+
// Node has a fixed assignment — use it, but still track Fibonacci position
|
|
398
|
+
seedIndex = this.nodeAssignment;
|
|
399
|
+
} else if (this.familyGroups.A.length > 0) {
|
|
400
|
+
// Use Fibonacci 24-cycle to pick the SST family, then round-robin within family
|
|
401
|
+
const fibRoot = fibonacciRoot(this.fibCyclePos);
|
|
402
|
+
const family = getFamily(fibRoot);
|
|
403
|
+
const familyGroup = this.familyGroups[family];
|
|
404
|
+
|
|
405
|
+
if (familyGroup.length > 0) {
|
|
406
|
+
// Round-robin within the selected family
|
|
407
|
+
const familyOffset = this.rotationIndex % familyGroup.length;
|
|
408
|
+
seedIndex = familyGroup[familyOffset];
|
|
409
|
+
telemetry.familySelections[family]++;
|
|
410
|
+
} else {
|
|
411
|
+
// Fallback: linear rotation if family group is empty
|
|
412
|
+
seedIndex = this.rotationIndex;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
this.fibCyclePos = (this.fibCyclePos + 1) % 24;
|
|
416
|
+
telemetry.fibonacciCyclePosition = this.fibCyclePos;
|
|
417
|
+
} else {
|
|
418
|
+
// Fallback: simple linear rotation (pre-ternary behavior)
|
|
419
|
+
seedIndex = this.rotationIndex;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const satelliteSeed = this.seeds[seedIndex % this.seeds.length];
|
|
423
|
+
|
|
424
|
+
// Rotate for next call (even if assigned, rotate for re-key diversity)
|
|
425
|
+
this.rotationIndex = (this.rotationIndex + 1) % this.seeds.length;
|
|
426
|
+
telemetry.rotations++;
|
|
427
|
+
|
|
428
|
+
// Expand 32-byte satellite seed to 64 bytes using SHA3 (ACCEL: native SHA-NI)
|
|
429
|
+
const expand0 = sha3_256(Buffer.concat([
|
|
430
|
+
Buffer.from(satelliteSeed),
|
|
431
|
+
Buffer.from('EXPAND-0'),
|
|
432
|
+
]));
|
|
433
|
+
const expand1 = sha3_256(Buffer.concat([
|
|
434
|
+
Buffer.from(satelliteSeed),
|
|
435
|
+
Buffer.from('EXPAND-1'),
|
|
436
|
+
]));
|
|
437
|
+
const expandedSeed = new Uint8Array(KEYGEN_SEED_BYTES);
|
|
438
|
+
expandedSeed.set(expand0, 0);
|
|
439
|
+
expandedSeed.set(expand1, 32);
|
|
440
|
+
|
|
441
|
+
// CSPRNG contribution
|
|
442
|
+
const csprng = randomBytes(KEYGEN_SEED_BYTES);
|
|
443
|
+
|
|
444
|
+
// XOR: two-source extractor
|
|
445
|
+
const hybridSeed = new Uint8Array(KEYGEN_SEED_BYTES);
|
|
446
|
+
for (let i = 0; i < KEYGEN_SEED_BYTES; i++) {
|
|
447
|
+
hybridSeed[i] = expandedSeed[i] ^ csprng[i];
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
telemetry.hybridSeeds++;
|
|
451
|
+
telemetry.lastSeedIndex = seedIndex;
|
|
452
|
+
|
|
453
|
+
log.trace('Hybrid seed generated (satellite ' + seedIndex +
|
|
454
|
+
', family ' + getFamilyOf(seedIndex) +
|
|
455
|
+
', fibPos ' + this.fibCyclePos +
|
|
456
|
+
', rotation ' + this.rotationIndex + ')');
|
|
457
|
+
return hybridSeed;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* ═══ TERNARY SEED QUALITY ═══
|
|
462
|
+
*
|
|
463
|
+
* Evaluate a seed's entropy quality as a balanced ternary verdict.
|
|
464
|
+
* Replaces the old boolean _checkBias with three-valued logic:
|
|
465
|
+
* POSITIVE (+1): Excellent entropy — diverse bytes, no bias patterns
|
|
466
|
+
* NEUTRAL ( 0): Acceptable entropy — passes minimum thresholds
|
|
467
|
+
* NEGATIVE (-1): Biased/rejected — fails quality checks
|
|
468
|
+
*
|
|
469
|
+
* @param {Uint8Array} seedBytes — 256-bit seed
|
|
470
|
+
* @param {number} index — satellite index
|
|
471
|
+
* @returns {Trit} — ternary quality verdict
|
|
472
|
+
*/
|
|
473
|
+
_checkBiasTernary(seedBytes, index) {
|
|
474
|
+
let zeros = 0;
|
|
475
|
+
let ones = 0;
|
|
476
|
+
const byteSet = new Set();
|
|
477
|
+
|
|
478
|
+
for (const b of seedBytes) {
|
|
479
|
+
if (b === 0x00) zeros++;
|
|
480
|
+
if (b === 0xFF) ones++;
|
|
481
|
+
byteSet.add(b);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// NEGATIVE: Hard reject — severe bias
|
|
485
|
+
if (zeros > SEED_BYTES * 0.5) {
|
|
486
|
+
log.warn('Seed ' + index + ': NEGATIVE — excessive zero bytes (' + zeros + '/' + SEED_BYTES + ')');
|
|
487
|
+
telemetry.ternaryQualityVerdicts.negative++;
|
|
488
|
+
return new Trit(NEGATIVE);
|
|
489
|
+
}
|
|
490
|
+
if (ones > SEED_BYTES * 0.5) {
|
|
491
|
+
log.warn('Seed ' + index + ': NEGATIVE — excessive 0xFF bytes (' + ones + '/' + SEED_BYTES + ')');
|
|
492
|
+
telemetry.ternaryQualityVerdicts.negative++;
|
|
493
|
+
return new Trit(NEGATIVE);
|
|
494
|
+
}
|
|
495
|
+
if (byteSet.size < SEED_BYTES * 0.25) {
|
|
496
|
+
log.warn('Seed ' + index + ': NEGATIVE — low byte diversity (' + byteSet.size + ' unique)');
|
|
497
|
+
telemetry.ternaryQualityVerdicts.negative++;
|
|
498
|
+
return new Trit(NEGATIVE);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// POSITIVE: Excellent — high diversity, balanced distribution
|
|
502
|
+
if (byteSet.size >= SEED_BYTES * 0.75 && zeros <= 2 && ones <= 2) {
|
|
503
|
+
telemetry.ternaryQualityVerdicts.positive++;
|
|
504
|
+
return new Trit(POSITIVE);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// NEUTRAL: Acceptable but not exceptional
|
|
508
|
+
telemetry.ternaryQualityVerdicts.neutral++;
|
|
509
|
+
return new Trit(NEUTRAL);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Backward-compatible wrapper — _checkBias still returns boolean for load().
|
|
514
|
+
* Internally uses ternary verdict.
|
|
515
|
+
* @private
|
|
516
|
+
*/
|
|
517
|
+
_checkBias(seedBytes, index) {
|
|
518
|
+
const verdict = this._checkBiasTernary(seedBytes, index);
|
|
519
|
+
this.qualityVerdicts.set(index, verdict);
|
|
520
|
+
return !verdict.isNegative; // POSITIVE and NEUTRAL both pass
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
524
|
+
// TERNARY-144: SST FAMILY SATELLITE GROUPING
|
|
525
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Classify all loaded satellite seeds into SST family groups.
|
|
529
|
+
*
|
|
530
|
+
* Each satellite index i is assigned to a family based on digitalRoot(i+1):
|
|
531
|
+
* Family A (DR 1,4,7) ← Physical Negative
|
|
532
|
+
* Family B (DR 2,5,8) ← Physical Positive
|
|
533
|
+
* Family C (DR 3,6,9) ← Governing Source
|
|
534
|
+
*
|
|
535
|
+
* For 144 satellites: each family gets exactly 48 members (perfectly balanced).
|
|
536
|
+
* This reflects 144's digital root (9) — a governing singularity that divides
|
|
537
|
+
* evenly across all three polarities.
|
|
538
|
+
*/
|
|
539
|
+
_buildFamilyGroups() {
|
|
540
|
+
this.familyGroups = { A: [], B: [], C: [] };
|
|
541
|
+
|
|
542
|
+
for (let i = 0; i < this.seeds.length; i++) {
|
|
543
|
+
// Use i+1 so satellite 0 → DR(1) = Family A, satellite 1 → DR(2) = Family B, etc.
|
|
544
|
+
const family = getFamilyOf(i + 1);
|
|
545
|
+
this.familyGroups[family].push(i);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
log.info('SST family groups: A=' + this.familyGroups.A.length +
|
|
549
|
+
' B=' + this.familyGroups.B.length +
|
|
550
|
+
' C=' + this.familyGroups.C.length);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
554
|
+
// TERNARY-144: 6-TRIT SATELLITE ADDRESSES
|
|
555
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Assign each satellite a 6-trit balanced ternary address.
|
|
559
|
+
*
|
|
560
|
+
* 144 in balanced ternary = "1TT100" (6 trits):
|
|
561
|
+
* 1×243 + (-1)×81 + (-1)×27 + 1×9 + 0×3 + 0×1 = 243 - 81 - 27 + 9 = 144
|
|
562
|
+
*
|
|
563
|
+
* Each satellite gets its index converted to a fixed-width 6-trit address.
|
|
564
|
+
* This enables ternary trie routing for seed lookups instead of array indexing.
|
|
565
|
+
*
|
|
566
|
+
* Satellite 0 → "000000" (0)
|
|
567
|
+
* Satellite 1 → "00000+1" ("000001")
|
|
568
|
+
* Satellite 72 → "10T100" (midpoint)
|
|
569
|
+
* Satellite 143 → just below "1TT100"
|
|
570
|
+
*/
|
|
571
|
+
_buildTritAddresses() {
|
|
572
|
+
this.tritAddresses.clear();
|
|
573
|
+
this.tritIndex.clear();
|
|
574
|
+
|
|
575
|
+
for (let i = 0; i < this.seeds.length; i++) {
|
|
576
|
+
const addr = TritArray.fromDecimal(i, TRIT_ADDRESS_LENGTH);
|
|
577
|
+
this.tritAddresses.set(i, addr);
|
|
578
|
+
this.tritIndex.set(addr.toString(), i);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
log.info('Trit addresses assigned: ' + this.tritAddresses.size +
|
|
582
|
+
' satellites in 6-trit space (capacity: 3^5=' + Math.pow(3, 5) + ')');
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Look up a satellite seed by its 6-trit address.
|
|
587
|
+
*
|
|
588
|
+
* @param {TritArray | string} address — 6-trit balanced ternary address
|
|
589
|
+
* @returns {Uint8Array | null} — seed bytes, or null if not found
|
|
590
|
+
*/
|
|
591
|
+
getSeedByTritAddress(address) {
|
|
592
|
+
telemetry.tritAddressLookups++;
|
|
593
|
+
const key = address instanceof TritArray ? address.toString() : address;
|
|
594
|
+
const index = this.tritIndex.get(key);
|
|
595
|
+
if (index === undefined) return null;
|
|
596
|
+
return { index, seed: this.seeds[index] ?? null };
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Get the 6-trit address for a satellite index.
|
|
601
|
+
* @param {number} index
|
|
602
|
+
* @returns {TritArray | null}
|
|
603
|
+
*/
|
|
604
|
+
getTritAddress(index) {
|
|
605
|
+
return this.tritAddresses.get(index) ?? null;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
609
|
+
// TERNARY-144: BATCH SEED CONSENSUS
|
|
610
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
611
|
+
|
|
612
|
+
/**
|
|
613
|
+
* Validate a batch of seed quality verdicts using TritArray consensus.
|
|
614
|
+
*
|
|
615
|
+
* After loading, each seed has a ternary quality verdict (from _checkBiasTernary).
|
|
616
|
+
* This method aggregates them using TritArray.majority() to determine the
|
|
617
|
+
* overall quality of the seed batch.
|
|
618
|
+
*
|
|
619
|
+
* A batch with POSITIVE majority indicates high-quality quantum entropy.
|
|
620
|
+
* A batch with NEUTRAL majority suggests mixed quality (acceptable).
|
|
621
|
+
* A batch with NEGATIVE majority triggers a warning — seeds may be compromised.
|
|
622
|
+
*
|
|
623
|
+
* @returns {{ majority: Trit, counts: Object, balanced: boolean }}
|
|
624
|
+
*/
|
|
625
|
+
batchQualityConsensus() {
|
|
626
|
+
telemetry.batchConsensusRuns++;
|
|
627
|
+
|
|
628
|
+
const verdicts = [];
|
|
629
|
+
for (let i = 0; i < this.seeds.length; i++) {
|
|
630
|
+
const v = this.qualityVerdicts.get(i);
|
|
631
|
+
verdicts.push(v ? v.value : NEUTRAL);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (verdicts.length === 0) {
|
|
635
|
+
return { majority: new Trit(NEUTRAL), counts: { negative: 0, neutral: 0, positive: 0 }, total: 0, balanced: true };
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const tritArr = new TritArray(verdicts);
|
|
639
|
+
const majority = tritArr.majority();
|
|
640
|
+
const counts = tritArr.count();
|
|
641
|
+
const balanced = tritArr.isBalanced();
|
|
642
|
+
|
|
643
|
+
if (majority.isNegative) {
|
|
644
|
+
log.warn('STEADYWATCH batch consensus: NEGATIVE — seed quality may be compromised');
|
|
645
|
+
} else if (majority.isPositive) {
|
|
646
|
+
log.info('STEADYWATCH batch consensus: POSITIVE — excellent seed quality');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
return { majority, counts, total: verdicts.length, balanced };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
653
|
+
// TERNARY-144: FAMILY-AWARE SEED SELECTION
|
|
654
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* Select a seed from a specific SST family.
|
|
658
|
+
*
|
|
659
|
+
* @param {string} family — 'A', 'B', or 'C'
|
|
660
|
+
* @param {number} [offset=0] — offset within the family group
|
|
661
|
+
* @returns {{ index: number, seed: Uint8Array, address: TritArray, quality: Trit } | null}
|
|
662
|
+
*/
|
|
663
|
+
selectFromFamily(family, offset = 0) {
|
|
664
|
+
const group = this.familyGroups[family];
|
|
665
|
+
if (!group || group.length === 0) return null;
|
|
666
|
+
|
|
667
|
+
const index = group[offset % group.length];
|
|
668
|
+
return {
|
|
669
|
+
index,
|
|
670
|
+
seed: this.seeds[index],
|
|
671
|
+
family,
|
|
672
|
+
address: this.tritAddresses.get(index) || null,
|
|
673
|
+
quality: this.qualityVerdicts.get(index) || new Trit(NEUTRAL),
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Select a seed using the Fibonacci 24-cycle family mapping.
|
|
679
|
+
* Each call advances the cycle position and picks from the indicated family.
|
|
680
|
+
*
|
|
681
|
+
* The SST family at Fibonacci position n determines which pool to draw from:
|
|
682
|
+
* fibRoot(0) = 1 → Family A → pick from NEGATIVE polarity satellites
|
|
683
|
+
* fibRoot(1) = 1 → Family A → same
|
|
684
|
+
* fibRoot(2) = 2 → Family B → pick from POSITIVE polarity satellites
|
|
685
|
+
* fibRoot(3) = 3 → Family C → pick from GOVERNING satellites
|
|
686
|
+
* ...repeats every 24 positions (the Fibonacci digital root cycle)
|
|
687
|
+
*
|
|
688
|
+
* At cycle positions 12 and 24: fibRoot = 9 → Family C → singularity marker
|
|
689
|
+
*
|
|
690
|
+
* @returns {{ index: number, seed: Uint8Array, family: string, fibPosition: number, fibRoot: number }}
|
|
691
|
+
*/
|
|
692
|
+
selectByFibonacciCycle() {
|
|
693
|
+
const pos = this.fibCyclePos;
|
|
694
|
+
const root = fibonacciRoot(pos);
|
|
695
|
+
const family = getFamily(root);
|
|
696
|
+
const familyTrit = toFamilyTrit(root);
|
|
697
|
+
|
|
698
|
+
const group = this.familyGroups[family];
|
|
699
|
+
if (!group || group.length === 0) {
|
|
700
|
+
// Fallback to linear
|
|
701
|
+
return {
|
|
702
|
+
index: this.rotationIndex % this.seeds.length,
|
|
703
|
+
seed: this.seeds[this.rotationIndex % this.seeds.length],
|
|
704
|
+
family,
|
|
705
|
+
fibPosition: pos,
|
|
706
|
+
fibRoot: root,
|
|
707
|
+
};
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Use rotation index modulo family group size
|
|
711
|
+
const familyOffset = this.rotationIndex % group.length;
|
|
712
|
+
const index = group[familyOffset];
|
|
713
|
+
|
|
714
|
+
// Advance cycle
|
|
715
|
+
this.fibCyclePos = (this.fibCyclePos + 1) % 24;
|
|
716
|
+
telemetry.familySelections[family]++;
|
|
717
|
+
telemetry.fibonacciCyclePosition = this.fibCyclePos;
|
|
718
|
+
|
|
719
|
+
return {
|
|
720
|
+
index,
|
|
721
|
+
seed: this.seeds[index],
|
|
722
|
+
family,
|
|
723
|
+
fibPosition: pos,
|
|
724
|
+
fibRoot: root,
|
|
725
|
+
familyTrit: familyTrit.value,
|
|
726
|
+
address: this.tritAddresses.get(index)?.toString() || null,
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Build all ternary-144 structures after seeds are loaded.
|
|
732
|
+
* Called automatically at end of load() and generateTestSeeds().
|
|
733
|
+
* @private
|
|
734
|
+
*/
|
|
735
|
+
_buildTernaryStructures() {
|
|
736
|
+
this._buildFamilyGroups();
|
|
737
|
+
this._buildTritAddresses();
|
|
738
|
+
|
|
739
|
+
// Log the mathematical harmony
|
|
740
|
+
const totalDR = digitalRoot(this.seeds.length);
|
|
741
|
+
const totalFamily = getFamily(totalDR);
|
|
742
|
+
const totalTrit = toFamilyTrit(this.seeds.length);
|
|
743
|
+
|
|
744
|
+
log.info('Ternary-144: DR(' + this.seeds.length + ')=' + totalDR +
|
|
745
|
+
' → Family ' + totalFamily +
|
|
746
|
+
' → TRIT ' + totalTrit.toChar() +
|
|
747
|
+
' | constellation address: ' + TritArray.fromDecimal(this.seeds.length, TRIT_ADDRESS_LENGTH).toString());
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* Get status report
|
|
752
|
+
*/
|
|
753
|
+
getStatus() {
|
|
754
|
+
// Quality consensus (if seeds loaded)
|
|
755
|
+
let consensus = null;
|
|
756
|
+
if (this.qualityVerdicts.size > 0) {
|
|
757
|
+
consensus = this.batchQualityConsensus();
|
|
758
|
+
consensus.majority = consensus.majority.toChar(); // serializable
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return {
|
|
762
|
+
initialized: this.initialized,
|
|
763
|
+
prime: this.prime,
|
|
764
|
+
seedCount: this.seeds.length,
|
|
765
|
+
nodeAssignment: this.nodeAssignment,
|
|
766
|
+
rotationIndex: this.rotationIndex,
|
|
767
|
+
metadata: this.metadata,
|
|
768
|
+
telemetry: { ...telemetry },
|
|
769
|
+
// Ternary-144 status
|
|
770
|
+
ternary: {
|
|
771
|
+
familyGroups: {
|
|
772
|
+
A: this.familyGroups.A.length,
|
|
773
|
+
B: this.familyGroups.B.length,
|
|
774
|
+
C: this.familyGroups.C.length,
|
|
775
|
+
},
|
|
776
|
+
fibCyclePos: this.fibCyclePos,
|
|
777
|
+
tritAddressCount: this.tritAddresses.size,
|
|
778
|
+
constellationDR: this.seeds.length > 0 ? digitalRoot(this.seeds.length) : 0,
|
|
779
|
+
constellationFamily: this.seeds.length > 0 ? getFamily(digitalRoot(this.seeds.length)) : null,
|
|
780
|
+
constellationAddress: this.seeds.length > 0
|
|
781
|
+
? TritArray.fromDecimal(this.seeds.length, TRIT_ADDRESS_LENGTH).toString()
|
|
782
|
+
: null,
|
|
783
|
+
consensus,
|
|
784
|
+
},
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// ===================================================================
|
|
790
|
+
// ENTROPY SENTINEL — NPU-Accelerated Quality Monitor
|
|
791
|
+
// ===================================================================
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Entropy Sentinel uses the ACCEL InferenceEngine (NPU/GPU/CPU) to
|
|
795
|
+
* score the quality of seed material before it enters keygen.
|
|
796
|
+
*
|
|
797
|
+
* Novel use: the AMD NPU or NVIDIA GPU runs a small ONNX model that
|
|
798
|
+
* detects patterns in byte sequences that indicate weak randomness:
|
|
799
|
+
* - Low bit-entropy (information density per byte)
|
|
800
|
+
* - Repeating patterns (autocorrelation)
|
|
801
|
+
* - Byte frequency bias
|
|
802
|
+
* - Sequential runs
|
|
803
|
+
*
|
|
804
|
+
* If no ONNX model is loaded, falls back to software statistical tests.
|
|
805
|
+
*/
|
|
806
|
+
class EntropySentinel {
|
|
807
|
+
constructor() {
|
|
808
|
+
this._inferenceEngine = null;
|
|
809
|
+
this._modelLoaded = false;
|
|
810
|
+
this._modelName = 'entropy-sentinel';
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/**
|
|
814
|
+
* Initialize with ACCEL inference engine reference.
|
|
815
|
+
* @param {import('../utils/accel.js').InferenceEngine} engine
|
|
816
|
+
*/
|
|
817
|
+
async initialize(engine) {
|
|
818
|
+
this._inferenceEngine = engine;
|
|
819
|
+
|
|
820
|
+
if (engine && engine.hasModel(this._modelName)) {
|
|
821
|
+
this._modelLoaded = true;
|
|
822
|
+
log.info('Entropy Sentinel: NPU/GPU model loaded');
|
|
823
|
+
} else {
|
|
824
|
+
log.debug('Entropy Sentinel: using software statistical tests (no ONNX model)');
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Convert a numeric score to a TRIBHUJ ternary verdict.
|
|
830
|
+
* ≥ 0.85 → POSITIVE (excellent entropy)
|
|
831
|
+
* ≥ 0.50 → NEUTRAL (acceptable entropy)
|
|
832
|
+
* < 0.50 → NEGATIVE (poor entropy — reject)
|
|
833
|
+
*
|
|
834
|
+
* @param {number} score — 0.0 to 1.0
|
|
835
|
+
* @returns {import('../../oracle/tribhuj.js').Trit}
|
|
836
|
+
*/
|
|
837
|
+
_scoreToVerdict(score) {
|
|
838
|
+
if (score >= 0.85) return new Trit(POSITIVE);
|
|
839
|
+
if (score >= 0.50) return new Trit(NEUTRAL);
|
|
840
|
+
return new Trit(NEGATIVE);
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Score entropy quality of a byte sequence.
|
|
845
|
+
* Returns a score from 0.0 (terrible) to 1.0 (excellent),
|
|
846
|
+
* plus a TRIBHUJ ternary verdict (POSITIVE/NEUTRAL/NEGATIVE).
|
|
847
|
+
*
|
|
848
|
+
* NPU path: runs ONNX model for pattern detection
|
|
849
|
+
* CPU path: bit-entropy + byte frequency chi-square
|
|
850
|
+
*
|
|
851
|
+
* @param {Uint8Array} data — seed or key material
|
|
852
|
+
* @returns {Promise<{ score: number, verdict: Trit, method: 'npu'|'gpu'|'cpu', details: Object }>}
|
|
853
|
+
*/
|
|
854
|
+
async score(data) {
|
|
855
|
+
telemetry.sentinelChecks++;
|
|
856
|
+
|
|
857
|
+
// Try NPU/GPU inference first
|
|
858
|
+
if (this._modelLoaded && this._inferenceEngine) {
|
|
859
|
+
try {
|
|
860
|
+
// Model expects exactly 32 features — bin input bytes into 32 segments
|
|
861
|
+
const MODEL_INPUTS = 32;
|
|
862
|
+
const inputTensor = new Float32Array(MODEL_INPUTS);
|
|
863
|
+
if (data.length <= MODEL_INPUTS) {
|
|
864
|
+
// Short input: use directly, pad remainder with 0
|
|
865
|
+
for (let i = 0; i < data.length; i++) inputTensor[i] = data[i] / 255.0;
|
|
866
|
+
} else {
|
|
867
|
+
// Longer input: average each bin for representative features
|
|
868
|
+
const binSize = data.length / MODEL_INPUTS;
|
|
869
|
+
for (let b = 0; b < MODEL_INPUTS; b++) {
|
|
870
|
+
const start = Math.floor(b * binSize);
|
|
871
|
+
const end = Math.floor((b + 1) * binSize);
|
|
872
|
+
let sum = 0;
|
|
873
|
+
for (let i = start; i < end; i++) sum += data[i];
|
|
874
|
+
inputTensor[b] = (sum / (end - start)) / 255.0;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const result = await this._inferenceEngine.infer(this._modelName, {
|
|
879
|
+
seed_bytes: inputTensor,
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
if (result && result.quality_score) {
|
|
883
|
+
const score = result.quality_score[0];
|
|
884
|
+
telemetry.sentinelPasses += score >= MIN_ENTROPY_RATIO ? 1 : 0;
|
|
885
|
+
telemetry.sentinelFails += score < MIN_ENTROPY_RATIO ? 1 : 0;
|
|
886
|
+
|
|
887
|
+
const provider = this._inferenceEngine.provider || '';
|
|
888
|
+
return {
|
|
889
|
+
score,
|
|
890
|
+
verdict: this._scoreToVerdict(score),
|
|
891
|
+
method: provider.includes('Dml') ? 'npu'
|
|
892
|
+
: provider.includes('CUDA') ? 'gpu' : 'cpu',
|
|
893
|
+
details: { raw: result },
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
} catch (err) {
|
|
897
|
+
log.trace('Sentinel NPU inference failed, falling back to software:', err.message);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Software fallback: bit-entropy + chi-square
|
|
902
|
+
return this._softwareScore(data);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Software statistical scoring (no NPU required).
|
|
907
|
+
* @private
|
|
908
|
+
*/
|
|
909
|
+
_softwareScore(data) {
|
|
910
|
+
const len = data.length;
|
|
911
|
+
if (len === 0) return { score: 0, verdict: new Trit(NEGATIVE), method: 'cpu', details: {} };
|
|
912
|
+
|
|
913
|
+
// 1. Bit-entropy (information density per byte, max 8.0 bits)
|
|
914
|
+
const freq = new Uint32Array(256);
|
|
915
|
+
for (const b of data) freq[b]++;
|
|
916
|
+
|
|
917
|
+
let bitEntropy = 0;
|
|
918
|
+
for (let i = 0; i < 256; i++) {
|
|
919
|
+
if (freq[i] === 0) continue;
|
|
920
|
+
const p = freq[i] / len;
|
|
921
|
+
bitEntropy -= p * Math.log2(p);
|
|
922
|
+
}
|
|
923
|
+
const entropyNorm = bitEntropy / 8.0; // Normalized to [0, 1]
|
|
924
|
+
|
|
925
|
+
// 2. Chi-square test for uniform distribution
|
|
926
|
+
const expected = len / 256;
|
|
927
|
+
let chiSquare = 0;
|
|
928
|
+
for (let i = 0; i < 256; i++) {
|
|
929
|
+
const diff = freq[i] - expected;
|
|
930
|
+
chiSquare += (diff * diff) / expected;
|
|
931
|
+
}
|
|
932
|
+
// Normalize: chi-square for 255 DOF, p=0.05 critical ~ 293.25
|
|
933
|
+
const chiNorm = Math.max(0, 1 - chiSquare / 600);
|
|
934
|
+
|
|
935
|
+
// 3. Run length test (consecutive identical bytes)
|
|
936
|
+
let maxRun = 0;
|
|
937
|
+
let currentRun = 1;
|
|
938
|
+
for (let i = 1; i < len; i++) {
|
|
939
|
+
if (data[i] === data[i - 1]) {
|
|
940
|
+
currentRun++;
|
|
941
|
+
if (currentRun > maxRun) maxRun = currentRun;
|
|
942
|
+
} else {
|
|
943
|
+
currentRun = 1;
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
const runNorm = Math.max(0, 1 - maxRun / 8);
|
|
947
|
+
|
|
948
|
+
// 4. Autocorrelation at lag 1
|
|
949
|
+
let mean = 0;
|
|
950
|
+
for (const b of data) mean += b;
|
|
951
|
+
mean /= len;
|
|
952
|
+
let num = 0, den = 0;
|
|
953
|
+
for (let i = 0; i < len - 1; i++) {
|
|
954
|
+
num += (data[i] - mean) * (data[i + 1] - mean);
|
|
955
|
+
den += (data[i] - mean) ** 2;
|
|
956
|
+
}
|
|
957
|
+
const autocorr = den === 0 ? 0 : Math.abs(num / den);
|
|
958
|
+
const autoNorm = Math.max(0, 1 - autocorr * 5);
|
|
959
|
+
|
|
960
|
+
// Combined score (weighted)
|
|
961
|
+
const score = entropyNorm * 0.4 + chiNorm * 0.25 + runNorm * 0.15 + autoNorm * 0.2;
|
|
962
|
+
|
|
963
|
+
telemetry.sentinelPasses += score >= MIN_ENTROPY_RATIO ? 1 : 0;
|
|
964
|
+
telemetry.sentinelFails += score < MIN_ENTROPY_RATIO ? 1 : 0;
|
|
965
|
+
|
|
966
|
+
return {
|
|
967
|
+
score: Math.round(score * 1000) / 1000,
|
|
968
|
+
verdict: this._scoreToVerdict(score),
|
|
969
|
+
method: 'cpu',
|
|
970
|
+
details: {
|
|
971
|
+
bitEntropyPerByte: Math.round(bitEntropy * 1000) / 1000,
|
|
972
|
+
entropyNorm: Math.round(entropyNorm * 1000) / 1000,
|
|
973
|
+
chiSquare: Math.round(chiSquare * 10) / 10,
|
|
974
|
+
chiNorm: Math.round(chiNorm * 1000) / 1000,
|
|
975
|
+
maxRunLength: maxRun,
|
|
976
|
+
runNorm: Math.round(runNorm * 1000) / 1000,
|
|
977
|
+
autocorrelation: Math.round(autocorr * 1000) / 1000,
|
|
978
|
+
autoNorm: Math.round(autoNorm * 1000) / 1000,
|
|
979
|
+
},
|
|
980
|
+
};
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
// ===================================================================
|
|
985
|
+
// UTILITIES
|
|
986
|
+
// ===================================================================
|
|
987
|
+
|
|
988
|
+
function hexToUint8(hex) {
|
|
989
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
990
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
991
|
+
bytes[i] = parseInt(hex.substring(i * 2, i * 2 + 2), 16);
|
|
992
|
+
}
|
|
993
|
+
return bytes;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// ===================================================================
|
|
997
|
+
// SINGLETONS & EXPORTS
|
|
998
|
+
// ===================================================================
|
|
999
|
+
|
|
1000
|
+
/** Global STEADYWATCH seed store */
|
|
1001
|
+
export const seedStore = new SteadywatchSeedStore();
|
|
1002
|
+
|
|
1003
|
+
/** Global Entropy Sentinel */
|
|
1004
|
+
export const sentinel = new EntropySentinel();
|
|
1005
|
+
|
|
1006
|
+
/**
|
|
1007
|
+
* Initialize STEADYWATCH subsystem.
|
|
1008
|
+
*
|
|
1009
|
+
* @param {Object} options
|
|
1010
|
+
* @param {string} [options.seedFile] — path to satellite seeds JSON
|
|
1011
|
+
* @param {number} [options.nodeIndex] — assigned satellite index for this node
|
|
1012
|
+
* @param {number} [options.prime=5] — Hurwitz prime for test seed generation
|
|
1013
|
+
* @param {boolean} [options.generateTest=false] — generate test seeds if no file
|
|
1014
|
+
* @param {import('../utils/accel.js').InferenceEngine} [options.inferenceEngine] — ACCEL inference engine
|
|
1015
|
+
* @returns {Promise<{ initialized: boolean, seedCount: number, sentinel: boolean }>}
|
|
1016
|
+
*/
|
|
1017
|
+
export async function initialize(options = {}) {
|
|
1018
|
+
const {
|
|
1019
|
+
seedFile,
|
|
1020
|
+
nodeIndex,
|
|
1021
|
+
prime = 5,
|
|
1022
|
+
generateTest = false,
|
|
1023
|
+
inferenceEngine = null,
|
|
1024
|
+
} = options;
|
|
1025
|
+
|
|
1026
|
+
// Load seeds
|
|
1027
|
+
let loaded = false;
|
|
1028
|
+
if (seedFile) {
|
|
1029
|
+
loaded = seedStore.load(seedFile);
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (!loaded && generateTest) {
|
|
1033
|
+
const defaultPath = join(
|
|
1034
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
1035
|
+
'../data/steadywatch-seeds-p' + prime + '.json'
|
|
1036
|
+
);
|
|
1037
|
+
seedStore.generateTestSeeds(prime, defaultPath);
|
|
1038
|
+
loaded = seedStore.initialized;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// Assign node
|
|
1042
|
+
if (loaded && typeof nodeIndex === 'number') {
|
|
1043
|
+
seedStore.assignNode(nodeIndex);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Initialize Sentinel
|
|
1047
|
+
if (inferenceEngine) {
|
|
1048
|
+
await sentinel.initialize(inferenceEngine);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const status = {
|
|
1052
|
+
initialized: seedStore.initialized,
|
|
1053
|
+
seedCount: seedStore.seeds.length,
|
|
1054
|
+
sentinel: sentinel._modelLoaded,
|
|
1055
|
+
};
|
|
1056
|
+
|
|
1057
|
+
log.info('STEADYWATCH initialized:', status);
|
|
1058
|
+
return status;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Get a hybrid quantum+CSPRNG seed for ML-KEM-768 keygen.
|
|
1063
|
+
* This is the primary integration point for ANNEX.
|
|
1064
|
+
*
|
|
1065
|
+
* @returns {Uint8Array} — 64-byte seed
|
|
1066
|
+
*/
|
|
1067
|
+
export function getHybridSeed() {
|
|
1068
|
+
return seedStore.getHybridSeed();
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
/**
|
|
1072
|
+
* Score entropy quality of arbitrary byte data.
|
|
1073
|
+
* Uses NPU/GPU if available, software fallback otherwise.
|
|
1074
|
+
* Returns numeric score + TRIBHUJ ternary verdict.
|
|
1075
|
+
*
|
|
1076
|
+
* @param {Uint8Array} data
|
|
1077
|
+
* @returns {Promise<{ score: number, verdict: Trit, method: string, details: Object }>}
|
|
1078
|
+
*/
|
|
1079
|
+
export async function scoreEntropy(data) {
|
|
1080
|
+
return sentinel.score(data);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Select a seed using Fibonacci-cycle family-aware rotation.
|
|
1085
|
+
* Advances the 24-position Fibonacci cycle, determines the SST
|
|
1086
|
+
* family for the current position, and selects from matching satellites.
|
|
1087
|
+
*
|
|
1088
|
+
* @returns {{ seed: Uint8Array, index: number, family: string, fibPos: number, address: TritArray|null }}
|
|
1089
|
+
*/
|
|
1090
|
+
export function selectByFibonacciCycle() {
|
|
1091
|
+
return seedStore.selectByFibonacciCycle();
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
/**
|
|
1095
|
+
* Get seed by 6-trit balanced ternary address.
|
|
1096
|
+
* @param {string|TritArray} address — e.g. "1TT100" or TritArray instance
|
|
1097
|
+
* @returns {{ seed: Uint8Array, index: number }|null}
|
|
1098
|
+
*/
|
|
1099
|
+
export function getSeedByTritAddress(address) {
|
|
1100
|
+
return seedStore.getSeedByTritAddress(address);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Run batch quality consensus across all satellite seeds.
|
|
1105
|
+
* Returns TritArray majority verdict and per-family counts.
|
|
1106
|
+
*
|
|
1107
|
+
* @returns {{ majority: Trit, counts: Object, total: number }}
|
|
1108
|
+
*/
|
|
1109
|
+
export function batchQualityConsensus() {
|
|
1110
|
+
return seedStore.batchQualityConsensus();
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
/**
|
|
1114
|
+
* Get STEADYWATCH status and telemetry.
|
|
1115
|
+
*/
|
|
1116
|
+
export function getStatus() {
|
|
1117
|
+
return {
|
|
1118
|
+
...seedStore.getStatus(),
|
|
1119
|
+
sentinel: {
|
|
1120
|
+
modelLoaded: sentinel._modelLoaded,
|
|
1121
|
+
provider: sentinel._inferenceEngine?.provider || 'none',
|
|
1122
|
+
},
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
export {
|
|
1127
|
+
SteadywatchSeedStore, EntropySentinel,
|
|
1128
|
+
SATELLITE_COUNTS, MIN_ENTROPY_RATIO,
|
|
1129
|
+
TRIT_ADDRESS_LENGTH, FAMILY_GROUPS,
|
|
1130
|
+
};
|
|
1131
|
+
|
|
1132
|
+
export default {
|
|
1133
|
+
initialize,
|
|
1134
|
+
getHybridSeed,
|
|
1135
|
+
scoreEntropy,
|
|
1136
|
+
selectByFibonacciCycle,
|
|
1137
|
+
getSeedByTritAddress,
|
|
1138
|
+
batchQualityConsensus,
|
|
1139
|
+
getStatus,
|
|
1140
|
+
seedStore,
|
|
1141
|
+
sentinel,
|
|
1142
|
+
};
|