yakmesh 2.8.2 → 3.0.0

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