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,393 @@
1
+ /**
2
+ * Enigma recipient-to-recipient end-to-end encryption (E2EE), ZK-bound.
3
+ *
4
+ * Non-circular ZK binding (v2, supersedes the noteCommit-in-AAD design):
5
+ * the ciphertext AAD can never commit to the final circuit note, because
6
+ * note = Poseidon([ownerKey, rho, payloadCommit]) and payloadCommit exists
7
+ * only AFTER encryption. The binding is therefore staged:
8
+ *
9
+ * 1. PRE-ENCRYPTION — noteContext (client-side, Poseidon):
10
+ * scopeCommit = Poseidon([fieldFromScope(scope)])
11
+ * noteContext = Poseidon([ownerKey, rho, scopeCommit])
12
+ * fieldFromScope(scope) = BigInt('0x' + sha256(utf8(scope))) mod r.
13
+ * noteContext is known before encryption and is what the AAD binds.
14
+ *
15
+ * 2. ENCRYPTION — per-note random DEK (32 bytes, CSPRNG); payload encrypted
16
+ * with XChaCha20-Poly1305 under (DEK, random 24-byte nonce). AAD:
17
+ * AAD = utf8(SCHEMA) || 0x00 || utf8(scope) || 0x00 || noteContext32BE
18
+ * The DEK is wrapped once per recipient with RFC 9180 HPKE base mode
19
+ * DHKEM(X25519, HKDF-SHA256) + ChaCha20Poly1305; the same AAD is passed
20
+ * to each HPKE seal/open, binding every envelope to (schema, scope,
21
+ * noteContext). Recipient keypairs are fresh X25519 keys generated and
22
+ * stored independently — NEVER derived from a Solana wallet secret.
23
+ *
24
+ * 3. POST-ENCRYPTION — payloadCommit (ZK-facing contract; consumed by the
25
+ * action_transition circuit as a private input):
26
+ * ctSha256 = sha256(ciphertext) // raw 32 bytes
27
+ * envelopeSha256 = sha256(canonicalJson(envelopes)) // raw 32 bytes
28
+ * payloadCommit = BigInt('0x' + hex(sha256(
29
+ * utf8('enigma.e2ee.v1')
30
+ * || noteContext32BE // raw 32 bytes
31
+ * || nonce // raw 24 bytes
32
+ * || ctSha256
33
+ * || envelopeSha256))) mod r
34
+ * The nonce is INSIDE the commitment, so payloadCommit binds the full
35
+ * AEAD parameters, the AAD context (via noteContext), and every
36
+ * recipient envelope. r = BN254 scalar field
37
+ * 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001.
38
+ * canonicalJson sorts object keys recursively and preserves array order
39
+ * (envelope order = recipient order given at encryption).
40
+ *
41
+ * 4. FINAL NOTE — deriveFinalNote (matches witness.mjs / the circuit):
42
+ * note = Poseidon([ownerKey, rho, payloadCommit])
43
+ * This is the leaf appended to the note tree; the circuit constrains the
44
+ * same relation in both append and revoke modes.
45
+ *
46
+ * All field elements cross the API as decimal strings (snarkjs convention);
47
+ * normalizeFieldElement32 also accepts 0x-hex strings and 32-byte BE bytes.
48
+ */
49
+
50
+ import { createHash, randomBytes } from 'node:crypto';
51
+ import { CipherSuite, DhkemX25519HkdfSha256, HkdfSha256 } from '@hpke/core';
52
+ import { Chacha20Poly1305 } from '@hpke/chacha20poly1305';
53
+ import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
54
+ import { x25519 } from '@noble/curves/ed25519.js';
55
+ import { buildPoseidon } from 'circomlibjs';
56
+
57
+ export const E2EE_SCHEMA = 'enigma.e2ee.v1';
58
+ export const PAYLOAD_COMMIT_DOMAIN = 'enigma.e2ee.v1';
59
+ export const HPKE_WRAP_INFO = 'enigma.e2ee.v1/hpke-wrap';
60
+
61
+ /** BN254 (alt_bn128) scalar field modulus. */
62
+ export const BN254_SCALAR_FIELD = BigInt(
63
+ '0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001',
64
+ );
65
+
66
+ const DEK_BYTES = 32;
67
+ const NONCE_BYTES = 24; // XChaCha20-Poly1305 extended nonce.
68
+ const X25519_KEY_BYTES = 32;
69
+
70
+ const textEncoder = new TextEncoder();
71
+
72
+ const poseidonC = await buildPoseidon();
73
+ const poseidonF = poseidonC.F;
74
+
75
+ /** Poseidon hash over an array of bigint field elements → bigint. */
76
+ export function poseidon(inputs) {
77
+ return poseidonF.toObject(poseidonC(inputs.map((x) => poseidonF.e(BigInt(x)))));
78
+ }
79
+
80
+ function sha256(...chunks) {
81
+ const hash = createHash('sha256');
82
+ for (const chunk of chunks) hash.update(chunk);
83
+ return hash.digest();
84
+ }
85
+
86
+ function toBytes(value, label) {
87
+ if (value instanceof Uint8Array) return value;
88
+ if (typeof value === 'string') return textEncoder.encode(value);
89
+ throw new TypeError(`${label} must be a string or Uint8Array`);
90
+ }
91
+
92
+ function b64encode(bytes) {
93
+ return Buffer.from(bytes).toString('base64');
94
+ }
95
+
96
+ function b64decode(value, label) {
97
+ if (value instanceof Uint8Array) return value;
98
+ if (typeof value !== 'string') throw new TypeError(`${label} must be a base64 string`);
99
+ return new Uint8Array(Buffer.from(value, 'base64'));
100
+ }
101
+
102
+ function toField(value, label) {
103
+ let v;
104
+ try {
105
+ v = BigInt(value);
106
+ } catch {
107
+ throw new TypeError(`${label} must be a field element (bigint/number/decimal string)`);
108
+ }
109
+ if (v < 0n || v >= BN254_SCALAR_FIELD) {
110
+ throw new RangeError(`${label} out of range (< BN254 scalar field r required)`);
111
+ }
112
+ return v;
113
+ }
114
+
115
+ /**
116
+ * Deterministic JSON: object keys sorted recursively, array order preserved,
117
+ * no whitespace. Used to hash the recipient envelope set.
118
+ */
119
+ export function canonicalJson(value) {
120
+ if (value === null || typeof value !== 'object') return JSON.stringify(value);
121
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
122
+ const keys = Object.keys(value).sort();
123
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(',')}}`;
124
+ }
125
+
126
+ /**
127
+ * Normalize a field element to its canonical 32-byte big-endian form.
128
+ * Accepts:
129
+ * - Uint8Array/Buffer of exactly 32 bytes (already big-endian), or
130
+ * - '0x'-prefixed 1..64-hex-char big-endian string, or
131
+ * - decimal string of a BN254 field element (< r).
132
+ */
133
+ export function normalizeFieldElement32(value) {
134
+ if (value instanceof Uint8Array) {
135
+ if (value.length !== 32) {
136
+ throw new RangeError('field element bytes must be exactly 32 bytes');
137
+ }
138
+ return new Uint8Array(value);
139
+ }
140
+ if (typeof value !== 'string' || value.length === 0) {
141
+ throw new TypeError('field element must be a 32-byte Uint8Array, 0x-hex string, or decimal string');
142
+ }
143
+ let v;
144
+ if (/^0x[0-9a-fA-F]{1,64}$/.test(value) || /^[0-9]+$/.test(value)) {
145
+ v = BigInt(value);
146
+ } else {
147
+ throw new TypeError('field element string must be 0x-hex or decimal');
148
+ }
149
+ if (v < 0n || v >= BN254_SCALAR_FIELD) {
150
+ throw new RangeError('field element out of range (< BN254 scalar field r required)');
151
+ }
152
+ const out = new Uint8Array(32);
153
+ let rest = v;
154
+ for (let i = 31; i >= 0; i -= 1) {
155
+ out[i] = Number(rest & 0xffn);
156
+ rest >>= 8n;
157
+ }
158
+ return out;
159
+ }
160
+
161
+ /** scopeCommit = Poseidon([BigInt('0x' + sha256(utf8(scope))) mod r]) → decimal string. */
162
+ export function deriveScopeCommit(scope) {
163
+ if (typeof scope !== 'string' || scope.length === 0) {
164
+ throw new TypeError('scope must be a non-empty string');
165
+ }
166
+ const field = BigInt(`0x${sha256(textEncoder.encode(scope)).toString('hex')}`) % BN254_SCALAR_FIELD;
167
+ return poseidon([field]).toString(10);
168
+ }
169
+
170
+ /**
171
+ * noteContext = Poseidon([ownerKey, rho, scopeCommit]) → decimal string.
172
+ * Computed client-side BEFORE encryption; this is what the payload AAD binds.
173
+ */
174
+ export function deriveNoteContext({ ownerKey, rho, scope }) {
175
+ const scopeCommit = deriveScopeCommit(scope);
176
+ return poseidon([
177
+ toField(ownerKey, 'ownerKey'),
178
+ toField(rho, 'rho'),
179
+ BigInt(scopeCommit),
180
+ ]).toString(10);
181
+ }
182
+
183
+ /** AAD = utf8(schema) || 0x00 || utf8(scope) || 0x00 || noteContext32BE. */
184
+ export function deriveAad({ schema = E2EE_SCHEMA, scope, noteContext }) {
185
+ if (typeof scope !== 'string' || scope.length === 0) {
186
+ throw new TypeError('scope must be a non-empty string');
187
+ }
188
+ const schemaBytes = textEncoder.encode(schema);
189
+ const scopeBytes = textEncoder.encode(scope);
190
+ const contextBytes = normalizeFieldElement32(noteContext);
191
+ const aad = new Uint8Array(schemaBytes.length + 1 + scopeBytes.length + 1 + 32);
192
+ aad.set(schemaBytes, 0);
193
+ aad[schemaBytes.length] = 0;
194
+ aad.set(scopeBytes, schemaBytes.length + 1);
195
+ aad[schemaBytes.length + 1 + scopeBytes.length] = 0;
196
+ aad.set(contextBytes, schemaBytes.length + 1 + scopeBytes.length + 1);
197
+ return aad;
198
+ }
199
+
200
+ function createSuite() {
201
+ return new CipherSuite({
202
+ kem: new DhkemX25519HkdfSha256(),
203
+ kdf: new HkdfSha256(),
204
+ aead: new Chacha20Poly1305(),
205
+ });
206
+ }
207
+
208
+ /**
209
+ * Generate a fresh X25519 recipient keypair, independent of any wallet key.
210
+ * Returns raw 32-byte keys and their base64 forms.
211
+ */
212
+ export function generateRecipientKeyPair() {
213
+ const secretKey = x25519.utils.randomSecretKey();
214
+ const publicKey = x25519.getPublicKey(secretKey);
215
+ return {
216
+ secretKey,
217
+ publicKey,
218
+ secretKeyB64: b64encode(secretKey),
219
+ publicKeyB64: b64encode(publicKey),
220
+ };
221
+ }
222
+
223
+ /**
224
+ * ZK-facing payload commitment (see module header for the binding derivation).
225
+ * Binds noteContext (AAD context), the AEAD nonce, the ciphertext, and the
226
+ * canonical recipient envelope set. Returns the decimal-string field element.
227
+ */
228
+ export function derivePayloadCommit({ noteContext, nonceB64, ciphertextB64, envelopes }) {
229
+ const contextBytes = normalizeFieldElement32(noteContext);
230
+ const nonce = b64decode(nonceB64, 'nonceB64');
231
+ if (nonce.length !== NONCE_BYTES) {
232
+ throw new RangeError('nonce must be 24 bytes (XChaCha20-Poly1305)');
233
+ }
234
+ const ciphertext = b64decode(ciphertextB64, 'ciphertextB64');
235
+ if (!Array.isArray(envelopes)) throw new TypeError('envelopes must be an array');
236
+ const ctSha256 = sha256(ciphertext);
237
+ const envelopeSha256 = sha256(textEncoder.encode(canonicalJson(envelopes)));
238
+ const digest = sha256(
239
+ textEncoder.encode(PAYLOAD_COMMIT_DOMAIN),
240
+ contextBytes,
241
+ nonce,
242
+ ctSha256,
243
+ envelopeSha256,
244
+ );
245
+ const field = BigInt(`0x${digest.toString('hex')}`) % BN254_SCALAR_FIELD;
246
+ return field.toString(10);
247
+ }
248
+
249
+ /**
250
+ * Final circuit note = Poseidon([ownerKey, rho, payloadCommit]) → decimal
251
+ * string. Identical relation to witness.mjs buildActionWitness and the
252
+ * ActionTransition circuit's note well-formedness constraint.
253
+ */
254
+ export function deriveFinalNote({ ownerKey, rho, payloadCommit }) {
255
+ return poseidon([
256
+ toField(ownerKey, 'ownerKey'),
257
+ toField(rho, 'rho'),
258
+ toField(payloadCommit, 'payloadCommit'),
259
+ ]).toString(10);
260
+ }
261
+
262
+ /**
263
+ * Encrypt a payload to one or more recipients.
264
+ *
265
+ * @param {object} params
266
+ * @param {string|Uint8Array} params.plaintext
267
+ * @param {Array<string|Uint8Array>} params.recipients - X25519 public keys (base64 or raw 32B).
268
+ * @param {string} params.scope - binding scope string (e.g. 'memory/note').
269
+ * @param {string|Uint8Array} params.noteContext - deriveNoteContext output
270
+ * (decimal/0x-hex string or 32B BE); bound into the AAD and payloadCommit.
271
+ * @returns {{ciphertextB64: string, nonceB64: string,
272
+ * envelopes: Array<{recipientPubB64: string, encB64: string, ctB64: string}>,
273
+ * payloadCommit: string}}
274
+ */
275
+ export async function e2eeEncrypt({ plaintext, recipients, scope, noteContext }) {
276
+ if (!Array.isArray(recipients) || recipients.length === 0) {
277
+ throw new TypeError('recipients must be a non-empty array of X25519 public keys');
278
+ }
279
+ const plaintextBytes = toBytes(plaintext, 'plaintext');
280
+ const aad = deriveAad({ scope, noteContext });
281
+ const dek = randomBytes(DEK_BYTES);
282
+ try {
283
+ const nonce = randomBytes(NONCE_BYTES);
284
+ const ciphertext = xchacha20poly1305(new Uint8Array(dek), new Uint8Array(nonce), aad)
285
+ .encrypt(plaintextBytes);
286
+
287
+ const suite = createSuite();
288
+ const info = textEncoder.encode(HPKE_WRAP_INFO);
289
+ const envelopes = [];
290
+ for (const recipient of recipients) {
291
+ const recipientPub = b64decode(recipient, 'recipient public key');
292
+ if (recipientPub.length !== X25519_KEY_BYTES) {
293
+ throw new RangeError('recipient public key must be 32 bytes (X25519)');
294
+ }
295
+ const recipientPublicKey = await suite.kem.importKey('raw', recipientPub, true);
296
+ const sender = await suite.createSenderContext({ recipientPublicKey, info });
297
+ const wrappedDek = await sender.seal(new Uint8Array(dek), aad);
298
+ envelopes.push({
299
+ recipientPubB64: b64encode(recipientPub),
300
+ encB64: b64encode(new Uint8Array(sender.enc)),
301
+ ctB64: b64encode(new Uint8Array(wrappedDek)),
302
+ });
303
+ }
304
+
305
+ const ciphertextB64 = b64encode(ciphertext);
306
+ const nonceB64 = b64encode(nonce);
307
+ return {
308
+ ciphertextB64,
309
+ nonceB64,
310
+ envelopes,
311
+ payloadCommit: derivePayloadCommit({ noteContext, nonceB64, ciphertextB64, envelopes }),
312
+ };
313
+ } finally {
314
+ dek.fill(0);
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Decrypt a payload as one of its recipients.
320
+ *
321
+ * @param {object} params
322
+ * @param {string} params.ciphertextB64
323
+ * @param {string} params.nonceB64
324
+ * @param {Array<{recipientPubB64: string, encB64: string, ctB64: string}>} params.envelopes
325
+ * @param {string|Uint8Array} params.recipientSecretKey - base64 or raw 32B X25519 secret key.
326
+ * @param {string} params.scope - must match the scope used at encryption (AAD).
327
+ * @param {string|Uint8Array} params.noteContext - must match encryption (AAD).
328
+ * @param {string} [params.payloadCommit] - optional; verified when supplied.
329
+ * @returns {Uint8Array} the plaintext bytes.
330
+ * @throws if the key has no envelope, the envelope fails HPKE open, the
331
+ * payload fails AEAD verification (wrong AAD / tampering), or a supplied
332
+ * payloadCommit does not match.
333
+ */
334
+ export async function e2eeDecrypt({
335
+ ciphertextB64,
336
+ nonceB64,
337
+ envelopes,
338
+ recipientSecretKey,
339
+ scope,
340
+ noteContext,
341
+ payloadCommit,
342
+ }) {
343
+ const ciphertext = b64decode(ciphertextB64, 'ciphertextB64');
344
+ const nonce = b64decode(nonceB64, 'nonceB64');
345
+ if (nonce.length !== NONCE_BYTES) {
346
+ throw new RangeError('nonce must be 24 bytes (XChaCha20-Poly1305)');
347
+ }
348
+ if (!Array.isArray(envelopes)) throw new TypeError('envelopes must be an array');
349
+ const secretKey = new Uint8Array(b64decode(recipientSecretKey, 'recipientSecretKey'));
350
+ let dek;
351
+ try {
352
+ if (secretKey.length !== X25519_KEY_BYTES) {
353
+ throw new RangeError('recipientSecretKey must be 32 bytes (X25519)');
354
+ }
355
+ const aad = deriveAad({ scope, noteContext });
356
+
357
+ if (payloadCommit !== undefined) {
358
+ const expected = derivePayloadCommit({ noteContext, nonceB64, ciphertextB64, envelopes });
359
+ if (expected !== String(payloadCommit)) {
360
+ throw new Error('payloadCommit mismatch: ciphertext or envelopes were tampered');
361
+ }
362
+ }
363
+
364
+ const publicKeyB64 = b64encode(x25519.getPublicKey(secretKey));
365
+ const envelope = envelopes.find((env) => env && env.recipientPubB64 === publicKeyB64);
366
+ if (!envelope) {
367
+ throw new Error('no envelope for this recipient key (wrong recipient)');
368
+ }
369
+
370
+ const suite = createSuite();
371
+ const info = textEncoder.encode(HPKE_WRAP_INFO);
372
+ const recipientKey = await suite.kem.importKey('raw', secretKey, false);
373
+ try {
374
+ const recipient = await suite.createRecipientContext({
375
+ recipientKey,
376
+ enc: b64decode(envelope.encB64, 'envelope encB64'),
377
+ info,
378
+ });
379
+ dek = new Uint8Array(await recipient.open(b64decode(envelope.ctB64, 'envelope ctB64'), aad));
380
+ } catch (err) {
381
+ throw new Error(`HPKE envelope open failed: ${err.message}`);
382
+ }
383
+
384
+ try {
385
+ return xchacha20poly1305(dek, nonce, aad).decrypt(ciphertext);
386
+ } catch (err) {
387
+ throw new Error(`payload AEAD open failed: ${err.message}`);
388
+ }
389
+ } finally {
390
+ dek?.fill(0);
391
+ secretKey.fill(0);
392
+ }
393
+ }