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
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 = {
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
1140
|
-
|
|
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 =
|
|
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 =
|
|
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
|
}
|