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
package/identity/node-key.js
CHANGED
|
@@ -1,368 +1,669 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Yakmesh Node Identity - Post-Quantum Secure
|
|
3
|
-
* Uses ML-DSA-65 (Dilithium3) from FIPS 204
|
|
4
|
-
*
|
|
5
|
-
* Security Level: NIST Level 3 (~192-bit classical security)
|
|
6
|
-
* Quantum Resistant: Yes (lattice-based)
|
|
7
|
-
*
|
|
8
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
-
* ⚠️ SECURITY ARCHITECTURE - DO NOT MODIFY WITHOUT EXPLICIT REVIEW ⚠️
|
|
10
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
11
|
-
*
|
|
12
|
-
* IMPORTANT: Node IDs are composed of TWO parts:
|
|
13
|
-
* 1. NETWORK NAME - Derived from codebase hash (SAME for all nodes on network)
|
|
14
|
-
* 2. INSTANCE ID - Derived from public key hash (UNIQUE per node instance)
|
|
15
|
-
*
|
|
16
|
-
* This design ensures:
|
|
17
|
-
* - Nodes running identical code share the same network name
|
|
18
|
-
* - Each node instance has a unique identifier
|
|
19
|
-
* - Human-readable verification: same network name = same code = can peer
|
|
20
|
-
* - Codebase integrity is cryptographically bound to identity
|
|
21
|
-
*
|
|
22
|
-
* Format: "node-[network-name]-[instance-id]"
|
|
23
|
-
* Example: "node-qubit-lattice-prism-pq-a7x9"
|
|
24
|
-
* ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^
|
|
25
|
-
* Same for all nodes Unique per instance
|
|
26
|
-
*
|
|
27
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
-
* 🚫 REJECTED SIMPLIFICATIONS (Security Anti-Patterns):
|
|
29
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
-
*
|
|
31
|
-
* The following "simplifications" have been proposed and MUST BE REJECTED:
|
|
32
|
-
*
|
|
33
|
-
* ❌ NodeID = SHA3-256(publicKey)
|
|
34
|
-
* WHY REJECT: Removes codebase integrity verification. A malicious node
|
|
35
|
-
* running modified code would be indistinguishable from honest nodes.
|
|
36
|
-
*
|
|
37
|
-
* ❌ NodeID = base64(publicKey)
|
|
38
|
-
* WHY REJECT: Same as above, plus loses human-readable verification.
|
|
39
|
-
*
|
|
40
|
-
* ❌ NodeID = UUID or random string
|
|
41
|
-
* WHY REJECT: Breaks deterministic derivation. Cannot verify identity
|
|
42
|
-
* matches public key. Enables identity spoofing.
|
|
43
|
-
*
|
|
44
|
-
* ❌ Removing the codebase hash component
|
|
45
|
-
* WHY REJECT: Fundamental to network segmentation by code version.
|
|
46
|
-
* Prevents incompatible or malicious code from joining honest networks.
|
|
47
|
-
*
|
|
48
|
-
* The current two-part design (network + instance) via iO obfuscation is
|
|
49
|
-
* INTENTIONAL and provides security properties that simpler schemes lack.
|
|
50
|
-
*
|
|
51
|
-
* This warning added 2026-01-18 after a spec draft attempted to simplify
|
|
52
|
-
* NodeID to just SHA3-256(publicKey), which would have undermined the
|
|
53
|
-
* entire network integrity model.
|
|
54
|
-
*
|
|
55
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
56
|
-
*
|
|
57
|
-
* @module identity/node-key
|
|
58
|
-
* @version
|
|
59
|
-
*/
|
|
60
|
-
|
|
61
|
-
import { createLogger } from '../utils/logger.js';
|
|
62
|
-
|
|
63
|
-
const log = createLogger('identity:node-key');
|
|
64
|
-
|
|
65
|
-
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
66
|
-
import { sha3_256 } from '@noble/hashes/sha3.js';
|
|
67
|
-
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
68
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
69
|
-
import { join } from 'path';
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
*
|
|
107
|
-
* @returns {string}
|
|
108
|
-
*/
|
|
109
|
-
export function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
*
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
*
|
|
196
|
-
*/
|
|
197
|
-
export
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Yakmesh Node Identity - Post-Quantum Secure
|
|
3
|
+
* Uses ML-DSA-65 (Dilithium3) from FIPS 204
|
|
4
|
+
*
|
|
5
|
+
* Security Level: NIST Level 3 (~192-bit classical security)
|
|
6
|
+
* Quantum Resistant: Yes (lattice-based)
|
|
7
|
+
*
|
|
8
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
9
|
+
* ⚠️ SECURITY ARCHITECTURE - DO NOT MODIFY WITHOUT EXPLICIT REVIEW ⚠️
|
|
10
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT: Node IDs are composed of TWO parts:
|
|
13
|
+
* 1. NETWORK NAME - Derived from codebase hash (SAME for all nodes on network)
|
|
14
|
+
* 2. INSTANCE ID - Derived from public key hash (UNIQUE per node instance)
|
|
15
|
+
*
|
|
16
|
+
* This design ensures:
|
|
17
|
+
* - Nodes running identical code share the same network name
|
|
18
|
+
* - Each node instance has a unique identifier
|
|
19
|
+
* - Human-readable verification: same network name = same code = can peer
|
|
20
|
+
* - Codebase integrity is cryptographically bound to identity
|
|
21
|
+
*
|
|
22
|
+
* Format: "node-[network-name]-[instance-id]"
|
|
23
|
+
* Example: "node-qubit-lattice-prism-pq-a7x9"
|
|
24
|
+
* ^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^
|
|
25
|
+
* Same for all nodes Unique per instance
|
|
26
|
+
*
|
|
27
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
+
* 🚫 REJECTED SIMPLIFICATIONS (Security Anti-Patterns):
|
|
29
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
+
*
|
|
31
|
+
* The following "simplifications" have been proposed and MUST BE REJECTED:
|
|
32
|
+
*
|
|
33
|
+
* ❌ NodeID = SHA3-256(publicKey)
|
|
34
|
+
* WHY REJECT: Removes codebase integrity verification. A malicious node
|
|
35
|
+
* running modified code would be indistinguishable from honest nodes.
|
|
36
|
+
*
|
|
37
|
+
* ❌ NodeID = base64(publicKey)
|
|
38
|
+
* WHY REJECT: Same as above, plus loses human-readable verification.
|
|
39
|
+
*
|
|
40
|
+
* ❌ NodeID = UUID or random string
|
|
41
|
+
* WHY REJECT: Breaks deterministic derivation. Cannot verify identity
|
|
42
|
+
* matches public key. Enables identity spoofing.
|
|
43
|
+
*
|
|
44
|
+
* ❌ Removing the codebase hash component
|
|
45
|
+
* WHY REJECT: Fundamental to network segmentation by code version.
|
|
46
|
+
* Prevents incompatible or malicious code from joining honest networks.
|
|
47
|
+
*
|
|
48
|
+
* The current two-part design (network + instance) via iO obfuscation is
|
|
49
|
+
* INTENTIONAL and provides security properties that simpler schemes lack.
|
|
50
|
+
*
|
|
51
|
+
* This warning added 2026-01-18 after a spec draft attempted to simplify
|
|
52
|
+
* NodeID to just SHA3-256(publicKey), which would have undermined the
|
|
53
|
+
* entire network integrity model.
|
|
54
|
+
*
|
|
55
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
56
|
+
*
|
|
57
|
+
* @module identity/node-key
|
|
58
|
+
* @version 2.0.0 — Two-Layer Deterministic Identity
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
import { createLogger } from '../utils/logger.js';
|
|
62
|
+
|
|
63
|
+
const log = createLogger('identity:node-key');
|
|
64
|
+
|
|
65
|
+
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
66
|
+
import { sha3_256 as _nobleSha3 } from '@noble/hashes/sha3.js';
|
|
67
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
|
|
68
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, chmodSync, unlinkSync } from 'fs';
|
|
69
|
+
import { join } from 'path';
|
|
70
|
+
import { randomBytes } from 'crypto';
|
|
71
|
+
import { platform } from 'os';
|
|
72
|
+
|
|
73
|
+
// ACCEL: Hardware-accelerated crypto (native SHA3 via OpenSSL/SHA-NI, future liboqs)
|
|
74
|
+
import { sha3_256, mlDsa65Sign, mlDsa65Verify } from '../utils/accel.js';
|
|
75
|
+
|
|
76
|
+
// Import iO network identity for obfuscated node IDs
|
|
77
|
+
import { deriveNetworkName, deriveNetworkId, deriveVerificationPhrase } from '../oracle/network-identity.js';
|
|
78
|
+
|
|
79
|
+
// SLH-DSA (FIPS 205) backup signature — defense-in-depth against lattice breaks
|
|
80
|
+
import {
|
|
81
|
+
generateBackupSignatureKeyPair,
|
|
82
|
+
signBackup as slhDsaSign,
|
|
83
|
+
verifyBackup as slhDsaVerify,
|
|
84
|
+
} from '../security/crypto-config.js';
|
|
85
|
+
|
|
86
|
+
// Two-Layer Identity: Machine seed + deterministic derivation
|
|
87
|
+
import { MachineSeed, deriveNodeSecret, deriveBackupSecret } from './machine-seed.js';
|
|
88
|
+
|
|
89
|
+
// Cached codebase hash - set during first identity generation
|
|
90
|
+
let cachedCodebaseHash = null;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Set the codebase hash for node identity generation
|
|
94
|
+
* This MUST be called before generating any node IDs
|
|
95
|
+
*
|
|
96
|
+
* @param {string} hash - The codebase hash from the validation oracle
|
|
97
|
+
*/
|
|
98
|
+
export function setCodebaseHash(hash) {
|
|
99
|
+
if (cachedCodebaseHash && cachedCodebaseHash !== hash) {
|
|
100
|
+
console.warn('⚠️ Codebase hash changed - network identity will change!');
|
|
101
|
+
}
|
|
102
|
+
cachedCodebaseHash = hash;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the current codebase hash
|
|
107
|
+
* @returns {string|null} The codebase hash or null if not set
|
|
108
|
+
*/
|
|
109
|
+
export function getCodebaseHash() {
|
|
110
|
+
return cachedCodebaseHash;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Generate a node ID using iO obfuscation
|
|
115
|
+
*
|
|
116
|
+
* The node ID is composed of:
|
|
117
|
+
* 1. Network name - derived from CODEBASE hash (same for all nodes on network)
|
|
118
|
+
* 2. Instance ID - derived from PUBLIC KEY hash (unique per node)
|
|
119
|
+
*
|
|
120
|
+
* @param {Uint8Array} publicKey - The node's public key
|
|
121
|
+
* @param {string} codebaseHash - The codebase hash (optional, uses cached if not provided)
|
|
122
|
+
* @returns {string} Node ID like "node-qubit-lattice-prism-pq-a7x9"
|
|
123
|
+
*/
|
|
124
|
+
export function generateNodeId(publicKey, codebaseHash = null) {
|
|
125
|
+
const effectiveCodebaseHash = codebaseHash || cachedCodebaseHash;
|
|
126
|
+
|
|
127
|
+
if (!effectiveCodebaseHash) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
'Codebase hash not set! Call setCodebaseHash() with the oracle\'s selfHash ' +
|
|
130
|
+
'before generating node identities. This ensures all nodes on the same ' +
|
|
131
|
+
'network share the same network name.'
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// NETWORK NAME: Derived from codebase hash
|
|
136
|
+
// This is the SAME for all nodes running identical code
|
|
137
|
+
const networkName = deriveNetworkName(effectiveCodebaseHash, 3);
|
|
138
|
+
|
|
139
|
+
// INSTANCE ID: Derived from public key hash
|
|
140
|
+
// This is UNIQUE per node instance
|
|
141
|
+
const publicKeyHash = sha3_256(publicKey);
|
|
142
|
+
const publicKeyHashHex = bytesToHex(publicKeyHash);
|
|
143
|
+
const instanceId = deriveNetworkId(publicKeyHashHex);
|
|
144
|
+
|
|
145
|
+
// Format: "node-[network-name]-[instance-id]"
|
|
146
|
+
// e.g., "node-qubit-lattice-prism-pq-a7x9"
|
|
147
|
+
return `node-${networkName}-${instanceId}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Generate internal hash for private operations (NOT exposed externally)
|
|
152
|
+
* This is kept for signature verification and internal lookups
|
|
153
|
+
*/
|
|
154
|
+
export function generateInternalHash(publicKey) {
|
|
155
|
+
const hash = sha3_256(publicKey);
|
|
156
|
+
return bytesToHex(hash.slice(0, 16));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Generate ML-DSA-65 (Dilithium3) keypair for node identity.
|
|
161
|
+
*
|
|
162
|
+
* v2.0: Accepts an optional deterministic seed. When provided, the keypair
|
|
163
|
+
* is fully deterministic — same seed always produces the same keys.
|
|
164
|
+
* When omitted, falls back to random generation (for testing/standalone use).
|
|
165
|
+
*
|
|
166
|
+
* @param {Uint8Array} [deterministicSeed] - 32-byte seed from HKDF derivation
|
|
167
|
+
* @returns {{ publicKey: string, secretKey: string, algorithm: string, nistLevel: number }}
|
|
168
|
+
*/
|
|
169
|
+
export function generateKeyPair(deterministicSeed = null) {
|
|
170
|
+
const seed = deterministicSeed || crypto.getRandomValues(new Uint8Array(32));
|
|
171
|
+
const keyPair = ml_dsa65.keygen(seed);
|
|
172
|
+
return {
|
|
173
|
+
publicKey: bytesToHex(keyPair.publicKey),
|
|
174
|
+
secretKey: bytesToHex(keyPair.secretKey),
|
|
175
|
+
algorithm: 'ML-DSA-65',
|
|
176
|
+
nistLevel: 3,
|
|
177
|
+
deterministic: !!deterministicSeed,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Sign a message with the node's secret key
|
|
183
|
+
*/
|
|
184
|
+
export function signMessage(message, secretKeyHex) {
|
|
185
|
+
const secretKey = hexToBytes(secretKeyHex);
|
|
186
|
+
const messageBytes = typeof message === 'string'
|
|
187
|
+
? new TextEncoder().encode(message)
|
|
188
|
+
: message;
|
|
189
|
+
// ml_dsa65.sign takes (message, secretKey) — ACCEL-accelerated
|
|
190
|
+
const signature = mlDsa65Sign(messageBytes, secretKey);
|
|
191
|
+
return bytesToHex(signature);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Verify a signature from another node
|
|
196
|
+
*/
|
|
197
|
+
export function verifySignature(message, signatureHex, publicKeyHex) {
|
|
198
|
+
try {
|
|
199
|
+
const publicKey = hexToBytes(publicKeyHex);
|
|
200
|
+
const signature = hexToBytes(signatureHex);
|
|
201
|
+
const messageBytes = typeof message === 'string'
|
|
202
|
+
? new TextEncoder().encode(message)
|
|
203
|
+
: message;
|
|
204
|
+
// ml_dsa65.verify takes (signature, message, publicKey) — ACCEL-accelerated
|
|
205
|
+
return mlDsa65Verify(signature, messageBytes, publicKey);
|
|
206
|
+
} catch (e) {
|
|
207
|
+
console.error('Signature verification failed:', e.message);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Node Identity Manager
|
|
214
|
+
* Handles persistent storage and management of node identity
|
|
215
|
+
*
|
|
216
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
217
|
+
* v2.0 — TWO-LAYER DETERMINISTIC IDENTITY
|
|
218
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
219
|
+
*
|
|
220
|
+
* The oracle MUST be initialized before creating node identities.
|
|
221
|
+
*
|
|
222
|
+
* Layer 1 (Network): oracleHash → networkName, networkId, verPhrase (SHARED)
|
|
223
|
+
* Layer 2 (Node): seed + oracleHash + verPhrase → HKDF → keypair (UNIQUE)
|
|
224
|
+
*
|
|
225
|
+
* Private keys are NEVER stored on disk. They are derived fresh each startup
|
|
226
|
+
* from the machine seed + codebase hash + verification phrase.
|
|
227
|
+
* ═══════════════════════════════════════════════════════════════════════════════
|
|
228
|
+
*/
|
|
229
|
+
export class NodeIdentity {
|
|
230
|
+
constructor(dataDir = './data') {
|
|
231
|
+
this.dataDir = dataDir;
|
|
232
|
+
this.keyPath = join(dataDir, 'node-key.json'); // Public identity file
|
|
233
|
+
this.identity = null;
|
|
234
|
+
this.networkName = null;
|
|
235
|
+
this.verificationPhrase = null;
|
|
236
|
+
this.machineSeed = new MachineSeed(dataDir); // Layer 2 anchor
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Set restrictive file permissions on key file (owner read/write only).
|
|
241
|
+
* On Windows this is a no-op (NTFS ACLs handle access differently).
|
|
242
|
+
*/
|
|
243
|
+
_secureKeyFile() {
|
|
244
|
+
if (platform() !== 'win32') {
|
|
245
|
+
try {
|
|
246
|
+
chmodSync(this.keyPath, 0o600); // rw------- (owner only)
|
|
247
|
+
} catch (e) {
|
|
248
|
+
log.warn('Could not set restrictive permissions on key file', { error: e.message });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Initialize or load node identity using two-layer deterministic derivation.
|
|
255
|
+
*
|
|
256
|
+
* Flow:
|
|
257
|
+
* 1. Get oracleHash → derive Layer 1 (networkName, verPhrase)
|
|
258
|
+
* 2. Load/create machine seed (Layer 2 anchor)
|
|
259
|
+
* 3. HKDF(seed, oracleHash, verPhrase) → deterministic keypair
|
|
260
|
+
* 4. Record migration entry (seed + oracleHash → pubKeyHash)
|
|
261
|
+
* 5. Store public identity to disk (private key stays in memory only)
|
|
262
|
+
*
|
|
263
|
+
* @param {string} nodeName - Human-readable node name
|
|
264
|
+
* @param {string} region - Node region/location
|
|
265
|
+
* @param {Object} oracle - The validation oracle instance (provides codebase hash)
|
|
266
|
+
*/
|
|
267
|
+
async init(nodeName = 'Yakmesh Node', region = 'local', oracle = null) {
|
|
268
|
+
// Ensure data directory exists
|
|
269
|
+
if (!existsSync(this.dataDir)) {
|
|
270
|
+
mkdirSync(this.dataDir, { recursive: true });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ─── Layer 1: Network Identity (shared, code-derived) ───
|
|
274
|
+
let codebaseHash = cachedCodebaseHash;
|
|
275
|
+
if (oracle) {
|
|
276
|
+
const proof = oracle.generateCodeProof();
|
|
277
|
+
codebaseHash = proof.selfHash;
|
|
278
|
+
setCodebaseHash(codebaseHash);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!codebaseHash) {
|
|
282
|
+
throw new Error(
|
|
283
|
+
'Cannot initialize identity: codebase hash not available. ' +
|
|
284
|
+
'Pass the oracle instance to init() or call setCodebaseHash() first.'
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
this.networkName = deriveNetworkName(codebaseHash, 3);
|
|
289
|
+
this.verificationPhrase = deriveVerificationPhrase(codebaseHash);
|
|
290
|
+
|
|
291
|
+
// ─── Layer 2: Machine Seed (unique, hardware-bound) ───
|
|
292
|
+
await this.machineSeed.init();
|
|
293
|
+
|
|
294
|
+
// ─── Legacy migration: detect old encrypted-private-key format ───
|
|
295
|
+
if (existsSync(this.keyPath)) {
|
|
296
|
+
const oldData = JSON.parse(readFileSync(this.keyPath, 'utf8'));
|
|
297
|
+
if (oldData.secretKeyEnc || oldData.secretKey) {
|
|
298
|
+
log.warn('═══════════════════════════════════════════════════════════');
|
|
299
|
+
log.warn('LEGACY IDENTITY DETECTED — migrating to two-layer system');
|
|
300
|
+
log.warn('Old format: encrypted private key on disk');
|
|
301
|
+
log.warn('New format: deterministic derivation from machine seed');
|
|
302
|
+
log.warn('Old node-key.json will be archived as node-key.legacy.json');
|
|
303
|
+
log.warn('═══════════════════════════════════════════════════════════');
|
|
304
|
+
const legacyPath = join(this.dataDir, 'node-key.legacy.json');
|
|
305
|
+
writeFileSync(legacyPath, JSON.stringify(oldData, null, 2));
|
|
306
|
+
unlinkSync(this.keyPath);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// ─── Derive keypair deterministically ───
|
|
311
|
+
const seed = this.machineSeed.getSeed();
|
|
312
|
+
const nodeSecret = deriveNodeSecret(seed, codebaseHash, this.verificationPhrase);
|
|
313
|
+
const keyPair = generateKeyPair(nodeSecret);
|
|
314
|
+
|
|
315
|
+
const publicKeyBytes = hexToBytes(keyPair.publicKey);
|
|
316
|
+
const nodeId = generateNodeId(publicKeyBytes, codebaseHash);
|
|
317
|
+
const pubKeyHash = bytesToHex(sha3_256(publicKeyBytes));
|
|
318
|
+
|
|
319
|
+
// ─── Derive SLH-DSA backup keypair deterministically ───
|
|
320
|
+
const backupSecret = deriveBackupSecret(seed, codebaseHash, this.verificationPhrase);
|
|
321
|
+
// SLH-DSA keygen uses the first 32 bytes as seed material
|
|
322
|
+
const backupKeyPair = generateBackupSignatureKeyPair(backupSecret);
|
|
323
|
+
const backupPublicKeyHex = bytesToHex(backupKeyPair.publicKey);
|
|
324
|
+
const backupSecretKeyHex = bytesToHex(backupKeyPair.secretKey);
|
|
325
|
+
|
|
326
|
+
// ─── Record migration entry ───
|
|
327
|
+
this.machineSeed.recordMigration(codebaseHash, pubKeyHash, this.networkName);
|
|
328
|
+
|
|
329
|
+
// ─── Build in-memory identity (private keys NEVER touch disk) ───
|
|
330
|
+
this.identity = {
|
|
331
|
+
nodeId,
|
|
332
|
+
networkName: this.networkName,
|
|
333
|
+
verificationPhrase: this.verificationPhrase,
|
|
334
|
+
codebaseHash,
|
|
335
|
+
name: nodeName,
|
|
336
|
+
region,
|
|
337
|
+
publicKey: keyPair.publicKey,
|
|
338
|
+
secretKey: keyPair.secretKey, // IN MEMORY ONLY — derived, not stored
|
|
339
|
+
backupPublicKey: backupPublicKeyHex,
|
|
340
|
+
backupSecretKey: backupSecretKeyHex, // IN MEMORY ONLY — derived, not stored
|
|
341
|
+
algorithm: keyPair.algorithm,
|
|
342
|
+
backupAlgorithm: 'SLH-DSA-SHA2-192f',
|
|
343
|
+
nistLevel: keyPair.nistLevel,
|
|
344
|
+
deterministic: true,
|
|
345
|
+
createdAt: Date.now(),
|
|
346
|
+
capabilities: ['listings', 'chat', 'forum', 'qcoa'],
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
// ─── Store PUBLIC identity to disk (no secrets!) ───
|
|
350
|
+
const toStore = {
|
|
351
|
+
nodeId,
|
|
352
|
+
networkName: this.networkName,
|
|
353
|
+
verificationPhrase: this.verificationPhrase,
|
|
354
|
+
codebaseHash,
|
|
355
|
+
name: nodeName,
|
|
356
|
+
region,
|
|
357
|
+
publicKey: keyPair.publicKey,
|
|
358
|
+
backupPublicKey: backupPublicKeyHex,
|
|
359
|
+
algorithm: keyPair.algorithm,
|
|
360
|
+
backupAlgorithm: 'SLH-DSA-SHA2-192f',
|
|
361
|
+
nistLevel: keyPair.nistLevel,
|
|
362
|
+
deterministic: true,
|
|
363
|
+
migrationEntries: this.machineSeed.getMigrationChain().length,
|
|
364
|
+
createdAt: Date.now(),
|
|
365
|
+
};
|
|
366
|
+
writeFileSync(this.keyPath, JSON.stringify(toStore, null, 2));
|
|
367
|
+
this._secureKeyFile();
|
|
368
|
+
|
|
369
|
+
// ─── Log identity info ───
|
|
370
|
+
const migrationChain = this.machineSeed.getMigrationChain();
|
|
371
|
+
const isNewNetwork = this.machineSeed.isFirstRun() || migrationChain.length <= 1;
|
|
372
|
+
const previousId = this.machineSeed.getPreviousIdentity();
|
|
373
|
+
|
|
374
|
+
log.info(isNewNetwork ? 'Generated new deterministic identity' : 'Derived identity for network version', {
|
|
375
|
+
nodeId,
|
|
376
|
+
network: this.networkName,
|
|
377
|
+
algorithm: 'ML-DSA-65 (FIPS 204, NIST Level 3)',
|
|
378
|
+
backupAlgorithm: 'SLH-DSA-SHA2-192f (FIPS 205)',
|
|
379
|
+
derivation: 'HKDF-SHA3-256(seed + oracleHash + verPhrase)',
|
|
380
|
+
privateKeysOnDisk: false,
|
|
381
|
+
migrationChainLength: migrationChain.length,
|
|
382
|
+
previousNetwork: previousId?.networkName || 'none (first run)',
|
|
383
|
+
verificationPhrase: this.verificationPhrase,
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
return this.identity;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get public identity (safe to share with other nodes)
|
|
391
|
+
* Includes network identity for peer verification
|
|
392
|
+
*/
|
|
393
|
+
getPublicIdentity() {
|
|
394
|
+
if (!this.identity) throw new Error('Identity not initialized');
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
nodeId: this.identity.nodeId,
|
|
398
|
+
persistentId: this.machineSeed.getPersistentId(), // Constant across upgrades
|
|
399
|
+
networkName: this.identity.networkName || this.networkName,
|
|
400
|
+
verificationPhrase: this.identity.verificationPhrase || this.verificationPhrase,
|
|
401
|
+
name: this.identity.name,
|
|
402
|
+
region: this.identity.region,
|
|
403
|
+
publicKey: this.identity.publicKey,
|
|
404
|
+
backupPublicKey: this.identity.backupPublicKey || null, // SLH-DSA (FIPS 205)
|
|
405
|
+
algorithm: this.identity.algorithm,
|
|
406
|
+
backupAlgorithm: this.identity.backupAlgorithm || null,
|
|
407
|
+
nistLevel: this.identity.nistLevel,
|
|
408
|
+
capabilities: this.identity.capabilities,
|
|
409
|
+
createdAt: this.identity.createdAt,
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get network identity info (for human verification)
|
|
415
|
+
* All nodes on the same network should have matching values
|
|
416
|
+
*/
|
|
417
|
+
getNetworkIdentity() {
|
|
418
|
+
return {
|
|
419
|
+
networkName: this.identity?.networkName || this.networkName,
|
|
420
|
+
verificationPhrase: this.identity?.verificationPhrase || this.verificationPhrase,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Get the migration chain — history of this seed's identities across network versions.
|
|
426
|
+
* @returns {Object[]} Array of { oracleHash, pubKeyHash, networkName, timestamp }
|
|
427
|
+
*/
|
|
428
|
+
getMigrationChain() {
|
|
429
|
+
return this.machineSeed.getMigrationChain();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get the persistent 144T machine identity.
|
|
434
|
+
* This ID is CONSTANT across all code upgrades — it identifies the
|
|
435
|
+
* physical machine/node owner regardless of network version.
|
|
436
|
+
*
|
|
437
|
+
* Use this to link reputation across network upgrades.
|
|
438
|
+
*
|
|
439
|
+
* @returns {string} Persistent 144T identifier like "yak-TT00TTT00:TTT00TTT0:..."
|
|
440
|
+
*/
|
|
441
|
+
getPersistentId() {
|
|
442
|
+
return this.machineSeed.getPersistentId();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Generate a migration proof that cryptographically links the current identity
|
|
447
|
+
* to the previous identity on the migration chain.
|
|
448
|
+
*
|
|
449
|
+
* Both signatures together prove the same physical entity (seed) controls
|
|
450
|
+
* both identities — without revealing the seed.
|
|
451
|
+
*
|
|
452
|
+
* Peers verify: ML-DSA-65.verify(sigOld, "MIGRATE:"+newPubKey, oldPubKey)
|
|
453
|
+
* ML-DSA-65.verify(sigNew, "MIGRATE:"+oldPubKey, newPubKey)
|
|
454
|
+
*
|
|
455
|
+
* @param {string} oldOracleHash - Oracle hash of the previous network version
|
|
456
|
+
* @param {string} oldVerPhrase - Verification phrase of the previous network
|
|
457
|
+
* @returns {{ oldPubKey: string, newPubKey: string, sigOld: string, sigNew: string, timestamp: number } | null}
|
|
458
|
+
*/
|
|
459
|
+
generateMigrationProof(oldOracleHash, oldVerPhrase) {
|
|
460
|
+
if (!this.identity) throw new Error('Identity not initialized');
|
|
461
|
+
|
|
462
|
+
const seed = this.machineSeed.getSeed();
|
|
463
|
+
|
|
464
|
+
// Derive the OLD keypair from the same seed + old oracle hash
|
|
465
|
+
const oldSecret = deriveNodeSecret(seed, oldOracleHash, oldVerPhrase);
|
|
466
|
+
const oldKeyPair = generateKeyPair(oldSecret);
|
|
467
|
+
|
|
468
|
+
const newPubKey = this.identity.publicKey;
|
|
469
|
+
const timestamp = Date.now();
|
|
470
|
+
|
|
471
|
+
// Sign with OLD key: "I (old identity) vouch for this new identity"
|
|
472
|
+
const migrateMsg1 = `MIGRATE:${newPubKey}:${timestamp}`;
|
|
473
|
+
const sigOld = signMessage(migrateMsg1, oldKeyPair.secretKey);
|
|
474
|
+
|
|
475
|
+
// Sign with NEW key: "I (new identity) claim this old identity"
|
|
476
|
+
const migrateMsg2 = `MIGRATE:${oldKeyPair.publicKey}:${timestamp}`;
|
|
477
|
+
const sigNew = signMessage(migrateMsg2, this.identity.secretKey);
|
|
478
|
+
|
|
479
|
+
return {
|
|
480
|
+
oldPubKey: oldKeyPair.publicKey,
|
|
481
|
+
newPubKey,
|
|
482
|
+
sigOld,
|
|
483
|
+
sigNew,
|
|
484
|
+
timestamp,
|
|
485
|
+
oldNetwork: deriveNetworkName(oldOracleHash, 3),
|
|
486
|
+
newNetwork: this.networkName,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Verify a migration proof from another node.
|
|
492
|
+
*
|
|
493
|
+
* @param {Object} proof - The migration proof object
|
|
494
|
+
* @returns {{ valid: boolean, oldValid: boolean, newValid: boolean }}
|
|
495
|
+
*/
|
|
496
|
+
static verifyMigrationProof(proof) {
|
|
497
|
+
const { oldPubKey, newPubKey, sigOld, sigNew, timestamp } = proof;
|
|
498
|
+
|
|
499
|
+
const migrateMsg1 = `MIGRATE:${newPubKey}:${timestamp}`;
|
|
500
|
+
const migrateMsg2 = `MIGRATE:${oldPubKey}:${timestamp}`;
|
|
501
|
+
|
|
502
|
+
const oldValid = verifySignature(migrateMsg1, sigOld, oldPubKey);
|
|
503
|
+
const newValid = verifySignature(migrateMsg2, sigNew, newPubKey);
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
valid: oldValid && newValid,
|
|
507
|
+
oldValid,
|
|
508
|
+
newValid,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* Sign a message with this node's identity
|
|
514
|
+
*/
|
|
515
|
+
sign(message) {
|
|
516
|
+
if (!this.identity) throw new Error('Identity not initialized');
|
|
517
|
+
return signMessage(message, this.identity.secretKey);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Verify a message signature from another node
|
|
522
|
+
*/
|
|
523
|
+
verify(message, signature, publicKey) {
|
|
524
|
+
return verifySignature(message, signature, publicKey);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* Sign a JSON object (for mesh protocol messages)
|
|
529
|
+
*/
|
|
530
|
+
signObject(obj) {
|
|
531
|
+
const payload = JSON.stringify(obj);
|
|
532
|
+
return {
|
|
533
|
+
...obj,
|
|
534
|
+
_signature: this.sign(payload),
|
|
535
|
+
_signer: this.identity.nodeId,
|
|
536
|
+
_signedAt: Date.now(),
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* Verify a signed object from another node
|
|
542
|
+
*/
|
|
543
|
+
verifyObject(signedObj, publicKey) {
|
|
544
|
+
const { _signature, _signer, _signedAt, ...obj } = signedObj;
|
|
545
|
+
const payload = JSON.stringify(obj);
|
|
546
|
+
return this.verify(payload, _signature, publicKey);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
550
|
+
// SLH-DSA Dual Signature — Defense-in-Depth for Critical Operations
|
|
551
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
552
|
+
//
|
|
553
|
+
// Critical ops (content publish, identity registration, relay registration)
|
|
554
|
+
// produce BOTH an ML-DSA-65 signature (fast, lattice-based) AND an SLH-DSA
|
|
555
|
+
// signature (slower ~100-160ms, hash-based). An attacker must break BOTH
|
|
556
|
+
// lattice AND hash assumptions to forge a critical signature.
|
|
557
|
+
//
|
|
558
|
+
// Non-critical ops (regular messages, peer auth) continue using ML-DSA-65
|
|
559
|
+
// only for performance.
|
|
560
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Check if this node has SLH-DSA dual-sig capability
|
|
564
|
+
* @returns {boolean}
|
|
565
|
+
*/
|
|
566
|
+
hasDualSignature() {
|
|
567
|
+
return !!(this.identity?.backupSecretKey && this.identity?.backupPublicKey);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Sign a critical message with BOTH ML-DSA-65 AND SLH-DSA (defense-in-depth)
|
|
572
|
+
*
|
|
573
|
+
* Use for: content publish, identity registration, relay registration,
|
|
574
|
+
* revocation certificates — any operation worth the ~100-160ms cost.
|
|
575
|
+
*
|
|
576
|
+
* @param {string} message - Message to sign (string)
|
|
577
|
+
* @returns {{ primary: string, backup: string }} Both signatures as hex strings
|
|
578
|
+
* @throws {Error} If backup key not available
|
|
579
|
+
*/
|
|
580
|
+
signCritical(message) {
|
|
581
|
+
if (!this.identity) throw new Error('Identity not initialized');
|
|
582
|
+
if (!this.identity.backupSecretKey) {
|
|
583
|
+
throw new Error('SLH-DSA backup key not available — cannot create dual signature');
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const messageBytes = typeof message === 'string'
|
|
587
|
+
? new TextEncoder().encode(message)
|
|
588
|
+
: message;
|
|
589
|
+
|
|
590
|
+
// ML-DSA-65 primary signature (fast, ~1ms)
|
|
591
|
+
const primarySig = this.sign(message);
|
|
592
|
+
|
|
593
|
+
// SLH-DSA backup signature (slower, ~100-160ms, but hash-based security)
|
|
594
|
+
const backupSecretKey = hexToBytes(this.identity.backupSecretKey);
|
|
595
|
+
const backupSig = slhDsaSign(messageBytes, backupSecretKey);
|
|
596
|
+
|
|
597
|
+
return {
|
|
598
|
+
primary: primarySig,
|
|
599
|
+
backup: bytesToHex(backupSig),
|
|
600
|
+
};
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Verify a dual signature from another node (BOTH must be valid)
|
|
605
|
+
*
|
|
606
|
+
* @param {string} message - Original message
|
|
607
|
+
* @param {string} primarySigHex - ML-DSA-65 signature (hex)
|
|
608
|
+
* @param {string} backupSigHex - SLH-DSA signature (hex)
|
|
609
|
+
* @param {string} primaryPKHex - ML-DSA-65 public key (hex)
|
|
610
|
+
* @param {string} backupPKHex - SLH-DSA public key (hex)
|
|
611
|
+
* @returns {{ valid: boolean, primaryValid: boolean, backupValid: boolean }}
|
|
612
|
+
*/
|
|
613
|
+
verifyCritical(message, primarySigHex, backupSigHex, primaryPKHex, backupPKHex) {
|
|
614
|
+
const messageBytes = typeof message === 'string'
|
|
615
|
+
? new TextEncoder().encode(message)
|
|
616
|
+
: message;
|
|
617
|
+
|
|
618
|
+
// Verify ML-DSA-65 (primary)
|
|
619
|
+
const primaryValid = this.verify(message, primarySigHex, primaryPKHex);
|
|
620
|
+
|
|
621
|
+
// Verify SLH-DSA (backup)
|
|
622
|
+
let backupValid = false;
|
|
623
|
+
try {
|
|
624
|
+
const backupSig = hexToBytes(backupSigHex);
|
|
625
|
+
const backupPK = hexToBytes(backupPKHex);
|
|
626
|
+
backupValid = slhDsaVerify(backupSig, messageBytes, backupPK);
|
|
627
|
+
} catch (e) {
|
|
628
|
+
log.error('SLH-DSA backup verification failed', { error: e.message });
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
valid: primaryValid && backupValid,
|
|
633
|
+
primaryValid,
|
|
634
|
+
backupValid,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Sign a JSON object with dual signature (for critical mesh protocol messages)
|
|
640
|
+
* @param {Object} obj - Object to sign
|
|
641
|
+
* @returns {Object} Object with _signature, _backupSignature, _signer, _signedAt
|
|
642
|
+
*/
|
|
643
|
+
signCriticalObject(obj) {
|
|
644
|
+
const payload = JSON.stringify(obj);
|
|
645
|
+
const sigs = this.signCritical(payload);
|
|
646
|
+
return {
|
|
647
|
+
...obj,
|
|
648
|
+
_signature: sigs.primary,
|
|
649
|
+
_backupSignature: sigs.backup,
|
|
650
|
+
_signer: this.identity.nodeId,
|
|
651
|
+
_signedAt: Date.now(),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Verify a dual-signed object from another node
|
|
657
|
+
* @param {Object} signedObj - Object with _signature and _backupSignature
|
|
658
|
+
* @param {string} primaryPK - ML-DSA-65 public key (hex)
|
|
659
|
+
* @param {string} backupPK - SLH-DSA public key (hex)
|
|
660
|
+
* @returns {{ valid: boolean, primaryValid: boolean, backupValid: boolean }}
|
|
661
|
+
*/
|
|
662
|
+
verifyCriticalObject(signedObj, primaryPK, backupPK) {
|
|
663
|
+
const { _signature, _backupSignature, _signer, _signedAt, ...obj } = signedObj;
|
|
664
|
+
const payload = JSON.stringify(obj);
|
|
665
|
+
return this.verifyCritical(payload, _signature, _backupSignature, primaryPK, backupPK);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
export default NodeIdentity;
|