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.
Files changed (232) hide show
  1. package/CHANGELOG.md +637 -0
  2. package/CONTRIBUTING.md +42 -0
  3. package/Caddyfile +77 -0
  4. package/README.md +119 -29
  5. package/adapters/adapter-mlv-bible/README.md +124 -0
  6. package/adapters/adapter-mlv-bible/index.js +400 -0
  7. package/adapters/chat-mod-adapter.js +532 -0
  8. package/adapters/content-adapter.js +273 -0
  9. package/content/api.js +50 -41
  10. package/content/index.js +2 -2
  11. package/content/store.js +355 -173
  12. package/dashboard/index.html +19 -3
  13. package/database/replication.js +117 -37
  14. package/docs/CRYPTO-AGILITY.md +204 -0
  15. package/docs/MTLS-RESEARCH.md +367 -0
  16. package/docs/NAMCHE-SPEC.md +681 -0
  17. package/docs/PEERQUANTA-YAKMESH-INTEGRATION.md +407 -0
  18. package/docs/PRECISION-DISCLOSURE.md +96 -0
  19. package/docs/README.md +76 -0
  20. package/docs/ROADMAP-2.4.0.md +447 -0
  21. package/docs/ROADMAP-2.5.0.md +244 -0
  22. package/docs/SECURITY-AUDIT-REPORT.md +306 -0
  23. package/docs/SST-INTEGRATION.md +712 -0
  24. package/docs/STEADYWATCH-IMPLEMENTATION.md +303 -0
  25. package/docs/TERNARY-AUDIT-REPORT.md +247 -0
  26. package/docs/TME-FAQ.md +221 -0
  27. package/docs/WHITEPAPER.md +623 -0
  28. package/docs/adapters.html +1001 -0
  29. package/docs/advanced-systems.html +1045 -0
  30. package/docs/annex.html +1046 -0
  31. package/docs/api.html +970 -0
  32. package/docs/business/response-templates.md +160 -0
  33. package/docs/c2c.html +1225 -0
  34. package/docs/cli.html +1332 -0
  35. package/docs/configuration.html +1248 -0
  36. package/docs/darshan.html +1085 -0
  37. package/docs/dharma.html +966 -0
  38. package/docs/docs-bundle.html +1075 -0
  39. package/docs/docs.css +3120 -0
  40. package/docs/docs.js +556 -0
  41. package/docs/doko.html +969 -0
  42. package/docs/geo-proof.html +858 -0
  43. package/docs/getting-started.html +840 -0
  44. package/docs/gumba-tutorial.html +1144 -0
  45. package/docs/gumba.html +1098 -0
  46. package/docs/index.html +914 -0
  47. package/docs/jhilke.html +1312 -0
  48. package/docs/karma.html +1100 -0
  49. package/docs/katha.html +1037 -0
  50. package/docs/lama.html +978 -0
  51. package/docs/mandala.html +1067 -0
  52. package/docs/mani.html +964 -0
  53. package/docs/mantra.html +967 -0
  54. package/docs/mesh.html +1409 -0
  55. package/docs/nakpak.html +869 -0
  56. package/docs/namche.html +928 -0
  57. package/docs/nav-order.json +53 -0
  58. package/docs/prahari.html +1043 -0
  59. package/docs/prism-bash.min.js +1 -0
  60. package/docs/prism-javascript.min.js +1 -0
  61. package/docs/prism-json.min.js +1 -0
  62. package/docs/prism-tomorrow.min.css +1 -0
  63. package/docs/prism.min.js +1 -0
  64. package/docs/privacy.html +699 -0
  65. package/docs/quick-reference.html +1181 -0
  66. package/docs/sakshi.html +1402 -0
  67. package/docs/sandboxing.md +386 -0
  68. package/docs/seva.html +911 -0
  69. package/docs/sherpa.html +871 -0
  70. package/docs/studio.html +860 -0
  71. package/docs/stupa.html +995 -0
  72. package/docs/tailwind.min.css +2 -0
  73. package/docs/tattva.html +1332 -0
  74. package/docs/terms.html +686 -0
  75. package/docs/time-server-deployment.md +166 -0
  76. package/docs/time-sources.html +1392 -0
  77. package/docs/tivra.html +1127 -0
  78. package/docs/trademark-policy.html +686 -0
  79. package/docs/tribhuj.html +1183 -0
  80. package/docs/trust-security.html +1029 -0
  81. package/docs/tutorials/backup-recovery.html +654 -0
  82. package/docs/tutorials/dashboard.html +604 -0
  83. package/docs/tutorials/domain-setup.html +605 -0
  84. package/docs/tutorials/host-website.html +456 -0
  85. package/docs/tutorials/mesh-network.html +505 -0
  86. package/docs/tutorials/mobile-access.html +445 -0
  87. package/docs/tutorials/privacy.html +467 -0
  88. package/docs/tutorials/raspberry-pi.html +600 -0
  89. package/docs/tutorials/security-basics.html +539 -0
  90. package/docs/tutorials/share-files.html +431 -0
  91. package/docs/tutorials/troubleshooting.html +637 -0
  92. package/docs/tutorials/trust-karma.html +419 -0
  93. package/docs/tutorials/yak-protocol.html +456 -0
  94. package/docs/tutorials.html +1034 -0
  95. package/docs/vani.html +1270 -0
  96. package/docs/webserver.html +809 -0
  97. package/docs/yak-protocol.html +940 -0
  98. package/docs/yak-timeserver-design.md +475 -0
  99. package/docs/yakapp.html +1015 -0
  100. package/docs/ypc27.html +1069 -0
  101. package/docs/yurt.html +1344 -0
  102. package/embedded-docs/bundle.js +334 -74
  103. package/gossip/protocol.js +247 -27
  104. package/identity/key-resolver.js +262 -0
  105. package/identity/machine-seed.js +632 -0
  106. package/identity/node-key.js +669 -368
  107. package/identity/tribhuj-ratchet.js +506 -0
  108. package/knowledge-base.js +37 -8
  109. package/launcher/yakmesh.bat +62 -0
  110. package/launcher/yakmesh.sh +70 -0
  111. package/mesh/annex.js +462 -108
  112. package/mesh/beacon-broadcast.js +113 -1
  113. package/mesh/darshan.js +1718 -0
  114. package/mesh/gumba.js +1567 -0
  115. package/mesh/jhilke.js +651 -0
  116. package/mesh/katha.js +1012 -0
  117. package/mesh/nakpak-routing.js +8 -5
  118. package/mesh/network.js +724 -34
  119. package/mesh/pulse-sync.js +4 -1
  120. package/mesh/rate-limiter.js +127 -15
  121. package/mesh/seva.js +526 -0
  122. package/mesh/sherpa-discovery.js +89 -8
  123. package/mesh/sybil-defense.js +19 -5
  124. package/mesh/temporal-encoder.js +4 -3
  125. package/mesh/vani.js +1364 -0
  126. package/mesh/yurt.js +1340 -0
  127. package/models/entropy-sentinel.onnx +0 -0
  128. package/models/karma-trust.onnx +0 -0
  129. package/models/manifest.json +43 -0
  130. package/models/sakshi-anomaly.onnx +0 -0
  131. package/oracle/code-proof-protocol.js +7 -6
  132. package/oracle/codebase-lock.js +257 -28
  133. package/oracle/index.js +74 -15
  134. package/oracle/ma902-snmp.js +678 -0
  135. package/oracle/module-sealer.js +5 -3
  136. package/oracle/network-identity.js +16 -0
  137. package/oracle/packet-checksum.js +201 -0
  138. package/oracle/sst.js +579 -0
  139. package/oracle/ternary-144t.js +714 -0
  140. package/oracle/ternary-ml.js +481 -0
  141. package/oracle/time-api.js +239 -0
  142. package/oracle/time-source.js +137 -47
  143. package/oracle/validation-oracle-hardened.js +1111 -1071
  144. package/oracle/validation-oracle.js +4 -2
  145. package/oracle/ypc27.js +211 -0
  146. package/package.json +20 -3
  147. package/protocol/yak-handler.js +35 -9
  148. package/protocol/yak-protocol.js +28 -13
  149. package/reference/cpp/yakmesh_mceliece_shard.cpp +168 -0
  150. package/reference/cpp/yakmesh_ypc27.cpp +179 -0
  151. package/sbom.json +87 -0
  152. package/scripts/security-audit.mjs +264 -0
  153. package/scripts/update-docs-nav.js +194 -0
  154. package/scripts/update-docs-sidebar.cjs +164 -0
  155. package/security/crypto-config.js +4 -3
  156. package/security/dharma-moderation.js +517 -0
  157. package/security/doko-identity.js +193 -143
  158. package/security/domain-consensus.js +86 -85
  159. package/security/fs-hardening.js +620 -0
  160. package/security/hardware-attestation.js +5 -3
  161. package/security/hybrid-trust.js +227 -87
  162. package/security/karma-rate-limiter.js +692 -0
  163. package/security/khata-protocol.js +22 -21
  164. package/security/khata-trust-integration.js +277 -150
  165. package/security/memory-safety.js +635 -0
  166. package/security/mesh-auth.js +11 -10
  167. package/security/mesh-revocation.js +373 -5
  168. package/security/namche-gateway.js +298 -69
  169. package/security/sakshi.js +460 -3
  170. package/security/sangha.js +770 -0
  171. package/security/secure-config.js +473 -0
  172. package/security/silicon-parity.js +13 -10
  173. package/security/steadywatch.js +1142 -0
  174. package/security/strike-system.js +32 -3
  175. package/security/temporal-signing.js +488 -0
  176. package/security/trit-commitment.js +464 -0
  177. package/server/crypto/annex.js +247 -0
  178. package/server/darshan-api.js +343 -0
  179. package/server/index.js +3259 -362
  180. package/server/komm-api.js +668 -0
  181. package/utils/accel.js +2273 -0
  182. package/utils/ternary-id.js +79 -0
  183. package/utils/verify-worker.js +57 -0
  184. package/webserver/index.js +95 -5
  185. package/assets/yakmesh-logo.png +0 -0
  186. package/assets/yakmesh-logo.svg +0 -80
  187. package/assets/yakmesh-logo2.png +0 -0
  188. package/assets/yakmesh-logo2sm.png +0 -0
  189. package/assets/ymsm.png +0 -0
  190. package/website/assets/silhouettes/adapters.svg +0 -107
  191. package/website/assets/silhouettes/api-endpoints.svg +0 -115
  192. package/website/assets/silhouettes/atomic-clock.svg +0 -83
  193. package/website/assets/silhouettes/base-camp.svg +0 -81
  194. package/website/assets/silhouettes/bridge.svg +0 -69
  195. package/website/assets/silhouettes/docs-bundle.svg +0 -113
  196. package/website/assets/silhouettes/doko-basket.svg +0 -70
  197. package/website/assets/silhouettes/fortress.svg +0 -93
  198. package/website/assets/silhouettes/gateway.svg +0 -54
  199. package/website/assets/silhouettes/gears.svg +0 -93
  200. package/website/assets/silhouettes/globe-satellite.svg +0 -67
  201. package/website/assets/silhouettes/karma-wheel.svg +0 -137
  202. package/website/assets/silhouettes/lama-council.svg +0 -141
  203. package/website/assets/silhouettes/mandala-network.svg +0 -169
  204. package/website/assets/silhouettes/mani-stones.svg +0 -149
  205. package/website/assets/silhouettes/mantra-wheel.svg +0 -116
  206. package/website/assets/silhouettes/mesh-nodes.svg +0 -113
  207. package/website/assets/silhouettes/nakpak.svg +0 -56
  208. package/website/assets/silhouettes/peak-lightning.svg +0 -73
  209. package/website/assets/silhouettes/sherpa.svg +0 -69
  210. package/website/assets/silhouettes/stupa-tower.svg +0 -119
  211. package/website/assets/silhouettes/tattva-eye.svg +0 -78
  212. package/website/assets/silhouettes/terminal.svg +0 -74
  213. package/website/assets/silhouettes/webserver.svg +0 -145
  214. package/website/assets/silhouettes/yak.svg +0 -78
  215. package/website/assets/yakmesh-logo.png +0 -0
  216. package/website/assets/yakmesh-logo.webp +0 -0
  217. package/website/assets/yakmesh-logo128x140.webp +0 -0
  218. package/website/assets/yakmesh-logo2.png +0 -0
  219. package/website/assets/yakmesh-logo2.svg +0 -51
  220. package/website/assets/yakmesh-logo40x44.webp +0 -0
  221. package/website/assets/yakmesh.gif +0 -0
  222. package/website/assets/yakmesh.ico +0 -0
  223. package/website/assets/yakmesh.jpg +0 -0
  224. package/website/assets/yakmesh.pdf +0 -0
  225. package/website/assets/yakmesh.png +0 -0
  226. package/website/assets/yakmesh.svg +0 -70
  227. package/website/assets/yakmesh128.webp +0 -0
  228. package/website/assets/yakmesh32.png +0 -0
  229. package/website/assets/yakmesh32.svg +0 -65
  230. package/website/assets/yakmesh32o.ico +0 -2
  231. package/website/assets/yakmesh32o.svg +0 -65
  232. package/website/assets/yakmesh32o.svgz +0 -0
