enigma-memory 0.1.18 → 0.1.22

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 (276) hide show
  1. package/README.md +76 -24
  2. package/apps/cli/bin/enigma-desktop.mjs +140 -0
  3. package/apps/cli/bin/enigma-terminal.mjs +78 -0
  4. package/apps/cli/bin/enigma.mjs +1923 -285
  5. package/apps/desktop/electron-main.cjs +217 -0
  6. package/apps/desktop/package.json +12 -0
  7. package/apps/desktop/src/app.js +264 -7
  8. package/apps/desktop/src/index.html +3514 -1373
  9. package/apps/desktop/src/launch-electron.mjs +51 -0
  10. package/apps/desktop/src/server.mjs +2914 -0
  11. package/apps/desktop/src/styles.css +2972 -260
  12. package/apps/desktop/src/zk-browser-prove.mjs +53 -0
  13. package/apps/desktop/src/zk-state.mjs +1789 -0
  14. package/apps/gateway/bin/enigma-gateway.mjs +102 -5
  15. package/apps/gateway/src/server.mjs +271 -8
  16. package/apps/ios/EnigmaCore/Package.swift +12 -0
  17. package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
  18. package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
  19. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
  20. package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
  21. package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
  22. package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
  23. package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
  24. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
  25. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
  26. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
  27. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
  28. package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
  29. package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
  30. package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
  31. package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
  32. package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
  33. package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
  34. package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
  35. package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
  36. package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
  37. package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
  38. package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
  39. package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
  40. package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
  41. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
  42. package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
  43. package/apps/native-host/README.md +19 -8
  44. package/apps/native-host/bin/enigma-native-host.mjs +229 -13
  45. package/apps/relay/bin/enigma-relay.mjs +103 -5
  46. package/apps/relay/src/federation-runtime.mjs +618 -0
  47. package/apps/relay/src/server.mjs +310 -9
  48. package/apps/verifier/bin/enigma-verify.mjs +327 -11
  49. package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
  50. package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
  51. package/cortex-v3/circuits/build/vk_bytes.json +32 -0
  52. package/cortex-v3/proving-assets.json +64 -0
  53. package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
  54. package/cortex-v3/zk/action-transition-vk.json +119 -0
  55. package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
  56. package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
  57. package/cortex-v3/zk/intent-witness.mjs +365 -0
  58. package/cortex-v3/zk/intent-witness.test.mjs +485 -0
  59. package/cortex-v3/zk/proving-assets.mjs +203 -0
  60. package/cortex-v3/zk/sale-witness.mjs +783 -0
  61. package/cortex-v3/zk/sale-witness.test.mjs +784 -0
  62. package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
  63. package/cortex-v3/zk/settlement-evidence.mjs +722 -0
  64. package/cortex-v3/zk/setup-intent.mjs +688 -0
  65. package/cortex-v3/zk/setup-sale.mjs +666 -0
  66. package/cortex-v3/zk/setup.mjs +594 -0
  67. package/cortex-v3/zk/witness.mjs +184 -0
  68. package/cortex-v3/zk/zk-codec.mjs +232 -0
  69. package/cortex-v3/zk/zk-codec.test.mjs +293 -0
  70. package/cortex-v3/zk/zk-settle.mjs +370 -0
  71. package/cortex-v3/zk/zk-tree.mjs +256 -0
  72. package/cortex-v3/zk/zk-tree.test.mjs +419 -0
  73. package/deploy/docker-compose.local-production-simulation.yml +36 -0
  74. package/docs/browser-extension-install.md +8 -6
  75. package/docs/client-connectors.md +15 -11
  76. package/docs/developer-ecosystem.md +15 -13
  77. package/docs/enigma-memory-ready-conformance.md +11 -9
  78. package/docs/install-anywhere.md +61 -28
  79. package/docs/installers-and-desktop.md +8 -7
  80. package/docs/novelty-invention-candidates.md +161 -161
  81. package/docs/proof-network-claim-boundaries.md +320 -318
  82. package/examples/01-quickstart-agent/index.mjs +49 -0
  83. package/examples/01_agent_memory_quickstart.mjs +57 -0
  84. package/examples/02-multi-agent-swarm/index.mjs +57 -0
  85. package/examples/02_cross_model_passport.mjs +64 -0
  86. package/examples/03-langchain-memory/index.mjs +41 -0
  87. package/examples/03_poseidon_commitment_verification.mjs +71 -0
  88. package/examples/04-python-trading-agent/trader.py +49 -0
  89. package/examples/README.md +27 -0
  90. package/examples/ci/github-actions.yml +7 -2
  91. package/package.json +142 -11
  92. package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
  93. package/packages/connectors/src/index.js +196 -4
  94. package/packages/connectors/swarm-router.mjs +168 -0
  95. package/packages/core/src/index.js +248 -1
  96. package/packages/core/src/version.mjs +7 -0
  97. package/packages/dev-tools/package.json +19 -0
  98. package/packages/dev-tools/src/index.js +4 -0
  99. package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
  100. package/packages/dev-tools/src/swarm-simulator.js +101 -0
  101. package/packages/dev-tools/src/vault-inspector.js +114 -0
  102. package/packages/dev-tools/src/vector-benchmark.js +100 -0
  103. package/packages/developer-platform/src/access-credentials.js +341 -0
  104. package/packages/developer-platform/src/http.js +132 -0
  105. package/packages/developer-platform/src/index.js +4 -0
  106. package/packages/developer-platform/src/usage-http.js +60 -0
  107. package/packages/developer-platform/src/usage.js +295 -0
  108. package/packages/enclave-runtime/attestation.mjs +159 -0
  109. package/packages/enclave-runtime/index.mjs +47 -0
  110. package/packages/enclave-runtime/session-manager.mjs +253 -0
  111. package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
  112. package/packages/enigma-reflex/package.json +14 -0
  113. package/packages/enigma-reflex/src/index.js +204 -0
  114. package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
  115. package/packages/enigma-reflex/training/requirements.txt +8 -0
  116. package/packages/enigma-reflex/training/train.py +314 -0
  117. package/packages/enigma-weave/LICENSE +22 -0
  118. package/packages/enigma-weave/UPSTREAM.json +21 -0
  119. package/packages/enigma-weave/package.json +14 -0
  120. package/packages/enigma-weave/src/index.js +286 -0
  121. package/packages/hosted-cloud/src/index.js +80 -5
  122. package/packages/importers/src/index.js +432 -0
  123. package/packages/inference-runtime/src/browser.js +401 -0
  124. package/packages/inference-runtime/src/chat.js +265 -0
  125. package/packages/inference-runtime/src/code.js +407 -0
  126. package/packages/inference-runtime/src/contracts.js +162 -0
  127. package/packages/inference-runtime/src/http.js +232 -0
  128. package/packages/inference-runtime/src/image.js +186 -0
  129. package/packages/inference-runtime/src/index.js +10 -0
  130. package/packages/inference-runtime/src/model-router.js +320 -0
  131. package/packages/inference-runtime/src/platform.js +125 -0
  132. package/packages/inference-runtime/src/privacy.js +400 -0
  133. package/packages/inference-runtime/src/video.js +253 -0
  134. package/packages/mcp-server/README.md +22 -6
  135. package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
  136. package/packages/mcp-server/src/index.js +1418 -105
  137. package/packages/mcp-server/src/oauth.js +561 -0
  138. package/packages/mcp-server/src/private-handoff.js +84 -0
  139. package/packages/mcp-server/src/remote-http.js +273 -0
  140. package/packages/mcp-server/src/remote-policy.js +72 -0
  141. package/packages/mcp-server/swarm-bridge.mjs +361 -0
  142. package/packages/mesh/index.d.ts +283 -0
  143. package/packages/mesh/package.json +23 -0
  144. package/packages/mesh/src/crypto.js +189 -0
  145. package/packages/mesh/src/federation-packets.js +353 -0
  146. package/packages/mesh/src/gossip.js +311 -0
  147. package/packages/mesh/src/index.js +6 -0
  148. package/packages/mesh/src/protocol.js +255 -0
  149. package/packages/mesh/src/router.js +279 -0
  150. package/packages/mesh/src/transport.js +306 -0
  151. package/packages/passport/src/index.js +426 -1
  152. package/packages/private-economy/src/credits-http.js +100 -0
  153. package/packages/private-economy/src/credits.js +447 -0
  154. package/packages/private-economy/src/index.js +5 -0
  155. package/packages/private-economy/src/payments-http.js +120 -0
  156. package/packages/private-economy/src/payments.js +509 -0
  157. package/packages/private-economy/src/x402.js +346 -0
  158. package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
  159. package/packages/rag/index.d.ts +182 -0
  160. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
  161. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
  162. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
  163. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
  164. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
  165. package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
  166. package/packages/rag/package.json +27 -0
  167. package/packages/rag/src/blinded-search.js +109 -0
  168. package/packages/rag/src/bm25.js +169 -0
  169. package/packages/rag/src/embeddings.js +459 -0
  170. package/packages/rag/src/hybrid.js +76 -0
  171. package/packages/rag/src/index.js +38 -0
  172. package/packages/rag/src/reranker.js +61 -0
  173. package/packages/rag/src/research.js +107 -0
  174. package/packages/rag/src/vector-store.js +430 -0
  175. package/packages/rag/src/verify-model-artifacts.mjs +4 -0
  176. package/packages/sdk/index.d.ts +760 -0
  177. package/packages/sdk/package.json +33 -0
  178. package/packages/sdk/python/README.md +24 -0
  179. package/packages/sdk/python/enigma_sdk.py +250 -0
  180. package/packages/sdk/python/pyproject.toml +34 -0
  181. package/packages/sdk/python/requirements.txt +1 -0
  182. package/packages/sdk/python/setup.py +20 -0
  183. package/packages/sdk/src/federation/capability-grant.js +389 -0
  184. package/packages/sdk/src/federation/federation-router.js +360 -0
  185. package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
  186. package/packages/sdk/src/federation/index.js +3 -0
  187. package/packages/sdk/src/index.js +1796 -0
  188. package/packages/sdk/src/intelligence/contradiction.js +337 -0
  189. package/packages/sdk/src/intelligence/decision-engine.js +155 -0
  190. package/packages/sdk/src/intelligence/index.js +4 -0
  191. package/packages/sdk/src/intelligence/ontology.js +122 -0
  192. package/packages/sdk/src/intelligence/temporal.js +123 -0
  193. package/packages/sdk/src/market-client.js +142 -0
  194. package/packages/sdk/src/mesh-client.js +110 -0
  195. package/packages/sdk/src/middleware/index.js +3 -0
  196. package/packages/sdk/src/middleware/langchain.js +159 -0
  197. package/packages/sdk/src/middleware/llamaindex.js +101 -0
  198. package/packages/sdk/src/middleware/vercel-ai.js +112 -0
  199. package/packages/sdk/src/rag-client.js +85 -0
  200. package/packages/sdk/src/swarm-orchestrator.js +260 -0
  201. package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
  202. package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
  203. package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
  204. package/packages/snapcompact/index.d.ts +284 -0
  205. package/packages/snapcompact/package.json +25 -0
  206. package/packages/snapcompact/src/index.js +716 -0
  207. package/packages/storage/PACKAGE_CONTRACT.md +1 -1
  208. package/packages/terminal-console/animations.mjs +240 -0
  209. package/packages/terminal-console/auto-anchor.mjs +220 -0
  210. package/packages/terminal-console/banner.mjs +91 -0
  211. package/packages/terminal-console/commands.mjs +459 -0
  212. package/packages/terminal-console/delegation.mjs +152 -0
  213. package/packages/terminal-console/index.mjs +5 -0
  214. package/packages/terminal-console/outbox.mjs +143 -0
  215. package/packages/terminal-console/phantom-bridge.mjs +637 -0
  216. package/packages/terminal-console/repl.mjs +136 -0
  217. package/packages/terminal-console/signer-store.mjs +130 -0
  218. package/packages/terminal-console/solana-rpc.mjs +214 -0
  219. package/packages/terminal-console/solana-transport.mjs +189 -0
  220. package/packages/terminal-tui/dashboard.mjs +214 -0
  221. package/packages/terminal-tui/index.mjs +28 -0
  222. package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
  223. package/packages/terminal-tui/telemetry-hud.mjs +137 -0
  224. package/packages/vault/index.d.ts +449 -0
  225. package/packages/vault/package.json +27 -0
  226. package/packages/vault/src/e2ee.mjs +393 -0
  227. package/packages/vault/src/enclave.js +481 -0
  228. package/packages/vault/src/erasure.js +207 -0
  229. package/packages/vault/src/index.js +1018 -155
  230. package/packages/vault/src/persistence.js +307 -0
  231. package/packages/vault/src/poseidon.js +354 -0
  232. package/packages/vault/src/receipt.js +459 -0
  233. package/scripts/benchmark-optical-context.mjs +166 -0
  234. package/scripts/bootstrap-enigma.mjs +502 -0
  235. package/scripts/build-edge-backend-workers.mjs +20 -5
  236. package/scripts/build-goal-completion-audit.mjs +72 -25
  237. package/scripts/build-hosted-api-key-lifecycle.mjs +26 -8
  238. package/scripts/build-hosted-customer-lifecycle.mjs +20 -3
  239. package/scripts/build-hosted-probe-worker.mjs +19 -4
  240. package/scripts/build-installer-assets.mjs +41 -21
  241. package/scripts/build-operator-evidence-starter.mjs +59 -1
  242. package/scripts/build-production-backend-env-kit.mjs +2 -0
  243. package/scripts/build-production-unblocker.mjs +3 -0
  244. package/scripts/check.mjs +17 -3
  245. package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
  246. package/scripts/install-enigma-local.mjs +18 -5
  247. package/scripts/release-audit.mjs +65 -109
  248. package/scripts/release-provenance.mjs +6 -0
  249. package/scripts/run-backend-readiness-smoke.mjs +112 -10
  250. package/scripts/scan-secrets.mjs +1 -0
  251. package/scripts/simulate-production-env.mjs +7 -2
  252. package/scripts/validate-hosted-backend-live.mjs +112 -1
  253. package/specs/antibody-pack-v1.schema.json +95 -0
  254. package/specs/antigen-envelope-v1.schema.json +81 -0
  255. package/specs/boundary-manifest-v1.schema.json +35 -35
  256. package/specs/capsule-v1.schema.json +55 -55
  257. package/specs/claim-boundary-manifest-v1.schema.json +22 -22
  258. package/specs/claim-ledger-v1.schema.json +291 -0
  259. package/specs/context-passport-v1.schema.json +59 -0
  260. package/specs/deletion-tombstone-v1.schema.json +26 -26
  261. package/specs/evidence-packet-v1.schema.json +177 -0
  262. package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
  263. package/specs/immune-scan-report-v1.schema.json +112 -0
  264. package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
  265. package/specs/memory-atom-v1.schema.json +59 -0
  266. package/specs/memory-event-v1.schema.json +42 -42
  267. package/specs/passport-v1.schema.json +50 -50
  268. package/specs/proof-of-non-use-v1.schema.json +65 -0
  269. package/specs/quarantine-record-v1.schema.json +126 -0
  270. package/specs/receipt-v1.schema.json +61 -61
  271. package/specs/state-checkpoint-v1.schema.json +37 -37
  272. package/specs/trust-bundle-v1.schema.json +56 -56
  273. package/specs/trust-card-v1.schema.json +119 -0
  274. package/docs/proof-network-launch-plan.md +0 -421
  275. package/packages/metering/PACKAGE_CONTRACT.md +0 -20
  276. package/scripts/build-ai-orchestration-plan.mjs +0 -248
