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
|
@@ -1,1071 +1,1111 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ╔═══════════════════════════════════════════════════════════════════════════════╗
|
|
3
|
-
* ║ YAKMESH VALIDATION ORACLE - HARDENED ║
|
|
4
|
-
* ║ ║
|
|
5
|
-
* ║ ⚠️ CRITICAL SECURITY MODULE - DO NOT MODIFY WITHOUT REVIEW ⚠️ ║
|
|
6
|
-
* ║ ║
|
|
7
|
-
* ║ This module is the cryptographic foundation of network identity. ║
|
|
8
|
-
* ║ ANY change to this file will change the codebase hash, which will: ║
|
|
9
|
-
* ║ - Create a new network (nodes won't peer with old network) ║
|
|
10
|
-
* ║ - Invalidate all existing node identities ║
|
|
11
|
-
* ║ - Require coordinated deployment across all nodes ║
|
|
12
|
-
* ║ ║
|
|
13
|
-
* ║ STABILITY REQUIREMENTS: ║
|
|
14
|
-
* ║ 1. All paths normalized to forward slashes (cross-platform) ║
|
|
15
|
-
* ║ 2. Deterministic file ordering (localeCompare sort) ║
|
|
16
|
-
* ║ 3. Consistent hash algorithm (SHA3-256) ║
|
|
17
|
-
* ║ 4. Frozen singleton pattern (no runtime modification) ║
|
|
18
|
-
* ║ ║
|
|
19
|
-
* ║ BEFORE MODIFYING: ║
|
|
20
|
-
* ║ - Document the change in CHANGELOG.md ║
|
|
21
|
-
* ║ - Coordinate with all node operators ║
|
|
22
|
-
* ║ - Plan network migration strategy ║
|
|
23
|
-
* ║ - Update version number below ║
|
|
24
|
-
* ║ ║
|
|
25
|
-
* ║ Last verified: 2026-01-19 | Version: 1.2.0-hardened ║
|
|
26
|
-
* ╚═══════════════════════════════════════════════════════════════════════════════╝
|
|
27
|
-
*
|
|
28
|
-
* PeerQuanta Validation Oracle - HARDENED VERSION
|
|
29
|
-
*
|
|
30
|
-
* Security-hardened self-verifying oracle with protection against:
|
|
31
|
-
* - Runtime tampering (Object.freeze, prototype sealing)
|
|
32
|
-
* - Prototype pollution (null prototype objects)
|
|
33
|
-
* - Race conditions (validation locking)
|
|
34
|
-
* - Edge case inputs (comprehensive validation)
|
|
35
|
-
*
|
|
36
|
-
* @module ValidationOracle
|
|
37
|
-
* @version 1.2.0-hardened
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
import { sha3_256, sha3_512 } from '@noble/hashes/sha3.js';
|
|
41
|
-
import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils.js';
|
|
42
|
-
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
43
|
-
|
|
44
|
-
import {
|
|
45
|
-
import {
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// ============================================================
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
* @param {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
this.#state = new Trit(state);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this.#
|
|
135
|
-
this.#
|
|
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
|
-
get
|
|
165
|
-
|
|
166
|
-
get
|
|
167
|
-
get
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
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
|
-
if (
|
|
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
|
-
this
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
MODULE_SEAL.
|
|
376
|
-
MODULE_SEAL.
|
|
377
|
-
MODULE_SEAL.
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
406
|
-
*
|
|
407
|
-
*
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
this.
|
|
434
|
-
this.
|
|
435
|
-
this.
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
'
|
|
453
|
-
'
|
|
454
|
-
'
|
|
455
|
-
'
|
|
456
|
-
'
|
|
457
|
-
'
|
|
458
|
-
'
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
'
|
|
464
|
-
'
|
|
465
|
-
'
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
'
|
|
471
|
-
'
|
|
472
|
-
'
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
return
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
* SECURITY:
|
|
777
|
-
*/
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
return ValidationResult.failure('
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
//
|
|
832
|
-
const
|
|
833
|
-
if (!
|
|
834
|
-
return ValidationResult.failure('
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
if (
|
|
890
|
-
return ValidationResult.failure('
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
//
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
return ValidationResult.
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
if (
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
//
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ╔═══════════════════════════════════════════════════════════════════════════════╗
|
|
3
|
+
* ║ YAKMESH VALIDATION ORACLE - HARDENED ║
|
|
4
|
+
* ║ ║
|
|
5
|
+
* ║ ⚠️ CRITICAL SECURITY MODULE - DO NOT MODIFY WITHOUT REVIEW ⚠️ ║
|
|
6
|
+
* ║ ║
|
|
7
|
+
* ║ This module is the cryptographic foundation of network identity. ║
|
|
8
|
+
* ║ ANY change to this file will change the codebase hash, which will: ║
|
|
9
|
+
* ║ - Create a new network (nodes won't peer with old network) ║
|
|
10
|
+
* ║ - Invalidate all existing node identities ║
|
|
11
|
+
* ║ - Require coordinated deployment across all nodes ║
|
|
12
|
+
* ║ ║
|
|
13
|
+
* ║ STABILITY REQUIREMENTS: ║
|
|
14
|
+
* ║ 1. All paths normalized to forward slashes (cross-platform) ║
|
|
15
|
+
* ║ 2. Deterministic file ordering (localeCompare sort) ║
|
|
16
|
+
* ║ 3. Consistent hash algorithm (SHA3-256) ║
|
|
17
|
+
* ║ 4. Frozen singleton pattern (no runtime modification) ║
|
|
18
|
+
* ║ ║
|
|
19
|
+
* ║ BEFORE MODIFYING: ║
|
|
20
|
+
* ║ - Document the change in CHANGELOG.md ║
|
|
21
|
+
* ║ - Coordinate with all node operators ║
|
|
22
|
+
* ║ - Plan network migration strategy ║
|
|
23
|
+
* ║ - Update version number below ║
|
|
24
|
+
* ║ ║
|
|
25
|
+
* ║ Last verified: 2026-01-19 | Version: 1.2.0-hardened ║
|
|
26
|
+
* ╚═══════════════════════════════════════════════════════════════════════════════╝
|
|
27
|
+
*
|
|
28
|
+
* PeerQuanta Validation Oracle - HARDENED VERSION
|
|
29
|
+
*
|
|
30
|
+
* Security-hardened self-verifying oracle with protection against:
|
|
31
|
+
* - Runtime tampering (Object.freeze, prototype sealing)
|
|
32
|
+
* - Prototype pollution (null prototype objects)
|
|
33
|
+
* - Race conditions (validation locking)
|
|
34
|
+
* - Edge case inputs (comprehensive validation)
|
|
35
|
+
*
|
|
36
|
+
* @module ValidationOracle
|
|
37
|
+
* @version 1.2.0-hardened
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { sha3_256 as _nobleSha3, sha3_512 } from '@noble/hashes/sha3.js';
|
|
41
|
+
import { bytesToHex, hexToBytes, utf8ToBytes } from '@noble/hashes/utils.js';
|
|
42
|
+
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa.js';
|
|
43
|
+
// ACCEL: Hardware-accelerated crypto
|
|
44
|
+
import { sha3_256, mlDsa65Verify } from '../utils/accel.js';
|
|
45
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
46
|
+
import { fileURLToPath } from 'url';
|
|
47
|
+
import { dirname, join } from 'path';
|
|
48
|
+
import { createLogger } from '../utils/logger.js';
|
|
49
|
+
import { Trit, TritState, POSITIVE, NEUTRAL, NEGATIVE } from './tribhuj.js';
|
|
50
|
+
|
|
51
|
+
const log = createLogger('oracle:validation');
|
|
52
|
+
|
|
53
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
54
|
+
const __dirname = dirname(__filename);
|
|
55
|
+
|
|
56
|
+
// ============================================================
|
|
57
|
+
// SECURITY: Create objects with null prototype to prevent pollution
|
|
58
|
+
// ============================================================
|
|
59
|
+
const createSafeObject = (obj = {}) => Object.assign(Object.create(null), obj);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* SECURITY: Deep freeze an object to prevent modification
|
|
63
|
+
*/
|
|
64
|
+
function deepFreeze(obj) {
|
|
65
|
+
if (obj === null || typeof obj !== 'object') return obj;
|
|
66
|
+
|
|
67
|
+
Object.getOwnPropertyNames(obj).forEach(prop => {
|
|
68
|
+
const val = obj[prop];
|
|
69
|
+
if (val && typeof val === 'object') {
|
|
70
|
+
deepFreeze(val);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return Object.freeze(obj);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* SECURITY: Safe property access that prevents prototype pollution
|
|
79
|
+
*/
|
|
80
|
+
function safeGet(obj, key) {
|
|
81
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Oracle Module Seal - Cryptographic binding of code identity
|
|
89
|
+
* SECURITY: Uses null prototype and is frozen after initialization
|
|
90
|
+
*/
|
|
91
|
+
const MODULE_SEAL = createSafeObject({
|
|
92
|
+
version: '1.1.0-hardened',
|
|
93
|
+
sourceHash: null,
|
|
94
|
+
behaviorFingerprint: null,
|
|
95
|
+
genesisTimestamp: null,
|
|
96
|
+
functionHashes: null,
|
|
97
|
+
frozen: false,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validation Result - Immutable return type for all validations
|
|
102
|
+
*
|
|
103
|
+
* Now uses TERNARY logic (TRIBHUJ):
|
|
104
|
+
* VALID (+1) = Definitively valid, can propagate
|
|
105
|
+
* INVALID (-1) = Definitively invalid, reject
|
|
106
|
+
* PENDING (0) = Indeterminate, awaiting consensus/propagation
|
|
107
|
+
*
|
|
108
|
+
* The PENDING state prevents "flapping" in distributed consensus:
|
|
109
|
+
* nodes can acknowledge receipt without committing to validity.
|
|
110
|
+
*/
|
|
111
|
+
export class ValidationResult {
|
|
112
|
+
#state; // Trit: VALID(+1), INVALID(-1), PENDING(0)
|
|
113
|
+
#reason;
|
|
114
|
+
#proof;
|
|
115
|
+
#timestamp;
|
|
116
|
+
#oracleVersion;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {number|Trit} state - Ternary state: +1 (valid), -1 (invalid), 0 (pending)
|
|
120
|
+
* @param {string|null} reason - Reason for invalid/pending state
|
|
121
|
+
* @param {object|null} proof - Cryptographic proof
|
|
122
|
+
*/
|
|
123
|
+
constructor(state, reason = null, proof = null) {
|
|
124
|
+
// Accept Trit, number, or boolean (backwards compat)
|
|
125
|
+
if (state instanceof Trit) {
|
|
126
|
+
this.#state = state;
|
|
127
|
+
} else if (typeof state === 'boolean') {
|
|
128
|
+
// BACKWARDS COMPAT: true → VALID, false → INVALID
|
|
129
|
+
this.#state = new Trit(state ? POSITIVE : NEGATIVE);
|
|
130
|
+
} else {
|
|
131
|
+
this.#state = new Trit(state);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.#reason = reason;
|
|
135
|
+
this.#proof = proof ? deepFreeze({ ...proof }) : null;
|
|
136
|
+
this.#timestamp = Date.now();
|
|
137
|
+
this.#oracleVersion = MODULE_SEAL.version;
|
|
138
|
+
|
|
139
|
+
// SECURITY: Freeze the instance
|
|
140
|
+
Object.freeze(this);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
144
|
+
// Ternary State Accessors
|
|
145
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
/** The ternary state as a Trit */
|
|
148
|
+
get state() { return this.#state; }
|
|
149
|
+
|
|
150
|
+
/** Is this definitively VALID? (+1) */
|
|
151
|
+
get isValid() { return this.#state.isPositive; }
|
|
152
|
+
|
|
153
|
+
/** Is this definitively INVALID? (-1) */
|
|
154
|
+
get isInvalid() { return this.#state.isNegative; }
|
|
155
|
+
|
|
156
|
+
/** Is this PENDING/indeterminate? (0) */
|
|
157
|
+
get isPending() { return this.#state.isNeutral; }
|
|
158
|
+
|
|
159
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
160
|
+
// Backwards Compatibility (boolean interface)
|
|
161
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/** @deprecated Use isValid instead. Returns true only for VALID state. */
|
|
164
|
+
get valid() { return this.#state.isPositive; }
|
|
165
|
+
|
|
166
|
+
get reason() { return this.#reason; }
|
|
167
|
+
get proof() { return this.#proof; }
|
|
168
|
+
get timestamp() { return this.#timestamp; }
|
|
169
|
+
get oracleVersion() { return this.#oracleVersion; }
|
|
170
|
+
|
|
171
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
172
|
+
// Static Constructors
|
|
173
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
/** Create a VALID (+1) result */
|
|
176
|
+
static success(proof = null) {
|
|
177
|
+
return new ValidationResult(POSITIVE, null, proof);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Create an INVALID (-1) result */
|
|
181
|
+
static failure(reason) {
|
|
182
|
+
return new ValidationResult(NEGATIVE, reason, null);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Create a PENDING (0) result - awaiting consensus/propagation */
|
|
186
|
+
static pending(reason = 'AWAITING_CONSENSUS', proof = null) {
|
|
187
|
+
return new ValidationResult(NEUTRAL, reason, proof);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
191
|
+
// Ternary Logic Operations
|
|
192
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Combine two validation results using ternary AND.
|
|
196
|
+
* Both must be VALID for result to be VALID.
|
|
197
|
+
* If either is INVALID, result is INVALID.
|
|
198
|
+
* Otherwise PENDING.
|
|
199
|
+
*/
|
|
200
|
+
and(other) {
|
|
201
|
+
const newState = this.#state.and(other.state);
|
|
202
|
+
const reason = this.#reason || other.reason;
|
|
203
|
+
const proof = this.#proof || other.proof;
|
|
204
|
+
return new ValidationResult(newState, reason, proof);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Combine two validation results using ternary OR.
|
|
209
|
+
* Either being VALID makes result VALID.
|
|
210
|
+
* Both must be INVALID for result to be INVALID.
|
|
211
|
+
* Otherwise PENDING.
|
|
212
|
+
*/
|
|
213
|
+
or(other) {
|
|
214
|
+
const newState = this.#state.or(other.state);
|
|
215
|
+
const reason = this.isValid ? null : (this.#reason || other.reason);
|
|
216
|
+
const proof = this.#proof || other.proof;
|
|
217
|
+
return new ValidationResult(newState, reason, proof);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Consensus operation: agree on validity.
|
|
222
|
+
* If both agree (same state), return that state.
|
|
223
|
+
* If they disagree, return PENDING.
|
|
224
|
+
*/
|
|
225
|
+
consensus(other) {
|
|
226
|
+
const newState = this.#state.consensus(other.state);
|
|
227
|
+
const reason = newState.isNeutral ? 'CONSENSUS_DISAGREEMENT' : this.#reason;
|
|
228
|
+
return new ValidationResult(newState, reason, this.#proof);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
232
|
+
// Serialization
|
|
233
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
234
|
+
|
|
235
|
+
toJSON() {
|
|
236
|
+
return deepFreeze({
|
|
237
|
+
state: this.#state.value, // -1, 0, or +1
|
|
238
|
+
valid: this.#state.isPositive, // Backwards compat
|
|
239
|
+
isValid: this.#state.isPositive,
|
|
240
|
+
isInvalid: this.#state.isNegative,
|
|
241
|
+
isPending: this.#state.isNeutral,
|
|
242
|
+
reason: this.#reason,
|
|
243
|
+
proof: this.#proof,
|
|
244
|
+
timestamp: this.#timestamp,
|
|
245
|
+
oracleVersion: this.#oracleVersion,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
toString() {
|
|
250
|
+
const stateStr = this.isValid ? 'VALID' : (this.isInvalid ? 'INVALID' : 'PENDING');
|
|
251
|
+
return `ValidationResult(${stateStr}${this.#reason ? ': ' + this.#reason : ''})`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Content Hash - Deterministic hashing of any content
|
|
257
|
+
*/
|
|
258
|
+
export function contentHash(data) {
|
|
259
|
+
if (typeof data === 'string') {
|
|
260
|
+
return bytesToHex(sha3_256(utf8ToBytes(data)));
|
|
261
|
+
}
|
|
262
|
+
if (data instanceof Uint8Array) {
|
|
263
|
+
return bytesToHex(sha3_256(data));
|
|
264
|
+
}
|
|
265
|
+
// For objects, use deterministic JSON serialization
|
|
266
|
+
return bytesToHex(sha3_256(utf8ToBytes(deterministicStringify(data))));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Deterministic JSON stringify - Guarantees same output for same input
|
|
271
|
+
* SECURITY: Ignores __proto__, constructor, prototype keys
|
|
272
|
+
*/
|
|
273
|
+
export function deterministicStringify(obj, seen = new WeakSet()) {
|
|
274
|
+
if (obj === null || obj === undefined) return 'null';
|
|
275
|
+
if (typeof obj === 'boolean' || typeof obj === 'number') return String(obj);
|
|
276
|
+
if (typeof obj === 'string') return JSON.stringify(obj);
|
|
277
|
+
|
|
278
|
+
// SECURITY: Detect circular references
|
|
279
|
+
if (typeof obj === 'object') {
|
|
280
|
+
if (seen.has(obj)) {
|
|
281
|
+
return '"[Circular]"';
|
|
282
|
+
}
|
|
283
|
+
seen.add(obj);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (Array.isArray(obj)) {
|
|
287
|
+
return '[' + obj.map(item => deterministicStringify(item, seen)).join(',') + ']';
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (typeof obj === 'object') {
|
|
291
|
+
// SECURITY: Filter dangerous keys and use hasOwnProperty
|
|
292
|
+
const keys = Object.keys(obj)
|
|
293
|
+
.filter(k => k !== '__proto__' && k !== 'constructor' && k !== 'prototype')
|
|
294
|
+
.filter(k => Object.prototype.hasOwnProperty.call(obj, k))
|
|
295
|
+
.sort();
|
|
296
|
+
const pairs = keys.map(k => JSON.stringify(k) + ':' + deterministicStringify(obj[k], seen));
|
|
297
|
+
return '{' + pairs.join(',') + '}';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return String(obj);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* SECURITY: Validation lock to prevent race conditions
|
|
305
|
+
*/
|
|
306
|
+
class ValidationLock {
|
|
307
|
+
#locks = new Map();
|
|
308
|
+
#maxConcurrent = 100;
|
|
309
|
+
|
|
310
|
+
async acquire(key) {
|
|
311
|
+
if (this.#locks.size >= this.#maxConcurrent) {
|
|
312
|
+
throw new Error('MAX_CONCURRENT_VALIDATIONS');
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
while (this.#locks.has(key)) {
|
|
316
|
+
await new Promise(resolve => setTimeout(resolve, 1));
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
this.#locks.set(key, Date.now());
|
|
320
|
+
return () => this.#locks.delete(key);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
isLocked(key) {
|
|
324
|
+
return this.#locks.has(key);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* The Validation Oracle - HARDENED VERSION
|
|
330
|
+
* Self-verifying, deterministic, tamper-resistant validation engine
|
|
331
|
+
*/
|
|
332
|
+
export class ValidationOracle {
|
|
333
|
+
// SECURITY: Private fields cannot be accessed externally
|
|
334
|
+
#initialized = false;
|
|
335
|
+
#selfHash = null;
|
|
336
|
+
#functionRegistry = new Map();
|
|
337
|
+
#testVectors = [];
|
|
338
|
+
#behaviorFingerprint = null;
|
|
339
|
+
#validationLock = new ValidationLock();
|
|
340
|
+
#submissionCache = new Map();
|
|
341
|
+
#frozen = false;
|
|
342
|
+
|
|
343
|
+
// SECURITY: Rate limiting
|
|
344
|
+
#rateLimits = new Map();
|
|
345
|
+
#maxRequestsPerSecond = 100;
|
|
346
|
+
|
|
347
|
+
constructor() {
|
|
348
|
+
// SECURITY: Prevent prototype pollution on this instance
|
|
349
|
+
Object.setPrototypeOf(this, ValidationOracle.prototype);
|
|
350
|
+
|
|
351
|
+
this.#initialize();
|
|
352
|
+
|
|
353
|
+
// SECURITY: Freeze the prototype to prevent method modification
|
|
354
|
+
Object.freeze(ValidationOracle.prototype);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Initialize the oracle and verify its own integrity
|
|
359
|
+
*/
|
|
360
|
+
#initialize() {
|
|
361
|
+
log.info('Initializing Validation Oracle (HARDENED)');
|
|
362
|
+
|
|
363
|
+
// 1. Compute hash of our own source code
|
|
364
|
+
this.#selfHash = this.#computeSelfHash();
|
|
365
|
+
log.debug('Self-hash computed', { hash: this.#selfHash.slice(0, 16) });
|
|
366
|
+
|
|
367
|
+
// 2. Register all validation functions with their hashes
|
|
368
|
+
this.#registerFunctions();
|
|
369
|
+
|
|
370
|
+
// 3. Generate behavior fingerprint from test vectors
|
|
371
|
+
this.#generateBehaviorFingerprint();
|
|
372
|
+
|
|
373
|
+
// 4. Seal the module (only once - first instance wins)
|
|
374
|
+
if (!MODULE_SEAL.frozen) {
|
|
375
|
+
MODULE_SEAL.sourceHash = this.#selfHash;
|
|
376
|
+
MODULE_SEAL.functionHashes = Object.fromEntries(this.#functionRegistry);
|
|
377
|
+
MODULE_SEAL.behaviorFingerprint = this.#behaviorFingerprint;
|
|
378
|
+
MODULE_SEAL.genesisTimestamp = Date.now();
|
|
379
|
+
MODULE_SEAL.frozen = true;
|
|
380
|
+
|
|
381
|
+
// SECURITY: Freeze MODULE_SEAL
|
|
382
|
+
deepFreeze(MODULE_SEAL);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
this.#initialized = true;
|
|
386
|
+
log.info('Validation Oracle initialized and sealed (HARDENED)');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* SECURITY: Freeze the oracle instance after initialization
|
|
391
|
+
* Once frozen, no modifications are possible
|
|
392
|
+
*/
|
|
393
|
+
freeze() {
|
|
394
|
+
if (this.#frozen) return;
|
|
395
|
+
this.#frozen = true;
|
|
396
|
+
Object.freeze(this);
|
|
397
|
+
log.info('Oracle instance frozen - no further modifications possible');
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Compute hash of all critical source files
|
|
402
|
+
* This ensures nodes with different codebases cannot peer
|
|
403
|
+
*
|
|
404
|
+
* SECURITY: Hashes the ENTIRE codebase, not just selected files.
|
|
405
|
+
* Any modification to ANY source file will produce a different hash,
|
|
406
|
+
* making it impossible for nodes with different code to communicate.
|
|
407
|
+
*
|
|
408
|
+
* This is the core of the Code Proof Protocol: mathematical certainty
|
|
409
|
+
* that all peering nodes run identical code.
|
|
410
|
+
*/
|
|
411
|
+
#computeSelfHash() {
|
|
412
|
+
try {
|
|
413
|
+
// Get the root directory (parent of oracle/)
|
|
414
|
+
const rootDir = join(__dirname, '..');
|
|
415
|
+
|
|
416
|
+
// Collect ALL source files recursively
|
|
417
|
+
const allSources = [];
|
|
418
|
+
this.#walkDirectory(rootDir, allSources);
|
|
419
|
+
|
|
420
|
+
// Sort for deterministic ordering across all platforms
|
|
421
|
+
allSources.sort((a, b) => a.path.localeCompare(b.path));
|
|
422
|
+
|
|
423
|
+
// Compute hash of entire codebase
|
|
424
|
+
const codebaseContent = allSources
|
|
425
|
+
.map(f => `=== ${f.path} ===\n${f.content}`)
|
|
426
|
+
.join('\n');
|
|
427
|
+
|
|
428
|
+
return contentHash(codebaseContent);
|
|
429
|
+
} catch (e) {
|
|
430
|
+
log.error('Failed to hash codebase', { error: e.message });
|
|
431
|
+
// Fallback: hash the function definitions
|
|
432
|
+
const functionSources = [
|
|
433
|
+
this.validateListing.toString(),
|
|
434
|
+
this.validateUser.toString(),
|
|
435
|
+
this.validateSignature.toString(),
|
|
436
|
+
this.validateQCoA.toString(),
|
|
437
|
+
this.resolveConflict.toString(),
|
|
438
|
+
];
|
|
439
|
+
return contentHash(functionSources.join('\n'));
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Recursively walk directory and collect all source files
|
|
445
|
+
* @private
|
|
446
|
+
*/
|
|
447
|
+
#walkDirectory(dir, results, baseDir = null) {
|
|
448
|
+
if (!baseDir) baseDir = dir;
|
|
449
|
+
|
|
450
|
+
// Directories to EXCLUDE from hash (not part of codebase logic)
|
|
451
|
+
const EXCLUDE_DIRS = [
|
|
452
|
+
'node_modules', // Dependencies (version-locked via package-lock.json)
|
|
453
|
+
'.git', // Git metadata
|
|
454
|
+
'.github', // CI/CD configs
|
|
455
|
+
'data', // Runtime data
|
|
456
|
+
'database', // User data
|
|
457
|
+
'logs', // Runtime logs
|
|
458
|
+
'models', // ONNX model binaries + manifest (not codebase logic)
|
|
459
|
+
'.vscode', // Editor config
|
|
460
|
+
'coverage', // Test coverage
|
|
461
|
+
'dist', // Build output
|
|
462
|
+
'build', // Build output
|
|
463
|
+
'tests', // Test suites — not runtime code
|
|
464
|
+
'test-nodes', // Test node configurations
|
|
465
|
+
'deploy-packages', // Build system output + scripts
|
|
466
|
+
'deploy', // Deploy configs (Caddyfile, systemd units)
|
|
467
|
+
'scripts', // Dev/build scripts
|
|
468
|
+
'docs', // Documentation
|
|
469
|
+
'website', // Marketing site
|
|
470
|
+
'marketing', // Marketing materials
|
|
471
|
+
'announcements', // Discord/Telegram/X announcements
|
|
472
|
+
'assets', // Static assets
|
|
473
|
+
'types', // TypeScript definitions
|
|
474
|
+
'shortcuts', // Shell shortcuts
|
|
475
|
+
'memory-bank', // AI context docs
|
|
476
|
+
'yakbot', // Separate bot package (own node_modules)
|
|
477
|
+
'hostinger', // Hostinger-specific configs
|
|
478
|
+
'cli', // CLI tool — not core runtime
|
|
479
|
+
'dashboard', // Web dashboard UI assets
|
|
480
|
+
'templates', // Template files
|
|
481
|
+
'examples', // Example files
|
|
482
|
+
];
|
|
483
|
+
|
|
484
|
+
// File extensions that ARE part of the codebase
|
|
485
|
+
const SOURCE_EXTENSIONS = [
|
|
486
|
+
'.js', '.mjs', '.cjs', // JavaScript
|
|
487
|
+
'.json', // Config (package.json matters!)
|
|
488
|
+
'.ts', '.tsx', // TypeScript (if any)
|
|
489
|
+
];
|
|
490
|
+
|
|
491
|
+
// Files to explicitly EXCLUDE
|
|
492
|
+
const EXCLUDE_FILES = [
|
|
493
|
+
'package-lock.json', // Too volatile, deps locked by package.json
|
|
494
|
+
'.env', // Environment-specific
|
|
495
|
+
'.env.local',
|
|
496
|
+
'vitest.config.js', // Test runner config
|
|
497
|
+
'knowledge-base.js', // Dev knowledge base
|
|
498
|
+
'update-docs-nav.cjs', // Dev script
|
|
499
|
+
'convert-tests.cjs', // Dev utility
|
|
500
|
+
];
|
|
501
|
+
|
|
502
|
+
// Filename prefix patterns to EXCLUDE (dev/test/audit scripts)
|
|
503
|
+
const EXCLUDE_PREFIXES = [
|
|
504
|
+
'test-', // Test scripts (test-multinode.mjs, test-crypto.mjs, etc.)
|
|
505
|
+
'audit-', // Audit scripts
|
|
506
|
+
'verify-', // Verification scripts
|
|
507
|
+
];
|
|
508
|
+
|
|
509
|
+
try {
|
|
510
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
511
|
+
|
|
512
|
+
for (const entry of entries) {
|
|
513
|
+
const fullPath = join(dir, entry.name);
|
|
514
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
515
|
+
// CRITICAL: Normalize ALL paths to forward slashes for cross-platform
|
|
516
|
+
// consistency. Without this, Windows (\) and Linux (/) produce different
|
|
517
|
+
// hashes for identical codebases, causing network fragmentation.
|
|
518
|
+
// DO NOT CHANGE THIS LINE without understanding the consequences.
|
|
519
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
520
|
+
const relativePath = fullPath.replace(baseDir, '').replace(/^[/\\]/, '').replace(/\\/g, '/');
|
|
521
|
+
|
|
522
|
+
if (entry.isDirectory()) {
|
|
523
|
+
// Skip excluded directories
|
|
524
|
+
if (EXCLUDE_DIRS.includes(entry.name)) continue;
|
|
525
|
+
// Skip all data-* directories (test data, runtime state)
|
|
526
|
+
if (entry.name.startsWith('data-') || entry.name.startsWith('data_')) continue;
|
|
527
|
+
// Recurse into subdirectory
|
|
528
|
+
this.#walkDirectory(fullPath, results, baseDir);
|
|
529
|
+
} else if (entry.isFile()) {
|
|
530
|
+
// Skip excluded files
|
|
531
|
+
if (EXCLUDE_FILES.includes(entry.name)) continue;
|
|
532
|
+
// Skip test files (*.test.js, *.spec.js, etc.)
|
|
533
|
+
if (/\.(test|spec)\.(js|mjs|cjs)$/.test(entry.name)) continue;
|
|
534
|
+
// Skip dev/test/audit prefix scripts
|
|
535
|
+
if (EXCLUDE_PREFIXES.some(p => entry.name.startsWith(p))) continue;
|
|
536
|
+
|
|
537
|
+
// Check extension
|
|
538
|
+
const ext = entry.name.slice(entry.name.lastIndexOf('.'));
|
|
539
|
+
if (!SOURCE_EXTENSIONS.includes(ext)) continue;
|
|
540
|
+
|
|
541
|
+
// Read and store file content
|
|
542
|
+
try {
|
|
543
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
544
|
+
results.push({ path: relativePath, content });
|
|
545
|
+
} catch (readErr) {
|
|
546
|
+
// Include read errors in hash (missing file = different hash)
|
|
547
|
+
results.push({ path: relativePath, content: `ERROR: ${readErr.message}` });
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} catch (dirErr) {
|
|
552
|
+
log.warn('Cannot read directory', { dir, error: dirErr.message });
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Register validation functions with their source hashes
|
|
558
|
+
*/
|
|
559
|
+
#registerFunctions() {
|
|
560
|
+
const functions = [
|
|
561
|
+
['validateListing', this.validateListing],
|
|
562
|
+
['validateUser', this.validateUser],
|
|
563
|
+
['validateSignature', this.validateSignature],
|
|
564
|
+
['validateQCoA', this.validateQCoA],
|
|
565
|
+
['resolveConflict', this.resolveConflict],
|
|
566
|
+
['computeTrustScore', this.computeTrustScore],
|
|
567
|
+
];
|
|
568
|
+
|
|
569
|
+
for (const [name, fn] of functions) {
|
|
570
|
+
const fnHash = contentHash(fn.toString());
|
|
571
|
+
this.#functionRegistry.set(name, fnHash);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Generate behavior fingerprint from test vectors
|
|
577
|
+
*/
|
|
578
|
+
#generateBehaviorFingerprint() {
|
|
579
|
+
this.#testVectors = [
|
|
580
|
+
{
|
|
581
|
+
fn: 'validateListing',
|
|
582
|
+
input: { title: 'Test', price: 100, currency: 'BTC', user_id: 1 },
|
|
583
|
+
expectedValid: true,
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
fn: 'validateListing',
|
|
587
|
+
input: { title: '', price: 100, currency: 'BTC', user_id: 1 },
|
|
588
|
+
expectedValid: false,
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
fn: 'validateListing',
|
|
592
|
+
input: { title: 'Test', price: -1, currency: 'BTC', user_id: 1 },
|
|
593
|
+
expectedValid: false,
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
fn: 'validateListing',
|
|
597
|
+
input: { title: 'Test', price: 0.0001, currency: 'BTC', user_id: 1 },
|
|
598
|
+
expectedValid: true,
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
fn: 'validateListing',
|
|
602
|
+
input: { title: 'Test', price: Number.MAX_SAFE_INTEGER, currency: 'BTC', user_id: 1 },
|
|
603
|
+
expectedValid: true,
|
|
604
|
+
},
|
|
605
|
+
];
|
|
606
|
+
|
|
607
|
+
const outputs = this.#testVectors.map(tv => {
|
|
608
|
+
if (tv.fn === 'validateListing') {
|
|
609
|
+
return this.validateListing(tv.input).valid;
|
|
610
|
+
}
|
|
611
|
+
return null;
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
this.#behaviorFingerprint = contentHash(outputs);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// ============================================================
|
|
618
|
+
// PUBLIC GETTERS (no setters - immutable)
|
|
619
|
+
// ============================================================
|
|
620
|
+
|
|
621
|
+
get selfHash() {
|
|
622
|
+
return this.#selfHash;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
get behaviorFingerprint() {
|
|
626
|
+
return this.#behaviorFingerprint;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
get isInitialized() {
|
|
630
|
+
return this.#initialized;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
get isFrozen() {
|
|
634
|
+
return this.#frozen;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ============================================================
|
|
638
|
+
// SELF-VERIFICATION METHODS
|
|
639
|
+
// ============================================================
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Verify own integrity - call this periodically
|
|
643
|
+
*/
|
|
644
|
+
verifySelfIntegrity() {
|
|
645
|
+
const currentHash = this.#computeSelfHash();
|
|
646
|
+
|
|
647
|
+
if (currentHash !== this.#selfHash) {
|
|
648
|
+
throw new Error(`INTEGRITY VIOLATION: Code has been modified! ` +
|
|
649
|
+
`Expected ${this.#selfHash}, got ${currentHash}`);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Also verify behavior fingerprint
|
|
653
|
+
const currentBehavior = this.#testVectors.map(tv => {
|
|
654
|
+
if (tv.fn === 'validateListing') {
|
|
655
|
+
return this.validateListing(tv.input).valid;
|
|
656
|
+
}
|
|
657
|
+
return null;
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
const currentFingerprint = contentHash(currentBehavior);
|
|
661
|
+
if (currentFingerprint !== this.#behaviorFingerprint) {
|
|
662
|
+
throw new Error(`BEHAVIOR VIOLATION: Validation logic has changed!`);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
return ValidationResult.success({ selfHash: this.#selfHash });
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Generate a proof that we're running the correct code
|
|
670
|
+
*/
|
|
671
|
+
generateCodeProof(challenge = null) {
|
|
672
|
+
const effectiveChallenge = challenge || this.#selfHash;
|
|
673
|
+
const proofInput = effectiveChallenge + this.#selfHash + this.#behaviorFingerprint;
|
|
674
|
+
const response = contentHash(proofInput);
|
|
675
|
+
|
|
676
|
+
return deepFreeze({
|
|
677
|
+
oracleVersion: MODULE_SEAL.version,
|
|
678
|
+
selfHash: this.#selfHash,
|
|
679
|
+
behaviorFingerprint: this.#behaviorFingerprint,
|
|
680
|
+
challenge: effectiveChallenge,
|
|
681
|
+
response: response,
|
|
682
|
+
functionHashes: Object.fromEntries(this.#functionRegistry),
|
|
683
|
+
timestamp: Date.now(),
|
|
684
|
+
version: MODULE_SEAL.version,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Verify another node's code proof
|
|
690
|
+
*/
|
|
691
|
+
verifyCodeProof(proof) {
|
|
692
|
+
if (!proof || typeof proof !== 'object') {
|
|
693
|
+
return ValidationResult.failure('INVALID_PROOF_FORMAT');
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (proof.selfHash !== this.#selfHash) {
|
|
697
|
+
return ValidationResult.failure('SELF_HASH_MISMATCH');
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (proof.behaviorFingerprint !== this.#behaviorFingerprint) {
|
|
701
|
+
return ValidationResult.failure('BEHAVIOR_FINGERPRINT_MISMATCH');
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const expectedResponse = contentHash(
|
|
705
|
+
proof.challenge + this.#selfHash + this.#behaviorFingerprint
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
if (proof.response !== expectedResponse) {
|
|
709
|
+
return ValidationResult.failure('CHALLENGE_RESPONSE_INVALID');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
for (const [name, hash] of this.#functionRegistry) {
|
|
713
|
+
if (safeGet(proof.functionHashes, name) !== hash) {
|
|
714
|
+
return ValidationResult.failure(`FUNCTION_HASH_MISMATCH: ${name}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
return ValidationResult.success({ verified: true, peerHash: proof.selfHash });
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Get the module seal - public identity of this oracle
|
|
723
|
+
*/
|
|
724
|
+
getModuleSeal() {
|
|
725
|
+
return { ...MODULE_SEAL };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
getValidationMethods() {
|
|
729
|
+
return ['listing', 'qcoa', 'user'];
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async validate(contentType, content) {
|
|
733
|
+
// SECURITY: Rate limiting
|
|
734
|
+
const now = Date.now();
|
|
735
|
+
const key = `validate:${contentType}`;
|
|
736
|
+
const lastRequest = this.#rateLimits.get(key) || 0;
|
|
737
|
+
|
|
738
|
+
if (now - lastRequest < 1000 / this.#maxRequestsPerSecond) {
|
|
739
|
+
return { valid: false, errors: ['RATE_LIMITED'] };
|
|
740
|
+
}
|
|
741
|
+
this.#rateLimits.set(key, now);
|
|
742
|
+
|
|
743
|
+
let validationResult;
|
|
744
|
+
|
|
745
|
+
switch (contentType) {
|
|
746
|
+
case 'listing':
|
|
747
|
+
validationResult = await this.validateListingAsync(content);
|
|
748
|
+
break;
|
|
749
|
+
case 'qcoa':
|
|
750
|
+
validationResult = this.validateQCoA(content);
|
|
751
|
+
break;
|
|
752
|
+
case 'user':
|
|
753
|
+
validationResult = this.validateUser(content);
|
|
754
|
+
break;
|
|
755
|
+
default:
|
|
756
|
+
return { valid: false, errors: [`Unknown content type: ${contentType}`] };
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
if (!validationResult.valid) {
|
|
760
|
+
return { valid: false, errors: [validationResult.reason] };
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return deepFreeze({
|
|
764
|
+
valid: true,
|
|
765
|
+
contentHash: contentHash(content),
|
|
766
|
+
validatorHash: this.#selfHash,
|
|
767
|
+
validatedAt: Date.now(),
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// ============================================================
|
|
772
|
+
// VALIDATION FUNCTIONS - Pure, deterministic, tamper-resistant
|
|
773
|
+
// ============================================================
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* SECURITY: Async validation with locking to prevent race conditions
|
|
777
|
+
*/
|
|
778
|
+
async validateListingAsync(listing) {
|
|
779
|
+
if (!listing) {
|
|
780
|
+
return ValidationResult.failure('LISTING_NULL');
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const lockKey = listing.id || contentHash(listing);
|
|
784
|
+
const release = await this.#validationLock.acquire(lockKey);
|
|
785
|
+
|
|
786
|
+
try {
|
|
787
|
+
// Check for duplicate submission
|
|
788
|
+
const listingHash = contentHash(listing);
|
|
789
|
+
if (this.#submissionCache.has(listingHash)) {
|
|
790
|
+
return ValidationResult.failure('DUPLICATE_SUBMISSION');
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const result = this.validateListing(listing);
|
|
794
|
+
|
|
795
|
+
if (result.valid) {
|
|
796
|
+
// Cache successful validations to prevent duplicates
|
|
797
|
+
this.#submissionCache.set(listingHash, Date.now());
|
|
798
|
+
|
|
799
|
+
// Cleanup old cache entries (older than 1 hour)
|
|
800
|
+
const oneHourAgo = Date.now() - 3600000;
|
|
801
|
+
for (const [hash, time] of this.#submissionCache) {
|
|
802
|
+
if (time < oneHourAgo) {
|
|
803
|
+
this.#submissionCache.delete(hash);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
return result;
|
|
809
|
+
} finally {
|
|
810
|
+
release();
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/**
|
|
815
|
+
* Validate a marketplace listing
|
|
816
|
+
* SECURITY: Comprehensive input validation
|
|
817
|
+
*/
|
|
818
|
+
validateListing(listing) {
|
|
819
|
+
// Required fields check
|
|
820
|
+
if (!listing || typeof listing !== 'object') {
|
|
821
|
+
return ValidationResult.failure('LISTING_NULL');
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
// SECURITY: Prevent prototype pollution - check own properties only
|
|
825
|
+
if (Object.prototype.hasOwnProperty.call(listing, '__proto__') ||
|
|
826
|
+
Object.prototype.hasOwnProperty.call(listing, 'constructor') ||
|
|
827
|
+
Object.prototype.hasOwnProperty.call(listing, 'prototype')) {
|
|
828
|
+
return ValidationResult.failure('INVALID_LISTING_STRUCTURE');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// Title validation
|
|
832
|
+
const title = safeGet(listing, 'title');
|
|
833
|
+
if (!title || typeof title !== 'string') {
|
|
834
|
+
return ValidationResult.failure('INVALID_TITLE');
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (title.length === 0 || title.length > 200) {
|
|
838
|
+
return ValidationResult.failure('TITLE_LENGTH_INVALID');
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// SECURITY: Check for control characters in title
|
|
842
|
+
if (/[\x00-\x1F\x7F]/.test(title)) {
|
|
843
|
+
return ValidationResult.failure('INVALID_TITLE_CHARS');
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Price validation - HARDENED
|
|
847
|
+
const price = safeGet(listing, 'price');
|
|
848
|
+
if (typeof price !== 'number') {
|
|
849
|
+
return ValidationResult.failure('INVALID_PRICE_TYPE');
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// SECURITY: Comprehensive price validation
|
|
853
|
+
if (!Number.isFinite(price)) {
|
|
854
|
+
return ValidationResult.failure('INVALID_PRICE_INFINITE');
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
if (price <= 0) {
|
|
858
|
+
return ValidationResult.failure('INVALID_PRICE_NEGATIVE');
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// SECURITY: Minimum price (prevent dust attacks)
|
|
862
|
+
if (price < 0.00000001) {
|
|
863
|
+
return ValidationResult.failure('PRICE_TOO_SMALL');
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// SECURITY: Maximum price (prevent overflow)
|
|
867
|
+
if (price > Number.MAX_SAFE_INTEGER) {
|
|
868
|
+
return ValidationResult.failure('PRICE_TOO_LARGE');
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Currency validation
|
|
872
|
+
const currency = safeGet(listing, 'currency');
|
|
873
|
+
if (!currency || typeof currency !== 'string') {
|
|
874
|
+
return ValidationResult.failure('INVALID_CURRENCY');
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// SECURITY: Strict currency validation (alphanumeric only, max 10 chars)
|
|
878
|
+
if (!/^[A-Za-z0-9]{1,10}$/.test(currency)) {
|
|
879
|
+
return ValidationResult.failure('INVALID_CURRENCY_FORMAT');
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
const validCurrencies = ['BTC', 'ETH', 'QRL', 'USD', 'EUR', 'GBP'];
|
|
883
|
+
if (!validCurrencies.includes(currency.toUpperCase())) {
|
|
884
|
+
return ValidationResult.failure('UNSUPPORTED_CURRENCY');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// User ID validation
|
|
888
|
+
const userId = safeGet(listing, 'user_id');
|
|
889
|
+
if (typeof userId !== 'number' || !Number.isInteger(userId) || userId <= 0) {
|
|
890
|
+
return ValidationResult.failure('INVALID_USER_ID');
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Trade type validation
|
|
894
|
+
const tradeType = safeGet(listing, 'trade_type');
|
|
895
|
+
if (tradeType && !['buy', 'sell'].includes(tradeType)) {
|
|
896
|
+
return ValidationResult.failure('INVALID_TRADE_TYPE');
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
// Compute content hash for the listing
|
|
900
|
+
const listingHash = contentHash({
|
|
901
|
+
title: title,
|
|
902
|
+
price: price,
|
|
903
|
+
currency: currency.toUpperCase(),
|
|
904
|
+
user_id: userId,
|
|
905
|
+
trade_type: tradeType || 'sell',
|
|
906
|
+
});
|
|
907
|
+
|
|
908
|
+
return ValidationResult.success({ contentHash: listingHash });
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Validate a user identity
|
|
913
|
+
*/
|
|
914
|
+
validateUser(user) {
|
|
915
|
+
if (!user || typeof user !== 'object') {
|
|
916
|
+
return ValidationResult.failure('USER_NULL');
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
const userId = safeGet(user, 'user_id');
|
|
920
|
+
if (typeof userId !== 'number' || !Number.isInteger(userId) || userId <= 0) {
|
|
921
|
+
return ValidationResult.failure('INVALID_USER_ID');
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const username = safeGet(user, 'username');
|
|
925
|
+
if (!username || typeof username !== 'string') {
|
|
926
|
+
return ValidationResult.failure('INVALID_USERNAME');
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (username.length < 3 || username.length > 50) {
|
|
930
|
+
return ValidationResult.failure('USERNAME_LENGTH_INVALID');
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// SECURITY: Username must be alphanumeric with underscores only
|
|
934
|
+
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
|
935
|
+
return ValidationResult.failure('INVALID_USERNAME_CHARS');
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
const publicKey = safeGet(user, 'public_key');
|
|
939
|
+
if (publicKey) {
|
|
940
|
+
if (typeof publicKey !== 'string' || !/^[a-fA-F0-9]{64,}$/.test(publicKey)) {
|
|
941
|
+
return ValidationResult.failure('INVALID_PUBLIC_KEY_FORMAT');
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
return ValidationResult.success({ userId: userId });
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Validate a cryptographic signature (ML-DSA-65)
|
|
950
|
+
*/
|
|
951
|
+
validateSignature(message, signature, publicKey) {
|
|
952
|
+
if (!message || !signature || !publicKey) {
|
|
953
|
+
return ValidationResult.failure('MISSING_SIGNATURE_PARAMS');
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// SECURITY: Validate hex format
|
|
957
|
+
if (!/^[a-fA-F0-9]+$/.test(signature) || !/^[a-fA-F0-9]+$/.test(publicKey)) {
|
|
958
|
+
return ValidationResult.failure('INVALID_HEX_FORMAT');
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
try {
|
|
962
|
+
const messageBytes = typeof message === 'string'
|
|
963
|
+
? utf8ToBytes(message)
|
|
964
|
+
: message;
|
|
965
|
+
const sigBytes = hexToBytes(signature);
|
|
966
|
+
const pubKeyBytes = hexToBytes(publicKey);
|
|
967
|
+
|
|
968
|
+
// ML-DSA65 verify order: (signature, message, publicKey)
|
|
969
|
+
const valid = mlDsa65Verify(sigBytes, messageBytes, pubKeyBytes);
|
|
970
|
+
|
|
971
|
+
if (!valid) {
|
|
972
|
+
return ValidationResult.failure('SIGNATURE_INVALID');
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
return ValidationResult.success({
|
|
976
|
+
signatureValid: true,
|
|
977
|
+
messageHash: contentHash(messageBytes),
|
|
978
|
+
});
|
|
979
|
+
} catch (e) {
|
|
980
|
+
return ValidationResult.failure(`SIGNATURE_ERROR: ${e.message}`);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/**
|
|
985
|
+
* Validate a QCoA (Quantum Certificate of Authenticity)
|
|
986
|
+
*/
|
|
987
|
+
validateQCoA(certificate) {
|
|
988
|
+
if (!certificate || typeof certificate !== 'object') {
|
|
989
|
+
return ValidationResult.failure('CERTIFICATE_NULL');
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const required = ['cert_hash', 'origin_public_key', 'origin_signature', 'asset_type'];
|
|
993
|
+
for (const field of required) {
|
|
994
|
+
if (!safeGet(certificate, field)) {
|
|
995
|
+
return ValidationResult.failure(`MISSING_FIELD: ${field}`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// SECURITY: Validate formats
|
|
1000
|
+
const certHash = safeGet(certificate, 'cert_hash');
|
|
1001
|
+
const publicKey = safeGet(certificate, 'origin_public_key');
|
|
1002
|
+
const signature = safeGet(certificate, 'origin_signature');
|
|
1003
|
+
|
|
1004
|
+
if (!/^[a-fA-F0-9]+$/.test(certHash)) {
|
|
1005
|
+
return ValidationResult.failure('INVALID_CERT_HASH_FORMAT');
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
if (!/^[a-fA-F0-9]+$/.test(publicKey)) {
|
|
1009
|
+
return ValidationResult.failure('INVALID_PUBLIC_KEY_FORMAT');
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
if (!/^[a-fA-F0-9]+$/.test(signature)) {
|
|
1013
|
+
return ValidationResult.failure('INVALID_SIGNATURE_FORMAT');
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
// Verify the signature
|
|
1017
|
+
try {
|
|
1018
|
+
const sigResult = this.validateSignature(
|
|
1019
|
+
certHash,
|
|
1020
|
+
signature,
|
|
1021
|
+
publicKey
|
|
1022
|
+
);
|
|
1023
|
+
|
|
1024
|
+
if (!sigResult.valid) {
|
|
1025
|
+
return ValidationResult.failure('QCOA_SIGNATURE_INVALID');
|
|
1026
|
+
}
|
|
1027
|
+
} catch (e) {
|
|
1028
|
+
return ValidationResult.failure(`QCOA_SIGNATURE_ERROR: ${e.message}`);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
return ValidationResult.success({
|
|
1032
|
+
certHash: certHash,
|
|
1033
|
+
assetType: safeGet(certificate, 'asset_type'),
|
|
1034
|
+
verified: true,
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Deterministic conflict resolution
|
|
1040
|
+
* Two nodes with the same inputs will always pick the same winner
|
|
1041
|
+
*/
|
|
1042
|
+
resolveConflict(entry1, entry2) {
|
|
1043
|
+
if (!entry1 && !entry2) return null;
|
|
1044
|
+
if (!entry1) return entry2;
|
|
1045
|
+
if (!entry2) return entry1;
|
|
1046
|
+
|
|
1047
|
+
const hash1 = contentHash(entry1);
|
|
1048
|
+
const hash2 = contentHash(entry2);
|
|
1049
|
+
|
|
1050
|
+
// SECURITY: Pure deterministic - hash comparison
|
|
1051
|
+
if (hash1 < hash2) return entry1;
|
|
1052
|
+
if (hash2 < hash1) return entry2;
|
|
1053
|
+
|
|
1054
|
+
// If hashes are equal, they're the same content
|
|
1055
|
+
return entry1;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* Compute trust score for a user based on history
|
|
1060
|
+
*/
|
|
1061
|
+
computeTrustScore(userHistory) {
|
|
1062
|
+
if (!userHistory || !Array.isArray(userHistory)) {
|
|
1063
|
+
return 0;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Pure function - same history always gives same score
|
|
1067
|
+
let score = 0;
|
|
1068
|
+
|
|
1069
|
+
for (const event of userHistory) {
|
|
1070
|
+
const type = safeGet(event, 'type');
|
|
1071
|
+
const success = safeGet(event, 'success');
|
|
1072
|
+
const verified = safeGet(event, 'verified');
|
|
1073
|
+
|
|
1074
|
+
if (type === 'trade' && success === true) {
|
|
1075
|
+
score += 10;
|
|
1076
|
+
}
|
|
1077
|
+
if (type === 'attestation' && verified === true) {
|
|
1078
|
+
score += 5;
|
|
1079
|
+
}
|
|
1080
|
+
if (type === 'dispute' && safeGet(event, 'lost') === true) {
|
|
1081
|
+
score -= 20;
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
// Normalize to 0-100
|
|
1086
|
+
return Math.max(0, Math.min(100, score));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// ============================================================
|
|
1091
|
+
// SINGLETON EXPORT with security hardening
|
|
1092
|
+
// ============================================================
|
|
1093
|
+
|
|
1094
|
+
let oracleInstance = null;
|
|
1095
|
+
|
|
1096
|
+
export function getOracle() {
|
|
1097
|
+
if (!oracleInstance) {
|
|
1098
|
+
oracleInstance = new ValidationOracle();
|
|
1099
|
+
oracleInstance.freeze();
|
|
1100
|
+
}
|
|
1101
|
+
return oracleInstance;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
export function createOracle() {
|
|
1105
|
+
const oracle = new ValidationOracle();
|
|
1106
|
+
oracle.freeze();
|
|
1107
|
+
return oracle;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// Export for testing
|
|
1111
|
+
export { MODULE_SEAL, deepFreeze, safeGet, createSafeObject };
|