@@ -0,0 +1,632 @@
1
+ /**
2
+ * Yakmesh Machine Seed — Deterministic Identity Anchor
3
+ *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * TWO-LAYER IDENTITY ARCHITECTURE
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
8
+ * Layer 1 — NETWORK IDENTITY (shared, code-derived):
9
+ * oracleHash → networkName, networkId, verificationPhrase
10
+ * Every node with byte-identical code arrives at the same network.
11
+ * Machine data is EXCLUDED entirely. This is the peering contract.
12
+ *
13
+ * Layer 2 — NODE IDENTITY (unique, seed-derived):
14
+ * machineSeed + oracleHash + verPhrase → HKDF → ml_dsa65.keygen()
15
+ * The seed creates uniqueness WITHIN the network, not a separate network.
16
+ * If the codebase changes, the same seed produces a DIFFERENT keypair
17
+ * on the new network — the verification phrase acts as domain separator.
18
+ *
19
+ * ═══════════════════════════════════════════════════════════════════════════════
20
+ * SEED STORAGE — HARDWARE-BOUND + YPC-27 INTEGRITY
21
+ * ═══════════════════════════════════════════════════════════════════════════════
22
+ *
23
+ * The seed is NOT stored in plaintext. It is encrypted under a key derived
24
+ * from machine-specific hardware markers (hostname, platform, dataDir).
25
+ * An attacker who copies the seed file to another machine cannot decrypt it.
26
+ *
27
+ * Additionally, a YPC-27 polynomial checksum seals the seed. SIS-hardness
28
+ * (Short Integer Solution in Z[x]/(x^27-1) mod 3) makes it computationally
29
+ * infeasible to craft a different seed that produces the same checksum.
30
+ * This detects both accidental corruption AND deliberate seed injection.
31
+ *
32
+ * ═══════════════════════════════════════════════════════════════════════════════
33
+ * MIGRATION CHAIN — REPUTATION CONTINUITY
34
+ * ═══════════════════════════════════════════════════════════════════════════════
35
+ *
36
+ * When the codebase updates, the same seed produces a new keypair (because
37
+ * the oracle hash changed). The migration chain records each (oracleHash,
38
+ * publicKey) pair so reputation can be cryptographically transferred:
39
+ *
40
+ * migrationProof = sign(oldKey, "MIGRATE:" + newPubKey) +
41
+ * sign(newKey, "MIGRATE:" + oldPubKey)
42
+ *
43
+ * Both signatures together prove the same physical entity controls both
44
+ * identities without revealing the seed.
45
+ *
46
+ * @module identity/machine-seed
47
+ * @version 1.0.0
48
+ */
49
+
50
+ import { createLogger } from '../utils/logger.js';
51
+
52
+ const log = createLogger('identity:machine-seed');
53
+
54
+ import { sha3_256 } from '../utils/accel.js';
55
+ import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
56
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync } from 'fs';
57
+ import { join } from 'path';
58
+ import { createCipheriv, createDecipheriv, scryptSync, randomBytes } from 'crypto';
59
+ import { hostname, platform, cpus } from 'os';
60
+ import { hkdf } from '@noble/hashes/hkdf.js';
61
+ import { sha3_256 as _sha3 } from '@noble/hashes/sha3.js';
62
+
63
+ // YPC-27 polynomial integrity
64
+ import { YPC27Checksum, Poly27, bytesToTrits, DEFAULT_SEED as YPC27_DEFAULT_SEED } from '../oracle/ypc27.js';
65
+
66
+ // SST family analysis
67
+ import { bytesToFamilyTrits, analyzeBytesFamilies } from '../oracle/sst.js';
68
+
69
+ // iO derivation for domain separation
70
+ import { deriveVerificationPhrase } from '../oracle/network-identity.js';
71
+
72
+ // 144T ternary addressing for persistent machine identity
73
+ import { TritAddress, TOTAL_TRITS } from '../oracle/ternary-144t.js';
74
+
75
+
76
+ // =============================================================================
77
+ // CONSTANTS
78
+ // =============================================================================
79
+
80
+ /** Seed size in bytes */
81
+ const SEED_BYTES = 32;
82
+
83
+ /** Seed file name */
84
+ const SEED_FILENAME = 'machine-seed.json';
85
+
86
+ /** Current seed file schema version — bumped for persistentId144T */
87
+ const SCHEMA_VERSION = 2;
88
+
89
+ /** Persistent ID prefix */
90
+ const PERSISTENT_ID_PREFIX = 'yak';
91
+
92
+
93
+ // =============================================================================
94
+ // HARDWARE FINGERPRINT
95
+ // =============================================================================
96
+
97
+ /**
98
+ * Derive a machine-specific fingerprint from hardware markers.
99
+ * This is used to encrypt the seed at rest — the encrypted seed
100
+ * is useless on any other machine.
101
+ *
102
+ * Uses: hostname, platform, CPU model, dataDir
103
+ * NOT in oracle hash (these are local-only values).
104
+ *
105
+ * @param {string} dataDir - The node's data directory path
106
+ * @returns {Buffer} 32-byte hardware-derived key
107
+ */
108
+ function deriveHardwareKey(dataDir) {
109
+ const cpuModel = cpus()[0]?.model || 'unknown-cpu';
110
+ const fingerprint = `yakmesh:machine-seed:${hostname()}:${platform()}:${cpuModel}:${dataDir}`;
111
+ const salt = sha3_256(new TextEncoder().encode('yakmesh-hardware-salt-2026'));
112
+ // scrypt: N=2^14, r=8, p=1, 32-byte key
113
+ return scryptSync(fingerprint, Buffer.from(salt), 32, {
114
+ N: 16384, r: 8, p: 1,
115
+ });
116
+ }
117
+
118
+
119
+ // =============================================================================
120
+ // SEED ENCRYPTION
121
+ // =============================================================================
122
+
123
+ /**
124
+ * Encrypt the seed for at-rest storage using hardware-derived key.
125
+ * AES-256-GCM provides authenticated encryption.
126
+ *
127
+ * @param {Uint8Array} seed - Raw 32-byte seed
128
+ * @param {string} dataDir - For hardware key derivation
129
+ * @returns {{ ciphertext: string, nonce: string, tag: string }}
130
+ */
131
+ function encryptSeed(seed, dataDir) {
132
+ const key = deriveHardwareKey(dataDir);
133
+ const nonce = randomBytes(12);
134
+ const cipher = createCipheriv('aes-256-gcm', key, nonce);
135
+ const encrypted = Buffer.concat([
136
+ cipher.update(Buffer.from(seed)),
137
+ cipher.final(),
138
+ ]);
139
+ return {
140
+ ciphertext: encrypted.toString('hex'),
141
+ nonce: nonce.toString('hex'),
142
+ tag: cipher.getAuthTag().toString('hex'),
143
+ };
144
+ }
145
+
146
+ /**
147
+ * Decrypt the seed from at-rest storage.
148
+ *
149
+ * @param {{ ciphertext: string, nonce: string, tag: string }} enc
150
+ * @param {string} dataDir - For hardware key derivation
151
+ * @returns {Uint8Array} Raw 32-byte seed
152
+ */
153
+ function decryptSeed(enc, dataDir) {
154
+ const key = deriveHardwareKey(dataDir);
155
+ const decipher = createDecipheriv(
156
+ 'aes-256-gcm',
157
+ key,
158
+ Buffer.from(enc.nonce, 'hex'),
159
+ );
160
+ decipher.setAuthTag(Buffer.from(enc.tag, 'hex'));
161
+ const decrypted = Buffer.concat([
162
+ decipher.update(Buffer.from(enc.ciphertext, 'hex')),
163
+ decipher.final(),
164
+ ]);
165
+ return new Uint8Array(decrypted);
166
+ }
167
+
168
+
169
+ // =============================================================================
170
+ // YPC-27 INTEGRITY SEAL
171
+ // =============================================================================
172
+
173
+ /**
174
+ * Compute YPC-27 polynomial checksum of the seed.
175
+ * SIS-hard: an attacker cannot craft a different seed with the same checksum.
176
+ *
177
+ * @param {Uint8Array} seed - Raw 32-byte seed
178
+ * @returns {string} Hex-encoded YPC-27 checksum
179
+ */
180
+ function computeSeedChecksum(seed) {
181
+ const hasher = new YPC27Checksum(YPC27_DEFAULT_SEED);
182
+ hasher.update(seed);
183
+ return hasher.digestHex();
184
+ }
185
+
186
+ /**
187
+ * Verify YPC-27 checksum of a seed.
188
+ *
189
+ * @param {Uint8Array} seed - Raw seed bytes
190
+ * @param {string} expectedHex - Expected YPC-27 checksum hex
191
+ * @returns {boolean}
192
+ */
193
+ function verifySeedChecksum(seed, expectedHex) {
194
+ const computed = computeSeedChecksum(seed);
195
+ return computed === expectedHex;
196
+ }
197
+
198
+
199
+ // =============================================================================
200
+ // KEYPAIR DERIVATION — DETERMINISTIC FROM SEED + NETWORK
201
+ // =============================================================================
202
+
203
+ /**
204
+ * Derive a deterministic 32-byte secret for ML-DSA-65 keygen.
205
+ *
206
+ * The derivation uses HKDF-SHA3-256 with:
207
+ * ikm = machineSeed (32 bytes, unique per machine)
208
+ * salt = oracleHash (hex string of codebase hash)
209
+ * info = verificationPhrase + ":node-keypair" (domain separator)
210
+ *
211
+ * If the codebase changes (oracleHash changes), the derived secret changes,
212
+ * producing a completely different keypair — even with the same seed.
213
+ * The verification phrase provides additional binding to the network identity.
214
+ *
215
+ * @param {Uint8Array} seed - Raw 32-byte machine seed
216
+ * @param {string} oracleHash - Codebase hash from the validation oracle
217
+ * @param {string} verPhrase - Network verification phrase (from iO)
218
+ * @returns {Uint8Array} 32-byte deterministic secret for ml_dsa65.keygen()
219
+ */
220
+ export function deriveNodeSecret(seed, oracleHash, verPhrase) {
221
+ if (!seed || seed.length !== SEED_BYTES) {
222
+ throw new Error(`Machine seed must be ${SEED_BYTES} bytes, got ${seed?.length}`);
223
+ }
224
+ if (!oracleHash || typeof oracleHash !== 'string') {
225
+ throw new Error('Oracle hash (codebase hash) is required for key derivation');
226
+ }
227
+ if (!verPhrase || typeof verPhrase !== 'string') {
228
+ throw new Error('Verification phrase is required for key derivation');
229
+ }
230
+
231
+ const salt = new TextEncoder().encode(oracleHash);
232
+ const info = new TextEncoder().encode(`${verPhrase}:node-keypair`);
233
+
234
+ // HKDF-SHA3-256: one-way derivation, 32-byte output
235
+ return hkdf(_sha3, seed, salt, info, 32);
236
+ }
237
+
238
+ /**
239
+ * Derive a deterministic 72-byte secret for SLH-DSA backup keygen.
240
+ * Uses a different info domain separator than the primary key.
241
+ * SLH-DSA-SHA2-192f requires a 72-byte seed (FIPS 205).
242
+ *
243
+ * @param {Uint8Array} seed - Raw 32-byte machine seed
244
+ * @param {string} oracleHash - Codebase hash
245
+ * @param {string} verPhrase - Network verification phrase
246
+ * @returns {Uint8Array} 72-byte deterministic secret for SLH-DSA keygen
247
+ */
248
+ export function deriveBackupSecret(seed, oracleHash, verPhrase) {
249
+ if (!seed || seed.length !== SEED_BYTES) {
250
+ throw new Error(`Machine seed must be ${SEED_BYTES} bytes`);
251
+ }
252
+ const salt = new TextEncoder().encode(oracleHash);
253
+ const info = new TextEncoder().encode(`${verPhrase}:backup-keypair-slh-dsa`);
254
+ return hkdf(_sha3, seed, salt, info, 72);
255
+ }
256
+
257
+
258
+ // =============================================================================
259
+ // MIGRATION CHAIN
260
+ // =============================================================================
261
+
262
+ /**
263
+ * Compute persistent machine ID in 144T ternary format.
264
+ * This ID is CONSTANT across all code upgrades — derived directly from seed.
265
+ *
266
+ * Format: "yak-[tier1-144T]" where tier1 is 36 trits (≈57 bits entropy)
267
+ * Example: "yak-TT00TTT00:TTT00TTT0:0TTT00TTT:00TTT00TT"
268
+ *
269
+ * @param {Uint8Array} seed - Raw 32-byte machine seed
270
+ * @returns {string} Persistent 144T identifier
271
+ */
272
+ function computePersistentId144T(seed) {
273
+ // Domain-separate from other derivations — "persistent-machine-id" context
274
+ const context = new TextEncoder().encode('yakmesh:persistent-machine-id:v1');
275
+ const combined = new Uint8Array(seed.length + context.length);
276
+ combined.set(seed);
277
+ combined.set(context, seed.length);
278
+
279
+ const hash = sha3_256(combined);
280
+ const hex = bytesToHex(hash);
281
+
282
+ // Convert to 144T, extract tier 1 for compact persistent ID
283
+ const tritAddr = TritAddress.fromHex(hex);
284
+ const tier1 = tritAddr.toString().split('.')[0];
285
+
286
+ return `${PERSISTENT_ID_PREFIX}-${tier1}`;
287
+ }
288
+
289
+ /**
290
+ * Add a migration entry to the chain.
291
+ * Records that this seed derived a specific publicKey under a specific oracleHash.
292
+ *
293
+ * @param {Object[]} chain - Existing migration chain entries
294
+ * @param {string} oracleHash - Current oracle hash
295
+ * @param {string} pubKeyHash - SHA3-256 hash of the public key (not the full key!)
296
+ * @param {string} networkName - Human-readable network name
297
+ * @returns {Object[]} Updated chain
298
+ */
299
+ function addMigrationEntry(chain, oracleHash, pubKeyHash, networkName) {
300
+ const entry = {
301
+ oracleHash: oracleHash.slice(0, 24), // First 24 chars, enough for lookup
302
+ pubKeyHash: pubKeyHash.slice(0, 32), // First 32 chars
303
+ networkName,
304
+ timestamp: new Date().toISOString(),
305
+ };
306
+
307
+ // Don't duplicate — if the same oracleHash already exists, skip
308
+ const existing = chain.find(e => e.oracleHash === entry.oracleHash);
309
+ if (existing) return chain;
310
+
311
+ return [...chain, entry];
312
+ }
313
+
314
+
315
+ // =============================================================================
316
+ // SST ANALYSIS (INFORMATIONAL)
317
+ // =============================================================================
318
+
319
+ /**
320
+ * Compute SST family analysis of the seed.
321
+ * Purely informational — stored for display and future trust integration.
322
+ *
323
+ * @param {Uint8Array} seed - Raw seed bytes
324
+ * @returns {{ a: number, b: number, c: number }} Family proportions
325
+ */
326
+ function analyzeSeedFamilies(seed) {
327
+ return analyzeBytesFamilies(seed);
328
+ }
329
+
330
+
331
+ // =============================================================================
332
+ // MACHINE SEED MANAGER
333
+ // =============================================================================
334
+
335
+ /**
336
+ * MachineSeed — Manages the persistent machine seed and its integrity.
337
+ *
338
+ * The seed is the long-term identity anchor. Keypairs are ephemeral
339
+ * projections of the seed onto a specific network version.
340
+ *
341
+ * File stored: machine-seed.json in dataDir
342
+ * Contents: { encryptedSeed, ypc27Checksum, persistentId144T, migrationChain[], sstFamilies, schemaVersion }
343
+ * NEVER stored: raw seed, private keys
344
+ *
345
+ * The persistentId144T is constant across ALL code upgrades — it identifies
346
+ * the physical machine/node owner regardless of which network version is running.
347
+ */
348
+ export class MachineSeed {
349
+ /**
350
+ * @param {string} dataDir - Data directory (same as NodeIdentity)
351
+ */
352
+ constructor(dataDir = './data') {
353
+ this.dataDir = dataDir;
354
+ this.seedPath = join(dataDir, SEED_FILENAME);
355
+ this.seed = null; // Raw seed (in memory only)
356
+ this.persistentId = null; // 144T persistent identity (constant across upgrades)
357
+ this.migrationChain = []; // History of (oracleHash, pubKeyHash) pairs
358
+ this.sstFamilies = null; // SST family analysis
359
+ this.created = false; // True if seed was just generated (first run)
360
+ }
361
+
362
+ /**
363
+ * Set restrictive file permissions on seed file (owner read/write only).
364
+ * On Windows this is a no-op (NTFS ACLs handle access differently).
365
+ */
366
+ _secureFile() {
367
+ if (platform() !== 'win32') {
368
+ try {
369
+ chmodSync(this.seedPath, 0o600);
370
+ } catch (e) {
371
+ log.warn('Could not set restrictive permissions on seed file', { error: e.message });
372
+ }
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Initialize the machine seed.
378
+ *
379
+ * - If seed file exists: decrypt, verify YPC-27 checksum, load migration chain
380
+ * - If no seed file: generate random seed, compute checksum, save encrypted
381
+ *
382
+ * @returns {boolean} true if seed loaded/created successfully
383
+ */
384
+ async init() {
385
+ // Ensure data directory exists
386
+ if (!existsSync(this.dataDir)) {
387
+ mkdirSync(this.dataDir, { recursive: true });
388
+ }
389
+
390
+ if (existsSync(this.seedPath)) {
391
+ return this._loadExisting();
392
+ } else {
393
+ return this._generateNew();
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Load and verify an existing seed file.
399
+ * @returns {boolean}
400
+ * @private
401
+ */
402
+ _loadExisting() {
403
+ try {
404
+ const data = JSON.parse(readFileSync(this.seedPath, 'utf8'));
405
+
406
+ // Schema check
407
+ if (data.schemaVersion !== SCHEMA_VERSION) {
408
+ log.warn('Seed file schema version mismatch', {
409
+ expected: SCHEMA_VERSION,
410
+ got: data.schemaVersion,
411
+ });
412
+ // Future: add migration logic for schema upgrades
413
+ }
414
+
415
+ // Decrypt seed using hardware-derived key
416
+ const seed = decryptSeed(data.encryptedSeed, this.dataDir);
417
+
418
+ // Verify YPC-27 integrity checksum
419
+ if (!verifySeedChecksum(seed, data.ypc27Checksum)) {
420
+ log.error('🚨 YPC-27 SEED INTEGRITY CHECK FAILED — possible tampering!');
421
+ log.error('Seed checksum does not match. The seed file may have been modified.');
422
+ log.error('Refusing to use potentially compromised seed.');
423
+ throw new Error(
424
+ 'YPC-27 seed integrity check failed. Seed file may be tampered. ' +
425
+ 'Delete machine-seed.json to generate a new identity (reputation will be lost).'
426
+ );
427
+ }
428
+
429
+ this.seed = seed;
430
+ this.persistentId = data.persistentId144T || computePersistentId144T(seed);
431
+ this.migrationChain = data.migrationChain || [];
432
+ this.sstFamilies = data.sstFamilies || analyzeSeedFamilies(seed);
433
+ this.created = false;
434
+
435
+ // Schema migration: add persistentId if missing (v1 → v2)
436
+ if (!data.persistentId144T) {
437
+ log.info('Migrating seed file to v2 (adding persistentId144T)');
438
+ this._updateSeedFile();
439
+ }
440
+
441
+ log.info('Machine seed loaded and verified', {
442
+ ypc27: '✓',
443
+ persistentId: this.persistentId,
444
+ migrationEntries: this.migrationChain.length,
445
+ sstFamilies: this.sstFamilies,
446
+ });
447
+
448
+ return true;
449
+ } catch (e) {
450
+ if (e.message.includes('YPC-27')) throw e; // Re-throw integrity failures
451
+
452
+ log.error('Failed to load machine seed', { error: e.message });
453
+ throw new Error(
454
+ `Cannot load machine seed: ${e.message}. ` +
455
+ 'Wrong machine? Seed file is encrypted to the machine that created it.'
456
+ );
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Generate a new random seed, compute integrity seal, save encrypted.
462
+ * @returns {boolean}
463
+ * @private
464
+ */
465
+ _generateNew() {
466
+ log.info('Generating new machine seed (first run on this machine)');
467
+
468
+ // Cryptographically random 32-byte seed
469
+ const seed = new Uint8Array(randomBytes(SEED_BYTES));
470
+
471
+ // Compute YPC-27 integrity checksum (SIS-hard polynomial seal)
472
+ const ypc27Checksum = computeSeedChecksum(seed);
473
+
474
+ // Compute persistent 144T identity (constant across all upgrades)
475
+ const persistentId144T = computePersistentId144T(seed);
476
+
477
+ // Compute SST family analysis
478
+ const sstFamilies = analyzeSeedFamilies(seed);
479
+
480
+ // Encrypt seed under hardware-derived key
481
+ const encryptedSeed = encryptSeed(seed, this.dataDir);
482
+
483
+ // Save seed file
484
+ const seedFile = {
485
+ schemaVersion: SCHEMA_VERSION,
486
+ encryptedSeed,
487
+ ypc27Checksum,
488
+ persistentId144T,
489
+ sstFamilies,
490
+ migrationChain: [],
491
+ createdAt: new Date().toISOString(),
492
+ };
493
+
494
+ writeFileSync(this.seedPath, JSON.stringify(seedFile, null, 2));
495
+ this._secureFile();
496
+
497
+ this.seed = seed;
498
+ this.persistentId = persistentId144T;
499
+ this.migrationChain = [];
500
+ this.sstFamilies = sstFamilies;
501
+ this.created = true;
502
+
503
+ log.info('Machine seed generated and secured', {
504
+ persistentId: persistentId144T,
505
+ ypc27Checksum: ypc27Checksum.slice(0, 12) + '...',
506
+ sstFamilies,
507
+ encrypted: true,
508
+ hardwareBound: true,
509
+ });
510
+
511
+ return true;
512
+ }
513
+
514
+ /**
515
+ * Record a migration entry after deriving a keypair.
516
+ *
517
+ * @param {string} oracleHash - Current oracle hash
518
+ * @param {string} pubKeyHash - SHA3-256(publicKey) hex
519
+ * @param {string} networkName - Human-readable network name
520
+ */
521
+ recordMigration(oracleHash, pubKeyHash, networkName) {
522
+ this.migrationChain = addMigrationEntry(
523
+ this.migrationChain, oracleHash, pubKeyHash, networkName
524
+ );
525
+
526
+ // Update seed file with new migration entry
527
+ this._updateSeedFile();
528
+ }
529
+
530
+ /**
531
+ * Get the previous identity on the migration chain (for migration proofs).
532
+ *
533
+ * @returns {{ oracleHash: string, pubKeyHash: string, networkName: string, timestamp: string } | null}
534
+ */
535
+ getPreviousIdentity() {
536
+ if (this.migrationChain.length < 2) return null;
537
+ return this.migrationChain[this.migrationChain.length - 2];
538
+ }
539
+
540
+ /**
541
+ * Get the full migration chain for reputation lookup.
542
+ * @returns {Object[]}
543
+ */
544
+ getMigrationChain() {
545
+ return [...this.migrationChain];
546
+ }
547
+
548
+ /**
549
+ * Check if this seed has been used on a previous network version.
550
+ * @param {string} oracleHash - Current oracle hash
551
+ * @returns {boolean}
552
+ */
553
+ hasNetworkHistory(oracleHash) {
554
+ const currentEntry = this.migrationChain.find(
555
+ e => e.oracleHash === oracleHash.slice(0, 24)
556
+ );
557
+ return this.migrationChain.length > 0 && !currentEntry;
558
+ }
559
+
560
+ /**
561
+ * Update the seed file on disk (for migration chain updates).
562
+ * Re-encrypts the seed and updates all metadata.
563
+ * @private
564
+ */
565
+ _updateSeedFile() {
566
+ if (!this.seed) {
567
+ log.error('Cannot update seed file: seed not loaded');
568
+ return;
569
+ }
570
+
571
+ const encryptedSeed = encryptSeed(this.seed, this.dataDir);
572
+ const ypc27Checksum = computeSeedChecksum(this.seed);
573
+ const persistentId144T = this.persistentId || computePersistentId144T(this.seed);
574
+ const sstFamilies = this.sstFamilies || analyzeSeedFamilies(this.seed);
575
+
576
+ const existing = existsSync(this.seedPath)
577
+ ? JSON.parse(readFileSync(this.seedPath, 'utf8'))
578
+ : {};
579
+
580
+ const seedFile = {
581
+ schemaVersion: SCHEMA_VERSION,
582
+ encryptedSeed,
583
+ ypc27Checksum,
584
+ persistentId144T,
585
+ sstFamilies,
586
+ migrationChain: this.migrationChain,
587
+ createdAt: existing.createdAt || new Date().toISOString(),
588
+ updatedAt: new Date().toISOString(),
589
+ };
590
+
591
+ writeFileSync(this.seedPath, JSON.stringify(seedFile, null, 2));
592
+ this._secureFile();
593
+ }
594
+
595
+ /**
596
+ * Get raw seed bytes (for key derivation — NEVER log or transmit).
597
+ * @returns {Uint8Array}
598
+ */
599
+ getSeed() {
600
+ if (!this.seed) {
601
+ throw new Error('Machine seed not initialized. Call init() first.');
602
+ }
603
+ return this.seed;
604
+ }
605
+
606
+ /**
607
+ * Get the persistent 144T machine identity.
608
+ * This ID is CONSTANT across all code upgrades — it identifies the
609
+ * physical machine/node owner regardless of network version.
610
+ *
611
+ * Format: "yak-[tier1-144T]"
612
+ * Example: "yak-TT00TTT00:TTT00TTT0:0TTT00TTT:00TTT00TT"
613
+ *
614
+ * @returns {string} Persistent 144T identifier
615
+ */
616
+ getPersistentId() {
617
+ if (!this.persistentId) {
618
+ throw new Error('Machine seed not initialized. Call init() first.');
619
+ }
620
+ return this.persistentId;
621
+ }
622
+
623
+ /**
624
+ * Check if seed was just created (first run).
625
+ * @returns {boolean}
626
+ */
627
+ isFirstRun() {
628
+ return this.created;
629
+ }
630
+ }
631
+
632
+ export default MachineSeed;