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
@@ -9,10 +9,17 @@
9
9
  * @version 2.2.0
10
10
  */
11
11
 
12
- import { sha3_256 } from '@noble/hashes/sha3.js';
12
+ import { sha3_256 as _nobleSha3 } from '@noble/hashes/sha3.js';
13
13
  import { bytesToHex, hexToBytes } from '@noble/hashes/utils.js';
14
14
  import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
15
+ // ACCEL: Hardware-accelerated crypto
16
+ import { sha3_256, mlDsa65Sign, mlDsa65Verify } from '../utils/accel.js';
15
17
  import { createLogger } from '../utils/logger.js';
18
+ import { ternaryId } from '../utils/ternary-id.js';
19
+
20
+ // ═══ TRIBHUJ — Balanced ternary for validation verdicts ═══
21
+ // POSITIVE: check passed, NEUTRAL: check skipped/not applicable, NEGATIVE: check failed
22
+ import { POSITIVE, NEUTRAL, NEGATIVE } from '../oracle/tribhuj.js';
16
23
 
17
24
  const log = createLogger('security:doko');
18
25
 
@@ -53,16 +60,16 @@ export class DOKODocument {
53
60
  this.publicKey = options.publicKey || null;
54
61
  this.created = options.created || Date.now();
55
62
  this.expires = options.expires || (this.created + DEFAULT_EXPIRY_MS);
56
-
63
+
57
64
  // Claims - assertions about the identity
58
65
  this.claims = options.claims || {};
59
-
66
+
60
67
  // Extensions - optional capabilities and bindings
61
68
  this.extensions = options.extensions || {};
62
-
69
+
63
70
  // Endorsements - signed attestations from other DOKOs
64
71
  this.endorsements = options.endorsements || [];
65
-
72
+
66
73
  // Self-signature
67
74
  this.signature = options.signature || null;
68
75
  }
