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,637 @@
1
+ // Enigma Sovereign Terminal — Cryptographically Authenticated Phantom Browser Companion Bridge
2
+ // Enforces domain-separated Ed25519 signature verification (Sign-In with Solana),
3
+ // server-side balance fetching, strict Host/Origin checking (no wildcard CORS), exact 64 KiB body limits, and XSS prevention.
4
+
5
+ import http from 'node:http';
6
+ import crypto from 'node:crypto';
7
+ import { spawn } from 'node:child_process';
8
+
9
+ export const SOLANA_MAINNET_RPC = 'https://api.mainnet-beta.solana.com';
10
+ export const OFFICIAL_TOKEN_CA = 'EknDj8VZUHL6FhV9pSk3m6xP2U2sDzwjRDVstKeQpump';
11
+
12
+ const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
13
+ const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
14
+ const BASE58_MAP = new Map();
15
+ for (let i = 0; i < BASE58_ALPHABET.length; i += 1) BASE58_MAP.set(BASE58_ALPHABET[i], BigInt(i));
16
+
17
+ const MAX_BODY_BYTES = 64 * 1024; // 64 KiB limit
18
+
19
+ export function decodeBase58(str, expectedLength = undefined) {
20
+ if (typeof str !== 'string' || str.length === 0) return null;
21
+ let num = 0n;
22
+ for (let i = 0; i < str.length; i += 1) {
23
+ const val = BASE58_MAP.get(str[i]);
24
+ if (val === undefined) return null;
25
+ num = num * 58n + val;
26
+ }
27
+ let hex = num.toString(16);
28
+ if (hex.length % 2 !== 0) hex = '0' + hex;
29
+ const bytes = hex ? Buffer.from(hex, 'hex') : Buffer.alloc(0);
30
+ let leadingZeros = 0;
31
+ for (let i = 0; i < str.length && str[i] === '1'; i += 1) {
32
+ leadingZeros += 1;
33
+ }
34
+ const result = Buffer.concat([Buffer.alloc(leadingZeros), bytes]);
35
+ if (typeof expectedLength === 'number' && expectedLength > 0) {
36
+ if (result.length < expectedLength) {
37
+ return Buffer.concat([Buffer.alloc(expectedLength - result.length), result]);
38
+ }
39
+ if (result.length > expectedLength) {
40
+ const excess = result.length - expectedLength;
41
+ for (let i = 0; i < excess; i++) {
42
+ if (result[i] !== 0) return null;
43
+ }
44
+ return result.subarray(excess);
45
+ }
46
+ return result;
47
+ }
48
+ return result;
49
+ }
50
+
51
+ export function encodeBase58(buffer) {
52
+ if (!Buffer.isBuffer(buffer) || buffer.length === 0) return '';
53
+ let leadingZeros = 0;
54
+ for (let i = 0; i < buffer.length && buffer[i] === 0; i += 1) {
55
+ leadingZeros += 1;
56
+ }
57
+ let num = BigInt('0x' + (buffer.toString('hex') || '0'));
58
+ let encoded = '';
59
+ while (num > 0n) {
60
+ const rem = Number(num % 58n);
61
+ num = num / 58n;
62
+ encoded = BASE58_ALPHABET[rem] + encoded;
63
+ }
64
+ return '1'.repeat(leadingZeros) + encoded;
65
+ }
66
+
67
+ export function buildPhantomChallenge({ origin, nonce, issuedAt, expiresAt }) {
68
+ return [
69
+ 'Enigma Sovereign Terminal Session Authentication',
70
+ `Origin: ${origin}`,
71
+ `Nonce: ${nonce}`,
72
+ `Issued At: ${issuedAt}`,
73
+ `Expires At: ${expiresAt}`,
74
+ 'Statement: Sign this challenge to prove authority over your Solana wallet for the Enigma Terminal session. Zero private keys leave your wallet.',
75
+ ].join('\n');
76
+ }
77
+
78
+ function readCappedBody(req, res, callback) {
79
+ let body = '';
80
+ let bytes = 0;
81
+ let rejected = false;
82
+
83
+ req.on('data', (chunk) => {
84
+ if (rejected) return;
85
+ bytes += chunk.length;
86
+ if (bytes > MAX_BODY_BYTES) {
87
+ rejected = true;
88
+ req.pause();
89
+ res.writeHead(413, {
90
+ 'Content-Type': 'application/json',
91
+ 'Connection': 'close',
92
+ });
93
+ res.end(JSON.stringify({ ok: false, error: 'payload too large' }));
94
+ req.resume();
95
+ return;
96
+ }
97
+ body += chunk;
98
+ });
99
+ req.on('end', () => {
100
+ if (!rejected) {
101
+ callback(body);
102
+ }
103
+ });
104
+ }
105
+
106
+ export class PhantomBrowserBridge {
107
+ constructor(options = {}) {
108
+ this.port = options.port !== undefined ? options.port : 0; // 0 = dynamic OS port
109
+ this.host = '127.0.0.1';
110
+ this.cluster = options.cluster || 'mainnet-beta';
111
+ this.rpcUrl = options.rpcUrl || SOLANA_MAINNET_RPC;
112
+ this.server = null;
113
+ this.activeSession = null;
114
+ this.connectedWallet = null;
115
+ this.onWalletConnected = options.onWalletConnected || null;
116
+ this.fetchFn = options.fetchFn || globalThis.fetch;
117
+ }
118
+
119
+ // Generate an authenticated ephemeral session challenge
120
+ createSession() {
121
+ const nonce = crypto.randomBytes(32).toString('hex');
122
+ const now = Date.now();
123
+ const issuedAt = new Date(now).toISOString();
124
+ const expiresAt = new Date(now + 5 * 60 * 1000).toISOString(); // 5 min expiry
125
+ const origin = `http://${this.host}:${this.port || 8765}`;
126
+ const challenge = buildPhantomChallenge({ origin, nonce, issuedAt, expiresAt });
127
+
128
+ this.activeSession = {
129
+ nonce,
130
+ issuedAt,
131
+ expiresAt,
132
+ challenge,
133
+ consumed: false,
134
+ wallet: null,
135
+ };
136
+ return this.activeSession;
137
+ }
138
+
139
+ // Fetch verified SOL balance server-side directly from RPC
140
+ async fetchVerifiedBalance(pubkey) {
141
+ try {
142
+ const res = await this.fetchFn(this.rpcUrl, {
143
+ method: 'POST',
144
+ headers: { 'Content-Type': 'application/json' },
145
+ body: JSON.stringify({
146
+ jsonrpc: '2.0',
147
+ id: 'balance-verify',
148
+ method: 'getBalance',
149
+ params: [pubkey],
150
+ }),
151
+ });
152
+ const data = await res.json();
153
+ const lamports = Number(data.result?.value);
154
+ if (Number.isFinite(lamports) && lamports >= 0) {
155
+ return (lamports / 1e9).toFixed(4);
156
+ }
157
+ } catch {
158
+ // Fallback on RPC failure
159
+ }
160
+ return '0.0000';
161
+ }
162
+
163
+ // Cryptographically verify the Ed25519 signature
164
+ verifyAuthSignature({ publicKey, signatureHex, nonce }) {
165
+ if (!this.activeSession || this.activeSession.nonce !== nonce) {
166
+ return { ok: false, error: 'invalid or unknown session nonce' };
167
+ }
168
+
169
+ if (this.activeSession.consumed) {
170
+ return { ok: false, error: 'session challenge has already been consumed (replay rejected)' };
171
+ }
172
+
173
+ if (Date.now() > Date.parse(this.activeSession.expiresAt)) {
174
+ return { ok: false, error: 'session challenge has expired' };
175
+ }
176
+
177
+ const pubkeyBytes = decodeBase58(publicKey);
178
+ if (!pubkeyBytes || pubkeyBytes.length !== 32) {
179
+ return { ok: false, error: 'invalid Base58 Ed25519 public key' };
180
+ }
181
+
182
+ const sigBytes = Buffer.from(signatureHex || '', 'hex');
183
+ if (sigBytes.length !== 64) {
184
+ return { ok: false, error: 'invalid Ed25519 signature length (expected 64 bytes)' };
185
+ }
186
+
187
+ try {
188
+ const spkiDer = Buffer.concat([ED25519_SPKI_PREFIX, pubkeyBytes]);
189
+ const keyObject = crypto.createPublicKey({
190
+ key: spkiDer,
191
+ format: 'der',
192
+ type: 'spki',
193
+ });
194
+
195
+ const messageBuffer = Buffer.from(this.activeSession.challenge, 'utf8');
196
+ const verified = crypto.verify(null, messageBuffer, keyObject, sigBytes);
197
+
198
+ if (!verified) {
199
+ return { ok: false, error: 'Ed25519 signature verification failed for claimed public key' };
200
+ }
201
+
202
+ // Atomically consume the session
203
+ this.activeSession.consumed = true;
204
+ return { ok: true, publicKey };
205
+ } catch (err) {
206
+ return { ok: false, error: `cryptographic verification error: ${err.message}` };
207
+ }
208
+ }
209
+
210
+ // Validate Host and Origin headers on the bridge
211
+ validateBridgeSecurity(req, res) {
212
+ const rawHost = req.headers['host'];
213
+ if (!rawHost) {
214
+ res.writeHead(403, { 'Content-Type': 'application/json' });
215
+ res.end(JSON.stringify({ ok: false, error: 'host header required' }));
216
+ return false;
217
+ }
218
+
219
+ let parsedHost;
220
+ try {
221
+ parsedHost = new URL(`http://${rawHost}`);
222
+ } catch {
223
+ res.writeHead(403, { 'Content-Type': 'application/json' });
224
+ res.end(JSON.stringify({ ok: false, error: 'malformed host header' }));
225
+ return false;
226
+ }
227
+
228
+ const hostName = parsedHost.hostname.toLowerCase();
229
+ const hostPort = Number(parsedHost.port || 80);
230
+ const validHostName = hostName === '127.0.0.1' || hostName === 'localhost';
231
+ const validHostPort = this.port === 0 || hostPort === this.port;
232
+
233
+ if (!validHostName || !validHostPort) {
234
+ res.writeHead(403, { 'Content-Type': 'application/json' });
235
+ res.end(JSON.stringify({ ok: false, error: 'host header rejected: must match exact bridge port' }));
236
+ return false;
237
+ }
238
+
239
+ const rawOrigin = req.headers['origin'];
240
+ if (rawOrigin) {
241
+ let parsedOrigin;
242
+ try {
243
+ parsedOrigin = new URL(rawOrigin);
244
+ } catch {
245
+ res.writeHead(403, { 'Content-Type': 'application/json' });
246
+ res.end(JSON.stringify({ ok: false, error: 'malformed origin header' }));
247
+ return false;
248
+ }
249
+
250
+ const originName = parsedOrigin.hostname.toLowerCase();
251
+ const originPort = Number(parsedOrigin.port || 80);
252
+ const validOrigin = (originName === '127.0.0.1' || originName === 'localhost') && (this.port === 0 || originPort === this.port);
253
+
254
+ if (!validOrigin) {
255
+ res.writeHead(403, { 'Content-Type': 'application/json' });
256
+ res.end(JSON.stringify({ ok: false, error: 'cross-origin request rejected' }));
257
+ return false;
258
+ }
259
+ }
260
+
261
+ return true;
262
+ }
263
+
264
+ // Handle incoming HTTP request
265
+ handleRequest(req, res) {
266
+ if (!this.validateBridgeSecurity(req, res)) return true;
267
+
268
+ const url = new URL(req.url, `http://${this.host}:${this.port || 80}`);
269
+
270
+ // Strict CORS: only echo exact local origin if valid
271
+ const origin = req.headers['origin'];
272
+ if (origin) {
273
+ res.setHeader('Access-Control-Allow-Origin', origin);
274
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
275
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
276
+ }
277
+
278
+ if (req.method === 'OPTIONS') {
279
+ res.writeHead(204);
280
+ res.end();
281
+ return true;
282
+ }
283
+
284
+ // Status endpoint for terminal polling
285
+ if (url.pathname === '/api/wallet/status' || url.pathname === '/api/status') {
286
+ res.writeHead(200, { 'Content-Type': 'application/json' });
287
+ res.end(JSON.stringify({
288
+ ok: true,
289
+ connected: Boolean(this.connectedWallet),
290
+ wallet: this.connectedWallet,
291
+ cluster: this.cluster,
292
+ tokenCa: OFFICIAL_TOKEN_CA,
293
+ }));
294
+ return true;
295
+ }
296
+
297
+ // Callback endpoint with Ed25519 signature verification and server-side balance fetching
298
+ if (url.pathname === '/api/callback' && req.method === 'POST') {
299
+ readCappedBody(req, res, async (body) => {
300
+ try {
301
+ const data = JSON.parse(body || '{}');
302
+ const verification = this.verifyAuthSignature(data);
303
+
304
+ if (!verification.ok) {
305
+ res.writeHead(403, { 'Content-Type': 'application/json' });
306
+ res.end(JSON.stringify({ ok: false, error: verification.error }));
307
+ return;
308
+ }
309
+
310
+ // Fetch verified balance server-side (ignore untrusted client-reported balance)
311
+ const verifiedBalance = await this.fetchVerifiedBalance(verification.publicKey);
312
+
313
+ this.connectedWallet = {
314
+ publicKey: verification.publicKey,
315
+ balance: verifiedBalance,
316
+ cluster: this.cluster,
317
+ authenticatedAt: new Date().toISOString(),
318
+ };
319
+
320
+ this.activeSession.wallet = this.connectedWallet;
321
+
322
+ if (typeof this.onWalletConnected === 'function') {
323
+ this.onWalletConnected(this.connectedWallet);
324
+ }
325
+
326
+ res.writeHead(200, { 'Content-Type': 'application/json' });
327
+ res.end(JSON.stringify({ ok: true, wallet: this.connectedWallet }));
328
+ } catch (err) {
329
+ res.writeHead(500, { 'Content-Type': 'application/json' });
330
+ res.end(JSON.stringify({ ok: false, error: err.message }));
331
+ }
332
+ });
333
+ return true;
334
+ }
335
+
336
+ // Serve the browser companion page
337
+ if (url.pathname === '/' || url.pathname === '/connect') {
338
+ const sessionNonce = url.searchParams.get('session') || (this.activeSession ? this.activeSession.nonce : '');
339
+ const challenge = this.activeSession ? this.activeSession.challenge : '';
340
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
341
+ res.end(this.renderCompanionHtml(sessionNonce, challenge));
342
+ return true;
343
+ }
344
+
345
+ return false;
346
+ }
347
+
348
+ // Start the local companion server
349
+ async startServer() {
350
+ if (this.server) return `http://${this.host}:${this.port}`;
351
+
352
+ return new Promise((resolve, reject) => {
353
+ this.server = http.createServer((req, res) => {
354
+ const handled = this.handleRequest(req, res);
355
+ if (!handled) {
356
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
357
+ res.end('Not Found');
358
+ }
359
+ });
360
+
361
+ this.server.on('error', (err) => {
362
+ reject(err);
363
+ });
364
+
365
+ this.server.listen(this.port, this.host, () => {
366
+ const addr = this.server.address();
367
+ this.port = addr.port;
368
+ resolve(`http://${this.host}:${this.port}`);
369
+ });
370
+ });
371
+ }
372
+
373
+ // Open the companion URL in the user's real browser
374
+ async openInSystemBrowser(sessionNonce) {
375
+ const url = `http://${this.host}:${this.port}/connect?session=${sessionNonce}`;
376
+ const startCmd = process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open';
377
+
378
+ if (process.platform === 'win32') {
379
+ spawn('cmd.exe', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }).unref();
380
+ } else {
381
+ spawn(startCmd, [url], { detached: true, stdio: 'ignore' }).unref();
382
+ }
383
+
384
+ return url;
385
+ }
386
+
387
+ // Idempotently close the server and release sockets
388
+ async close() {
389
+ if (!this.server) return;
390
+ return new Promise((resolve) => {
391
+ this.server.close(() => {
392
+ this.server = null;
393
+ resolve();
394
+ });
395
+ });
396
+ }
397
+
398
+ async stopServer() {
399
+ return this.close();
400
+ }
401
+
402
+ // Render the glowing Phantom Browser Companion HTML page with in-wallet signMessage
403
+ renderCompanionHtml(nonce, challengeText) {
404
+ const escapedChallenge = JSON.stringify(challengeText || '');
405
+ return `<!doctype html>
406
+ <html lang="en">
407
+ <head>
408
+ <meta charset="utf-8">
409
+ <meta name="viewport" content="width=device-width, initial-scale=1">
410
+ <title>Connect Phantom — Enigma Sovereign Terminal</title>
411
+ <style>
412
+ :root {
413
+ color-scheme: dark;
414
+ --bg: #07090e;
415
+ --card-bg: #0d121c;
416
+ --border: #1e283d;
417
+ --orange: #ff7a1a;
418
+ --green: #14f195;
419
+ --cyan: #00f0ff;
420
+ --text: #f0f4f8;
421
+ --dim: #7f8ea3;
422
+ --font-mono: "Geist Mono", Consolas, monospace;
423
+ }
424
+ * { box-sizing: border-box; margin: 0; padding: 0; }
425
+ body {
426
+ background: var(--bg);
427
+ color: var(--text);
428
+ font-family: var(--font-mono);
429
+ min-height: 100vh;
430
+ display: flex;
431
+ align-items: center;
432
+ justify-content: center;
433
+ padding: 20px;
434
+ }
435
+ .bridge-card {
436
+ width: 100%;
437
+ max-width: 540px;
438
+ background: var(--card-bg);
439
+ border: 1px solid var(--border);
440
+ border-radius: 16px;
441
+ padding: 32px;
442
+ box-shadow: 0 20px 60px rgba(0,0,0,0.5);
443
+ }
444
+ .header {
445
+ display: flex;
446
+ align-items: center;
447
+ gap: 12px;
448
+ margin-bottom: 20px;
449
+ border-bottom: 1px solid var(--border);
450
+ padding-bottom: 16px;
451
+ }
452
+ .logo-icon { font-size: 24px; color: var(--orange); }
453
+ h1 { font-size: 16px; font-weight: 700; letter-spacing: 0.06em; }
454
+ .badge {
455
+ font-size: 11px;
456
+ font-weight: 700;
457
+ color: var(--green);
458
+ background: rgba(20,241,149,0.1);
459
+ border: 1px solid rgba(20,241,149,0.3);
460
+ padding: 3px 8px;
461
+ border-radius: 6px;
462
+ margin-left: auto;
463
+ }
464
+ .desc { font-size: 13px; color: var(--dim); line-height: 1.6; margin-bottom: 20px; }
465
+ .status-box {
466
+ background: rgba(0,0,0,0.3);
467
+ border: 1px solid var(--border);
468
+ border-radius: 8px;
469
+ padding: 16px;
470
+ margin-bottom: 20px;
471
+ font-size: 12px;
472
+ }
473
+ .status-row { display: flex; justify-content: space-between; margin-bottom: 8px; }
474
+ .status-row:last-child { margin-bottom: 0; }
475
+ .label { color: var(--dim); }
476
+ .val { font-weight: 700; color: var(--text); }
477
+ .val.green { color: var(--green); }
478
+ .val.orange { color: var(--orange); }
479
+ .challenge-preview {
480
+ font-size: 11px;
481
+ color: #8c9bb0;
482
+ background: #06080d;
483
+ border: 1px solid var(--border);
484
+ border-radius: 6px;
485
+ padding: 10px;
486
+ margin-bottom: 20px;
487
+ white-space: pre-wrap;
488
+ max-height: 120px;
489
+ overflow-y: auto;
490
+ }
491
+ .connect-btn {
492
+ width: 100%;
493
+ background: linear-gradient(135deg, #ab9ff2, #7a66f0);
494
+ border: 1px solid #c4baff;
495
+ color: #0b091a;
496
+ font-family: inherit;
497
+ font-size: 14px;
498
+ font-weight: 800;
499
+ padding: 14px;
500
+ border-radius: 8px;
501
+ cursor: pointer;
502
+ display: flex;
503
+ align-items: center;
504
+ justify-content: center;
505
+ gap: 8px;
506
+ transition: all 0.2s ease;
507
+ }
508
+ .connect-btn:hover {
509
+ transform: translateY(-2px);
510
+ box-shadow: 0 8px 24px rgba(122,102,240,0.4);
511
+ }
512
+ .notice {
513
+ margin-top: 20px;
514
+ font-size: 11px;
515
+ color: var(--dim);
516
+ text-align: center;
517
+ line-height: 1.5;
518
+ }
519
+ </style>
520
+ </head>
521
+ <body>
522
+ <div class="bridge-card">
523
+ <div class="header">
524
+ <span class="logo-icon">⚡</span>
525
+ <h1>ENIGMA TERMINAL COMPANION</h1>
526
+ <span class="badge">SOLANA MAINNET</span>
527
+ </div>
528
+ <p class="desc">
529
+ Sign the domain-separated authentication challenge in Phantom to verify wallet ownership for your Enigma Terminal session. Your private key stays safely in Phantom.
530
+ </p>
531
+ <div class="status-box">
532
+ <div class="status-row">
533
+ <span class="label">Session Nonce:</span>
534
+ <span class="val orange" id="nonceVal">${nonce.slice(0, 16)}...</span>
535
+ </div>
536
+ <div class="status-row">
537
+ <span class="label">Cluster:</span>
538
+ <span class="val green">Solana Mainnet (mainnet-beta)</span>
539
+ </div>
540
+ <div class="status-row">
541
+ <span class="label">Status:</span>
542
+ <span class="val" id="statusVal">Waiting for Phantom signature...</span>
543
+ </div>
544
+ </div>
545
+ <div class="challenge-preview" id="challengeBox"></div>
546
+ <button class="connect-btn" id="connectBtn" type="button">
547
+ <span>👻</span>
548
+ <span id="btnText">Sign Challenge & Connect</span>
549
+ </button>
550
+ <p class="notice">
551
+ 🔒 Ed25519 Domain-Separated Signature • Single-use atomic nonce • Zero private keys exported
552
+ </p>
553
+ </div>
554
+
555
+ <script>
556
+ const sessionNonce = "${nonce}";
557
+ const challengeText = ${escapedChallenge};
558
+ const connectBtn = document.getElementById('connectBtn');
559
+ const btnText = document.getElementById('btnText');
560
+ const statusVal = document.getElementById('statusVal');
561
+ const challengeBox = document.getElementById('challengeBox');
562
+
563
+ if (challengeBox && challengeText) {
564
+ challengeBox.textContent = challengeText;
565
+ }
566
+
567
+ function getPhantomProvider() {
568
+ if ('phantom' in window && window.phantom?.solana?.isPhantom) {
569
+ return window.phantom.solana;
570
+ }
571
+ if ('solana' in window && window.solana?.isPhantom) {
572
+ return window.solana;
573
+ }
574
+ return null;
575
+ }
576
+
577
+ async function handleConnect() {
578
+ const provider = getPhantomProvider();
579
+ if (!provider) {
580
+ statusVal.textContent = 'Phantom extension not detected in this browser.';
581
+ statusVal.style.color = '#ff5555';
582
+ alert('Please install the Phantom Wallet extension from https://phantom.app and unlock it in Google Chrome.');
583
+ return;
584
+ }
585
+
586
+ try {
587
+ btnText.textContent = 'Awaiting Phantom Signature...';
588
+ const res = await provider.connect();
589
+ const pubkey = res.publicKey.toString();
590
+
591
+ // Sign the domain-separated authentication challenge
592
+ const encoded = new TextEncoder().encode(challengeText);
593
+ const signed = await provider.signMessage(encoded, 'utf8');
594
+ const signatureBytes = signed.signature || signed;
595
+ const signatureHex = Array.from(signatureBytes).map(b => b.toString(16).padStart(2, '0')).join('');
596
+
597
+ // Post signature back to terminal loopback server (server fetches verified balance via RPC)
598
+ const callbackRes = await fetch('/api/callback', {
599
+ method: 'POST',
600
+ headers: { 'Content-Type': 'application/json' },
601
+ body: JSON.stringify({
602
+ publicKey: pubkey,
603
+ signatureHex: signatureHex,
604
+ nonce: sessionNonce
605
+ })
606
+ });
607
+
608
+ const callbackData = await callbackRes.json();
609
+ if (!callbackData.ok) {
610
+ throw new Error(callbackData.error || 'Server signature verification failed');
611
+ }
612
+
613
+ const bal = callbackData.wallet?.balance || '0.0000';
614
+ statusVal.textContent = '✔ Authenticated: ' + pubkey.slice(0,4) + '...' + pubkey.slice(-4) + ' (' + bal + ' SOL)';
615
+ statusVal.className = 'val green';
616
+ btnText.textContent = '✔ Authenticated with Enigma Terminal';
617
+ connectBtn.style.background = '#14f195';
618
+ connectBtn.style.color = '#000';
619
+
620
+ setTimeout(() => {
621
+ document.body.innerHTML = '<div class="bridge-card" style="text-align:center"><h1 style="color:var(--green);margin-bottom:12px">✔ Wallet Authenticated Successfully!</h1><p class="desc">Cryptographic signature verified. You may now close this tab and return to the Enigma Terminal.</p></div>';
622
+ }, 1200);
623
+
624
+ } catch (err) {
625
+ statusVal.textContent = 'Authentication failed: ' + err.message;
626
+ btnText.textContent = 'Retry Phantom Connect';
627
+ }
628
+ }
629
+
630
+ connectBtn.addEventListener('click', handleConnect);
631
+ // Auto-prompt signature
632
+ setTimeout(handleConnect, 400);
633
+ </script>
634
+ </body>
635
+ </html>`;
636
+ }
637
+ }