@@ -0,0 +1,2914 @@
1
+ // Enigma Sovereign Terminal Desktop Server Module
2
+ // Reusable server library for enigma-desktop CLI bin, scripts, and programmatic hosts.
3
+ // Implements strict host/origin validation (exact 127.0.0.1 or localhost + bound port) to defeat DNS rebinding.
4
+
5
+ import http from 'node:http';
6
+ import { createRequire } from 'node:module';
7
+ import { ENIGMA_VERSION } from '../../../packages/core/src/version.mjs';
8
+ import fs from 'node:fs';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+ import crypto from 'node:crypto';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { spawn } from 'node:child_process';
14
+ import { TerminalSession } from '../../../packages/terminal-console/commands.mjs';
15
+ import { PhantomBrowserBridge, OFFICIAL_TOKEN_CA, SOLANA_MAINNET_RPC } from '../../../packages/terminal-console/phantom-bridge.mjs';
16
+ import { fetchSolanaStreamMetrics } from '../../../packages/terminal-console/solana-rpc.mjs';
17
+ import { AutoAnchorEngine } from '../../../packages/terminal-console/auto-anchor.mjs';
18
+ import { createSolanaProofMemoRef } from '../../../packages/terminal-console/solana-transport.mjs';
19
+ import { createProofNetworkAnchorBatch } from '../../../packages/proof-network/src/index.js';
20
+ import { normalizeSolanaLocalArtifact } from '../../cli/bin/enigma.mjs';
21
+ import { createZkRuntime, defaultZkStatePath, ZK_HONESTY_NOTE } from './zk-state.mjs';
22
+ import { EncryptedVectorStore } from '../../../packages/rag/src/index.js';
23
+ import { computeHidingCommitment, deriveKeysFromPassphrase, PBKDF2_KDF_SPEC } from '../../../packages/vault/src/index.js';
24
+ import { Enigma } from '../../../packages/sdk/src/index.js';
25
+ import { GhostMeshFederationBridge } from '../../../packages/sdk/src/federation/index.js';
26
+ import {
27
+ WebSocketTransport as FederationWebSocketTransport,
28
+ deriveDestination,
29
+ generateMeshIdentity,
30
+ } from '../../../packages/mesh/src/index.js';
31
+ import { createEnvironmentPlatformRuntime } from '../../../packages/inference-runtime/src/platform.js';
32
+ import { createInferenceHttpHandler } from '../../../packages/inference-runtime/src/http.js';
33
+ import { apiKeyFromAuthorization, createApiKeyService, createEphemeralApiKeyStore, createFileApiKeyStore } from '../../../packages/developer-platform/src/access-credentials.js';
34
+ import { createApiKeyHttpHandler } from '../../../packages/developer-platform/src/http.js';
35
+ import { createEphemeralUsageStore, createFileUsageStore, createUsageService } from '../../../packages/developer-platform/src/usage.js';
36
+ import { createUsageHttpHandler } from '../../../packages/developer-platform/src/usage-http.js';
37
+ import { createEphemeralCreditsStore, createFileCreditsStore, createPrivateCreditsService } from '../../../packages/private-economy/src/credits.js';
38
+ import { createPrivateCreditsHttpHandler } from '../../../packages/private-economy/src/credits-http.js';
39
+ import { createCreditPaymentService, createEphemeralPaymentStore, createFilePaymentStore, createSolanaPaymentVerifier, createStripeSubscriptionProvider } from '../../../packages/private-economy/src/payments.js';
40
+ import { createPaymentHttpHandler } from '../../../packages/private-economy/src/payments-http.js';
41
+ import { createX402FacilitatorClient, createX402ResourceHandler } from '../../../packages/private-economy/src/x402.js';
42
+ const DESKTOP_DIR = path.dirname(fileURLToPath(import.meta.url));
43
+
44
+ export function getDefaultBundlePath() {
45
+ if (process.env.ENIGMA_BUNDLE_PATH) {
46
+ return path.resolve(process.env.ENIGMA_BUNDLE_PATH);
47
+ }
48
+ return path.resolve(process.cwd(), '.enigma', 'bundle.json');
49
+ }
50
+ const MAX_BODY_BYTES = 64 * 1024; // 64 KB limit
51
+
52
+ const MIME_TYPES = {
53
+ '.html': 'text/html; charset=utf-8',
54
+ '.css': 'text/css; charset=utf-8',
55
+ '.mjs': 'text/javascript; charset=utf-8',
56
+ '.json': 'application/json',
57
+ '.png': 'image/png',
58
+ '.svg': 'image/svg+xml',
59
+ };
60
+
61
+ class MemoryCaptureStream {
62
+ constructor() {
63
+ this.buffer = '';
64
+ }
65
+ write(chunk) {
66
+ this.buffer += String(chunk);
67
+ return true;
68
+ }
69
+ clear() {
70
+ this.buffer = '';
71
+ }
72
+ }
73
+
74
+ export class DesktopOperationalError extends Error {
75
+ constructor(code, message, statusCode = 400) {
76
+ super(message);
77
+ this.name = 'DesktopOperationalError';
78
+ this.code = code;
79
+ this.statusCode = statusCode;
80
+ }
81
+ }
82
+
83
+ function adapterEntries(options = {}) {
84
+ const configured = options.modelAdapters
85
+ ?? options.providerAdapters
86
+ ?? options.agentAdapters
87
+ ?? options.adapters
88
+ ?? options.connectors
89
+ ?? [];
90
+ if (configured instanceof Map) return [...configured.entries()];
91
+ if (Array.isArray(configured)) return configured.map((adapter) => [adapter?.id, adapter]);
92
+ if (configured && typeof configured === 'object') return Object.entries(configured);
93
+ throw new TypeError('Desktop model adapters must be an array, object, or Map');
94
+ }
95
+ const DESKTOP_CONTEXT_CARRIER_NAMES = new Set(['text', 'bitmap-png']);
96
+ const DESKTOP_CONTEXT_CARRIER_REQUEST_MODES = new Set(['text', 'bitmap-png', 'auto']);
97
+
98
+ function normalizeCarrierNames(value) {
99
+ let names;
100
+ if (Array.isArray(value) || value instanceof Set) {
101
+ names = [...value];
102
+ } else if (typeof value === 'string') {
103
+ names = [value];
104
+ } else if (value && typeof value === 'object') {
105
+ const declared = value.supported ?? value.carriers ?? value.modes;
106
+ if (Array.isArray(declared) || declared instanceof Set) {
107
+ names = [...declared];
108
+ } else if (typeof declared === 'string') {
109
+ names = [declared];
110
+ } else {
111
+ names = [...DESKTOP_CONTEXT_CARRIER_NAMES].filter((name) => value[name] === true);
112
+ if (value.vision === true) names.push('bitmap-png');
113
+ }
114
+ } else {
115
+ names = [];
116
+ }
117
+ const normalized = [...new Set(['text', ...names.map((name) => String(name).trim())])];
118
+ const unsupported = normalized.find((name) => !DESKTOP_CONTEXT_CARRIER_NAMES.has(name));
119
+ if (unsupported) {
120
+ throw new TypeError(`Unsupported desktop context carrier capability: ${unsupported}`);
121
+ }
122
+ return Object.freeze(normalized);
123
+ }
124
+
125
+ function normalizeContextCarriersByModel(adapter, models) {
126
+ const configured = adapter.contextCarriersByModel;
127
+ const entries = configured instanceof Map
128
+ ? [...configured.entries()]
129
+ : (configured && typeof configured === 'object' ? Object.entries(configured) : []);
130
+ const declared = new Map(entries.map(([model, names]) => [String(model).trim(), names]));
131
+ for (const model of declared.keys()) {
132
+ if (!models.includes(model)) {
133
+ throw new TypeError(`Desktop context carrier capability references unconfigured model: ${model}`);
134
+ }
135
+ }
136
+ const normalized = {};
137
+ for (const model of models) {
138
+ normalized[model] = normalizeCarrierNames(declared.get(model));
139
+ }
140
+ return Object.freeze(normalized);
141
+ }
142
+
143
+ function resolveDesktopContextCarrierRequest(payload, adapter, model) {
144
+ const requested = payload.contextCarrier;
145
+ if (!requested || typeof requested !== 'object' || Array.isArray(requested)) return null;
146
+ const mode = requested.mode === undefined ? 'text' : String(requested.mode);
147
+ if (!DESKTOP_CONTEXT_CARRIER_REQUEST_MODES.has(mode)) {
148
+ throw new DesktopOperationalError(
149
+ 'CONTEXT_CARRIER_MODE_INVALID',
150
+ 'contextCarrier.mode must be "text", "bitmap-png", or "auto"',
151
+ 400
152
+ );
153
+ }
154
+ const supported = adapter.contextCarriersByModel[model] || Object.freeze(['text']);
155
+ return {
156
+ mode,
157
+ provider: adapter.provider,
158
+ model,
159
+ vision: supported.includes('bitmap-png'),
160
+ costs: requested.costs ?? requested.costPolicy ?? requested.costAssumptions,
161
+ expectedReuse: requested.expectedReuse,
162
+ tokenEstimates: requested.tokenEstimates,
163
+ limits: requested.limits ?? requested.renderLimits,
164
+ tabWidth: requested.tabWidth,
165
+ collapseRepeatedLines: requested.collapseRepeatedLines,
166
+ repetition: requested.repetition,
167
+ };
168
+ }
169
+
170
+ function sanitizeDesktopAdapterResult(value, key = '', seen = new WeakSet()) {
171
+ if (typeof value === 'string') {
172
+ return value.startsWith('data:image/png;base64,') ? '' : value;
173
+ }
174
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
175
+ return { omittedBinaryBytes: value.byteLength };
176
+ }
177
+ if (!value || typeof value !== 'object') return value;
178
+ if (seen.has(value)) return '[circular]';
179
+ seen.add(value);
180
+ if (
181
+ key === 'contextCarrier'
182
+ || (
183
+ typeof value.selected === 'string'
184
+ && typeof value.textFallback === 'string'
185
+ && Array.isArray(value.blocks)
186
+ && value.descriptor
187
+ && value.decision
188
+ )
189
+ ) {
190
+ return {
191
+ descriptor: value.descriptor,
192
+ decision: value.decision,
193
+ };
194
+ }
195
+ if (Array.isArray(value)) {
196
+ return value.map((entry) => sanitizeDesktopAdapterResult(entry, '', seen));
197
+ }
198
+ if (
199
+ value?.source?.type === 'base64'
200
+ || value?.inlineData?.mimeType === 'image/png'
201
+ || value?.type === 'input_image'
202
+ ) {
203
+ return { omittedCarrierImage: true };
204
+ }
205
+ const sanitized = {};
206
+ for (const [entryKey, entryValue] of Object.entries(value)) {
207
+ sanitized[entryKey] = sanitizeDesktopAdapterResult(entryValue, entryKey, seen);
208
+ }
209
+ return sanitized;
210
+ }
211
+
212
+ const DESKTOP_PRIVACY_MODES = new Set(['off', 'anchor-guard']);
213
+ const DESKTOP_ANCHOR_GUARD_PATTERNS = Object.freeze([
214
+ {
215
+ category: 'email',
216
+ expression: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
217
+ },
218
+ {
219
+ category: 'evm_wallet',
220
+ expression: /\b0x[a-fA-F0-9]{40}\b/g,
221
+ },
222
+ {
223
+ category: 'ipv4',
224
+ expression: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
225
+ valid: (value) => value.split('.').every((part) => Number(part) <= 255),
226
+ },
227
+ {
228
+ category: 'phone',
229
+ expression: /(?:\+\d{1,3}[\s.-]?)?(?:\(\d{3}\)|\d{3})[\s.-]\d{3}[\s.-]\d{4}\b/g,
230
+ },
231
+ {
232
+ category: 'base58_wallet',
233
+ expression: /\b[1-9A-HJ-NP-Za-km-z]{32,44}\b/g,
234
+ },
235
+ ]);
236
+
237
+ function resolveDesktopPrivacyMode(payload) {
238
+ const requested = payload.privacy;
239
+ if (requested === undefined || requested === null) return 'off';
240
+ const mode = typeof requested === 'string' ? requested : requested?.mode;
241
+ if (!DESKTOP_PRIVACY_MODES.has(mode)) {
242
+ throw new DesktopOperationalError(
243
+ 'PRIVACY_MODE_INVALID',
244
+ 'privacy.mode must be "off" or "anchor-guard"',
245
+ 400
246
+ );
247
+ }
248
+ return mode;
249
+ }
250
+
251
+ function createDesktopAnchorGuard(mode) {
252
+ const replacements = [];
253
+ const tokensByValue = new Map();
254
+
255
+ function tokenFor(value, category) {
256
+ const prior = tokensByValue.get(value);
257
+ if (prior) return prior;
258
+ const token = `{{ENIGMA_${category.toUpperCase()}_${replacements.length + 1}}}`;
259
+ tokensByValue.set(value, token);
260
+ replacements.push({ category, token, value });
261
+ return token;
262
+ }
263
+
264
+ function redactText(value) {
265
+ if (mode === 'off' || typeof value !== 'string' || value.length === 0) return value;
266
+ let protectedText = value;
267
+ for (const pattern of DESKTOP_ANCHOR_GUARD_PATTERNS) {
268
+ protectedText = protectedText.replace(pattern.expression, (match) => {
269
+ if (pattern.valid && !pattern.valid(match)) return match;
270
+ return tokenFor(match, pattern.category);
271
+ });
272
+ }
273
+ return protectedText;
274
+ }
275
+
276
+ function transformStrings(value, transform, seen = new WeakMap()) {
277
+ if (typeof value === 'string') return transform(value);
278
+ if (!value || typeof value !== 'object' || Buffer.isBuffer(value) || value instanceof Uint8Array) {
279
+ return value;
280
+ }
281
+ if (seen.has(value)) return seen.get(value);
282
+ if (Array.isArray(value)) {
283
+ const transformed = [];
284
+ seen.set(value, transformed);
285
+ for (const entry of value) transformed.push(transformStrings(entry, transform, seen));
286
+ return transformed;
287
+ }
288
+ const transformed = {};
289
+ seen.set(value, transformed);
290
+ for (const [key, entry] of Object.entries(value)) {
291
+ transformed[key] = transformStrings(entry, transform, seen);
292
+ }
293
+ return transformed;
294
+ }
295
+
296
+ function redactValue(value) {
297
+ return transformStrings(value, redactText);
298
+ }
299
+
300
+ function restoreText(value) {
301
+ if (typeof value !== 'string' || replacements.length === 0) return value;
302
+ let restored = value;
303
+ for (const replacement of replacements) {
304
+ restored = restored.replaceAll(replacement.token, replacement.value);
305
+ }
306
+ return restored;
307
+ }
308
+
309
+ function restoreValue(value) {
310
+ return transformStrings(value, restoreText);
311
+ }
312
+
313
+ function report(invocation) {
314
+ if (mode === 'off') return null;
315
+ const categoryCounts = {};
316
+ for (const replacement of replacements) {
317
+ categoryCounts[replacement.category] = (categoryCounts[replacement.category] || 0) + 1;
318
+ }
319
+ const requestValue = String(invocation.prompt || '');
320
+ const retrievedValue = String(invocation.context || '');
321
+ const protectedInput = JSON.stringify({
322
+ schema: 'enigma.desktop.protected_adapter_input.v1',
323
+ requestValue,
324
+ retrievedValue,
325
+ });
326
+ const { commitment: protectedInputRef } = computeHidingCommitment(protectedInput);
327
+ return {
328
+ schema: 'enigma.desktop.anchor_guard_report.v1',
329
+ mode,
330
+ redactionCount: replacements.length,
331
+ categories: Object.fromEntries(Object.entries(categoryCounts).sort(([a], [b]) => a.localeCompare(b))),
332
+ protectedInputRef,
333
+ inputShape: {
334
+ requestChars: requestValue.length,
335
+ retrievedChars: retrievedValue.length,
336
+ },
337
+ limitations: [
338
+ 'Pattern-based guard covers email, EVM wallet, IPv4, formatted phone, and base58 wallet anchors.',
339
+ 'It does not identify arbitrary names, places, or sensitive prose.',
340
+ 'Configured adapters control final transport; this unsigned operational report is not cryptographic proof.',
341
+ ],
342
+ };
343
+ }
344
+ return { redactValue, restoreValue, report };
345
+ }
346
+
347
+
348
+ export function createDesktopModelAdapterRegistry(options = {}) {
349
+ const registry = new Map();
350
+ for (const [configuredId, candidate] of adapterEntries(options)) {
351
+ const adapter = typeof candidate === 'function'
352
+ ? { id: configuredId, invoke: candidate }
353
+ : candidate;
354
+ if (!adapter || typeof adapter !== 'object' || typeof adapter.invoke !== 'function') continue;
355
+ const id = String(adapter.id ?? configuredId ?? '').trim();
356
+ const provider = String(adapter.provider ?? adapter.providerId ?? '').trim();
357
+ const models = Array.isArray(adapter.models)
358
+ ? adapter.models.map((model) => String(model).trim()).filter(Boolean)
359
+ : [String(adapter.model ?? '').trim()].filter(Boolean);
360
+ if (!id || !provider || models.length === 0) {
361
+ throw new TypeError('Each desktop model adapter requires id, provider, at least one model, and invoke()');
362
+ }
363
+ if (registry.has(id)) throw new TypeError(`Duplicate desktop model adapter id: ${id}`);
364
+ const uniqueModels = Object.freeze([...new Set(models)]);
365
+ registry.set(id, Object.freeze({
366
+ id,
367
+ provider,
368
+ models: uniqueModels,
369
+ contextCarriersByModel: normalizeContextCarriersByModel(adapter, uniqueModels),
370
+ invoke: adapter.invoke.bind(adapter),
371
+ }));
372
+ }
373
+ return registry;
374
+ }
375
+
376
+ export function resolveDesktopModelAdapter(registry, selection = {}) {
377
+ const provider = String(selection.provider ?? '').trim();
378
+ const model = String(selection.model ?? '').trim();
379
+ const adapterId = String(selection.adapterId ?? selection.adapter_id ?? '').trim();
380
+ const configured = [...registry.values()];
381
+ if (configured.length === 0) {
382
+ throw new DesktopOperationalError(
383
+ 'MODEL_ADAPTER_NOT_CONFIGURED',
384
+ 'No desktop model adapter is configured. Configure an authenticated provider connector in createDesktopServer({ modelAdapters }) and restart the desktop host.',
385
+ 503
386
+ );
387
+ }
388
+ if (!provider || !model) {
389
+ throw new DesktopOperationalError(
390
+ 'MODEL_SELECTION_REQUIRED',
391
+ 'provider and model are required; select a configured desktop model adapter',
392
+ 400
393
+ );
394
+ }
395
+ const adapter = configured.find((entry) =>
396
+ (!adapterId || entry.id === adapterId)
397
+ && entry.provider === provider
398
+ && entry.models.includes(model)
399
+ );
400
+ if (adapter) return adapter;
401
+ throw new DesktopOperationalError(
402
+ 'MODEL_ADAPTER_NOT_CONFIGURED',
403
+ `No configured desktop model adapter matches provider "${provider}", model "${model}"${adapterId ? `, adapter "${adapterId}"` : ''}.`,
404
+ 422
405
+ );
406
+ }
407
+
408
+ export function createDesktopServer(options = {}) {
409
+ let boundPort = options.port !== undefined ? options.port : 8765;
410
+ const host = '127.0.0.1';
411
+ let ephemeralTempDir = null;
412
+ let bundlePath;
413
+ if (options.inMemoryOnly === true) {
414
+ ephemeralTempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'enigma-ephemeral-sandbox-'));
415
+ bundlePath = options.bundlePath ? path.resolve(options.bundlePath) : path.join(ephemeralTempDir, 'bundle.json');
416
+ } else {
417
+ bundlePath = options.bundlePath ? path.resolve(options.bundlePath) : getDefaultBundlePath();
418
+ }
419
+ const authToken = options.authToken || crypto.randomBytes(32).toString('hex');
420
+ const oauthBearerAuthorizer = options.authorizeBearer || options.oauth?.authorizeBearer || null;
421
+ if (oauthBearerAuthorizer !== null && typeof oauthBearerAuthorizer !== 'function') {
422
+ throw new TypeError('authorizeBearer must be a function');
423
+ }
424
+ const apiKeyStore = options.apiKeyStore || (options.inMemoryOnly === true
425
+ ? createEphemeralApiKeyStore()
426
+ : createFileApiKeyStore({ path: options.apiKeyStorePath || path.join(path.dirname(bundlePath), 'api-keys.json') }));
427
+ const apiKeys = options.apiKeys || createApiKeyService({
428
+ store: apiKeyStore,
429
+ environment: options.apiKeyEnvironment || 'live',
430
+ });
431
+ const usageStore = options.usageStore || (options.inMemoryOnly === true
432
+ ? createEphemeralUsageStore()
433
+ : createFileUsageStore({ path: options.usageStorePath || path.join(path.dirname(bundlePath), 'usage.json') }));
434
+ const usage = options.usage || createUsageService({
435
+ store: usageStore,
436
+ pricing: options.usagePricing || {},
437
+ defaultPrincipal: { ownerId: 'local-owner', tenantId: 'local' },
438
+ });
439
+ const creditsStore = options.creditsStore || (options.inMemoryOnly === true
440
+ ? createEphemeralCreditsStore()
441
+ : createFileCreditsStore({ path: options.creditsStorePath || path.join(path.dirname(bundlePath), 'credits.json') }));
442
+ const credits = options.credits || createPrivateCreditsService({ store: creditsStore });
443
+ const recoveryAttempts = [];
444
+ const configuredPaymentPackages = options.paymentPackages || JSON.parse(process.env.ENIGMA_CREDIT_PACKAGES_JSON || '[]');
445
+ const usdcDestinationTokenAccount = options.usdcDestinationTokenAccount || process.env.ENIGMA_USDC_TREASURY_TOKEN_ACCOUNT || null;
446
+ const tokenBurnMint = options.tokenBurnMint || process.env.ENIGMA_CREDIT_BURN_MINT || null;
447
+ const verifySolanaPayment = options.verifySolanaPayment || ((usdcDestinationTokenAccount || tokenBurnMint)
448
+ ? createSolanaPaymentVerifier({ rpcUrl: options.solanaPaymentRpcUrl || process.env.ENIGMA_SOLANA_PAYMENT_RPC || SOLANA_MAINNET_RPC })
449
+ : null);
450
+ const stripeApiKey = process.env.ENIGMA_STRIPE_SECRET_KEY || null;
451
+ const stripeWebhookSecret = process.env.ENIGMA_STRIPE_WEBHOOK_SECRET || null;
452
+ if (!options.stripe && Boolean(stripeApiKey) !== Boolean(stripeWebhookSecret)) {
453
+ throw new Error('Stripe subscription rail requires both ENIGMA_STRIPE_SECRET_KEY and ENIGMA_STRIPE_WEBHOOK_SECRET');
454
+ }
455
+ const stripe = options.stripe || (stripeApiKey && stripeWebhookSecret
456
+ ? createStripeSubscriptionProvider({
457
+ apiKey: stripeApiKey,
458
+ webhookSecret: stripeWebhookSecret,
459
+ baseUrl: process.env.ENIGMA_STRIPE_API_BASE || undefined,
460
+ })
461
+ : null);
462
+ const paymentStore = options.paymentStore || (options.inMemoryOnly === true
463
+ ? createEphemeralPaymentStore()
464
+ : createFilePaymentStore({ path: options.paymentStorePath || path.join(path.dirname(bundlePath), 'payments.json') }));
465
+ const payments = options.payments || createCreditPaymentService({
466
+ store: paymentStore,
467
+ credits,
468
+ packages: configuredPaymentPackages,
469
+ usdcDestinationTokenAccount,
470
+ tokenBurnMint,
471
+ verifySolanaPayment,
472
+ stripe,
473
+ });
474
+ function localSessionPrincipal(req) {
475
+ const candidate = String(req.headers['x-enigma-auth-token'] || '');
476
+ const actual = Buffer.from(candidate);
477
+ const expected = Buffer.from(authToken);
478
+ if (actual.length !== expected.length || !crypto.timingSafeEqual(actual, expected)) return null;
479
+ return { subject: 'local-owner', ownerId: 'local-owner', tenantId: 'local', scopes: apiKeys.supportedScopes, authentication: 'desktop_session' };
480
+ }
481
+ function requiredApiScopes(url) {
482
+ if (url.pathname.startsWith('/v1/images/')) return ['images:generate'];
483
+ if (url.pathname.startsWith('/v1/videos/')) return ['video:generate'];
484
+ if (url.pathname.startsWith('/v1/code/')) return ['code:execute'];
485
+ if (url.pathname.startsWith('/v1/browser/')) return ['browser:control'];
486
+ return ['models:invoke'];
487
+ }
488
+ async function authenticateApiRequest(req, requiredScopes) {
489
+ const local = localSessionPrincipal(req);
490
+ if (local) {
491
+ usage.setPrincipal(local);
492
+ req.enigmaPrincipal = local;
493
+ return local;
494
+ }
495
+ const rawApiKey = apiKeyFromAuthorization(req.headers.authorization);
496
+ let principal = rawApiKey ? await apiKeys.authenticate(rawApiKey, { requiredScopes }) : null;
497
+ if (!principal && oauthBearerAuthorizer) {
498
+ principal = await oauthBearerAuthorizer({
499
+ authorization: req.headers.authorization,
500
+ requiredScopes,
501
+ request: req,
502
+ });
503
+ }
504
+ if (principal) {
505
+ usage.setPrincipal(principal);
506
+ req.enigmaPrincipal = principal;
507
+ }
508
+ return principal;
509
+ }
510
+ const apiKeyHttp = createApiKeyHttpHandler({
511
+ apiKeys,
512
+ authorizeAdmin: async ({ request }) => localSessionPrincipal(request),
513
+ });
514
+ const usageHttp = createUsageHttpHandler({
515
+ usage,
516
+ authorize: async ({ request, requiredScopes }) => authenticateApiRequest(request, requiredScopes),
517
+ });
518
+ const creditsHttp = createPrivateCreditsHttpHandler({
519
+ credits,
520
+ authorizeCreation: async ({ request }) => Boolean(await authenticateApiRequest(request, ['credits:create'])),
521
+ admitRecovery: async ({ request }) => {
522
+ if (!await authenticateApiRequest(request, ['credits:create'])) return false;
523
+ const cutoff = Date.now() - 60_000;
524
+ while (recoveryAttempts.length && recoveryAttempts[0] < cutoff) recoveryAttempts.shift();
525
+ if (recoveryAttempts.length >= 5) return false;
526
+ recoveryAttempts.push(Date.now());
527
+ return true;
528
+ },
529
+ });
530
+ const paymentHttp = createPaymentHttpHandler({
531
+ payments,
532
+ stripe,
533
+ successUrl: () => `http://${host}:${boundPort}/?payment=success`,
534
+ cancelUrl: () => `http://${host}:${boundPort}/?payment=cancelled`,
535
+ });
536
+ const instanceId = crypto.randomUUID();
537
+
538
+ const phantomBridge = new PhantomBrowserBridge({
539
+ port: 0,
540
+ rpcUrl: SOLANA_MAINNET_RPC,
541
+ });
542
+
543
+ const autoAnchorEngine = new AutoAnchorEngine({
544
+ rootDir: path.dirname(bundlePath),
545
+ safeStorage: options.safeStorage,
546
+ cluster: 'mainnet-beta',
547
+ });
548
+ const stdoutCapture = new MemoryCaptureStream();
549
+ const stderrCapture = new MemoryCaptureStream();
550
+
551
+ const vectorStoreDir = path.dirname(bundlePath);
552
+ const shouldPersistVectorStore = options.inMemoryOnly !== true && options.persistVectorStore !== false;
553
+ const shouldPersistSdkState = options.inMemoryOnly !== true && options.persistSdkState !== false;
554
+ const requiresPersistentKey = shouldPersistVectorStore || shouldPersistSdkState;
555
+ const vectorStorePath = options.vectorStorePath
556
+ ? path.resolve(options.vectorStorePath)
557
+ : path.join(vectorStoreDir, 'vector_store.enc.json');
558
+ const vectorStoreKeyPath = path.join(vectorStoreDir, 'vector_store.key.enc');
559
+
560
+ function getOrDeriveVectorMasterKey() {
561
+ if (options.vaultKey) {
562
+ return Buffer.isBuffer(options.vaultKey) ? options.vaultKey : Buffer.from(options.vaultKey, 'hex');
563
+ }
564
+ if (options.customKey || (options.zk && options.zk.customKey)) {
565
+ const k = String(options.customKey || options.zk.customKey);
566
+ return crypto.createHash('sha256').update(`enigma.custom_key.vector.${k}`).digest();
567
+ }
568
+ const passphraseInput = options.passphrase || process.env.ENIGMA_PASSPHRASE;
569
+ if (passphraseInput) {
570
+ const saltPath = path.join(vectorStoreDir, 'vector_store.kdf.json');
571
+ let salt;
572
+ if (fs.existsSync(saltPath)) {
573
+ try {
574
+ const kdfData = JSON.parse(fs.readFileSync(saltPath, 'utf8'));
575
+ salt = Buffer.from(kdfData.salt, 'base64');
576
+ } catch (e) {
577
+ salt = crypto.randomBytes(16);
578
+ }
579
+ } else {
580
+ salt = crypto.randomBytes(16);
581
+ fs.mkdirSync(vectorStoreDir, { recursive: true });
582
+ fs.writeFileSync(saltPath, JSON.stringify({ algorithm: 'pbkdf2', salt: salt.toString('base64'), iterations: PBKDF2_KDF_SPEC.iterations }), 'utf8');
583
+ }
584
+ return deriveKeysFromPassphrase(passphraseInput, salt, PBKDF2_KDF_SPEC).vaultKey;
585
+ }
586
+ if (options.safeStorage && typeof options.safeStorage.encryptString === 'function' && typeof options.safeStorage.decryptString === 'function') {
587
+ if (fs.existsSync(vectorStoreKeyPath)) {
588
+ const encData = JSON.parse(fs.readFileSync(vectorStoreKeyPath, 'utf8'));
589
+ if (encData.scheme !== 'enigma.safestorage.v1' || !encData.encryptedHex) {
590
+ throw new Error('Corrupted safeStorage vector key custody file');
591
+ }
592
+ const plainHex = options.safeStorage.decryptString(Buffer.from(encData.encryptedHex, 'hex'));
593
+ return Buffer.from(plainHex, 'hex');
594
+ } else {
595
+ const freshKey = crypto.randomBytes(32);
596
+ const encrypted = options.safeStorage.encryptString(freshKey.toString('hex'));
597
+ fs.mkdirSync(vectorStoreDir, { recursive: true });
598
+ fs.writeFileSync(vectorStoreKeyPath, JSON.stringify({
599
+ scheme: 'enigma.safestorage.v1',
600
+ encryptedHex: Buffer.from(encrypted).toString('hex'),
601
+ createdAt: Date.now(),
602
+ }, null, 2), 'utf8');
603
+ return freshKey;
604
+ }
605
+ }
606
+ if (options.allowHostBoundDemo === true || process.env.ENIGMA_ALLOW_HOSTBOUND === '1') {
607
+ const hostPayload = `${crypto.createHash('sha256').update(bundlePath).digest('hex')}:demo_host_key`;
608
+ return crypto.scryptSync(hostPayload, 'enigma.hostbound.vector.salt', 32);
609
+ }
610
+ if (!requiresPersistentKey) {
611
+ return crypto.randomBytes(32);
612
+ }
613
+ throw new Error(
614
+ 'Vector store key custody not configured: provide safeStorage (Electron), a vaultKey, a passphrase (or ENIGMA_PASSPHRASE), or set allowHostBoundDemo:true for local CLI demos.'
615
+ );
616
+ }
617
+
618
+ let masterVectorKey = null;
619
+ if (requiresPersistentKey) {
620
+ masterVectorKey = getOrDeriveVectorMasterKey();
621
+ }
622
+
623
+ const ownsVectorStore = !options.vectorStore || options.ownVectorStore === true;
624
+ const ownsEmbeddingProvider = !options.embeddingProvider || options.ownEmbeddingProvider === true;
625
+ let vectorStore = options.vectorStore || new EncryptedVectorStore({
626
+ quantize: true,
627
+ ...(options.embeddingProvider ? { embeddingProvider: options.embeddingProvider } : {}),
628
+ });
629
+ if (shouldPersistVectorStore && fs.existsSync(vectorStorePath)) {
630
+ try {
631
+ const encryptedBundle = JSON.parse(fs.readFileSync(vectorStorePath, 'utf8'));
632
+ vectorStore = EncryptedVectorStore.decryptFromVault(encryptedBundle, masterVectorKey, {
633
+ ...(options.embeddingProvider || options.vectorStore?.embedder
634
+ ? { embeddingProvider: options.embeddingProvider || options.vectorStore.embedder }
635
+ : {}),
636
+ });
637
+ } catch (err) {
638
+ throw new Error(
639
+ `Failed to decrypt desktop vector store at "${vectorStorePath}": ${err.message}. `
640
+ + 'The existing encrypted file has been preserved intact. '
641
+ + 'Provide the original passphrase via ENIGMA_PASSPHRASE or use --passphrase-file to unlock.'
642
+ );
643
+ }
644
+ }
645
+
646
+ function persistVectorStore() {
647
+ if (!shouldPersistVectorStore || !vectorStorePath) return;
648
+ const bundle = vectorStore.encryptForVault(masterVectorKey);
649
+ fs.mkdirSync(path.dirname(vectorStorePath), { recursive: true });
650
+ const tmp = `${vectorStorePath}.tmp.${crypto.randomBytes(3).toString('hex')}`;
651
+ fs.writeFileSync(tmp, JSON.stringify(bundle, null, 2), 'utf8');
652
+ fs.renameSync(tmp, vectorStorePath);
653
+ }
654
+
655
+ const modelAdapters = createDesktopModelAdapterRegistry(options);
656
+ const platformRuntime = options.platformRuntime && typeof options.platformRuntime === 'object'
657
+ ? options.platformRuntime
658
+ : createEnvironmentPlatformRuntime({ usage });
659
+ const platformHttp = createInferenceHttpHandler({
660
+ ...platformRuntime,
661
+ authorize: async (req) => authenticateApiRequest(req, requiredApiScopes(new URL(req.url, `http://${host}:${boundPort}`))),
662
+ });
663
+ const x402Accepts = options.x402Accepts || (process.env.ENIGMA_X402_ACCEPTS_JSON ? JSON.parse(process.env.ENIGMA_X402_ACCEPTS_JSON) : null);
664
+ const x402ResourceBaseUrl = options.x402ResourceBaseUrl || process.env.ENIGMA_X402_RESOURCE_BASE_URL || null;
665
+ const x402Facilitator = options.x402Facilitator || (process.env.ENIGMA_X402_FACILITATOR_URL
666
+ ? createX402FacilitatorClient({
667
+ baseUrl: process.env.ENIGMA_X402_FACILITATOR_URL,
668
+ apiKey: process.env.ENIGMA_X402_FACILITATOR_API_KEY || null,
669
+ })
670
+ : null);
671
+ const x402ConfiguredParts = [x402Accepts, x402ResourceBaseUrl, x402Facilitator].filter(Boolean).length;
672
+ if (x402ConfiguredParts !== 0 && x402ConfiguredParts !== 3) {
673
+ throw new Error('x402 chat requires accepts, resource base URL, and facilitator configuration together');
674
+ }
675
+ const x402Http = x402ConfiguredParts === 3
676
+ ? createX402ResourceHandler({
677
+ facilitator: x402Facilitator,
678
+ routes: [{
679
+ method: 'POST',
680
+ path: '/v1/x402/chat/completions',
681
+ resource: {
682
+ url: new URL('/v1/x402/chat/completions', x402ResourceBaseUrl).toString(),
683
+ description: 'Enigma private-routing chat completion',
684
+ mimeType: 'application/json',
685
+ serviceName: 'Enigma',
686
+ tags: ['ai', 'privacy'],
687
+ },
688
+ accepts: x402Accepts,
689
+ execute: async ({ rawBody }) => {
690
+ if (!platformRuntime.chat) throw new Error('chat runtime is not configured');
691
+ let payload;
692
+ try { payload = JSON.parse(rawBody.toString('utf8') || '{}'); }
693
+ catch { throw new TypeError('x402 chat body must be valid JSON'); }
694
+ return { body: await platformRuntime.chat.complete(payload) };
695
+ },
696
+ }],
697
+ })
698
+ : async () => false;
699
+ const sdkOptions = options.sdk && typeof options.sdk === 'object' ? options.sdk : {};
700
+ const federationOptions = options.federation && typeof options.federation === 'object'
701
+ ? options.federation
702
+ : {};
703
+ const sdkScope = String(sdkOptions.scope || 'enigma.desktop').trim();
704
+ const sdkStoragePath = shouldPersistSdkState
705
+ ? path.resolve(sdkOptions.storagePath || path.join(vectorStoreDir, 'desktop_agent_state.enc.json'))
706
+ : null;
707
+ const operationalStatePath = shouldPersistSdkState
708
+ ? path.resolve(sdkOptions.operationalStatePath || path.join(vectorStoreDir, 'desktop_sdk_runtime.enc.json'))
709
+ : null;
710
+ const sdkVaultKey = masterVectorKey
711
+ ? Buffer.from(crypto.hkdfSync(
712
+ 'sha256',
713
+ masterVectorKey,
714
+ Buffer.from('enigma.desktop.sdk.salt.v1', 'utf8'),
715
+ Buffer.from('enigma.desktop.sdk.v1', 'utf8'),
716
+ 32
717
+ ))
718
+ : crypto.randomBytes(32);
719
+ const operationalKey = Buffer.from(crypto.hkdfSync(
720
+ 'sha256',
721
+ sdkVaultKey,
722
+ Buffer.from('enigma.desktop.operational.salt.v1', 'utf8'),
723
+ Buffer.from('enigma.desktop.operational.v1', 'utf8'),
724
+ 32
725
+ ));
726
+ const operationalAad = Buffer.from('enigma.desktop.sdk_runtime.v1', 'utf8');
727
+ let operationalState = null;
728
+ let sdkPromise = null;
729
+ let federationBridgePromise = null;
730
+ let federationBridge = null;
731
+ let federationTransport = null;
732
+ let agentTurnQueue = Promise.resolve();
733
+ const federationEvents = [];
734
+
735
+ function serializeIdentity(identity) {
736
+ return {
737
+ scope: identity.scope,
738
+ publicKey: identity.publicKey.toString('base64'),
739
+ secretKey: identity.secretKey.toString('base64'),
740
+ encryptionPublicKey: identity.encryptionPublicKey.toString('base64'),
741
+ encryptionSecretKey: identity.encryptionSecretKey.toString('base64'),
742
+ };
743
+ }
744
+
745
+ function deserializeIdentity(value) {
746
+ if (!value || typeof value !== 'object') throw new Error('Desktop SDK identity state is missing');
747
+ const identity = {
748
+ scope: String(value.scope || ''),
749
+ publicKey: Buffer.from(value.publicKey || '', 'base64'),
750
+ secretKey: Buffer.from(value.secretKey || '', 'base64'),
751
+ encryptionPublicKey: Buffer.from(value.encryptionPublicKey || '', 'base64'),
752
+ encryptionSecretKey: Buffer.from(value.encryptionSecretKey || '', 'base64'),
753
+ };
754
+ for (const [name, key] of Object.entries(identity)) {
755
+ if (name !== 'scope' && key.length !== 32) {
756
+ throw new Error(`Desktop SDK identity ${name} must be exactly 32 bytes`);
757
+ }
758
+ }
759
+ if (identity.scope !== sdkScope) {
760
+ throw new Error(`Desktop SDK identity scope "${identity.scope}" does not match "${sdkScope}"`);
761
+ }
762
+ identity.destination = deriveDestination(
763
+ identity.publicKey,
764
+ identity.encryptionPublicKey,
765
+ identity.scope
766
+ );
767
+ identity.destinationHex = identity.destination.toString('hex');
768
+ return identity;
769
+ }
770
+
771
+ function readOperationalState() {
772
+ if (operationalState) return operationalState;
773
+ if (!operationalStatePath || !fs.existsSync(operationalStatePath)) {
774
+ operationalState = {
775
+ schema: 'enigma.desktop.sdk_runtime.v1',
776
+ identity: serializeIdentity(generateMeshIdentity(sdkScope)),
777
+ federation: null,
778
+ };
779
+ return operationalState;
780
+ }
781
+ try {
782
+ const envelope = JSON.parse(fs.readFileSync(operationalStatePath, 'utf8'));
783
+ if (envelope?.schema !== 'enigma.desktop.encrypted_sdk_runtime.v1') {
784
+ throw new Error('unsupported encrypted runtime schema');
785
+ }
786
+ const decipher = crypto.createDecipheriv(
787
+ 'aes-256-gcm',
788
+ operationalKey,
789
+ Buffer.from(envelope.iv, 'base64')
790
+ );
791
+ decipher.setAAD(operationalAad);
792
+ decipher.setAuthTag(Buffer.from(envelope.authTag, 'base64'));
793
+ const plaintext = Buffer.concat([
794
+ decipher.update(Buffer.from(envelope.ciphertext, 'base64')),
795
+ decipher.final(),
796
+ ]);
797
+ const value = JSON.parse(plaintext.toString('utf8'));
798
+ if (value?.schema !== 'enigma.desktop.sdk_runtime.v1') {
799
+ throw new Error('invalid decrypted runtime schema');
800
+ }
801
+ deserializeIdentity(value.identity);
802
+ operationalState = value;
803
+ return operationalState;
804
+ } catch (error) {
805
+ throw new Error(
806
+ `Failed to decrypt desktop SDK runtime at "${operationalStatePath}": ${error.message}. `
807
+ + 'The encrypted file has been preserved; restore the original desktop key custody.'
808
+ );
809
+ }
810
+ }
811
+
812
+ function persistOperationalState() {
813
+ if (!operationalStatePath) return;
814
+ const state = readOperationalState();
815
+ if (federationBridge) state.federation = federationBridge.exportState();
816
+ const iv = crypto.randomBytes(12);
817
+ const cipher = crypto.createCipheriv('aes-256-gcm', operationalKey, iv);
818
+ cipher.setAAD(operationalAad);
819
+ const ciphertext = Buffer.concat([
820
+ cipher.update(Buffer.from(JSON.stringify(state), 'utf8')),
821
+ cipher.final(),
822
+ ]);
823
+ const envelope = {
824
+ schema: 'enigma.desktop.encrypted_sdk_runtime.v1',
825
+ iv: iv.toString('base64'),
826
+ authTag: cipher.getAuthTag().toString('base64'),
827
+ ciphertext: ciphertext.toString('base64'),
828
+ };
829
+ fs.mkdirSync(path.dirname(operationalStatePath), { recursive: true });
830
+ const temporaryPath = `${operationalStatePath}.tmp.${crypto.randomBytes(4).toString('hex')}`;
831
+ fs.writeFileSync(temporaryPath, JSON.stringify(envelope, null, 2), 'utf8');
832
+ fs.renameSync(temporaryPath, operationalStatePath);
833
+ }
834
+
835
+ function getSdk() {
836
+ if (!sdkPromise) {
837
+ const pending = (async () => {
838
+ const identity = deserializeIdentity(readOperationalState().identity);
839
+ const connectOptions = {
840
+ agentId: sdkOptions.agentId || 'enigma-desktop',
841
+ scope: sdkScope,
842
+ storagePath: sdkStoragePath,
843
+ vaultKey: sdkVaultKey,
844
+ identity,
845
+ ...(sdkOptions.ragClient ? { ragClient: sdkOptions.ragClient } : {}),
846
+ ...(sdkOptions.ownRagClient === true ? { ownRagClient: true } : {}),
847
+ };
848
+ const instance = sdkOptions.enigmaFactory
849
+ ? await sdkOptions.enigmaFactory(connectOptions)
850
+ : await Enigma.connect(connectOptions);
851
+ persistOperationalState();
852
+ return instance;
853
+ })();
854
+ sdkPromise = pending;
855
+ pending.catch(() => {
856
+ if (sdkPromise === pending) sdkPromise = null;
857
+ });
858
+ }
859
+ return sdkPromise;
860
+ }
861
+
862
+ function configuredFederationPeers() {
863
+ const configured = federationOptions.registeredPeers ?? federationOptions.peerDescriptors ?? [];
864
+ if (configured instanceof Map) return [...configured.entries()];
865
+ if (Array.isArray(configured)) {
866
+ return configured.map((peer) => [peer?.peerId ?? peer?.peer_id, peer]);
867
+ }
868
+ if (configured && typeof configured === 'object') return Object.entries(configured);
869
+ throw new TypeError('federation registeredPeers must be an array, object, or Map');
870
+ }
871
+
872
+ function recordFederationEvent(kind, event) {
873
+ federationEvents.push({
874
+ kind,
875
+ at: new Date().toISOString(),
876
+ packetId: event?.packet_id ?? event?.packetId ?? null,
877
+ peerId: event?.fromPeerId ?? null,
878
+ });
879
+ if (federationEvents.length > 25) federationEvents.splice(0, federationEvents.length - 25);
880
+ persistOperationalState();
881
+ }
882
+
883
+ async function getFederationBridge() {
884
+ if (!federationBridgePromise) {
885
+ const pending = (async () => {
886
+ const enigma = await getSdk();
887
+ federationTransport = federationOptions.transport || new FederationWebSocketTransport({
888
+ id: federationOptions.transportId || 'desktop-federation-ws',
889
+ host: '127.0.0.1',
890
+ port: federationOptions.port ?? 0,
891
+ peers: federationOptions.peerAddresses || [],
892
+ });
893
+ const bridge = new GhostMeshFederationBridge({
894
+ enigma,
895
+ transport: federationTransport,
896
+ scope: sdkScope,
897
+ manageTransport: federationOptions.manageTransport !== false,
898
+ requestTimeoutMs: federationOptions.requestTimeoutMs,
899
+ state: readOperationalState().federation || undefined,
900
+ });
901
+ for (const [peerId, peer] of configuredFederationPeers()) {
902
+ bridge.registerPeer(peerId, peer);
903
+ }
904
+ bridge.on('grant', (event) => recordFederationEvent('grant', event));
905
+ bridge.on('query', (event) => recordFederationEvent('query', event));
906
+ bridge.on('response', (event) => recordFederationEvent('response', event));
907
+ bridge.on('revocation', (event) => recordFederationEvent('revocation', event));
908
+ bridge.on('rejected', (event) => recordFederationEvent('rejected', event));
909
+ federationBridge = bridge;
910
+ return bridge;
911
+ })();
912
+ federationBridgePromise = pending;
913
+ pending.catch(() => {
914
+ if (federationBridgePromise === pending) federationBridgePromise = null;
915
+ });
916
+ }
917
+ return federationBridgePromise;
918
+ }
919
+
920
+ function federationStatus(bridge) {
921
+ const endpoint = bridge.localEndpoint;
922
+ const transport = typeof federationTransport?.getStats === 'function'
923
+ ? federationTransport.getStats()
924
+ : {
925
+ id: federationTransport?.id || null,
926
+ running: Boolean(federationTransport?.running),
927
+ };
928
+ return {
929
+ ok: true,
930
+ schema: 'enigma.desktop.federation_status.v1',
931
+ started: bridge.started,
932
+ scope: bridge.scope,
933
+ endpoint: {
934
+ destinationHex: endpoint.destinationHex
935
+ || deriveDestination(endpoint.publicKey, endpoint.encryptionPublicKey, bridge.scope).toString('hex'),
936
+ publicKeyHex: Buffer.from(endpoint.publicKey).toString('hex'),
937
+ encryptionPublicKeyHex: Buffer.from(endpoint.encryptionPublicKey).toString('hex'),
938
+ },
939
+ transport,
940
+ registeredPeers: [...bridge.peers.values()].map((peer) => ({
941
+ peerId: peer.peerId,
942
+ scope: peer.scope,
943
+ destinationHex: peer.destination.toString('hex'),
944
+ publicKeyHex: peer.publicKey.toString('hex'),
945
+ encryptionPublicKeyHex: peer.encryptionPublicKey.toString('hex'),
946
+ })),
947
+ activeGrantIds: [...bridge.knownGrants.keys()],
948
+ pendingQueries: bridge.pendingQueries.size,
949
+ recentEvents: federationEvents.slice().reverse(),
950
+ persisted: Boolean(operationalStatePath),
951
+ };
952
+ }
953
+
954
+ function enqueueAgentTurn(action) {
955
+ const pending = agentTurnQueue.then(action);
956
+ agentTurnQueue = pending.catch(() => {});
957
+ return pending;
958
+ }
959
+
960
+ const session = new TerminalSession({
961
+ bundlePath,
962
+ io: {
963
+ stdin: process.stdin,
964
+ stdout: stdoutCapture,
965
+ stderr: stderrCapture,
966
+ },
967
+ });
968
+
969
+ session.phantomBridge = phantomBridge;
970
+ phantomBridge.onWalletConnected = (wallet) => {
971
+ session.connectedWallet = wallet;
972
+ };
973
+ session.autoAnchorEngine = autoAnchorEngine;
974
+
975
+ let executeQueue = Promise.resolve();
976
+ let zkRuntimePromise = null;
977
+ let zkActionQueue = Promise.resolve();
978
+
979
+ function getZkRuntime() {
980
+ if (!zkRuntimePromise) {
981
+ const zkOptions = options.zk && typeof options.zk === 'object' ? options.zk : {};
982
+ const pending = createZkRuntime({
983
+ ...zkOptions,
984
+ safeStorage: zkOptions.safeStorage ?? options.safeStorage,
985
+ statePath: zkOptions.statePath || defaultZkStatePath(bundlePath),
986
+ });
987
+ zkRuntimePromise = pending;
988
+ pending.catch(() => {
989
+ if (zkRuntimePromise === pending) zkRuntimePromise = null;
990
+ });
991
+ }
992
+ return zkRuntimePromise;
993
+ }
994
+
995
+ function enqueueZkAction(action) {
996
+ const pending = zkActionQueue.then(action);
997
+ zkActionQueue = pending.catch(() => {});
998
+ return pending;
999
+ }
1000
+ // --- GhostMesh P2P Mesh Subsystem -----------------------------------------
1001
+ let meshNode = null;
1002
+ const meshInbox = [];
1003
+ const meshSaleOffers = [];
1004
+ const meshZkProofs = [];
1005
+
1006
+ async function getMeshNode() {
1007
+ if (!meshNode) {
1008
+ const meshOpts = options.mesh && typeof options.mesh === 'object' ? options.mesh : {};
1009
+ const { MeshGossipNode, WebSocketTransport } = await import('../../../packages/mesh/src/index.js');
1010
+ meshNode = new MeshGossipNode({
1011
+ scope: meshOpts.scope || 'enigma.memory',
1012
+ nodeId: meshOpts.nodeId,
1013
+ });
1014
+ if (meshOpts.port !== undefined && meshOpts.port !== null) {
1015
+ const wsTransport = new WebSocketTransport({
1016
+ port: meshOpts.port,
1017
+ peers: meshOpts.peers || [],
1018
+ });
1019
+ meshNode.addTransport(wsTransport);
1020
+ }
1021
+ meshNode.on('capsule', (capsule) => {
1022
+ meshInbox.push({ ...capsule, receivedAt: new Date().toISOString() });
1023
+ });
1024
+ meshNode.on('sale:offer', (offer) => {
1025
+ meshSaleOffers.push({ ...offer, receivedAt: new Date().toISOString() });
1026
+ });
1027
+ meshNode.on('zk:proof', (proof) => {
1028
+ meshZkProofs.push({ ...proof, receivedAt: new Date().toISOString() });
1029
+ });
1030
+ await meshNode.start();
1031
+ }
1032
+ return meshNode;
1033
+ }
1034
+ // --- Custody-local proving + opaque relayer sessions -----------------------
1035
+ // Private proving inputs never enter the renderer. The custody runtime
1036
+ // proves locally, stores the public proof beside a single-use opaque handle,
1037
+ // and zeroizes private pending values after settlement, expiry, or eviction.
1038
+ const FR_MODULUS = BigInt('0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001');
1039
+ const PREPARE_TTL_MS = 10 * 60 * 1000;
1040
+ const PREPARE_MAX_SESSIONS = 32;
1041
+ const pendingPrepares = new Map();
1042
+ const relayInFlight = new Set();
1043
+ const scrubPrepareValue = (value) => {
1044
+ if (!value || typeof value !== 'object') return;
1045
+ if (Buffer.isBuffer(value) || value instanceof Uint8Array) {
1046
+ value.fill(0);
1047
+ return;
1048
+ }
1049
+ for (const key of Object.keys(value)) {
1050
+ const entry = value[key];
1051
+ if (entry && typeof entry === 'object') scrubPrepareValue(entry);
1052
+ else if (typeof entry === 'string') value[key] = '';
1053
+ else if (typeof entry === 'bigint') value[key] = 0n;
1054
+ else if (typeof entry === 'number') value[key] = 0;
1055
+ else value[key] = null;
1056
+ }
1057
+ };
1058
+ const clearPendingPrepare = (entry) => {
1059
+ if (!entry) return;
1060
+ scrubPrepareValue(entry.pending);
1061
+ entry.pending = null;
1062
+ entry.proofBytes = null;
1063
+ entry.publicInputs = null;
1064
+ };
1065
+ // Witness material the relayer must NEVER receive; presence is a hard 400.
1066
+ const RELAY_FORBIDDEN_FIELDS = [
1067
+ 'ownerKey', 'rho', 'payloadCommit', 'witness', 'witnessInput',
1068
+ 'noteOpenings', 'recipientSecretKeyB64', 'secretKey', 'plaintext',
1069
+ ];
1070
+ const PROVER_ASSET_ROUTE = {
1071
+ 'action_transition.wasm': { key: 'wasm', contentType: 'application/wasm' },
1072
+ 'action_transition.zkey': { key: 'zkey', contentType: 'application/octet-stream' },
1073
+ 'snarkjs.min.js': { key: 'snarkjs', contentType: 'text/javascript; charset=utf-8' },
1074
+ };
1075
+
1076
+ async function proverAssetFiles(runtime) {
1077
+ const zkOptions = options.zk && typeof options.zk === 'object' ? options.zk : {};
1078
+ // wasm/zkey: via the checksum-pinned resolver when available (packaged
1079
+ // installs lack circuits/build); injected-deps tests keep the fixture paths.
1080
+ const assets = typeof runtime.getProvingAssetPaths === 'function'
1081
+ ? await runtime.getProvingAssetPaths()
1082
+ : runtime.artifactPaths;
1083
+ let snarkjs = null;
1084
+ if (zkOptions.snarkjsBundlePath) {
1085
+ snarkjs = path.resolve(zkOptions.snarkjsBundlePath);
1086
+ } else {
1087
+ // snarkjs is a declared runtime dependency: resolve its package entry
1088
+ // and take the sibling IIFE bundle (build/snarkjs.min.js).
1089
+ try {
1090
+ const req = createRequire(import.meta.url);
1091
+ const main = req.resolve('snarkjs');
1092
+ const candidate = path.join(path.dirname(main), 'snarkjs.min.js');
1093
+ if (fs.existsSync(candidate)) snarkjs = candidate;
1094
+ } catch { /* fall through to legacy walk */ }
1095
+ if (!snarkjs) {
1096
+ let dir = path.dirname(assets.zkey);
1097
+ for (let i = 0; i < 4 && !snarkjs; i += 1) {
1098
+ dir = path.dirname(dir);
1099
+ const candidate = path.join(dir, 'node_modules', 'snarkjs', 'build', 'snarkjs.min.js');
1100
+ if (fs.existsSync(candidate)) snarkjs = candidate;
1101
+ }
1102
+ }
1103
+ }
1104
+ return { wasm: assets.wasm, zkey: assets.zkey, snarkjs };
1105
+ }
1106
+
1107
+ function validateRelayShape(proof, publicInputs, expectedInputCount) {
1108
+ if (!proof || typeof proof !== 'object' || Array.isArray(proof)) {
1109
+ return 'proof must be an object { a, b, c } of hex strings (64/128/64 bytes)';
1110
+ }
1111
+ const expectedHexLength = { a: 128, b: 256, c: 128 };
1112
+ for (const [key, hexLength] of Object.entries(expectedHexLength)) {
1113
+ const value = proof[key];
1114
+ if (typeof value !== 'string' || !/^[0-9a-fA-F]+$/.test(value) || value.length !== hexLength) {
1115
+ return `proof.${key} must be a ${hexLength}-character hex string (${hexLength / 2} bytes)`;
1116
+ }
1117
+ }
1118
+ if (!Array.isArray(publicInputs) || publicInputs.length !== expectedInputCount) {
1119
+ return `publicInputs must be an array of ${expectedInputCount} × 64-hex field elements`;
1120
+ }
1121
+ for (let i = 0; i < publicInputs.length; i += 1) {
1122
+ const value = publicInputs[i];
1123
+ if (typeof value !== 'string' || !/^[0-9a-fA-F]{64}$/.test(value)) {
1124
+ return `publicInputs[${i}] must be a 64-character hex string`;
1125
+ }
1126
+ if (BigInt(`0x${value}`) >= FR_MODULUS) {
1127
+ return `publicInputs[${i}] is not a canonical BN254 scalar field element`;
1128
+ }
1129
+ }
1130
+ return null;
1131
+ }
1132
+
1133
+
1134
+ // --- Sealed Sale (ZKCP) marketplace helpers --------------------------------
1135
+ // SOVEREIGNTY INVARIANT: no user secret (ownerKey/rho/DEK/buyerSecret/
1136
+ // plaintext) may cross these endpoints. Proving happens in this process
1137
+ // against the checksum-pinned sealed_sale_release artifacts; the wire
1138
+ // carries only { proof bytes, 6 public inputs, pubkeys, terms } and public
1139
+ // commitments/ciphertext. The envelope triple leaves custody only in claim.
1140
+ const SALE_FORBIDDEN_FIELDS = [
1141
+ 'ownerKey', 'rho', 'dekLo', 'dekHi', 'buyerSecret', 'secretKey',
1142
+ 'recipientSecretKeyB64', 'noteOpenings', 'noteContext', 'witness',
1143
+ 'witnessInput', 'plaintext', 'esk', 'payloadCommit',
1144
+ 'epk', 'encDek', 'nonce', 'nonceHex',
1145
+ ];
1146
+ const saleForbiddenField = (payload) => {
1147
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return 'request body must be a JSON object';
1148
+ for (const field of SALE_FORBIDDEN_FIELDS) {
1149
+ if (field in payload) {
1150
+ return `sale endpoints accept only proof/public-input/commitment material — secret field "${field}" must never be sent`;
1151
+ }
1152
+ }
1153
+ return null;
1154
+ };
1155
+ const parseHex32 = (value, name) => {
1156
+ if (typeof value !== 'string' || !/^[0-9a-fA-F]{64}$/.test(value)) {
1157
+ return { error: `${name} must be a 64-character hex string (32 bytes)` };
1158
+ }
1159
+ return { bytes: Buffer.from(value, 'hex') };
1160
+ };
1161
+ const parseUint64 = (value, name) => {
1162
+ if (typeof value !== 'string' && typeof value !== 'number') return { error: `${name} must be an integer (string or number)` };
1163
+ if (!/^(0|[1-9][0-9]*)$/.test(String(value))) return { error: `${name} must be a non-negative integer` };
1164
+ const parsed = BigInt(value);
1165
+ if (parsed >= (1n << 64n)) return { error: `${name} is out of u64 range` };
1166
+ return { value: parsed };
1167
+ };
1168
+ const parseCiphertextChunks = (value) => {
1169
+ if (!Array.isArray(value) || value.length !== 32) {
1170
+ return { error: 'ciphertextChunks must be an array of 32 decimal-string field elements' };
1171
+ }
1172
+ for (let i = 0; i < value.length; i += 1) {
1173
+ const chunk = value[i];
1174
+ if (typeof chunk !== 'string' || !/^(0|[1-9][0-9]*)$/.test(chunk)) {
1175
+ return { error: `ciphertextChunks[${i}] must be a decimal string` };
1176
+ }
1177
+ if (BigInt(chunk) >= FR_MODULUS) {
1178
+ return { error: `ciphertextChunks[${i}] is not a canonical BN254 scalar field element` };
1179
+ }
1180
+ }
1181
+ return { chunks: value };
1182
+ };
1183
+ const parsePredecessorReceiptRefs = (value) => {
1184
+ if (value === undefined) return { refs: [] };
1185
+ if (!Array.isArray(value) || value.length > 16) {
1186
+ return { error: 'predecessorReceiptRefs must be an array of at most 16 ZK settlement receipt refs' };
1187
+ }
1188
+ if (value.some((ref) => typeof ref !== 'string' || !/^zksettle_[a-f0-9]{64}$/.test(ref))) {
1189
+ return { error: 'predecessorReceiptRefs contains an invalid ZK settlement receipt ref' };
1190
+ }
1191
+ return { refs: [...new Set(value)] };
1192
+ };
1193
+
1194
+ const SALE_ERROR_STATUS = {
1195
+ ZK_UNAVAILABLE: 503,
1196
+ BAD_REQUEST: 400,
1197
+ VK_GATE_REJECTED: 409,
1198
+ PROOF_STATE_MISMATCH: 409,
1199
+ NOTE_OPENING_MISSING: 409,
1200
+ NOTE_NOT_SALEABLE: 409,
1201
+ BUYER_KEY_MISSING: 409,
1202
+ SALE_ENVELOPE_MISSING: 409,
1203
+ SALE_ENVELOPE_MISMATCH: 409,
1204
+ SALE_NOT_CLAIMED: 409,
1205
+ SALE_ALREADY_CLAIMED: 409,
1206
+ SALE_TERMS_MISMATCH: 409,
1207
+ SALE_TERMS_NOT_BUYER_BOUND: 409,
1208
+ SALE_CIPHER_MISMATCH: 409,
1209
+ SALE_RECEIPT_MISSING: 404,
1210
+ SALE_OPEN_REJECTED: 422,
1211
+ };
1212
+ const saleErrorStatus = (err) => SALE_ERROR_STATUS[err.code]
1213
+ ?? (err.name === 'ProvingAssetError' ? 501 : 500);
1214
+ const receiptJson = (receipt) => ({
1215
+ saleTermsHash: Buffer.from(receipt.saleTermsHash).toString('hex'),
1216
+ keyCommit: Buffer.from(receipt.keyCommit).toString('hex'),
1217
+ cipherCommit: Buffer.from(receipt.cipherCommit).toString('hex'),
1218
+ envelopeCommit: Buffer.from(receipt.envelopeCommit).toString('hex'),
1219
+ payloadCommitPos: Buffer.from(receipt.payloadCommitPos).toString('hex'),
1220
+ epk: Buffer.from(receipt.epk).toString('hex'),
1221
+ encDek: Buffer.from(receipt.encDek).toString('hex'),
1222
+ nonce: Buffer.from(receipt.nonce).toString('hex'),
1223
+ slot: receipt.slot,
1224
+ claimed: !(receipt.epk.every((b) => b === 0) && receipt.encDek.every((b) => b === 0) && receipt.nonce.every((b) => b === 0)),
1225
+ });
1226
+
1227
+ function writeJson(res, statusCode, value) {
1228
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1229
+ res.end(JSON.stringify(value));
1230
+ }
1231
+
1232
+ function enqueueCommandExecution(commandLine) {
1233
+ const executePromise = executeQueue.then(async () => {
1234
+ stdoutCapture.clear();
1235
+ stderrCapture.clear();
1236
+ const exitCode = await session.executeCommand(commandLine);
1237
+ const output = stdoutCapture.buffer + (stderrCapture.buffer ? '\n' + stderrCapture.buffer : '');
1238
+ return {
1239
+ ok: exitCode === 0 || exitCode === 'EXIT',
1240
+ exitCode: exitCode === 'EXIT' ? 0 : (exitCode || 0),
1241
+ output: output || '(no output)',
1242
+ connectedWallet: session.connectedWallet || null,
1243
+ };
1244
+ });
1245
+ executeQueue = executePromise.catch(() => {});
1246
+ return executePromise;
1247
+ }
1248
+ function validateSecurityHeaders(req, res, { requireAuth = false } = {}) {
1249
+ const rawHost = req.headers['host'];
1250
+ if (!rawHost) {
1251
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1252
+ res.end(JSON.stringify({ ok: false, error: 'host header required' }));
1253
+ return false;
1254
+ }
1255
+
1256
+ let parsedHost;
1257
+ try {
1258
+ parsedHost = new URL(`http://${rawHost}`);
1259
+ } catch {
1260
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1261
+ res.end(JSON.stringify({ ok: false, error: 'malformed host header' }));
1262
+ return false;
1263
+ }
1264
+
1265
+ const hostName = parsedHost.hostname.toLowerCase();
1266
+ const hostPort = Number(parsedHost.port || 80);
1267
+ const validHostName = hostName === '127.0.0.1' || hostName === 'localhost';
1268
+ const validHostPort = boundPort === 0 || hostPort === boundPort;
1269
+
1270
+ if (!validHostName || !validHostPort) {
1271
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1272
+ res.end(JSON.stringify({ ok: false, error: 'host header rejected: must be exact 127.0.0.1 or localhost on bound port' }));
1273
+ return false;
1274
+ }
1275
+
1276
+ const rawOrigin = req.headers['origin'];
1277
+ if (rawOrigin) {
1278
+ let parsedOrigin;
1279
+ try {
1280
+ parsedOrigin = new URL(rawOrigin);
1281
+ } catch {
1282
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1283
+ res.end(JSON.stringify({ ok: false, error: 'malformed origin header' }));
1284
+ return false;
1285
+ }
1286
+
1287
+ const originName = parsedOrigin.hostname.toLowerCase();
1288
+ const originPort = Number(parsedOrigin.port || (parsedOrigin.protocol === 'https:' ? 443 : 80));
1289
+ const validOriginName = originName === '127.0.0.1' || originName === 'localhost';
1290
+ const validOriginPort = boundPort === 0 || originPort === boundPort;
1291
+
1292
+ if (!validOriginName || !validOriginPort) {
1293
+ res.writeHead(403, { 'Content-Type': 'application/json' });
1294
+ res.end(JSON.stringify({ ok: false, error: 'cross-origin request rejected' }));
1295
+ return false;
1296
+ }
1297
+ }
1298
+
1299
+ if (requireAuth) {
1300
+ const token = req.headers['x-enigma-auth-token'];
1301
+ if (!token || token !== authToken) {
1302
+ res.writeHead(401, { 'Content-Type': 'application/json' });
1303
+ res.end(JSON.stringify({ ok: false, error: 'unauthorized or invalid session token' }));
1304
+ return false;
1305
+ }
1306
+ }
1307
+
1308
+ return true;
1309
+ }
1310
+
1311
+ function readBody(req, res, callback) {
1312
+ let body = '';
1313
+ let bytes = 0;
1314
+ let rejected = false;
1315
+
1316
+ req.on('data', (chunk) => {
1317
+ if (rejected) return;
1318
+ bytes += chunk.length;
1319
+ if (bytes > MAX_BODY_BYTES) {
1320
+ rejected = true;
1321
+ req.pause();
1322
+ res.writeHead(413, {
1323
+ 'Content-Type': 'application/json',
1324
+ 'Connection': 'close',
1325
+ });
1326
+ res.end(JSON.stringify({ ok: false, error: 'payload too large' }));
1327
+ req.resume();
1328
+ return;
1329
+ }
1330
+ body += chunk;
1331
+ });
1332
+
1333
+ req.on('end', () => {
1334
+ if (!rejected) {
1335
+ callback(body);
1336
+ }
1337
+ });
1338
+ }
1339
+
1340
+ function writeOperationalError(res, error, fallbackCode, fallbackStatus = 500) {
1341
+ const statusCode = error instanceof DesktopOperationalError
1342
+ ? error.statusCode
1343
+ : fallbackStatus;
1344
+ writeJson(res, statusCode, {
1345
+ ok: false,
1346
+ error: {
1347
+ code: error instanceof DesktopOperationalError ? error.code : fallbackCode,
1348
+ message: error?.message || 'Desktop operational request failed',
1349
+ },
1350
+ });
1351
+ }
1352
+
1353
+ const server = http.createServer(async (req, res) => {
1354
+ // Apply host validation before serving any request (including static files/token)
1355
+ if (!validateSecurityHeaders(req, res, { requireAuth: false })) return;
1356
+
1357
+ const url = new URL(req.url, `http://${host}:${boundPort}`);
1358
+
1359
+ const origin = req.headers['origin'];
1360
+ if (origin) {
1361
+ res.setHeader('Access-Control-Allow-Origin', origin);
1362
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
1363
+ res.setHeader('Access-Control-Expose-Headers', 'PAYMENT-REQUIRED, PAYMENT-RESPONSE');
1364
+ res.setHeader('Access-Control-Allow-Headers', 'Authorization, Content-Type, X-Enigma-Auth-Token, PAYMENT-SIGNATURE');
1365
+ }
1366
+
1367
+ if (req.method === 'OPTIONS') {
1368
+ res.writeHead(204);
1369
+ res.end();
1370
+ return;
1371
+ }
1372
+ if (await apiKeyHttp(req, res)) return;
1373
+ if (await creditsHttp(req, res)) return;
1374
+ if (await paymentHttp(req, res)) return;
1375
+ if (await x402Http(req, res, url)) return;
1376
+ if (await usageHttp(req, res)) return;
1377
+ if (await usage.run({ principal: null }, () => platformHttp.handle(req, res, url))) return;
1378
+ if (url.pathname === '/api/health' && req.method === 'GET') {
1379
+ writeJson(res, 200, {
1380
+ ok: true,
1381
+ schema: 'enigma.desktop.health.v1',
1382
+ service: 'enigma-desktop',
1383
+ version: ENIGMA_VERSION,
1384
+ instanceId,
1385
+ authRequired: true,
1386
+ });
1387
+ return;
1388
+ }
1389
+
1390
+
1391
+ if (url.pathname === '/api/agent/adapters' && req.method === 'GET') {
1392
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1393
+ writeJson(res, 200, {
1394
+ ok: true,
1395
+ schema: 'enigma.desktop.model_adapters.v1',
1396
+ configured: modelAdapters.size > 0,
1397
+ adapters: [...modelAdapters.values()].map(({
1398
+ id,
1399
+ provider,
1400
+ models,
1401
+ contextCarriersByModel,
1402
+ }) => ({
1403
+ id,
1404
+ provider,
1405
+ models,
1406
+ contextCarriersByModel,
1407
+ })),
1408
+ });
1409
+ return;
1410
+ }
1411
+
1412
+ if (url.pathname === '/api/agent/turn' && req.method === 'POST') {
1413
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1414
+ readBody(req, res, async (body) => {
1415
+ let payload;
1416
+ try {
1417
+ payload = JSON.parse(body || '{}');
1418
+ } catch {
1419
+ writeOperationalError(
1420
+ res,
1421
+ new DesktopOperationalError('INVALID_JSON', 'request body must be valid JSON', 400),
1422
+ 'AGENT_TURN_FAILED'
1423
+ );
1424
+ return;
1425
+ }
1426
+ try {
1427
+ const prompt = typeof payload.prompt === 'string' ? payload.prompt.trim() : '';
1428
+ if (!prompt) {
1429
+ throw new DesktopOperationalError(
1430
+ 'AGENT_PROMPT_REQUIRED',
1431
+ 'prompt must be a non-empty string',
1432
+ 400
1433
+ );
1434
+ }
1435
+ const adapter = resolveDesktopModelAdapter(modelAdapters, payload);
1436
+ const selectedModel = String(payload.model).trim();
1437
+ const contextCarrier = resolveDesktopContextCarrierRequest(payload, adapter, selectedModel);
1438
+ const privacyMode = resolveDesktopPrivacyMode(payload);
1439
+ if (privacyMode === 'anchor-guard' && contextCarrier && contextCarrier.mode !== 'text') {
1440
+ throw new DesktopOperationalError(
1441
+ 'PRIVACY_CARRIER_UNSUPPORTED',
1442
+ 'Anchor Guard requires a text context carrier so protected anchors cannot be embedded in an image',
1443
+ 422
1444
+ );
1445
+ }
1446
+ const anchorGuard = createDesktopAnchorGuard(privacyMode);
1447
+ let privacyReport = null;
1448
+ const enigma = await getSdk();
1449
+ const turn = await enqueueAgentTurn(() => enigma.runAgentTurn({
1450
+ prompt,
1451
+ retrieval: payload.retrieval && typeof payload.retrieval === 'object'
1452
+ ? payload.retrieval
1453
+ : {},
1454
+ rememberResponse: payload.rememberResponse !== false,
1455
+ ...(contextCarrier ? { contextCarrier } : {}),
1456
+ metadata: {
1457
+ ...(payload.metadata && typeof payload.metadata === 'object' ? payload.metadata : {}),
1458
+ desktopModel: {
1459
+ adapterId: adapter.id,
1460
+ provider: adapter.provider,
1461
+ model: selectedModel,
1462
+ },
1463
+ },
1464
+ invoke: async (invocation) => {
1465
+ try {
1466
+ const protectedInvocation = privacyMode === 'off'
1467
+ ? invocation
1468
+ : anchorGuard.redactValue(invocation);
1469
+ privacyReport = anchorGuard.report(protectedInvocation);
1470
+ const adapterResult = await adapter.invoke({
1471
+ ...protectedInvocation,
1472
+ adapterId: adapter.id,
1473
+ provider: adapter.provider,
1474
+ model: selectedModel,
1475
+ });
1476
+ const sanitizedResult = sanitizeDesktopAdapterResult(adapterResult);
1477
+ return privacyMode === 'off'
1478
+ ? sanitizedResult
1479
+ : anchorGuard.restoreValue(sanitizedResult);
1480
+ } catch (error) {
1481
+ throw new DesktopOperationalError(
1482
+ 'MODEL_ADAPTER_INVOCATION_FAILED',
1483
+ `Configured model adapter "${adapter.id}" failed: ${error.message}`,
1484
+ 502
1485
+ );
1486
+ }
1487
+ },
1488
+ }));
1489
+ const contextRefs = turn.context.items.map((item) => ({
1490
+ memoryId: item.id,
1491
+ commitment: item.commitment || null,
1492
+ receiptRef: item.metadata?.agentTurn?.receiptRef
1493
+ || item.metadata?.updateReceiptRef
1494
+ || null,
1495
+ }));
1496
+ writeJson(res, 200, {
1497
+ ok: true,
1498
+ schema: 'enigma.desktop.agent_turn.v1',
1499
+ adapter: {
1500
+ id: adapter.id,
1501
+ provider: adapter.provider,
1502
+ model: selectedModel,
1503
+ },
1504
+ result: turn.result,
1505
+ context: {
1506
+ ...(turn.contextCarrier ? {} : { contextText: turn.context.contextText }),
1507
+ refs: contextRefs,
1508
+ estimatedTokens: turn.context.estimatedTokens,
1509
+ },
1510
+ memory: turn.memory
1511
+ ? { id: turn.memory.id, scope: turn.memory.scope, type: turn.memory.type }
1512
+ : null,
1513
+ receiptRefs: turn.receipts.refs,
1514
+ ...(turn.contextCarrier ? { contextCarrier: turn.contextCarrier } : {}),
1515
+ ...(privacyReport ? { privacyReport } : {}),
1516
+ });
1517
+ } catch (error) {
1518
+ writeOperationalError(res, error, 'AGENT_TURN_FAILED');
1519
+ }
1520
+ });
1521
+ return;
1522
+ }
1523
+
1524
+ if (url.pathname === '/api/mesh/federation/status' && req.method === 'GET') {
1525
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1526
+ try {
1527
+ const bridge = await getFederationBridge();
1528
+ writeJson(res, 200, federationStatus(bridge));
1529
+ } catch (error) {
1530
+ writeOperationalError(res, error, 'FEDERATION_BRIDGE_FAILED');
1531
+ }
1532
+ return;
1533
+ }
1534
+
1535
+ if (url.pathname === '/api/mesh/federation/start' && req.method === 'POST') {
1536
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1537
+ try {
1538
+ const bridge = await getFederationBridge();
1539
+ await bridge.start();
1540
+ persistOperationalState();
1541
+ writeJson(res, 200, federationStatus(bridge));
1542
+ } catch (error) {
1543
+ writeOperationalError(res, error, 'FEDERATION_BRIDGE_FAILED');
1544
+ }
1545
+ return;
1546
+ }
1547
+
1548
+
1549
+ // Real Command Execution API Endpoint
1550
+ if ((url.pathname === '/api/exec' || url.pathname === '/api/terminal/exec') && req.method === 'POST') {
1551
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1552
+
1553
+ readBody(req, res, async (body) => {
1554
+ try {
1555
+ const payload = JSON.parse(body || '{}');
1556
+ const commandLine = String(payload.command || '').trim();
1557
+
1558
+ if (!commandLine) {
1559
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1560
+ res.end(JSON.stringify({ ok: true, output: '', exitCode: 0 }));
1561
+ return;
1562
+ }
1563
+
1564
+ const result = await enqueueCommandExecution(commandLine);
1565
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1566
+ res.end(JSON.stringify(result));
1567
+ } catch (err) {
1568
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1569
+ res.end(JSON.stringify({ ok: false, error: err.message }));
1570
+ }
1571
+ });
1572
+ return;
1573
+ }
1574
+
1575
+ // ZK Proof Layer status. This endpoint is intentionally read-only and
1576
+ // fail-closed: operational failures are always a 200 JSON shape with
1577
+ // available=false, never an HTML error page.
1578
+ if (url.pathname === '/api/zk/status' && req.method === 'GET') {
1579
+ try {
1580
+ const runtime = await getZkRuntime();
1581
+ const result = await runtime.status();
1582
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1583
+ res.end(JSON.stringify({ ok: true, ...result }));
1584
+ } catch (err) {
1585
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1586
+ res.end(JSON.stringify({
1587
+ ok: true,
1588
+ available: false,
1589
+ reason: `ZK runtime initialization failed: ${err.message}`,
1590
+ cluster: options.zk?.cluster || 'localnet',
1591
+ sharedStatePda: null,
1592
+ programId: null,
1593
+ noteRoot: null,
1594
+ nullRoot: null,
1595
+ noteCount: null,
1596
+ nullCount: null,
1597
+ localNoteCount: null,
1598
+ localNullCount: null,
1599
+ provingArtifacts: false,
1600
+ honesty: ZK_HONESTY_NOTE,
1601
+ }));
1602
+ }
1603
+ return;
1604
+ }
1605
+
1606
+ // The prove + settle flow blocks honestly for the full local proof
1607
+ // (normally ~2s) and confirmed Solana transaction. Actions are serialized
1608
+ // so two requests can never mutate the same local tree snapshot.
1609
+ if (url.pathname === '/api/zk/append' && req.method === 'POST') {
1610
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1611
+ readBody(req, res, async (body) => {
1612
+ let payload;
1613
+ try {
1614
+ payload = JSON.parse(body || '{}');
1615
+ } catch {
1616
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1617
+ res.end(JSON.stringify({ ok: false, error: 'request body must be valid JSON' }));
1618
+ return;
1619
+ }
1620
+ const plaintext = typeof payload.plaintext === 'string' ? payload.plaintext : '';
1621
+ const scope = typeof payload.scope === 'string' ? payload.scope.trim() : '';
1622
+ if (!plaintext.trim()) {
1623
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1624
+ res.end(JSON.stringify({ ok: false, error: 'plaintext must be a non-empty string' }));
1625
+ return;
1626
+ }
1627
+
1628
+ if (!scope) {
1629
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1630
+ res.end(JSON.stringify({ ok: false, error: 'scope must be a non-empty string' }));
1631
+ return;
1632
+ }
1633
+ // saleable: commit the DEK-blinded plaintext fold (ZKCP listable)
1634
+ // instead of the E2EE payloadCommit; see /api/zk/sale/* below.
1635
+ const saleable = payload.saleable === true;
1636
+ try {
1637
+ const runtime = await getZkRuntime();
1638
+ const result = await enqueueZkAction(() => runtime.append({ plaintext, scope, saleable }));
1639
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1640
+ res.end(JSON.stringify({ ok: true, ...result }));
1641
+
1642
+ } catch (err) {
1643
+ const statusCode = err.code === 'ZK_UNAVAILABLE' ? 503
1644
+ : err.code === 'BAD_REQUEST' ? 400
1645
+ : err.code === 'PROOF_STATE_MISMATCH' ? 409
1646
+ : err.code === 'VK_GATE_REJECTED' ? 409
1647
+ : err.name === 'ProvingAssetError' ? 501
1648
+ : 500;
1649
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1650
+ res.end(JSON.stringify({ ok: false, error: err.message }));
1651
+ }
1652
+ });
1653
+ return;
1654
+ }
1655
+
1656
+ if (url.pathname === '/api/zk/revoke' && req.method === 'POST') {
1657
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1658
+ readBody(req, res, async (body) => {
1659
+ let payload;
1660
+ try {
1661
+ payload = JSON.parse(body || '{}');
1662
+ } catch {
1663
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1664
+ res.end(JSON.stringify({ ok: false, error: 'request body must be valid JSON' }));
1665
+ return;
1666
+ }
1667
+ const noteIndex = Number(payload.noteIndex);
1668
+ if (!Number.isInteger(noteIndex) || noteIndex < 0) {
1669
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1670
+ res.end(JSON.stringify({ ok: false, error: 'noteIndex must be a non-negative integer' }));
1671
+ return;
1672
+ }
1673
+ try {
1674
+ const runtime = await getZkRuntime();
1675
+ const result = await enqueueZkAction(() => runtime.revoke({ noteIndex }));
1676
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1677
+ res.end(JSON.stringify({ ok: true, ...result }));
1678
+
1679
+ } catch (err) {
1680
+ const statusCode = err.code === 'ZK_UNAVAILABLE' ? 503
1681
+ : err.code === 'NOTE_OPENING_MISSING' ? 409
1682
+ : err.code === 'PROOF_STATE_MISMATCH' ? 409
1683
+ : err.code === 'VK_GATE_REJECTED' ? 409
1684
+ : err.name === 'ProvingAssetError' ? 501
1685
+ : 500;
1686
+ res.writeHead(statusCode, { 'Content-Type': 'application/json' });
1687
+ res.end(JSON.stringify({ ok: false, error: err.message }));
1688
+ }
1689
+ });
1690
+ return;
1691
+ }
1692
+ if (url.pathname === '/api/zk/sync-leaves' && req.method === 'POST') {
1693
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1694
+ readBody(req, res, async (body) => {
1695
+ let payload;
1696
+ try {
1697
+ payload = JSON.parse(body || '{}');
1698
+ } catch {
1699
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1700
+ res.end(JSON.stringify({ ok: false, error: 'request body must be valid JSON' }));
1701
+ return;
1702
+ }
1703
+ if (!Array.isArray(payload.leaves)) {
1704
+ res.writeHead(400, { 'Content-Type': 'application/json' });
1705
+ res.end(JSON.stringify({ ok: false, error: 'leaves must be an array of leaf strings' }));
1706
+ return;
1707
+ }
1708
+ try {
1709
+ const runtime = await getZkRuntime();
1710
+ const result = await enqueueZkAction(() => runtime.syncNoteLeaves(payload.leaves));
1711
+ res.writeHead(200, { 'Content-Type': 'application/json' });
1712
+ res.end(JSON.stringify({ ok: true, ...result }));
1713
+ } catch (err) {
1714
+ res.writeHead(500, { 'Content-Type': 'application/json' });
1715
+ res.end(JSON.stringify({ ok: false, error: err.message }));
1716
+ }
1717
+ });
1718
+ return;
1719
+ }
1720
+ // --- GhostMesh Endpoints ---
1721
+ if (url.pathname === '/api/mesh/status' && req.method === 'GET') {
1722
+ try {
1723
+ const node = await getMeshNode();
1724
+ writeJson(res, 200, node.getStatus());
1725
+ } catch (err) {
1726
+ writeJson(res, 500, { ok: false, error: err.message });
1727
+ }
1728
+ return;
1729
+ }
1730
+ if (url.pathname === '/api/mesh/peers' && req.method === 'GET') {
1731
+ try {
1732
+ const node = await getMeshNode();
1733
+ const peers = typeof node.getPeers === 'function' ? node.getPeers() : [];
1734
+ writeJson(res, 200, { ok: true, count: peers.length, peers });
1735
+ } catch (err) {
1736
+ writeJson(res, 500, { ok: false, error: err.message });
1737
+ }
1738
+ return;
1739
+ }
1740
+
1741
+ if (url.pathname === '/api/mesh/announce' && req.method === 'POST') {
1742
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1743
+ readBody(req, res, async (body) => {
1744
+ let payload = {};
1745
+ try { payload = JSON.parse(body || '{}'); } catch {}
1746
+ try {
1747
+ const node = await getMeshNode();
1748
+ const packet = await node.announce(payload.metadata || {});
1749
+ writeJson(res, 200, { ok: true, packetId: packet.subarray(packet.length - 16).toString('hex') });
1750
+ } catch (err) {
1751
+ writeJson(res, 500, { ok: false, error: err.message });
1752
+ }
1753
+ });
1754
+ return;
1755
+ }
1756
+
1757
+ if (url.pathname === '/api/mesh/send' && req.method === 'POST') {
1758
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1759
+ readBody(req, res, async (body) => {
1760
+ let payload;
1761
+ try { payload = JSON.parse(body || '{}'); } catch {
1762
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
1763
+ return;
1764
+ }
1765
+ const recipientPub = payload.recipientX25519Pub || payload.recipientEd25519Pub;
1766
+ if (!payload.destination || !recipientPub || !payload.memory) {
1767
+ writeJson(res, 400, { ok: false, error: 'destination, recipientX25519Pub, and memory are required' });
1768
+ return;
1769
+ }
1770
+ try {
1771
+ const node = await getMeshNode();
1772
+ const result = await node.sendEncryptedCapsule(
1773
+ payload.destination,
1774
+ Buffer.from(recipientPub, 'hex'),
1775
+ payload.memory,
1776
+ payload.options || {}
1777
+ );
1778
+ writeJson(res, 200, { ok: true, ...result });
1779
+ } catch (err) {
1780
+ writeJson(res, 500, { ok: false, error: err.message });
1781
+ }
1782
+ });
1783
+ return;
1784
+ }
1785
+
1786
+ if (url.pathname === '/api/mesh/gossip-proof' && req.method === 'POST') {
1787
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1788
+ readBody(req, res, async (body) => {
1789
+ let payload;
1790
+ try { payload = JSON.parse(body || '{}'); } catch {
1791
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
1792
+ return;
1793
+ }
1794
+ try {
1795
+ const node = await getMeshNode();
1796
+ const packet = await node.broadcastZkProof(payload);
1797
+ writeJson(res, 200, { ok: true, packetId: packet.subarray(packet.length - 16).toString('hex') });
1798
+ } catch (err) {
1799
+ writeJson(res, 500, { ok: false, error: err.message });
1800
+ }
1801
+ });
1802
+ return;
1803
+ }
1804
+
1805
+ if (url.pathname === '/api/mesh/gossip-sale' && req.method === 'POST') {
1806
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1807
+ readBody(req, res, async (body) => {
1808
+ let payload;
1809
+ try { payload = JSON.parse(body || '{}'); } catch {
1810
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
1811
+ return;
1812
+ }
1813
+ try {
1814
+ const node = await getMeshNode();
1815
+ const packet = await node.broadcastSealedSaleOffer(payload);
1816
+ writeJson(res, 200, { ok: true, packetId: packet.subarray(packet.length - 16).toString('hex') });
1817
+ } catch (err) {
1818
+ writeJson(res, 500, { ok: false, error: err.message });
1819
+ }
1820
+ });
1821
+ return;
1822
+ }
1823
+
1824
+ if (url.pathname === '/api/mesh/inbox' && req.method === 'GET') {
1825
+ writeJson(res, 200, {
1826
+ ok: true,
1827
+ capsules: meshInbox,
1828
+ saleOffers: meshSaleOffers,
1829
+ zkProofs: meshZkProofs,
1830
+ });
1831
+ return;
1832
+ }
1833
+
1834
+ if (url.pathname === '/api/rag/remember' && req.method === 'POST') {
1835
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1836
+ readBody(req, res, async (body) => {
1837
+ let payload = {};
1838
+ try { payload = JSON.parse(body || '{}'); } catch {}
1839
+ if (!payload.text) {
1840
+ writeJson(res, 400, { ok: false, error: 'text is required' });
1841
+ return;
1842
+ }
1843
+ const atomId = payload.id || `atom_${Date.now()}_${crypto.randomBytes(3).toString('hex')}`;
1844
+ const result = await vectorStore.add(atomId, payload.text, {
1845
+ scope: payload.scope || 'enigma.global',
1846
+ importance: payload.importance || 1.0,
1847
+ ...payload.metadata,
1848
+ });
1849
+ persistVectorStore();
1850
+ writeJson(res, 200, { ok: true, id: atomId, ...result });
1851
+ });
1852
+ return;
1853
+ }
1854
+
1855
+ if (url.pathname === '/api/rag/recall' && req.method === 'POST') {
1856
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1857
+ readBody(req, res, async (body) => {
1858
+ let payload = {};
1859
+ try { payload = JSON.parse(body || '{}'); } catch {}
1860
+ if (!payload.query) {
1861
+ writeJson(res, 400, { ok: false, error: 'query is required' });
1862
+ return;
1863
+ }
1864
+ const results = await vectorStore.search(payload.query, {
1865
+ topK: payload.topK || 5,
1866
+ threshold: payload.threshold,
1867
+ scope: payload.scope,
1868
+ });
1869
+ writeJson(res, 200, { ok: true, results, count: results.length });
1870
+ });
1871
+ return;
1872
+ }
1873
+
1874
+ if (url.pathname === '/api/rag/compile-context' && req.method === 'POST') {
1875
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1876
+ readBody(req, res, async (body) => {
1877
+ let payload = {};
1878
+ try { payload = JSON.parse(body || '{}'); } catch {}
1879
+ if (!payload.query) {
1880
+ writeJson(res, 400, { ok: false, error: 'query is required' });
1881
+ return;
1882
+ }
1883
+ const pack = await vectorStore.compileContextPack(payload.query, {
1884
+ maxTokens: payload.maxTokens || 2000,
1885
+ topK: payload.topK || 8,
1886
+ scope: payload.scope,
1887
+ });
1888
+ writeJson(res, 200, { ok: true, ...pack });
1889
+ });
1890
+ return;
1891
+ }
1892
+ // Browser-proving asset manifest. Public ceremony artifacts (no auth), but
1893
+ // fail closed with a clean 404 JSON when any artifact is missing.
1894
+ if (url.pathname === '/api/zk/prover-assets' && req.method === 'GET') {
1895
+ try {
1896
+ const runtime = await getZkRuntime();
1897
+ const files = await proverAssetFiles(runtime);
1898
+ const missing = Object.entries(files).filter(([, file]) => !file || !fs.existsSync(file)).map(([name]) => name);
1899
+ if (missing.length > 0) {
1900
+ writeJson(res, 404, {
1901
+ ok: false,
1902
+ error: `proving artifacts missing: ${missing.map((name) => files[name]).join(', ')}`,
1903
+ });
1904
+ return;
1905
+ }
1906
+ writeJson(res, 200, {
1907
+ ok: true,
1908
+ circuit: 'action_transition',
1909
+ wasmUrl: '/api/zk/prover-assets/action_transition.wasm',
1910
+ zkeyUrl: '/api/zk/prover-assets/action_transition.zkey',
1911
+ snarkjsUrl: '/api/zk/prover-assets/snarkjs.min.js',
1912
+ sizeBytes: {
1913
+ wasm: fs.statSync(files.wasm).size,
1914
+ zkey: fs.statSync(files.zkey).size,
1915
+ snarkjs: fs.statSync(files.snarkjs).size,
1916
+ },
1917
+ });
1918
+ } catch (err) {
1919
+ writeJson(res, 500, { ok: false, error: err.message });
1920
+ }
1921
+ return;
1922
+ }
1923
+
1924
+ // Streams one whitelisted prover artifact (WASM / zkey / snarkjs bundle).
1925
+ if (url.pathname.startsWith('/api/zk/prover-assets/') && req.method === 'GET') {
1926
+ const name = decodeURIComponent(url.pathname.slice('/api/zk/prover-assets/'.length));
1927
+ const route = PROVER_ASSET_ROUTE[name];
1928
+ if (!route) {
1929
+ writeJson(res, 404, { ok: false, error: 'unknown prover asset' });
1930
+ return;
1931
+ }
1932
+ try {
1933
+ const runtime = await getZkRuntime();
1934
+ const file = (await proverAssetFiles(runtime))[route.key];
1935
+ if (!file || !fs.existsSync(file) || !fs.statSync(file).isFile()) {
1936
+ writeJson(res, 404, { ok: false, error: `proving artifact not available: ${name}` });
1937
+ return;
1938
+ }
1939
+ res.writeHead(200, {
1940
+ 'Content-Type': route.contentType,
1941
+ 'Content-Length': fs.statSync(file).size,
1942
+ 'Cache-Control': 'no-store',
1943
+ });
1944
+ fs.createReadStream(file).pipe(res);
1945
+ } catch (err) {
1946
+ writeJson(res, 500, { ok: false, error: err.message });
1947
+ }
1948
+ return;
1949
+ }
1950
+
1951
+ // Split phase 1: build and prove entirely inside local custody. The
1952
+ // renderer receives only a single-use opaque handle and public-safe
1953
+ // status metadata; private proving inputs never cross the HTTP boundary.
1954
+ if (url.pathname === '/api/zk/prepare' && req.method === 'POST') {
1955
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
1956
+ readBody(req, res, async (body) => {
1957
+ let payload;
1958
+ try {
1959
+ payload = JSON.parse(body || '{}');
1960
+ } catch {
1961
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
1962
+ return;
1963
+ }
1964
+ const mode = typeof payload.mode === 'string' ? payload.mode : '';
1965
+ if (mode !== 'append' && mode !== 'revoke') {
1966
+ writeJson(res, 400, { ok: false, error: 'mode must be "append" or "revoke"' });
1967
+ return;
1968
+ }
1969
+ let args;
1970
+ if (mode === 'append') {
1971
+ const plaintext = typeof payload.plaintext === 'string' ? payload.plaintext : '';
1972
+ const scope = typeof payload.scope === 'string' ? payload.scope.trim() : '';
1973
+ if (!plaintext.trim()) {
1974
+ writeJson(res, 400, { ok: false, error: 'plaintext must be a non-empty string' });
1975
+ return;
1976
+ }
1977
+ if (!scope) {
1978
+ writeJson(res, 400, { ok: false, error: 'scope must be a non-empty string' });
1979
+ return;
1980
+ }
1981
+
1982
+ args = { plaintext, scope, saleable: payload.saleable === true };
1983
+ } else {
1984
+ const noteIndex = Number(payload.noteIndex);
1985
+ if (!Number.isInteger(noteIndex) || noteIndex < 0) {
1986
+ writeJson(res, 400, { ok: false, error: 'noteIndex must be a non-negative integer' });
1987
+ return;
1988
+ }
1989
+ args = { noteIndex };
1990
+ }
1991
+ try {
1992
+ const runtime = await getZkRuntime();
1993
+ const prepared = await enqueueZkAction(() => (mode === 'append'
1994
+ ? runtime.prepareAppend(args)
1995
+ : runtime.prepareRevoke(args)));
1996
+ const proved = await runtime.provePreparedAction(prepared.witnessInput);
1997
+ const now = Date.now();
1998
+ for (const [id, entry] of pendingPrepares) {
1999
+ if (entry.expiresAt <= now) {
2000
+ clearPendingPrepare(entry);
2001
+ pendingPrepares.delete(id);
2002
+ }
2003
+ }
2004
+ while (pendingPrepares.size >= PREPARE_MAX_SESSIONS) {
2005
+ const oldestId = pendingPrepares.keys().next().value;
2006
+ clearPendingPrepare(pendingPrepares.get(oldestId));
2007
+ pendingPrepares.delete(oldestId);
2008
+ }
2009
+ const prepareId = crypto.randomBytes(16).toString('hex');
2010
+ pendingPrepares.set(prepareId, {
2011
+ mode,
2012
+ pending: prepared.pending,
2013
+ proofBytes: proved.proofBytes,
2014
+ publicInputs: proved.publicInputs,
2015
+ expiresAt: now + PREPARE_TTL_MS,
2016
+ });
2017
+ writeJson(res, 200, {
2018
+ ok: true,
2019
+ prepareId,
2020
+ mode,
2021
+ noteIndex: mode === 'revoke' ? args.noteIndex : null,
2022
+ proofReady: true,
2023
+ publicInputCount: proved.publicInputs.length,
2024
+ });
2025
+
2026
+
2027
+ } catch (err) {
2028
+ const statusCode = err.code === 'ZK_UNAVAILABLE' ? 503
2029
+ : err.code === 'NOTE_OPENING_MISSING' ? 409
2030
+ : err.code === 'BAD_REQUEST' ? 400
2031
+ : err.code === 'VK_GATE_REJECTED' ? 409
2032
+ : err.name === 'ProvingAssetError' ? 501
2033
+ : 500;
2034
+ writeJson(res, statusCode, { ok: false, error: err.message });
2035
+ }
2036
+ });
2037
+ return;
2038
+ }
2039
+
2040
+ // Split phase 2: append/revoke accept only { mode, prepareId }; the proof
2041
+ // stays attached to the opaque server-side session. Intent mode has no
2042
+ // private note opening and continues to accept public proof material.
2043
+ // The server payer settles, and one in-flight action per mode is allowed.
2044
+ if (url.pathname === '/api/zk/relay' && req.method === 'POST') {
2045
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2046
+ readBody(req, res, async (body) => {
2047
+ let payload;
2048
+ try {
2049
+ payload = JSON.parse(body || '{}');
2050
+ } catch {
2051
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
2052
+ return;
2053
+ }
2054
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
2055
+ writeJson(res, 400, { ok: false, error: 'request body must be a JSON object' });
2056
+ return;
2057
+ }
2058
+ for (const field of RELAY_FORBIDDEN_FIELDS) {
2059
+ if (field in payload) {
2060
+ writeJson(res, 400, {
2061
+ ok: false,
2062
+ error: `relay request contains forbidden private material ("${field}")`,
2063
+ });
2064
+ return;
2065
+ }
2066
+ }
2067
+ const mode = typeof payload.mode === 'string' ? payload.mode : '';
2068
+ if (mode !== 'append' && mode !== 'revoke' && mode !== 'intent') {
2069
+ writeJson(res, 400, { ok: false, error: 'mode must be "append", "revoke", or "intent"' });
2070
+ return;
2071
+ }
2072
+ if (mode === 'intent') {
2073
+ const shapeError = validateRelayShape(payload.proof, payload.publicInputs, 4);
2074
+ if (shapeError) {
2075
+ writeJson(res, 400, { ok: false, error: shapeError });
2076
+ return;
2077
+ }
2078
+ } else if ('proof' in payload || 'publicInputs' in payload) {
2079
+ writeJson(res, 400, {
2080
+ ok: false,
2081
+ error: 'append/revoke relay accepts only { mode, prepareId }; proof material remains in the opaque local prepare session',
2082
+ });
2083
+ return;
2084
+ }
2085
+ if (relayInFlight.has(mode)) {
2086
+ writeJson(res, 429, { ok: false, error: `relay already in flight for mode "${mode}"` });
2087
+ return;
2088
+ }
2089
+ let prepareEntry = null;
2090
+ if (mode !== 'intent') {
2091
+ const prepareId = typeof payload.prepareId === 'string' ? payload.prepareId : '';
2092
+ prepareEntry = prepareId ? pendingPrepares.get(prepareId) : null;
2093
+ if (!prepareEntry || prepareEntry.expiresAt <= Date.now()) {
2094
+ clearPendingPrepare(prepareEntry);
2095
+ pendingPrepares.delete(prepareId);
2096
+ writeJson(res, 409, { ok: false, error: 'unknown or expired prepare session; re-run /api/zk/prepare and prove again' });
2097
+ return;
2098
+ }
2099
+ if (prepareEntry.mode !== mode) {
2100
+ writeJson(res, 400, { ok: false, error: 'prepareId was issued for a different mode' });
2101
+ return;
2102
+ }
2103
+ }
2104
+ relayInFlight.add(mode);
2105
+ try {
2106
+ const runtime = await getZkRuntime();
2107
+ let proofBytes;
2108
+ let publicInputs;
2109
+ if (mode === 'intent') {
2110
+ proofBytes = {
2111
+ a: Buffer.from(payload.proof.a, 'hex'),
2112
+ b: Buffer.from(payload.proof.b, 'hex'),
2113
+ c: Buffer.from(payload.proof.c, 'hex'),
2114
+ };
2115
+ publicInputs = payload.publicInputs.map((hex) => Buffer.from(hex, 'hex'));
2116
+ } else {
2117
+ proofBytes = prepareEntry.proofBytes;
2118
+ publicInputs = prepareEntry.publicInputs;
2119
+ }
2120
+ let result;
2121
+ if (mode === 'intent') {
2122
+ result = await runtime.relayIntent({ proofBytes, publicInputs });
2123
+ } else {
2124
+ pendingPrepares.delete(payload.prepareId); // prepare sessions are single-use
2125
+ result = await enqueueZkAction(() => runtime.relayAction({ pending: prepareEntry.pending, proofBytes, publicInputs }));
2126
+ }
2127
+ writeJson(res, 200, { ok: true, mode, ...result });
2128
+
2129
+ } catch (err) {
2130
+ const statusCode = err.code === 'ZK_UNAVAILABLE' ? 503
2131
+ : err.code === 'NOTE_OPENING_MISSING' ? 409
2132
+ : err.code === 'PROOF_STATE_MISMATCH' ? 409
2133
+ : err.code === 'VK_GATE_REJECTED' ? 409
2134
+ : err.name === 'ProvingAssetError' ? 501
2135
+ : 500;
2136
+ writeJson(res, statusCode, { ok: false, error: err.message });
2137
+ } finally {
2138
+ if (mode !== 'intent') clearPendingPrepare(prepareEntry);
2139
+ relayInFlight.delete(mode);
2140
+ }
2141
+ });
2142
+ return;
2143
+ }
2144
+
2145
+
2146
+ // Phase 1 (seller): build the sale witness from local custody and prove
2147
+ // locally (~2s). Secrets stay server-side in memory for the request only.
2148
+ if (url.pathname === '/api/zk/sale/prepare-listing' && req.method === 'POST') {
2149
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2150
+ readBody(req, res, async (body) => {
2151
+ let payload;
2152
+ try {
2153
+ payload = JSON.parse(body || '{}');
2154
+ } catch {
2155
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
2156
+ return;
2157
+ }
2158
+ const forbidden = saleForbiddenField(payload);
2159
+ if (forbidden) {
2160
+ writeJson(res, 400, { ok: false, error: forbidden });
2161
+ return;
2162
+ }
2163
+ const noteIndex = Number(payload.noteIndex ?? payload.noteId);
2164
+ if (!Number.isInteger(noteIndex) || noteIndex < 0) {
2165
+ writeJson(res, 400, { ok: false, error: 'noteIndex (or noteId) must be a non-negative integer' });
2166
+ return;
2167
+ }
2168
+ const price = parseUint64(payload.priceLamports, 'priceLamports');
2169
+ if (price.error) {
2170
+ writeJson(res, 400, { ok: false, error: price.error });
2171
+ return;
2172
+ }
2173
+ const expirySlots = Number(payload.expirySlots);
2174
+ if (!Number.isInteger(expirySlots) || expirySlots < 1 || expirySlots > 2 ** 32) {
2175
+ writeJson(res, 400, { ok: false, error: 'expirySlots must be an integer between 1 and 2^32' });
2176
+ return;
2177
+ }
2178
+ let buyerPub = null;
2179
+ const hasBuyerXY = payload.buyerPubkeyX !== undefined || payload.buyerPubkeyY !== undefined;
2180
+ if (hasBuyerXY) {
2181
+ for (const key of ['buyerPubkeyX', 'buyerPubkeyY']) {
2182
+ if (typeof payload[key] !== 'string' || !/^(0|[1-9][0-9]*)$/.test(payload[key]) || BigInt(payload[key]) >= FR_MODULUS) {
2183
+ writeJson(res, 400, { ok: false, error: `${key} must be a canonical BN254 field element as a decimal string` });
2184
+ return;
2185
+ }
2186
+ }
2187
+ buyerPub = { x: payload.buyerPubkeyX, y: payload.buyerPubkeyY };
2188
+ }
2189
+ const buyerKeyLabel = payload.buyerKeyLabel === undefined ? null : String(payload.buyerKeyLabel);
2190
+ if (!buyerPub && buyerKeyLabel !== null && !/^[a-z0-9_-]{1,64}$/i.test(buyerKeyLabel)) {
2191
+ writeJson(res, 400, { ok: false, error: 'buyerKeyLabel must be 1-64 alphanumeric/-/_ characters' });
2192
+ return;
2193
+ }
2194
+ const mint = payload.mint === undefined ? null : String(payload.mint);
2195
+ try {
2196
+ const runtime = await getZkRuntime();
2197
+ const result = await enqueueZkAction(() => runtime.prepareSaleListing({
2198
+ noteIndex,
2199
+ priceLamports: price.value,
2200
+ buyerPub,
2201
+ buyerKeyLabel,
2202
+ expirySlots,
2203
+ mint,
2204
+ }));
2205
+ writeJson(res, 200, { ok: true, ...result });
2206
+ } catch (err) {
2207
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message, ...(err.name === 'ProvingAssetError' ? { code: err.code } : {}) });
2208
+ }
2209
+ });
2210
+ return;
2211
+ }
2212
+
2213
+ // Phase 2 (seller): attest on-chain. Accepts public { proofBytes,
2214
+ // publicInputs, predecessorReceiptRefs? }; the proof binds note state,
2215
+ // commitments, and terms while receipt refs bind lifecycle evidence.
2216
+ if (url.pathname === '/api/zk/sale/submit-listing' && req.method === 'POST') {
2217
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2218
+ readBody(req, res, async (body) => {
2219
+ let payload;
2220
+ try {
2221
+ payload = JSON.parse(body || '{}');
2222
+ } catch {
2223
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
2224
+ return;
2225
+ }
2226
+ const forbidden = saleForbiddenField(payload);
2227
+ if (forbidden) {
2228
+ writeJson(res, 400, { ok: false, error: forbidden });
2229
+ return;
2230
+ }
2231
+ const shapeError = validateRelayShape(payload.proofBytes, payload.publicInputs, 6);
2232
+ if (shapeError) {
2233
+ writeJson(res, 400, { ok: false, error: shapeError.replace(/^proof\./, 'proofBytes.') });
2234
+ return;
2235
+ }
2236
+ const predecessorRefs = parsePredecessorReceiptRefs(payload.predecessorReceiptRefs);
2237
+ if (predecessorRefs.error) {
2238
+ writeJson(res, 400, { ok: false, error: predecessorRefs.error });
2239
+ return;
2240
+ }
2241
+ try {
2242
+ const runtime = await getZkRuntime();
2243
+ const proofBytes = {
2244
+ a: Buffer.from(payload.proofBytes.a, 'hex'),
2245
+ b: Buffer.from(payload.proofBytes.b, 'hex'),
2246
+ c: Buffer.from(payload.proofBytes.c, 'hex'),
2247
+ };
2248
+ const publicInputs = payload.publicInputs.map((hex) => Buffer.from(hex, 'hex'));
2249
+ const result = await enqueueZkAction(() => runtime.submitSaleListing({
2250
+ proofBytes,
2251
+ publicInputs,
2252
+ predecessorReceiptRefs: predecessorRefs.refs,
2253
+ }));
2254
+ writeJson(res, 200, { ok: true, ...result });
2255
+ } catch (err) {
2256
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message });
2257
+ }
2258
+ });
2259
+ return;
2260
+ }
2261
+
2262
+ // Buyer pre-payment verification against the on-chain receipt. A failed
2263
+ // check is a verdict, not an error: 200 with ok:false and per-check detail.
2264
+ if (url.pathname === '/api/zk/sale/verify' && req.method === 'POST') {
2265
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2266
+ readBody(req, res, async (body) => {
2267
+ let payload;
2268
+ try {
2269
+ payload = JSON.parse(body || '{}');
2270
+ } catch {
2271
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
2272
+ return;
2273
+ }
2274
+ const forbidden = saleForbiddenField(payload);
2275
+ if (forbidden) {
2276
+ writeJson(res, 400, { ok: false, error: forbidden });
2277
+ return;
2278
+ }
2279
+ const terms = parseHex32(payload.saleTermsHashHex, 'saleTermsHashHex');
2280
+ const keyCommit = parseHex32(payload.keyCommitHex, 'keyCommitHex');
2281
+ const envelopeCommit = parseHex32(payload.envelopeCommitHex, 'envelopeCommitHex');
2282
+ const chunks = parseCiphertextChunks(payload.ciphertextChunks);
2283
+ for (const parsed of [terms, keyCommit, envelopeCommit, chunks]) {
2284
+ if (parsed.error) {
2285
+ writeJson(res, 400, { ok: false, error: parsed.error });
2286
+ return;
2287
+ }
2288
+ }
2289
+ try {
2290
+ const runtime = await getZkRuntime();
2291
+ const result = await runtime.verifySale({
2292
+ saleTermsHashBytes: terms.bytes,
2293
+ keyCommitBytes: keyCommit.bytes,
2294
+ envelopeCommitBytes: envelopeCommit.bytes,
2295
+ ciphertextChunks: chunks.chunks,
2296
+ });
2297
+ writeJson(res, 200, result);
2298
+ } catch (err) {
2299
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message });
2300
+ }
2301
+ });
2302
+ return;
2303
+ }
2304
+
2305
+ // Buyer escrow, pinned to the attested receipt; buyer keypair comes from
2306
+ // local custody and never appears on the wire.
2307
+ if (url.pathname === '/api/zk/sale/escrow' && req.method === 'POST') {
2308
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2309
+ readBody(req, res, async (body) => {
2310
+ let payload;
2311
+ try {
2312
+ payload = JSON.parse(body || '{}');
2313
+ } catch {
2314
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
2315
+ return;
2316
+ }
2317
+ const forbidden = saleForbiddenField(payload);
2318
+ if (forbidden) {
2319
+ writeJson(res, 400, { ok: false, error: forbidden });
2320
+ return;
2321
+ }
2322
+ const terms = parseHex32(payload.saleTermsHashHex, 'saleTermsHashHex');
2323
+ const keyCommit = parseHex32(payload.keyCommitHex, 'keyCommitHex');
2324
+ const envelopeCommit = parseHex32(payload.envelopeCommitHex, 'envelopeCommitHex');
2325
+ const price = parseUint64(payload.priceLamports, 'priceLamports');
2326
+ for (const parsed of [terms, keyCommit, envelopeCommit, price]) {
2327
+ if (parsed.error) {
2328
+ writeJson(res, 400, { ok: false, error: parsed.error });
2329
+ return;
2330
+ }
2331
+ }
2332
+ const expirySlot = Number(payload.expirySlot);
2333
+ if (!Number.isSafeInteger(expirySlot) || expirySlot < 0) {
2334
+ writeJson(res, 400, { ok: false, error: 'expirySlot must be a non-negative safe integer' });
2335
+ return;
2336
+ }
2337
+ const sellerPubkey = typeof payload.sellerPubkey === 'string' ? payload.sellerPubkey.trim() : '';
2338
+ if (!sellerPubkey) {
2339
+ writeJson(res, 400, { ok: false, error: 'sellerPubkey must be a non-empty base58 string' });
2340
+ return;
2341
+ }
2342
+ const mint = typeof payload.mint === 'string' ? payload.mint.trim() : '';
2343
+ if (!mint) {
2344
+ writeJson(res, 400, { ok: false, error: 'mint must be a non-empty base58 string (terms recompute)' });
2345
+ return;
2346
+ }
2347
+ const payloadLen = Number(payload.payloadLen);
2348
+ if (!Number.isSafeInteger(payloadLen) || payloadLen < 1) {
2349
+ writeJson(res, 400, { ok: false, error: 'payloadLen must be a positive safe integer (terms recompute)' });
2350
+ return;
2351
+ }
2352
+ const escrowChunks = parseCiphertextChunks(payload.ciphertextChunks);
2353
+ if (escrowChunks.error) {
2354
+ writeJson(res, 400, { ok: false, error: escrowChunks.error });
2355
+ return;
2356
+ }
2357
+ try {
2358
+ const runtime = await getZkRuntime();
2359
+ const result = await enqueueZkAction(() => runtime.createSaleEscrow({
2360
+ saleTermsHashBytes: terms.bytes,
2361
+ priceLamports: price.value,
2362
+ expirySlot,
2363
+ sellerPubkey,
2364
+ mint,
2365
+ payloadLen,
2366
+ ciphertextChunks: escrowChunks.chunks,
2367
+ buyerKeyLabel: typeof payload.buyerKeyLabel === 'string' ? payload.buyerKeyLabel : undefined,
2368
+ keyCommitBytes: keyCommit.bytes,
2369
+ envelopeCommitBytes: envelopeCommit.bytes,
2370
+ }));
2371
+ writeJson(res, 200, { ok: true, ...result });
2372
+ } catch (err) {
2373
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message });
2374
+ }
2375
+ });
2376
+ return;
2377
+ }
2378
+
2379
+
2380
+ // Seller claim: loads the sealed envelope from custody and reveals it
2381
+ // on-chain atomically with payment. The ONLY envelope egress path.
2382
+ if (url.pathname === '/api/zk/sale/claim' && req.method === 'POST') {
2383
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2384
+ readBody(req, res, async (body) => {
2385
+ let payload;
2386
+ try {
2387
+ payload = JSON.parse(body || '{}');
2388
+ } catch {
2389
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
2390
+ return;
2391
+ }
2392
+ const forbidden = saleForbiddenField(payload);
2393
+ if (forbidden) {
2394
+ writeJson(res, 400, { ok: false, error: forbidden });
2395
+ return;
2396
+ }
2397
+ const terms = parseHex32(payload.saleTermsHashHex, 'saleTermsHashHex');
2398
+ const keyCommit = parseHex32(payload.keyCommitHex, 'keyCommitHex');
2399
+ const envelopeCommit = parseHex32(payload.envelopeCommitHex, 'envelopeCommitHex');
2400
+ for (const parsed of [terms, keyCommit, envelopeCommit]) {
2401
+ if (parsed.error) {
2402
+ writeJson(res, 400, { ok: false, error: parsed.error });
2403
+ return;
2404
+ }
2405
+ }
2406
+ const predecessorRefs = parsePredecessorReceiptRefs(payload.predecessorReceiptRefs);
2407
+ if (predecessorRefs.error) {
2408
+ writeJson(res, 400, { ok: false, error: predecessorRefs.error });
2409
+ return;
2410
+ }
2411
+ const buyerPubkey = typeof payload.buyerPubkey === 'string' ? payload.buyerPubkey.trim() : '';
2412
+ if (!buyerPubkey) {
2413
+ writeJson(res, 400, { ok: false, error: 'buyerPubkey must be a non-empty base58 string' });
2414
+ return;
2415
+ }
2416
+ try {
2417
+ const runtime = await getZkRuntime();
2418
+ const result = await enqueueZkAction(() => runtime.claimSale({
2419
+ saleTermsHashBytes: terms.bytes,
2420
+ buyerPubkey,
2421
+ keyCommitBytes: keyCommit.bytes,
2422
+ envelopeCommitBytes: envelopeCommit.bytes,
2423
+ predecessorReceiptRefs: predecessorRefs.refs,
2424
+ }));
2425
+ writeJson(res, 200, { ok: true, ...result });
2426
+ } catch (err) {
2427
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message });
2428
+ }
2429
+ });
2430
+ return;
2431
+ }
2432
+
2433
+ // Buyer identity: public keys + SOL balance only — what a fresh install
2434
+ // must fund and hand to sellers. No secrets. Auth required (it is this
2435
+ // device's identity), like the other stateful endpoints.
2436
+ if (url.pathname === '/api/zk/sale/buyer-identity' && req.method === 'GET') {
2437
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2438
+ (async () => {
2439
+ try {
2440
+ const runtime = await getZkRuntime();
2441
+ const identity = await enqueueZkAction(() => runtime.buyerIdentity());
2442
+ writeJson(res, 200, { ok: true, ...identity });
2443
+ } catch (err) {
2444
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message });
2445
+ }
2446
+ })();
2447
+ return;
2448
+ }
2449
+
2450
+ // Public receipt view (read-only chain data — no auth, like /api/zk/status).
2451
+ if (url.pathname === '/api/zk/sale/receipt' && req.method === 'GET') {
2452
+ const keyCommit = parseHex32(url.searchParams.get('keyCommit'), 'keyCommit');
2453
+ const envelopeCommit = parseHex32(url.searchParams.get('envelopeCommit'), 'envelopeCommit');
2454
+ for (const parsed of [keyCommit, envelopeCommit]) {
2455
+ if (parsed.error) {
2456
+ writeJson(res, 400, { ok: false, error: parsed.error });
2457
+ return;
2458
+ }
2459
+ }
2460
+ try {
2461
+ const runtime = await getZkRuntime();
2462
+ const { receipt, receiptPda } = await runtime.saleReceipt({
2463
+ keyCommitBytes: keyCommit.bytes,
2464
+ envelopeCommitBytes: envelopeCommit.bytes,
2465
+ });
2466
+ if (!receipt) {
2467
+ writeJson(res, 404, { ok: false, error: 'no sale receipt on-chain for this keyCommit + envelopeCommit pair', receiptPda });
2468
+ return;
2469
+ }
2470
+ writeJson(res, 200, { ok: true, receipt: receiptJson(receipt), receiptPda });
2471
+ } catch (err) {
2472
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message });
2473
+ }
2474
+ return;
2475
+ }
2476
+
2477
+ // Buyer post-claim open: decrypts from the on-chain receipt with the
2478
+ // custodied buyer secret. Fails closed (409) while the envelope is zero.
2479
+ if (url.pathname === '/api/zk/sale/open' && req.method === 'POST') {
2480
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2481
+ readBody(req, res, async (body) => {
2482
+ let payload;
2483
+ try {
2484
+ payload = JSON.parse(body || '{}');
2485
+ } catch {
2486
+ writeJson(res, 400, { ok: false, error: 'request body must be valid JSON' });
2487
+ return;
2488
+ }
2489
+ const forbidden = saleForbiddenField(payload);
2490
+ if (forbidden) {
2491
+ writeJson(res, 400, { ok: false, error: forbidden });
2492
+ return;
2493
+ }
2494
+ const keyCommit = parseHex32(payload.keyCommitHex, 'keyCommitHex');
2495
+ const envelopeCommit = parseHex32(payload.envelopeCommitHex, 'envelopeCommitHex');
2496
+ const chunks = parseCiphertextChunks(payload.ciphertextChunks);
2497
+ for (const parsed of [keyCommit, envelopeCommit, chunks]) {
2498
+ if (parsed.error) {
2499
+ writeJson(res, 400, { ok: false, error: parsed.error });
2500
+ return;
2501
+ }
2502
+ }
2503
+ const predecessorRefs = parsePredecessorReceiptRefs(payload.predecessorReceiptRefs);
2504
+ if (predecessorRefs.error) {
2505
+ writeJson(res, 400, { ok: false, error: predecessorRefs.error });
2506
+ return;
2507
+ }
2508
+ let payloadLen = null;
2509
+ if (payload.payloadLen !== undefined) {
2510
+ payloadLen = Number(payload.payloadLen);
2511
+ if (!Number.isInteger(payloadLen) || payloadLen < 0 || payloadLen > 992) {
2512
+ writeJson(res, 400, { ok: false, error: 'payloadLen must be an integer between 0 and 992' });
2513
+ return;
2514
+ }
2515
+ }
2516
+ const buyerKeyLabel = payload.buyerKeyLabel === undefined ? null : String(payload.buyerKeyLabel);
2517
+ try {
2518
+ const runtime = await getZkRuntime();
2519
+ const result = await enqueueZkAction(() => runtime.openSale({
2520
+ keyCommitBytes: keyCommit.bytes,
2521
+ envelopeCommitBytes: envelopeCommit.bytes,
2522
+ ciphertextChunks: chunks.chunks,
2523
+ payloadLen,
2524
+ buyerKeyLabel,
2525
+ predecessorReceiptRefs: predecessorRefs.refs,
2526
+ }));
2527
+ writeJson(res, 200, { ok: true, ...result });
2528
+ } catch (err) {
2529
+ writeJson(res, saleErrorStatus(err), { ok: false, error: err.message });
2530
+ }
2531
+ });
2532
+ return;
2533
+ }
2534
+
2535
+ // Telemetry API
2536
+ if (url.pathname === '/api/telemetry') {
2537
+ try {
2538
+ const metrics = await fetchSolanaStreamMetrics({ cluster: 'mainnet-beta', rpcUrl: SOLANA_MAINNET_RPC });
2539
+ const latencyMs = metrics.latency_ms ?? 0;
2540
+ let epochPct = null;
2541
+ if (typeof metrics.slot_index === 'number' && typeof metrics.slots_in_epoch === 'number' && metrics.slots_in_epoch > 0) {
2542
+ epochPct = Math.min(100, Math.max(0, Number(((metrics.slot_index / metrics.slots_in_epoch) * 100).toFixed(1))));
2543
+ }
2544
+
2545
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2546
+ res.end(JSON.stringify({
2547
+ ok: Boolean(metrics.ok),
2548
+ slot: metrics.slot ?? null,
2549
+ epoch: metrics.epoch ?? null,
2550
+ epochPct,
2551
+ latencyMs,
2552
+ latency_ms: latencyMs,
2553
+ slot_index: metrics.slot_index ?? null,
2554
+ slots_in_epoch: metrics.slots_in_epoch ?? null,
2555
+ cluster: 'mainnet-beta',
2556
+ tokenCa: OFFICIAL_TOKEN_CA,
2557
+ connectedWallet: session.connectedWallet || null,
2558
+ }));
2559
+ } catch (err) {
2560
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2561
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2562
+ }
2563
+ return;
2564
+ }
2565
+
2566
+ // Wallet Status API
2567
+ if (url.pathname === '/api/wallet/status') {
2568
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2569
+ res.end(JSON.stringify({
2570
+ ok: true,
2571
+ connected: Boolean(session.connectedWallet),
2572
+ wallet: session.connectedWallet || null,
2573
+ tokenCa: OFFICIAL_TOKEN_CA,
2574
+ }));
2575
+ return;
2576
+ }
2577
+
2578
+ // Wallet Connect Trigger
2579
+ if (url.pathname === '/api/wallet/connect' && req.method === 'POST') {
2580
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2581
+
2582
+ try {
2583
+ if (!phantomBridge.server) {
2584
+ await phantomBridge.startServer();
2585
+ }
2586
+ const sessionData = phantomBridge.createSession();
2587
+ const companionUrl = await phantomBridge.openInSystemBrowser(sessionData.nonce);
2588
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2589
+ res.end(JSON.stringify({
2590
+ ok: true,
2591
+ companionUrl,
2592
+ nonce: sessionData.nonce,
2593
+ status: 'opened_in_browser',
2594
+ }));
2595
+ } catch (err) {
2596
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2597
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2598
+ }
2599
+ return;
2600
+ }
2601
+
2602
+ // Solana RPC Proxy API (eliminates browser CORS/Origin 403 blocks)
2603
+ if (url.pathname === '/api/rpc' && req.method === 'POST') {
2604
+ readBody(req, res, async (body) => {
2605
+ try {
2606
+ const rpcRes = await fetch(SOLANA_MAINNET_RPC, {
2607
+ method: 'POST',
2608
+ headers: { 'Content-Type': 'application/json' },
2609
+ body,
2610
+ });
2611
+ const rpcText = await rpcRes.text();
2612
+ res.writeHead(rpcRes.status, { 'Content-Type': 'application/json' });
2613
+ res.end(rpcText);
2614
+ } catch (err) {
2615
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2616
+ res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32603, message: err.message }, id: null }));
2617
+ }
2618
+ });
2619
+ return;
2620
+ }
2621
+
2622
+ // Active Anchor Memo payload API
2623
+ if (url.pathname === '/api/active-anchor-memo') {
2624
+ try {
2625
+ if (!fs.existsSync(bundlePath)) {
2626
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2627
+ res.end(JSON.stringify({ ok: false, error: 'Vault uninitialized. Please initialize your memory vault first (e.g. remember or quickstart).' }));
2628
+ return;
2629
+ }
2630
+
2631
+ let bundleData;
2632
+ try {
2633
+ bundleData = JSON.parse(fs.readFileSync(bundlePath, 'utf8'));
2634
+ } catch {
2635
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2636
+ res.end(JSON.stringify({ ok: false, error: 'Unreadable or malformed vault bundle JSON.' }));
2637
+ return;
2638
+ }
2639
+
2640
+ const candidate = bundleData?.bundle && typeof bundleData.bundle === 'object' ? bundleData.bundle : bundleData;
2641
+ const normalized = normalizeSolanaLocalArtifact(candidate, 'mainnet-beta', new Map());
2642
+ const { artifact: anchorBatch, schema } = normalized;
2643
+ const memoRef = createSolanaProofMemoRef(schema, anchorBatch, 'mainnet-beta');
2644
+
2645
+ // Atomically snapshot the exact anchor batch file next to bundle.json
2646
+ const anchorBatchPath = bundlePath.replace(/[^/\\]+$/, 'anchor-batch.json');
2647
+ fs.writeFileSync(anchorBatchPath, JSON.stringify(anchorBatch, null, 2), 'utf8');
2648
+
2649
+ // Fetch latest Mainnet blockhash server-side to guarantee valid transaction blockhash
2650
+ let blockhash = null;
2651
+ let lastValidBlockHeight = null;
2652
+ try {
2653
+ const bhRes = await fetch(SOLANA_MAINNET_RPC, {
2654
+ method: 'POST',
2655
+ headers: { 'Content-Type': 'application/json' },
2656
+ body: JSON.stringify({
2657
+ jsonrpc: '2.0',
2658
+ id: 'get-latest-blockhash',
2659
+ method: 'getLatestBlockhash',
2660
+ params: [{ commitment: 'confirmed' }],
2661
+ }),
2662
+ });
2663
+ const bhData = await bhRes.json();
2664
+ blockhash = bhData?.result?.value?.blockhash || null;
2665
+ lastValidBlockHeight = bhData?.result?.value?.lastValidBlockHeight || null;
2666
+ } catch {}
2667
+
2668
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2669
+ res.end(JSON.stringify({
2670
+ ok: true,
2671
+ bundlePath,
2672
+ anchorBatchPath,
2673
+ anchorBatch,
2674
+ memoRef,
2675
+ blockhash,
2676
+ lastValidBlockHeight,
2677
+ cluster: 'mainnet-beta',
2678
+ memoProgramId: 'MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr',
2679
+ }));
2680
+ } catch (err) {
2681
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2682
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2683
+ }
2684
+ return;
2685
+ }
2686
+ if (url.pathname === '/api/auto-anchor/status') {
2687
+ try {
2688
+ const isConfigured = autoAnchorEngine.isConfigured();
2689
+ const signerPubkey = autoAnchorEngine.signerStore.getPublicKey();
2690
+ const delegation = autoAnchorEngine.getDelegation();
2691
+ const outboxStats = autoAnchorEngine.outbox.getStats();
2692
+ let balance = null;
2693
+ if (signerPubkey) {
2694
+ try {
2695
+ balance = await autoAnchorEngine.transport.getBalance(signerPubkey);
2696
+ } catch {}
2697
+ }
2698
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2699
+ res.end(JSON.stringify({
2700
+ ok: true,
2701
+ isConfigured,
2702
+ signerPubkey,
2703
+ delegation,
2704
+ outboxStats,
2705
+ balance,
2706
+ cluster: 'mainnet-beta',
2707
+ }));
2708
+ } catch (err) {
2709
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2710
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2711
+ }
2712
+ return;
2713
+ }
2714
+
2715
+ // Auto-Anchor Setup Signer API
2716
+ if (url.pathname === '/api/auto-anchor/setup-signer' && req.method === 'POST') {
2717
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2718
+ try {
2719
+ const result = autoAnchorEngine.setupSigner();
2720
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2721
+ res.end(JSON.stringify({ ok: true, signerPubkey: result.publicKeyBase58 }));
2722
+ } catch (err) {
2723
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2724
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2725
+ }
2726
+ return;
2727
+ }
2728
+
2729
+ // Auto-Anchor Delegation Challenge API
2730
+ if (url.pathname === '/api/auto-anchor/challenge' && req.method === 'POST') {
2731
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2732
+ readBody(req, res, async (body) => {
2733
+ try {
2734
+ const payload = JSON.parse(body || '{}');
2735
+ const ownerPubkey = payload.ownerPubkey || session.connectedWallet?.publicKey;
2736
+ if (!ownerPubkey) {
2737
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2738
+ res.end(JSON.stringify({ ok: false, error: 'ownerPubkey required' }));
2739
+ return;
2740
+ }
2741
+ const challengeData = autoAnchorEngine.getDelegationChallenge({ ownerPubkey });
2742
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2743
+ res.end(JSON.stringify({ ok: true, ...challengeData }));
2744
+ } catch (err) {
2745
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2746
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2747
+ }
2748
+ });
2749
+ return;
2750
+ }
2751
+
2752
+ // Auto-Anchor Delegation Set API
2753
+ if (url.pathname === '/api/auto-anchor/delegate' && req.method === 'POST') {
2754
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2755
+ readBody(req, res, async (body) => {
2756
+ try {
2757
+ const delegation = JSON.parse(body || '{}');
2758
+ autoAnchorEngine.setDelegation(delegation);
2759
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2760
+ res.end(JSON.stringify({ ok: true, isConfigured: true, authorizationHash: delegation.authorization_hash }));
2761
+ } catch (err) {
2762
+ res.writeHead(400, { 'Content-Type': 'application/json' });
2763
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2764
+ }
2765
+ });
2766
+ return;
2767
+ }
2768
+
2769
+ // Auto-Anchor Process Next Outbox Item API
2770
+ if (url.pathname === '/api/auto-anchor/process' && req.method === 'POST') {
2771
+ if (!validateSecurityHeaders(req, res, { requireAuth: true })) return;
2772
+ try {
2773
+ const result = await autoAnchorEngine.processNextOutboxItem();
2774
+ res.writeHead(200, { 'Content-Type': 'application/json' });
2775
+ res.end(JSON.stringify({ ok: true, result }));
2776
+ } catch (err) {
2777
+ res.writeHead(500, { 'Content-Type': 'application/json' });
2778
+ res.end(JSON.stringify({ ok: false, error: err.message }));
2779
+ }
2780
+ return;
2781
+ }
2782
+
2783
+ // Static File Server
2784
+ // Defense in depth: the persisted ZK snapshot contains recipient secret
2785
+ // material and note openings. Reject its filename before static resolution,
2786
+ // even if a caller misconfigures the state path into this directory.
2787
+ if (path.basename(url.pathname).toLowerCase() === 'zk-tree-state.json') {
2788
+ res.writeHead(404, { 'Content-Type': 'application/json' });
2789
+ res.end(JSON.stringify({ ok: false, error: 'Not Found' }));
2790
+ return;
2791
+ }
2792
+ const reqUrl = url.pathname === '/' ? 'index.html' : url.pathname.replace(/^\/+/, '');
2793
+ const safePath = path.normalize(reqUrl).replace(/^(\.\.[\/\\])+/, '').replace(/^[\\\/]+/, '');
2794
+ const filePath = path.join(DESKTOP_DIR, safePath);
2795
+
2796
+ if (!fs.existsSync(filePath) || fs.statSync(filePath).isDirectory()) {
2797
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
2798
+ res.end('Not Found');
2799
+ return;
2800
+ }
2801
+
2802
+ const ext = path.extname(filePath).toLowerCase();
2803
+ const contentType = MIME_TYPES[ext] || 'application/octet-stream';
2804
+
2805
+ if (ext === '.html') {
2806
+ let html = fs.readFileSync(filePath, 'utf8');
2807
+ html = html.replace('__ENIGMA_AUTH_TOKEN__', authToken);
2808
+ // Version is injected at serve time from the package manifest — never
2809
+ // hard-code it in the markup (a stale string ships the wrong identity).
2810
+ html = html.replaceAll('__ENIGMA_VERSION__', ENIGMA_VERSION);
2811
+ res.writeHead(200, { 'Content-Type': contentType });
2812
+ res.end(html);
2813
+ return;
2814
+ }
2815
+
2816
+ res.writeHead(200, { 'Content-Type': contentType });
2817
+ fs.createReadStream(filePath).pipe(res);
2818
+ });
2819
+
2820
+ return {
2821
+ server,
2822
+ session,
2823
+ phantomBridge,
2824
+ autoAnchorEngine,
2825
+ authToken,
2826
+ apiKeys,
2827
+ usage,
2828
+ credits,
2829
+ payments,
2830
+ get port() { return boundPort; },
2831
+ host,
2832
+ listen: () => new Promise((resolve, reject) => {
2833
+ server.listen(boundPort, host, () => {
2834
+ const addr = server.address();
2835
+ boundPort = addr.port;
2836
+ resolve({
2837
+ url: `http://${host}:${boundPort}`,
2838
+ port: boundPort,
2839
+ host,
2840
+ authToken,
2841
+ });
2842
+ });
2843
+ server.on('error', reject);
2844
+ }),
2845
+ close: async () => {
2846
+ if (typeof platformRuntime.close === 'function') {
2847
+ await platformRuntime.close();
2848
+ } else {
2849
+ await platformRuntime.browser?.close?.();
2850
+ await platformRuntime.code?.close?.();
2851
+ }
2852
+ for (const entry of pendingPrepares.values()) clearPendingPrepare(entry);
2853
+ pendingPrepares.clear();
2854
+ await phantomBridge.close();
2855
+ if (federationBridge) {
2856
+ persistOperationalState();
2857
+ await federationBridge.stop();
2858
+ }
2859
+ if (meshNode) await meshNode.stop();
2860
+ if (sdkPromise) {
2861
+ try {
2862
+ const enigma = await sdkPromise;
2863
+ if (typeof enigma.dispose === 'function') await enigma.dispose();
2864
+ else await enigma.rag?.dispose?.();
2865
+ } catch {}
2866
+ }
2867
+ if (ownsVectorStore && ownsEmbeddingProvider) {
2868
+ try {
2869
+ await vectorStore.embedder?.dispose?.();
2870
+ } catch {}
2871
+ }
2872
+ if (ephemeralTempDir && fs.existsSync(ephemeralTempDir)) {
2873
+ try { fs.rmSync(ephemeralTempDir, { recursive: true, force: true }); } catch {}
2874
+ }
2875
+ return new Promise((resolve) => server.close(resolve));
2876
+ },
2877
+ };
2878
+ }
2879
+
2880
+ export async function startDesktopServer(options = {}) {
2881
+ const desktopServer = createDesktopServer(options);
2882
+ const info = await desktopServer.listen();
2883
+
2884
+ console.log(`\n============================================================`);
2885
+ console.log(`⚡ ENIGMA SOVEREIGN TERMINAL DESKTOP RUNNING`);
2886
+ console.log(` Origin: ${info.url}`);
2887
+ console.log(` Cluster: Solana Mainnet (mainnet-beta)`);
2888
+ console.log(` Security Mode: Hardened Localhost Auth (Strict Host/Origin Check)`);
2889
+ console.log(`============================================================\n`);
2890
+
2891
+ if (options.openBrowser !== false) {
2892
+ const chromePaths = [
2893
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
2894
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
2895
+ ];
2896
+ const chromeExe = chromePaths.find((p) => fs.existsSync(p));
2897
+
2898
+ if (chromeExe) {
2899
+ spawn(chromeExe, [`--app=${info.url}`, '--window-size=1240,780'], {
2900
+ detached: true,
2901
+ stdio: 'ignore',
2902
+ }).unref();
2903
+ } else {
2904
+ const startCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
2905
+ if (process.platform === 'win32') {
2906
+ spawn('cmd.exe', ['/c', 'start', '', info.url], { detached: true, stdio: 'ignore' }).unref();
2907
+ } else {
2908
+ spawn(startCmd, [info.url], { detached: true, stdio: 'ignore' }).unref();
2909
+ }
2910
+ }
2911
+ }
2912
+
2913
+ return desktopServer;
2914
+ }