@@ -75,20 +82,20 @@ export class DOKODocument {
75
82
  * Example: doko-trader-qubit-lattice-pq-a7x9
76
83
  */
77
84
  static computeDokoId(publicKey, type = DOKO_TYPES.USER) {
78
- const keyBytes = typeof publicKey === 'string'
79
- ? hexToBytes(publicKey)
85
+ const keyBytes = typeof publicKey === 'string'
86
+ ? hexToBytes(publicKey)
80
87
  : publicKey;
81
-
88
+
82
89
  const hash = sha3_256(new Uint8Array([
83
90
  ...Buffer.from(type),
84
91
  ...keyBytes
85
92
  ]));
86
-
93
+
87
94
  // Use iO obfuscation - never expose raw hash!
88
95
  const hashHex = bytesToHex(hash);
89
96
  const obfuscatedName = deriveNetworkName(hashHex, 2); // 2 words for brevity
90
97
  const shortId = deriveNetworkId(hashHex);
91
-
98
+
92
99
  // Format: doko-<type>-<obfuscated-name>-<short-id>
93
100
  // e.g., "doko-trader-qubit-lattice-pq-a7x9"
94
101
  return `doko-${type}-${obfuscatedName}-${shortId}`;
@@ -110,7 +117,7 @@ export class DOKODocument {
110
117
  extensions: this.extensions,
111
118
  // Note: endorsements and signature are NOT included
112
119
  };
113
-
120
+
114
121
  // Use a replacer function that sorts keys at ALL levels
115
122
  const sortedJsonString = JSON.stringify(canonical, (key, value) => {
116
123
  if (value && typeof value === 'object' && !Array.isArray(value)) {
@@ -121,7 +128,7 @@ export class DOKODocument {
121
128
  }
122
129
  return value;
123
130
  });
124
-
131
+
125
132
  return Buffer.from(sortedJsonString);
126
133
  }
127
134
 
@@ -190,10 +197,10 @@ export class DOKOGenerator {
190
197
  // Generate ML-DSA-65 keypair
191
198
  const seed = options.seed || crypto.getRandomValues(new Uint8Array(32));
192
199
  const keyPair = ml_dsa65.keygen(seed);
193
-
200
+
194
201
  const type = options.type || DOKO_TYPES.USER;
195
202
  const publicKeyHex = bytesToHex(keyPair.publicKey);
196
-
203
+
197
204
  const doko = new DOKODocument({
198
205
  type,
199
206
  dokoId: DOKODocument.computeDokoId(keyPair.publicKey, type),
@@ -203,12 +210,12 @@ export class DOKOGenerator {
203
210
  claims: options.claims || {},
204
211
  extensions: options.extensions || {},
205
212
  });
206
-
213
+
207
214
  // Self-sign (ml_dsa65.sign takes message first, then secretKey)
208
215
  const signableBytes = doko.getSignableBytes();
209
- const signature = ml_dsa65.sign(signableBytes, keyPair.secretKey);
216
+ const signature = mlDsa65Sign(signableBytes, keyPair.secretKey);
210
217
  doko.signature = bytesToHex(signature);
211
-
218
+
212
219
  return {
213
220
  doko,
214
221
  publicKey: keyPair.publicKey,
@@ -222,16 +229,16 @@ export class DOKOGenerator {
222
229
  * Generate a DOKO from an existing keypair
223
230
  */
224
231
  static fromKeyPair(publicKey, secretKey, options = {}) {
225
- const publicKeyBytes = typeof publicKey === 'string'
226
- ? hexToBytes(publicKey)
232
+ const publicKeyBytes = typeof publicKey === 'string'
233
+ ? hexToBytes(publicKey)
227
234
  : publicKey;
228
235
  const secretKeyBytes = typeof secretKey === 'string'
229
236
  ? hexToBytes(secretKey)
230
237
  : secretKey;
231
-
238
+
232
239
  const type = options.type || DOKO_TYPES.USER;
233
240
  const publicKeyHex = bytesToHex(publicKeyBytes);
234
-
241
+
235
242
  const doko = new DOKODocument({
236
243
  type,
237
244
  dokoId: DOKODocument.computeDokoId(publicKeyBytes, type),
@@ -241,12 +248,12 @@ export class DOKOGenerator {
241
248
  claims: options.claims || {},
242
249
  extensions: options.extensions || {},
243
250
  });
244
-
251
+
245
252
  // Self-sign (ml_dsa65.sign takes message first, then secretKey)
246
253
  const signableBytes = doko.getSignableBytes();
247
- const signature = ml_dsa65.sign(signableBytes, secretKeyBytes);
254
+ const signature = mlDsa65Sign(signableBytes, secretKeyBytes);
248
255
  doko.signature = bytesToHex(signature);
249
-
256
+
250
257
  return doko;
251
258
  }
252
259
 
@@ -271,6 +278,36 @@ export class DOKOGenerator {
271
278
  });
272
279
  }
273
280
 
281
+ /**
282
+ * Generate a Node DOKO for mesh network nodes
283
+ * Includes persistentId144T for cross-upgrade identity continuity
284
+ *
285
+ * @param {Object} options
286
+ * @param {string} options.persistentId - 144T persistent machine identity
287
+ * @param {string} options.nodeId - Current network-specific node ID
288
+ * @param {string} options.networkName - Network name from oracle
289
+ * @param {Uint8Array} [options.seed] - Optional deterministic seed
290
+ */
291
+ static generateNode(options = {}) {
292
+ if (!options.persistentId) {
293
+ throw new Error('Node DOKO requires persistentId (144T persistent machine identity)');
294
+ }
295
+ return DOKOGenerator.generate({
296
+ ...options,
297
+ type: DOKO_TYPES.NODE,
298
+ claims: {
299
+ ...options.claims,
300
+ nodeId: options.nodeId || null,
301
+ networkName: options.networkName || null,
302
+ },
303
+ extensions: {
304
+ ...options.extensions,
305
+ persistentId144T: options.persistentId, // Constant across upgrades
306
+ capabilities: options.capabilities || ['mesh', 'gossip', 'relay'],
307
+ },
308
+ });
309
+ }
310
+
274
311
  /**
275
312
  * Generate a Merchant DOKO for verified businesses
276
313
  */
@@ -304,22 +341,22 @@ export class DOKOValidator {
304
341
  if (!doko.signature || !doko.publicKey) {
305
342
  return { valid: false, reason: 'MISSING_SIGNATURE_OR_KEY' };
306
343
  }
307
-
344
+
308
345
  try {
309
346
  const publicKey = typeof doko.publicKey === 'string'
310
347
  ? hexToBytes(doko.publicKey)
311
348
  : doko.publicKey;
312
-
349
+
313
350
  const signature = typeof doko.signature === 'string'
314
351
  ? hexToBytes(doko.signature)
315
352
  : doko.signature;
316
-
353
+
317
354
  const doc = doko instanceof DOKODocument ? doko : DOKODocument.fromJSON(doko);
318
355
  const signableBytes = doc.getSignableBytes();
319
-
356
+
320
357
  // ml_dsa65.verify takes (signature, message, publicKey)
321
- const valid = ml_dsa65.verify(signature, signableBytes, publicKey);
322
-
358
+ const valid = mlDsa65Verify(signature, signableBytes, publicKey);
359
+
323
360
  return {
324
361
  valid,
325
362
  reason: valid ? 'SIGNATURE_VALID' : 'SIGNATURE_INVALID',
@@ -339,7 +376,7 @@ export class DOKOValidator {
339
376
  static verifyDokoId(doko) {
340
377
  const expectedId = DOKODocument.computeDokoId(doko.publicKey, doko.type);
341
378
  const valid = doko.dokoId === expectedId;
342
-
379
+
343
380
  return {
344
381
  valid,
345
382
  reason: valid ? 'DOKO_ID_VALID' : 'DOKO_ID_MISMATCH',
@@ -350,62 +387,77 @@ export class DOKOValidator {
350
387
 
351
388
  /**
352
389
  * Full validation of a DOKO document
390
+ * Returns both boolean `valid` (backward compat) and trit `verdict`
391
+ * (POSITIVE/NEUTRAL/NEGATIVE) for ternary-aware consumers.
353
392
  */
354
393
  static validate(doko, options = {}) {
355
394
  const doc = doko instanceof DOKODocument ? doko : DOKODocument.fromJSON(doko);
356
395
  const results = {
357
396
  valid: true,
397
+ verdict: POSITIVE, // TRIBHUJ trit: POSITIVE=valid, NEUTRAL=skipped, NEGATIVE=failed
358
398
  checks: {},
359
399
  };
360
-
400
+
361
401
  // Check structure
362
402
  if (!doc.version || !doc.type || !doc.dokoId || !doc.publicKey) {
363
403
  results.valid = false;
364
- results.checks.structure = { valid: false, reason: 'MISSING_REQUIRED_FIELDS' };
404
+ results.verdict = NEGATIVE;
405
+ results.checks.structure = { valid: false, verdict: NEGATIVE, reason: 'MISSING_REQUIRED_FIELDS' };
365
406
  return results;
366
407
  }
367
- results.checks.structure = { valid: true, reason: 'STRUCTURE_VALID' };
368
-
408
+ results.checks.structure = { valid: true, verdict: POSITIVE, reason: 'STRUCTURE_VALID' };
409
+
369
410
  // Check version
370
411
  if (doc.version !== DOKO_VERSION) {
371
412
  results.valid = false;
372
- results.checks.version = { valid: false, reason: 'VERSION_MISMATCH', expected: DOKO_VERSION };
413
+ results.verdict = NEGATIVE;
414
+ results.checks.version = { valid: false, verdict: NEGATIVE, reason: 'VERSION_MISMATCH', expected: DOKO_VERSION };
373
415
  return results;
374
416
  }
375
- results.checks.version = { valid: true, reason: 'VERSION_VALID' };
376
-
417
+ results.checks.version = { valid: true, verdict: POSITIVE, reason: 'VERSION_VALID' };
418
+
377
419
  // Check type
378
420
  if (!Object.values(DOKO_TYPES).includes(doc.type)) {
379
421
  results.valid = false;
380
- results.checks.type = { valid: false, reason: 'INVALID_TYPE' };
422
+ results.verdict = NEGATIVE;
423
+ results.checks.type = { valid: false, verdict: NEGATIVE, reason: 'INVALID_TYPE' };
381
424
  return results;
382
425
  }
383
- results.checks.type = { valid: true, reason: 'TYPE_VALID' };
384
-
385
- // Check expiration
426
+ results.checks.type = { valid: true, verdict: POSITIVE, reason: 'TYPE_VALID' };
427
+
428
+ // Check expiration — NEUTRAL if skipped by options
386
429
  if (!options.allowExpired && doc.isExpired()) {
387
430
  results.valid = false;
388
- results.checks.expiration = { valid: false, reason: 'DOCUMENT_EXPIRED' };
431
+ results.verdict = NEGATIVE;
432
+ results.checks.expiration = { valid: false, verdict: NEGATIVE, reason: 'DOCUMENT_EXPIRED' };
389
433
  return results;
390
434
  }
391
- results.checks.expiration = { valid: true, reason: 'NOT_EXPIRED' };
392
-
435
+ results.checks.expiration = {
436
+ valid: true,
437
+ verdict: options.allowExpired ? NEUTRAL : POSITIVE, // NEUTRAL = check skipped
438
+ reason: options.allowExpired ? 'EXPIRY_CHECK_SKIPPED' : 'NOT_EXPIRED',
439
+ };
440
+
393
441
  // Verify DOKO ID
394
442
  const idCheck = DOKOValidator.verifyDokoId(doc);
443
+ idCheck.verdict = idCheck.valid ? POSITIVE : NEGATIVE;
395
444
  results.checks.dokoId = idCheck;
396
445
  if (!idCheck.valid) {
397
446
  results.valid = false;
447
+ results.verdict = NEGATIVE;
398
448
  return results;
399
449
  }
400
-
450
+
401
451
  // Verify signature
402
452
  const sigCheck = DOKOValidator.verifySignature(doc);
453
+ sigCheck.verdict = sigCheck.valid ? POSITIVE : NEGATIVE;
403
454
  results.checks.signature = sigCheck;
404
455
  if (!sigCheck.valid) {
405
456
  results.valid = false;
457
+ results.verdict = NEGATIVE;
406
458
  return results;
407
459
  }
408
-
460
+
409
461
  return results;
410
462
  }
411
463
  }
@@ -431,17 +483,17 @@ export class DOKOEndorsement {
431
483
  created: Date.now(),
432
484
  expires: Date.now() + DEFAULT_EXPIRY_MS,
433
485
  };
434
-
486
+
435
487
  // Sign the endorsement
436
488
  // IMPORTANT: ml_dsa65.sign(message, secretKey) - message FIRST!
437
489
  const endorsementBytes = Buffer.from(JSON.stringify(endorsement, Object.keys(endorsement).sort()));
438
490
  const secretKey = typeof endorserSecretKey === 'string'
439
491
  ? hexToBytes(endorserSecretKey)
440
492
  : endorserSecretKey;
441
-
442
- const signature = ml_dsa65.sign(endorsementBytes, secretKey);
493
+
494
+ const signature = mlDsa65Sign(endorsementBytes, secretKey);
443
495
  endorsement.signature = bytesToHex(signature);
444
-
496
+
445
497
  return endorsement;
446
498
  }
447
499
 
@@ -454,22 +506,22 @@ export class DOKOEndorsement {
454
506
  if (endorsement.targetDokoId !== targetDoko.dokoId) {
455
507
  return { valid: false, reason: 'TARGET_MISMATCH' };
456
508
  }
457
-
509
+
458
510
  // Check not expired
459
511
  if (Date.now() > endorsement.expires) {
460
512
  return { valid: false, reason: 'ENDORSEMENT_EXPIRED' };
461
513
  }
462
-
514
+
463
515
  // Verify signature
464
516
  // IMPORTANT: ml_dsa65.verify(signature, message, publicKey) - signature FIRST!
465
517
  const { signature, ...endorsementData } = endorsement;
466
518
  const endorsementBytes = Buffer.from(JSON.stringify(endorsementData, Object.keys(endorsementData).sort()));
467
-
519
+
468
520
  const publicKey = hexToBytes(endorsement.endorserPublicKey);
469
521
  const sigBytes = hexToBytes(signature);
470
-
471
- const valid = ml_dsa65.verify(sigBytes, endorsementBytes, publicKey);
472
-
522
+
523
+ const valid = mlDsa65Verify(sigBytes, endorsementBytes, publicKey);
524
+
473
525
  return {
474
526
  valid,
475
527
  reason: valid ? 'ENDORSEMENT_VALID' : 'SIGNATURE_INVALID',
@@ -504,7 +556,7 @@ export class DOKOCertBinding {
504
556
  */
505
557
  static computeFingerprint(cert) {
506
558
  let derBytes;
507
-
559
+
508
560
  if (typeof cert === 'string') {
509
561
  // PEM format - extract the base64 content
510
562
  const pemMatch = cert.match(/-----BEGIN CERTIFICATE-----\s*([\s\S]+?)\s*-----END CERTIFICATE-----/);
@@ -518,7 +570,7 @@ export class DOKOCertBinding {
518
570
  } else {
519
571
  derBytes = cert;
520
572
  }
521
-
573
+
522
574
  const hash = sha3_256(new Uint8Array(derBytes));
523
575
  return bytesToHex(hash);
524
576
  }
@@ -537,7 +589,7 @@ export class DOKOCertBinding {
537
589
  if (!options.domain || !options.fingerprint) {
538
590
  throw new Error('domain and fingerprint are required');
539
591
  }
540
-
592
+
541
593
  return {
542
594
  domain: options.domain.toLowerCase(),
543
595
  fingerprint: options.fingerprint,
@@ -559,16 +611,16 @@ export class DOKOCertBinding {
559
611
  if (!doko.extensions) {
560
612
  doko.extensions = {};
561
613
  }
562
-
614
+
563
615
  if (!doko.extensions.sslBindings) {
564
616
  doko.extensions.sslBindings = [];
565
617
  }
566
-
618
+
567
619
  // Check if binding for this domain already exists
568
620
  const existingIdx = doko.extensions.sslBindings.findIndex(
569
621
  b => b.domain === binding.domain
570
622
  );
571
-
623
+
572
624
  if (existingIdx >= 0) {
573
625
  // Update existing binding
574
626
  doko.extensions.sslBindings[existingIdx] = binding;
@@ -576,10 +628,10 @@ export class DOKOCertBinding {
576
628
  // Add new binding
577
629
  doko.extensions.sslBindings.push(binding);
578
630
  }
579
-
631
+
580
632
  // Invalidate signature - document needs re-signing
581
633
  doko.signature = null;
582
-
634
+
583
635
  return doko;
584
636
  }
585
637
 
@@ -592,7 +644,7 @@ export class DOKOCertBinding {
592
644
  static verifyBinding(binding, cert) {
593
645
  const fingerprint = this.computeFingerprint(cert);
594
646
  const matches = fingerprint === binding.fingerprint;
595
-
647
+
596
648
  return {
597
649
  valid: matches,
598
650
  reason: matches ? 'FINGERPRINT_MATCH' : 'FINGERPRINT_MISMATCH',
@@ -632,17 +684,17 @@ export class DOKOCertBinding {
632
684
  if (!doko?.extensions?.sslBindings) {
633
685
  return false;
634
686
  }
635
-
687
+
636
688
  const initialLen = doko.extensions.sslBindings.length;
637
689
  doko.extensions.sslBindings = doko.extensions.sslBindings.filter(
638
690
  b => b.domain !== domain.toLowerCase()
639
691
  );
640
-
692
+
641
693
  if (doko.extensions.sslBindings.length < initialLen) {
642
694
  doko.signature = null; // Needs re-signing
643
695
  return true;
644
696
  }
645
-
697
+
646
698
  return false;
647
699
  }
648
700
 
@@ -658,30 +710,30 @@ export class DOKOCertBinding {
658
710
  count: bindings.length,
659
711
  bindings: [],
660
712
  };
661
-
713
+
662
714
  for (const binding of bindings) {
663
715
  const now = Date.now();
664
716
  const isExpired = binding.validTo && binding.validTo < now;
665
717
  const isNotYetValid = binding.validFrom && binding.validFrom > now;
666
-
718
+
667
719
  const result = {
668
720
  domain: binding.domain,
669
721
  fingerprint: binding.fingerprint.substring(0, 16) + '...',
670
722
  valid: !isExpired && !isNotYetValid,
671
723
  verified: binding.verified,
672
- reason: isExpired
673
- ? 'CERTIFICATE_EXPIRED'
674
- : isNotYetValid
675
- ? 'CERTIFICATE_NOT_YET_VALID'
724
+ reason: isExpired
725
+ ? 'CERTIFICATE_EXPIRED'
726
+ : isNotYetValid
727
+ ? 'CERTIFICATE_NOT_YET_VALID'
676
728
  : 'VALID',
677
729
  };
678
-
730
+
679
731
  results.bindings.push(result);
680
732
  if (!result.valid) {
681
733
  results.valid = false;
682
734
  }
683
735
  }
684
-
736
+
685
737
  return results;
686
738
  }
687
739
  }
@@ -740,7 +792,7 @@ export class DOKOTransfer {
740
792
 
741
793
  const now = Date.now();
742
794
  const expiresIn = options.expiresIn || 7 * 24 * 60 * 60 * 1000; // 7 days default
743
-
795
+
744
796
  const request = {
745
797
  version: '1.0',
746
798
  type: options.type,
@@ -763,10 +815,10 @@ export class DOKOTransfer {
763
815
  toDoko: request.toDoko,
764
816
  requestedAt: request.requestedAt,
765
817
  });
766
-
818
+
767
819
  const hash = sha3_256(Buffer.from(canonical));
768
820
  request.requestId = 'xfer-' + bytesToHex(hash).substring(0, 16);
769
-
821
+
770
822
  return request;
771
823
  }
772
824
 
@@ -881,7 +933,7 @@ export class DOKOTransfer {
881
933
 
882
934
  // Use ML-DSA-65 verification (already imported at top of file)
883
935
  // IMPORTANT: ml_dsa65.verify(signature, message, publicKey) - signature FIRST!
884
- const isValid = ml_dsa65.verify(signature, message, publicKey);
936
+ const isValid = mlDsa65Verify(signature, message, publicKey);
885
937
 
886
938
  return {
887
939
  valid: isValid,
@@ -909,7 +961,7 @@ export class DOKOTransfer {
909
961
  }
910
962
 
911
963
  const completedAt = Date.now();
912
-
964
+
913
965
  // Create transfer proof
914
966
  const proofData = JSON.stringify({
915
967
  requestId: transfer.requestId,
@@ -920,7 +972,7 @@ export class DOKOTransfer {
920
972
  authorization: transfer.authorization,
921
973
  completedAt,
922
974
  });
923
-
975
+
924
976
  const proofHash = sha3_256(Buffer.from(proofData));
925
977
 
926
978
  return {
@@ -996,7 +1048,7 @@ export class DOKOTransfer {
996
1048
  authorization: { ...proof.authorization, fromNodeId: undefined },
997
1049
  completedAt: proof.completion.completedAt,
998
1050
  });
999
-
1051
+
1000
1052
  // Note: Full proof verification would require the original authorization.fromNodeId
1001
1053
  checks.push({ check: 'proofHash', valid: true, reason: 'HASH_PRESENT' });
1002
1054
  }
@@ -1044,10 +1096,10 @@ export const REVOCATION_REASONS = {
1044
1096
  */
1045
1097
  export class DOKORevocation {
1046
1098
  static REVOCATION_STORE_KEY = 'doko-revocations';
1047
-
1099
+
1048
1100
  // In-memory revocation cache
1049
1101
  static _revocations = new Map(); // dokoId -> revocation certificate
1050
-
1102
+
1051
1103
  /**
1052
1104
  * Create a self-revocation certificate
1053
1105
  * Used when you still have access to the private key
@@ -1060,7 +1112,7 @@ export class DOKORevocation {
1060
1112
  */
1061
1113
  static createSelfRevocation(doko, privateKey, reason, options = {}) {
1062
1114
  const revokedAt = Date.now();
1063
-
1115
+
1064
1116
  const revocationData = {
1065
1117
  version: '1.0',
1066
1118
  type: 'self',
@@ -1071,25 +1123,25 @@ export class DOKORevocation {
1071
1123
  message: options.message || null,
1072
1124
  successorDokoId: options.successorDokoId || null, // New DOKO replacing this one
1073
1125
  };
1074
-
1126
+
1075
1127
  // Create canonical bytes for signing
1076
1128
  const dataBytes = new TextEncoder().encode(JSON.stringify(revocationData));
1077
-
1129
+
1078
1130
  // Sign with ML-DSA (message first, then secretKey)
1079
- const signature = ml_dsa65.sign(dataBytes, privateKey);
1080
-
1131
+ const signature = mlDsa65Sign(dataBytes, privateKey);
1132
+
1081
1133
  const certificate = {
1082
1134
  ...revocationData,
1083
1135
  signature: bytesToHex(signature),
1084
1136
  signatureAlgorithm: 'ML-DSA-65',
1085
1137
  };
1086
-
1138
+
1087
1139
  // Store locally
1088
1140
  DOKORevocation._revocations.set(doko.dokoId, certificate);
1089
-
1141
+
1090
1142
  return certificate;
1091
1143
  }
1092
-
1144
+
1093
1145
  /**
1094
1146
  * Create an emergency revocation using a pre-generated certificate
1095
1147
  *
@@ -1103,9 +1155,9 @@ export class DOKORevocation {
1103
1155
  if (!preGeneratedCert || !preGeneratedCert.emergencyToken) {
1104
1156
  throw new Error('Invalid emergency certificate');
1105
1157
  }
1106
-
1158
+
1107
1159
  const activatedAt = Date.now();
1108
-
1160
+
1109
1161
  const certificate = {
1110
1162
  version: '1.0',
1111
1163
  type: 'emergency',
@@ -1117,13 +1169,13 @@ export class DOKORevocation {
1117
1169
  signature: preGeneratedCert.signature,
1118
1170
  signatureAlgorithm: 'ML-DSA-65',
1119
1171
  };
1120
-
1172
+
1121
1173
  // Store locally
1122
1174
  DOKORevocation._revocations.set(certificate.dokoId, certificate);
1123
-
1175
+
1124
1176
  return certificate;
1125
1177
  }
1126
-
1178
+
1127
1179
  /**
1128
1180
  * Generate an emergency revocation certificate for future use
1129
1181
  * STORE THIS SECURELY OFFLINE!
@@ -1134,22 +1186,20 @@ export class DOKORevocation {
1134
1186
  */
1135
1187
  static generateEmergencyCertificate(doko, privateKey) {
1136
1188
  const createdAt = Date.now();
1137
-
1138
- // Generate random emergency token
1139
- const randomBytes = new Uint8Array(32);
1140
- crypto.getRandomValues(randomBytes);
1141
- const emergencyToken = bytesToHex(randomBytes);
1142
-
1189
+
1190
+ // Generate random emergency token (balanced ternary — '666' impossible)
1191
+ const emergencyToken = ternaryId(32);
1192
+
1143
1193
  const certData = {
1144
1194
  dokoId: doko.dokoId,
1145
1195
  createdAt,
1146
1196
  emergencyToken,
1147
1197
  };
1148
-
1198
+
1149
1199
  // Sign the emergency cert (message first, then secretKey)
1150
1200
  const dataBytes = new TextEncoder().encode(JSON.stringify(certData));
1151
- const signature = ml_dsa65.sign(dataBytes, privateKey);
1152
-
1201
+ const signature = mlDsa65Sign(dataBytes, privateKey);
1202
+
1153
1203
  return {
1154
1204
  ...certData,
1155
1205
  signature: bytesToHex(signature),
@@ -1157,7 +1207,7 @@ export class DOKORevocation {
1157
1207
  _warning: 'STORE THIS OFFLINE AND SECURELY! This is your break-glass recovery option.',
1158
1208
  };
1159
1209
  }
1160
-
1210
+
1161
1211
  /**
1162
1212
  * Verify a revocation certificate
1163
1213
  *
@@ -1170,31 +1220,31 @@ export class DOKORevocation {
1170
1220
  if (!certificate || !certificate.signature) {
1171
1221
  return { valid: false, reason: 'MISSING_SIGNATURE' };
1172
1222
  }
1173
-
1223
+
1174
1224
  // Extract signature
1175
1225
  const signature = hexToBytes(certificate.signature);
1176
-
1226
+
1177
1227
  // Reconstruct signable data
1178
1228
  const certCopy = { ...certificate };
1179
1229
  delete certCopy.signature;
1180
1230
  delete certCopy.signatureAlgorithm;
1181
-
1231
+
1182
1232
  const dataBytes = new TextEncoder().encode(JSON.stringify(certCopy));
1183
1233
  const pubKeyBytes = hexToBytes(publicKey);
1184
-
1234
+
1185
1235
  // Verify with ML-DSA (signature, message, publicKey)
1186
- const isValid = ml_dsa65.verify(signature, dataBytes, pubKeyBytes);
1187
-
1236
+ const isValid = mlDsa65Verify(signature, dataBytes, pubKeyBytes);
1237
+
1188
1238
  if (!isValid) {
1189
1239
  return { valid: false, reason: 'INVALID_SIGNATURE' };
1190
1240
  }
1191
-
1241
+
1192
1242
  return { valid: true, reason: null };
1193
1243
  } catch (e) {
1194
1244
  return { valid: false, reason: e.message };
1195
1245
  }
1196
1246
  }
1197
-
1247
+
1198
1248
  /**
1199
1249
  * Check if a DOKO is revoked
1200
1250
  *
@@ -1203,7 +1253,7 @@ export class DOKORevocation {
1203
1253
  */
1204
1254
  static isRevoked(dokoId) {
1205
1255
  const cert = DOKORevocation._revocations.get(dokoId);
1206
-
1256
+
1207
1257
  if (cert) {
1208
1258
  return {
1209
1259
  revoked: true,
@@ -1212,10 +1262,10 @@ export class DOKORevocation {
1212
1262
  revokedAt: cert.revokedAt || cert.activatedAt,
1213
1263
  };
1214
1264
  }
1215
-
1265
+
1216
1266
  return { revoked: false, certificate: null, reason: null };
1217
1267
  }
1218
-
1268
+
1219
1269
  /**
1220
1270
  * Add a revocation certificate (from gossip/sync)
1221
1271
  *
@@ -1226,17 +1276,17 @@ export class DOKORevocation {
1226
1276
  static addRevocation(certificate, publicKey) {
1227
1277
  // Verify the certificate
1228
1278
  const verification = DOKORevocation.verify(certificate, publicKey);
1229
-
1279
+
1230
1280
  if (!verification.valid) {
1231
1281
  return { success: false, reason: verification.reason };
1232
1282
  }
1233
-
1283
+
1234
1284
  // Store the revocation
1235
1285
  DOKORevocation._revocations.set(certificate.dokoId, certificate);
1236
-
1286
+
1237
1287
  return { success: true, reason: null };
1238
1288
  }
1239
-
1289
+
1240
1290
  /**
1241
1291
  * List all revocations
1242
1292
  *
@@ -1245,7 +1295,7 @@ export class DOKORevocation {
1245
1295
  static listRevocations() {
1246
1296
  return Array.from(DOKORevocation._revocations.values());
1247
1297
  }
1248
-
1298
+
1249
1299
  /**
1250
1300
  * Export revocations for sync/backup
1251
1301
  *
@@ -1254,7 +1304,7 @@ export class DOKORevocation {
1254
1304
  static export() {
1255
1305
  return DOKORevocation.listRevocations();
1256
1306
  }
1257
-
1307
+
1258
1308
  /**
1259
1309
  * Import revocations (with verification)
1260
1310
  *
@@ -1265,17 +1315,17 @@ export class DOKORevocation {
1265
1315
  static import(certificates, publicKeyMap) {
1266
1316
  let imported = 0;
1267
1317
  let failed = 0;
1268
-
1318
+
1269
1319
  for (const cert of certificates) {
1270
- const publicKey = publicKeyMap instanceof Map
1320
+ const publicKey = publicKeyMap instanceof Map
1271
1321
  ? publicKeyMap.get(cert.dokoId)
1272
1322
  : publicKeyMap[cert.dokoId];
1273
-
1323
+
1274
1324
  if (!publicKey) {
1275
1325
  failed++;
1276
1326
  continue;
1277
1327
  }
1278
-
1328
+
1279
1329
  const result = DOKORevocation.addRevocation(cert, publicKey);
1280
1330
  if (result.success) {
1281
1331
  imported++;
@@ -1283,29 +1333,29 @@ export class DOKORevocation {
1283
1333
  failed++;
1284
1334
  }
1285
1335
  }
1286
-
1336
+
1287
1337
  return { imported, failed };
1288
1338
  }
1289
-
1339
+
1290
1340
  /**
1291
1341
  * Clear all revocations (for testing)
1292
1342
  */
1293
1343
  static _clear() {
1294
1344
  DOKORevocation._revocations.clear();
1295
1345
  }
1296
-
1346
+
1297
1347
  /**
1298
1348
  * Get revocation statistics
1299
1349
  */
1300
1350
  static getStats() {
1301
1351
  const byReason = {};
1302
1352
  const byType = {};
1303
-
1353
+
1304
1354
  for (const cert of DOKORevocation._revocations.values()) {
1305
1355
  byReason[cert.reason] = (byReason[cert.reason] || 0) + 1;
1306
1356
  byType[cert.type] = (byType[cert.type] || 0) + 1;
1307
1357
  }
1308
-
1358
+
1309
1359
  return {
1310
1360
  total: DOKORevocation._revocations.size,
1311
1361
  byReason,
@@ -1335,17 +1385,17 @@ export class DOKOStore {
1335
1385
  return { success: false, error: 'VALIDATION_FAILED', details: validation };
1336
1386
  }
1337
1387
  }
1338
-
1388
+
1339
1389
  const doc = doko instanceof DOKODocument ? doko : DOKODocument.fromJSON(doko);
1340
-
1390
+
1341
1391
  this.documents.set(doc.dokoId, doc);
1342
1392
  this.byPublicKey.set(doc.publicKey, doc.dokoId);
1343
-
1393
+
1344
1394
  // Index by userId if present (PeerQuanta integration)
1345
1395
  if (doc.claims?.userId) {
1346
1396
  this.byUserId.set(doc.claims.userId, doc.dokoId);
1347
1397
  }
1348
-
1398
+
1349
1399
  return { success: true, dokoId: doc.dokoId };
1350
1400
  }
1351
1401
 
@@ -1423,11 +1473,11 @@ export class DOKOStore {
1423
1473
  for (const type of Object.values(DOKO_TYPES)) {
1424
1474
  byType[type] = 0;
1425
1475
  }
1426
-
1476
+
1427
1477
  for (const doc of this.documents.values()) {
1428
1478
  byType[doc.type] = (byType[doc.type] || 0) + 1;
1429
1479
  }
1430
-
1480
+
1431
1481
  return {
1432
1482
  total: this.documents.size,
1433
1483
  byType,
@@ -1447,7 +1497,7 @@ export class DOKOStore {
1447
1497
  import(dokos, options = {}) {
1448
1498
  let imported = 0;
1449
1499
  let failed = 0;
1450
-
1500
+
1451
1501
  for (const doko of dokos) {
1452
1502
  const result = this.add(doko, options);
1453
1503
  if (result.success) {
@@ -1456,7 +1506,7 @@ export class DOKOStore {
1456
1506
  failed++;
1457
1507
  }
1458
1508
  }
1459
-
1509
+
1460
1510
  return { imported, failed };
1461
1511
  }
1462
1512
  }