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,481 @@
1
+ import {
2
+ createCipheriv,
3
+ createDecipheriv,
4
+ createHash,
5
+ generateKeyPairSync,
6
+ randomBytes,
7
+ randomUUID,
8
+ sign,
9
+ verify,
10
+ X509Certificate,
11
+ } from 'node:crypto';
12
+ import { createPoseidonCommitment } from './poseidon.js';
13
+
14
+ export const ATTESTATION_SCHEMA = 'enigma.enclave.attestation.v2';
15
+
16
+ export const PROVIDER_TYPE_SIMULATED = 'simulated_software';
17
+ export const PROVIDER_TYPE_AWS_NITRO = 'aws_nitro_enclave';
18
+ export const PROVIDER_TYPE_INTEL_SGX = 'intel_sgx_dcap';
19
+ export const PROVIDER_TYPE_TPM2 = 'tpm_2_0_nvram';
20
+
21
+ /**
22
+ * Pinned Hardware Vendor Root Certificates & Trust Anchors
23
+ */
24
+ export const TRUSTED_VENDOR_ROOTS = Object.freeze({
25
+ // AWS Nitro Enclaves Root CA (Amazon Root CA 1 SHA-256 Fingerprint)
26
+ AWS_NITRO_ROOT_FINGERPRINT: '2c:43:35:00:cc:aa:da:b8:31:0a:f9:90:35:b6:65:be:4a:36:7a:a2:6a:bf:f6:30:17:8c:fc:19:1b:2f:bc:e6',
27
+ // Standard TPM 2.0 Manufacturer Root Authority Fingerprints
28
+ TPM_TRUSTED_ISSUERS: new Set([
29
+ 'TCG Trusted Platform Module Root CA',
30
+ 'Intel TPM EK Root CA',
31
+ 'Infineon TPM Root CA',
32
+ ]),
33
+ });
34
+
35
+ /**
36
+ * Base Abstract Interface for Hardware Secure Enclave Providers.
37
+ */
38
+ export class EnclaveProvider {
39
+ constructor(providerType) {
40
+ if (new.target === EnclaveProvider) {
41
+ throw new TypeError('Cannot construct EnclaveProvider instances directly');
42
+ }
43
+ this.providerType = providerType;
44
+ }
45
+
46
+ async getMeasurement() {
47
+ throw new Error('getMeasurement() must be implemented');
48
+ }
49
+
50
+ async createRecord(plaintext, metadata) {
51
+ throw new Error('createRecord() must be implemented');
52
+ }
53
+
54
+ async decryptRecord(recordId, ciphertext, iv, authTag) {
55
+ throw new Error('decryptRecord() must be implemented');
56
+ }
57
+
58
+ async zeroizeRecord(recordId) {
59
+ throw new Error('zeroizeRecord() must be implemented');
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Explicit Test Simulator: Local Software Mock
65
+ * For local testing and CI only. Explicitly signed by a designated test authority.
66
+ */
67
+ export class SimulatedEnclaveProvider extends EnclaveProvider {
68
+ constructor({ simulationLabel = 'local_dev_simulator', testAuthorityKeyPair = null } = {}) {
69
+ super(PROVIDER_TYPE_SIMULATED);
70
+ this.simulationLabel = simulationLabel;
71
+ this.isSimulated = true;
72
+ this.securityLevel = 'TEST_ONLY_UNTRUSTED_RAM';
73
+
74
+ if (testAuthorityKeyPair) {
75
+ this._signingKey = testAuthorityKeyPair.privateKey;
76
+ this.publicKeyPem = testAuthorityKeyPair.publicKey.export({ type: 'spki', format: 'pem' });
77
+ } else {
78
+ const { publicKey, privateKey } = generateKeyPairSync('ed25519');
79
+ this._signingKey = privateKey;
80
+ this.publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
81
+ }
82
+
83
+ this.enclaveId = `sim_${createHash('sha256').update(this.publicKeyPem).digest('hex').slice(0, 16)}`;
84
+ this.pcrDigest = createHash('sha256').update(`simulated.pcr.${simulationLabel}`).digest('hex');
85
+
86
+ this._memory = new Map();
87
+ this._tombstones = new Map();
88
+ }
89
+
90
+ async getMeasurement() {
91
+ return {
92
+ providerType: this.providerType,
93
+ isSimulated: true,
94
+ enclaveId: this.enclaveId,
95
+ pcrDigest: this.pcrDigest,
96
+ publicKeyPem: this.publicKeyPem,
97
+ };
98
+ }
99
+
100
+ async createRecord(plaintext, metadata = {}) {
101
+ const recordId = randomUUID();
102
+ const keyBuf = randomBytes(32);
103
+ const iv = randomBytes(12);
104
+ const salt = randomBytes(32);
105
+
106
+ const { commitment } = createPoseidonCommitment(keyBuf, salt);
107
+
108
+ const cipher = createCipheriv('aes-256-gcm', keyBuf, iv);
109
+ const plainBuf = Buffer.isBuffer(plaintext) ? plaintext : Buffer.from(plaintext, 'utf-8');
110
+ const ciphertext = Buffer.concat([cipher.update(plainBuf), cipher.final()]);
111
+ const authTag = cipher.getAuthTag();
112
+
113
+ this._memory.set(recordId, {
114
+ key: keyBuf,
115
+ commitment,
116
+ salt: salt.toString('hex'),
117
+ metadata,
118
+ createdAt: Date.now(),
119
+ status: 'ACTIVE',
120
+ });
121
+
122
+ return {
123
+ recordId,
124
+ ciphertext: ciphertext.toString('base64'),
125
+ iv: iv.toString('base64'),
126
+ authTag: authTag.toString('base64'),
127
+ commitment,
128
+ salt: salt.toString('hex'),
129
+ enclaveId: this.enclaveId,
130
+ isSimulated: true,
131
+ pcrDigest: this.pcrDigest,
132
+ };
133
+ }
134
+
135
+ async decryptRecord(recordId, ciphertextBase64, ivBase64, authTagBase64) {
136
+ const record = this._memory.get(recordId);
137
+ if (!record || record.status !== 'ACTIVE') {
138
+ throw new Error(`RECORD_UNAVAILABLE: Record ${recordId} key does not exist or has been zeroized.`);
139
+ }
140
+
141
+ const decipher = createDecipheriv('aes-256-gcm', record.key, Buffer.from(ivBase64, 'base64'));
142
+ decipher.setAuthTag(Buffer.from(authTagBase64, 'base64'));
143
+
144
+ const decrypted = Buffer.concat([
145
+ decipher.update(Buffer.from(ciphertextBase64, 'base64')),
146
+ decipher.final(),
147
+ ]);
148
+
149
+ return decrypted.toString('utf-8');
150
+ }
151
+
152
+ async zeroizeRecord(recordId) {
153
+ const record = this._memory.get(recordId);
154
+ if (!record) {
155
+ throw new Error(`RECORD_NOT_FOUND: ${recordId} is not in simulator storage.`);
156
+ }
157
+
158
+ if (record.status === 'ZEROIZED') {
159
+ return this._tombstones.get(recordId);
160
+ }
161
+
162
+ // Overwrite buffer in RAM
163
+ record.key.fill(0xff);
164
+ randomBytes(record.key.length).copy(record.key);
165
+ record.key.fill(0x00);
166
+ record.status = 'ZEROIZED';
167
+ this._memory.delete(recordId);
168
+
169
+ const timestamp = new Date().toISOString();
170
+ const nullifierHash = createHash('sha256')
171
+ .update(`${record.commitment}:${record.salt}:${timestamp}`)
172
+ .digest('hex');
173
+
174
+ const attestationPayload = {
175
+ schema: ATTESTATION_SCHEMA,
176
+ action: 'KEY_ZEROIZED',
177
+ recordId,
178
+ commitment: record.commitment,
179
+ nullifierHash: `0x${nullifierHash}`,
180
+ enclaveId: this.enclaveId,
181
+ providerType: this.providerType,
182
+ isSimulated: true,
183
+ pcrDigest: this.pcrDigest,
184
+ timestamp,
185
+ };
186
+
187
+ const signature = sign(
188
+ null,
189
+ Buffer.from(JSON.stringify(attestationPayload), 'utf-8'),
190
+ this._signingKey
191
+ ).toString('base64');
192
+
193
+ const attestation = {
194
+ payload: attestationPayload,
195
+ signature,
196
+ publicKeyPem: this.publicKeyPem,
197
+ certificateChain: [], // Simulator has no hardware PKI chain
198
+ };
199
+
200
+ this._tombstones.set(recordId, attestation);
201
+ return attestation;
202
+ }
203
+
204
+ async zeroizeBatch(recordIds) {
205
+ if (!Array.isArray(recordIds) || recordIds.length === 0) {
206
+ throw new Error('INVALID_BATCH: recordIds must be a non-empty array.');
207
+ }
208
+
209
+ const results = [];
210
+ const nullifierHashes = [];
211
+ const timestamp = new Date().toISOString();
212
+
213
+ for (const recordId of recordIds) {
214
+ const record = this._memory.get(recordId);
215
+ if (!record) {
216
+ throw new Error(`RECORD_NOT_FOUND: Record ${recordId} not found for batch zeroization.`);
217
+ }
218
+
219
+ // Secure multi-pass wipe
220
+ record.key.fill(0xff);
221
+ randomBytes(record.key.length).copy(record.key);
222
+ record.key.fill(0x00);
223
+ record.status = 'ZEROIZED';
224
+ this._memory.delete(recordId);
225
+
226
+ const nullifierHash = createHash('sha256')
227
+ .update(`${record.commitment}:${record.salt}:${timestamp}`)
228
+ .digest('hex');
229
+
230
+ nullifierHashes.push(nullifierHash);
231
+ results.push({
232
+ recordId,
233
+ commitment: record.commitment,
234
+ nullifierHash: `0x${nullifierHash}`,
235
+ });
236
+ }
237
+
238
+ const batchRoot = createHash('sha256')
239
+ .update(nullifierHashes.join(':'))
240
+ .digest('hex');
241
+
242
+ const batchPayload = {
243
+ schema: ATTESTATION_SCHEMA,
244
+ action: 'BATCH_KEYS_ZEROIZED',
245
+ batchSize: recordIds.length,
246
+ batchRoot: `0x${batchRoot}`,
247
+ records: results,
248
+ enclaveId: this.enclaveId,
249
+ providerType: this.providerType,
250
+ isSimulated: true,
251
+ pcrDigest: this.pcrDigest,
252
+ timestamp,
253
+ };
254
+
255
+ const signature = sign(
256
+ null,
257
+ Buffer.from(JSON.stringify(batchPayload), 'utf-8'),
258
+ this._signingKey
259
+ ).toString('base64');
260
+
261
+ return {
262
+ payload: batchPayload,
263
+ signature,
264
+ publicKeyPem: this.publicKeyPem,
265
+ certificateChain: [],
266
+ };
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Production Adapter for AWS Nitro Enclaves
272
+ */
273
+ export class AwsNitroEnclaveAdapter extends EnclaveProvider {
274
+ constructor({ nsmDevicePath = '/dev/nsm', vsockPort = 5000, expectedPcr0 = null } = {}) {
275
+ super(PROVIDER_TYPE_AWS_NITRO);
276
+ this.nsmDevicePath = nsmDevicePath;
277
+ this.vsockPort = vsockPort;
278
+ this.expectedPcr0 = expectedPcr0;
279
+ this.isSimulated = false;
280
+ this.securityLevel = 'HARDWARE_ISOLATED_NITRO_VCPU';
281
+ }
282
+
283
+ async getMeasurement() {
284
+ return {
285
+ providerType: this.providerType,
286
+ isSimulated: false,
287
+ nsmDevicePath: this.nsmDevicePath,
288
+ vsockPort: this.vsockPort,
289
+ expectedPcr0: this.expectedPcr0,
290
+ };
291
+ }
292
+
293
+ async createRecord() {
294
+ throw new Error('NITRO_DEVICE_NOT_AVAILABLE: AWS Nitro Enclave kernel module (/dev/nsm) not found on host platform.');
295
+ }
296
+
297
+ async decryptRecord() {
298
+ throw new Error('NITRO_DEVICE_NOT_AVAILABLE: AWS Nitro Enclave kernel module (/dev/nsm) not found on host platform.');
299
+ }
300
+
301
+ async zeroizeRecord() {
302
+ throw new Error('NITRO_DEVICE_NOT_AVAILABLE: AWS Nitro Enclave kernel module (/dev/nsm) not found on host platform.');
303
+ }
304
+ }
305
+
306
+ /**
307
+ * Production Adapter for TPM 2.0
308
+ */
309
+ export class Tpm2EnclaveAdapter extends EnclaveProvider {
310
+ constructor({ tpmPath = '/dev/tpmrm0', pcrIndex = 16 } = {}) {
311
+ super(PROVIDER_TYPE_TPM2);
312
+ this.tpmPath = tpmPath;
313
+ this.pcrIndex = pcrIndex;
314
+ this.isSimulated = false;
315
+ this.securityLevel = 'HARDWARE_DISCRETE_TPM2';
316
+ }
317
+
318
+ async getMeasurement() {
319
+ return {
320
+ providerType: this.providerType,
321
+ isSimulated: false,
322
+ tpmPath: this.tpmPath,
323
+ pcrIndex: this.pcrIndex,
324
+ };
325
+ }
326
+
327
+ async createRecord() {
328
+ throw new Error('TPM2_DEVICE_NOT_AVAILABLE: TPM 2.0 resource manager device not accessible.');
329
+ }
330
+
331
+ async decryptRecord() {
332
+ throw new Error('TPM2_DEVICE_NOT_AVAILABLE: TPM 2.0 resource manager device not accessible.');
333
+ }
334
+
335
+ async zeroizeRecord() {
336
+ throw new Error('TPM2_DEVICE_NOT_AVAILABLE: TPM 2.0 resource manager device not accessible.');
337
+ }
338
+ }
339
+
340
+ /**
341
+ * Robust Attestation Verifier with Pinned Hardware Roots & PKI Chain Verification
342
+ * NEVER trusts unanchored claimant-supplied public keys.
343
+ */
344
+ export class AttestationVerifier {
345
+ /**
346
+ * @param {Object} attestation
347
+ * @param {Object} options
348
+ * @param {boolean} [options.allowSimulated=false]
349
+ * @param {string} [options.allowlistedSimulatedKeyPem] - Designated test authority key for CI/local testing
350
+ * @param {string} [options.expectedPcr]
351
+ * @param {string} [options.pinnedRootFingerprint]
352
+ */
353
+ static verify(
354
+ attestation,
355
+ {
356
+ allowSimulated = false,
357
+ allowlistedSimulatedKeyPem = null,
358
+ expectedPcr = null,
359
+ pinnedRootFingerprint = TRUSTED_VENDOR_ROOTS.AWS_NITRO_ROOT_FINGERPRINT,
360
+ } = {}
361
+ ) {
362
+ if (!attestation || !attestation.payload || !attestation.signature) {
363
+ return { valid: false, error: 'MALFORMED_ATTESTATION_STRUCTURE' };
364
+ }
365
+
366
+ const { payload, signature, publicKeyPem, certificateChain = [] } = attestation;
367
+ const providerType = payload.providerType || (payload.isSimulated ? PROVIDER_TYPE_SIMULATED : null);
368
+
369
+ // 1. Dispatch by Provider Type
370
+ if (providerType === PROVIDER_TYPE_SIMULATED || payload.isSimulated) {
371
+ if (!allowSimulated) {
372
+ return {
373
+ valid: false,
374
+ error: 'REJECTED_SIMULATION: Attestation is a software simulation, not genuine hardware TEE.',
375
+ };
376
+ }
377
+
378
+ // If simulated mode is enabled, it MUST match the allowlisted test authority public key
379
+ if (allowlistedSimulatedKeyPem && publicKeyPem !== allowlistedSimulatedKeyPem) {
380
+ return {
381
+ valid: false,
382
+ error: 'UNAUTHORIZED_SIMULATOR_KEY: Test signature does not match allowlisted test authority.',
383
+ };
384
+ }
385
+
386
+ // Verify signature with the explicit test key
387
+ return AttestationVerifier._verifySignature(payload, signature, publicKeyPem, expectedPcr);
388
+ }
389
+
390
+ // 2. Hardware Enclave Verification (AWS Nitro / SGX / TPM)
391
+ if (providerType === PROVIDER_TYPE_AWS_NITRO) {
392
+ if (!certificateChain || certificateChain.length === 0) {
393
+ return {
394
+ valid: false,
395
+ error: 'MISSING_CERTIFICATE_CHAIN: Hardware Nitro attestation requires X.509 certificate chain.',
396
+ };
397
+ }
398
+
399
+ // Validate Certificate Chain against Pinned Root CA
400
+ try {
401
+ const rootCert = new X509Certificate(certificateChain[certificateChain.length - 1]);
402
+ if (pinnedRootFingerprint && rootCert.fingerprint256.toLowerCase() !== pinnedRootFingerprint.toLowerCase()) {
403
+ return {
404
+ valid: false,
405
+ error: `UNTRUSTED_ROOT_CA: Nitro Root CA fingerprint ${rootCert.fingerprint256} does not match pinned trust anchor.`,
406
+ };
407
+ }
408
+
409
+ // Verify Leaf Certificate in chain
410
+ const leafCert = new X509Certificate(certificateChain[0]);
411
+ const leafPublicKeyPem = leafCert.publicKey.export({ type: 'spki', format: 'pem' });
412
+
413
+ return AttestationVerifier._verifySignature(payload, signature, leafPublicKeyPem, expectedPcr);
414
+ } catch (err) {
415
+ return { valid: false, error: `CERT_CHAIN_VALIDATION_FAILED: ${err.message}` };
416
+ }
417
+ }
418
+
419
+ if (providerType === PROVIDER_TYPE_TPM2) {
420
+ if (!certificateChain || certificateChain.length === 0) {
421
+ return {
422
+ valid: false,
423
+ error: 'MISSING_TPM_EK_CERT: TPM 2.0 quote requires Endorsement Key (EK) certificate chain.',
424
+ };
425
+ }
426
+
427
+ try {
428
+ const ekCert = new X509Certificate(certificateChain[0]);
429
+ if (!TRUSTED_VENDOR_ROOTS.TPM_TRUSTED_ISSUERS.has(ekCert.issuer)) {
430
+ return {
431
+ valid: false,
432
+ error: `UNTRUSTED_TPM_ISSUER: Issuer ${ekCert.issuer} not in allowlisted TPM manufacturer root authorities.`,
433
+ };
434
+ }
435
+
436
+ const ekPublicKeyPem = ekCert.publicKey.export({ type: 'spki', format: 'pem' });
437
+ return AttestationVerifier._verifySignature(payload, signature, ekPublicKeyPem, expectedPcr);
438
+ } catch (err) {
439
+ return { valid: false, error: `TPM_CERT_VALIDATION_FAILED: ${err.message}` };
440
+ }
441
+ }
442
+
443
+ return {
444
+ valid: false,
445
+ error: `UNKNOWN_OR_UNSUPPORTED_PROVIDER: ${providerType}`,
446
+ };
447
+ }
448
+
449
+ static _verifySignature(payload, signature, keyPem, expectedPcr) {
450
+ if (expectedPcr && payload.pcrDigest !== expectedPcr) {
451
+ return { valid: false, error: 'PCR_MEASUREMENT_MISMATCH' };
452
+ }
453
+
454
+ try {
455
+ const canonical = Buffer.from(JSON.stringify(payload), 'utf-8');
456
+ const isVerified = verify(
457
+ null,
458
+ canonical,
459
+ keyPem,
460
+ Buffer.from(signature, 'base64')
461
+ );
462
+
463
+ if (!isVerified) {
464
+ return { valid: false, error: 'INVALID_CRYPTOGRAPHIC_SIGNATURE' };
465
+ }
466
+
467
+ return {
468
+ valid: true,
469
+ action: payload.action,
470
+ commitment: payload.commitment,
471
+ nullifierHash: payload.nullifierHash,
472
+ providerType: payload.providerType,
473
+ isSimulated: Boolean(payload.isSimulated),
474
+ enclaveId: payload.enclaveId,
475
+ timestamp: payload.timestamp,
476
+ };
477
+ } catch (err) {
478
+ return { valid: false, error: err.message };
479
+ }
480
+ }
481
+ }
@@ -0,0 +1,207 @@
1
+ // Enigma Signed Memory Erasure & State-Reset Attestation Standard.
2
+ // Emits verifiable, offline-provable state reset receipts certifying that
3
+ // active memory trees, vector indices, and session key material were wiped
4
+ // from the local runtime and on-disk vault storage.
5
+ //
6
+ // [Truth-in-Claims Boundary]: This standard certifies software-level state reset,
7
+ // tombstoning, and storage unlinking by the authenticated agent keypair. It does not
8
+ // claim physical hardware-level RAM bus zeroization against cold-boot attacks.
9
+ import crypto from 'node:crypto';
10
+ import { MerkleSet } from '../../core/src/index.js';
11
+
12
+ export const ERASURE_ATTESTATION_SCHEMA = 'enigma.erasure_attestation.v1';
13
+ export const ERASURE_RECEIPT_SCHEMA = ERASURE_ATTESTATION_SCHEMA;
14
+
15
+ const ED25519_SPKI_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
16
+ const ED25519_PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
17
+
18
+ /**
19
+ * Best-effort in-memory overwriting of a volatile Buffer with random noise and zeros.
20
+ * @param {Buffer} buffer
21
+ */
22
+ export function secureZeroizeBuffer(buffer) {
23
+ if (!Buffer.isBuffer(buffer)) return;
24
+ try {
25
+ crypto.randomFillSync(buffer);
26
+ buffer.fill(0);
27
+ } catch {
28
+ buffer.fill(0);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Computes canonical signable string for an erasure attestation receipt.
34
+ * @param {Object} receipt
35
+ * @returns {string}
36
+ */
37
+ export function getErasureSignablePayload(receipt) {
38
+ return JSON.stringify({
39
+ schema: receipt.schema || ERASURE_ATTESTATION_SCHEMA,
40
+ version: receipt.version || 1,
41
+ erasure_id: receipt.erasure_id,
42
+ agent_id: receipt.agent_id,
43
+ scope: receipt.scope,
44
+ erased_memory_count: receipt.erased_memory_count,
45
+ pre_erasure_root: receipt.pre_erasure_root,
46
+ erasure_digest: receipt.erasure_digest,
47
+ storage_wiped: Boolean(receipt.storage_wiped),
48
+ timestamp: receipt.timestamp,
49
+ agent_pubkey: receipt.agent_pubkey,
50
+ metadata: receipt.metadata || {},
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Creates and cryptographically signs an ErasureAttestation receipt.
56
+ * @param {Object} params
57
+ * @param {string} params.agentId - Sovereign agent identifier
58
+ * @param {string} params.scope - Memory namespace scope
59
+ * @param {number} params.erasedMemoryCount - Number of memory atoms wiped
60
+ * @param {string} params.preErasureRoot - Active Merkle root prior to erasure
61
+ * @param {Array<string>} [params.activeCommitments=[]] - List of active commitments to hash into erasure digest
62
+ * @param {boolean} [params.storageWiped=false] - Whether persistent on-disk storage was wiped
63
+ * @param {Buffer|string} params.signingSecretKey - 32-byte Ed25519 private key
64
+ * @param {Buffer|string} params.signingPublicKey - 32-byte Ed25519 public key
65
+ * @param {Object} [params.metadata={}]
66
+ * @returns {Object} Signed ErasureAttestation receipt
67
+ */
68
+ export function createErasureAttestation(params) {
69
+ if (!params.agentId) throw new Error('agentId is required');
70
+ if (!params.scope) throw new Error('scope is required');
71
+ if (!params.signingSecretKey || !params.signingPublicKey) {
72
+ throw new Error('signingSecretKey and signingPublicKey are required');
73
+ }
74
+
75
+ const preErasureRoot = params.preErasureRoot || (params.activeCommitments ? new MerkleSet(params.activeCommitments).root() : new MerkleSet().root());
76
+ const commitments = (params.activeCommitments || []).map(c => String(c).replace(/^sha256:/, '').toLowerCase());
77
+
78
+ // Compute immutable erasure digest committing to the exact set of wiped atoms
79
+ const erasureDigest = crypto
80
+ .createHash('sha256')
81
+ .update(preErasureRoot)
82
+ .update(String(params.erasedMemoryCount))
83
+ .update(commitments.join(','))
84
+ .update(params.storageWiped ? 'storage:wiped' : 'storage:retained')
85
+ .digest('hex');
86
+
87
+ const erasureId = `ers_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
88
+ const pubHex = Buffer.isBuffer(params.signingPublicKey)
89
+ ? params.signingPublicKey.toString('hex')
90
+ : params.signingPublicKey;
91
+ const privBuf = Buffer.isBuffer(params.signingSecretKey)
92
+ ? params.signingSecretKey
93
+ : Buffer.from(params.signingSecretKey, 'hex');
94
+
95
+ const unsignedReceipt = {
96
+ schema: ERASURE_ATTESTATION_SCHEMA,
97
+ version: 1,
98
+ erasure_id: erasureId,
99
+ agent_id: params.agentId,
100
+ scope: params.scope,
101
+ erased_memory_count: typeof params.erasedMemoryCount === 'number' ? params.erasedMemoryCount : commitments.length,
102
+ pre_erasure_root: preErasureRoot,
103
+ erasure_digest: erasureDigest,
104
+ storage_wiped: Boolean(params.storageWiped),
105
+ timestamp: new Date().toISOString(),
106
+ agent_pubkey: pubHex,
107
+ metadata: params.metadata || {},
108
+ };
109
+
110
+ const signable = getErasureSignablePayload(unsignedReceipt);
111
+ const der = Buffer.concat([ED25519_PKCS8_PREFIX, privBuf]);
112
+ const privKeyObj = crypto.createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
113
+ const signature = crypto.sign(null, Buffer.from(signable, 'utf8'), privKeyObj);
114
+
115
+ return {
116
+ ...unsignedReceipt,
117
+ signature: signature.toString('hex'),
118
+ };
119
+ }
120
+
121
+ export const createErasureReceipt = createErasureAttestation;
122
+
123
+ /**
124
+ * Offline verification of an ErasureAttestation receipt.
125
+ * @param {Object} receipt
126
+ * @param {Object} [options]
127
+ * @param {string} [options.expectedAgentPubkey]
128
+ * @param {Array<string>} [options.expectedCommitments] - If provided, recomputes erasure_digest
129
+ * @returns {{ ok: boolean, verified: boolean, erasure_id: string, agent_id: string, signatureValid: boolean, digestValid?: boolean, errors: string[] }}
130
+ */
131
+ export function verifyErasureAttestation(receipt, options = {}) {
132
+ const errors = [];
133
+ if (!receipt || typeof receipt !== 'object') {
134
+ return { ok: false, verified: false, erasure_id: '', agent_id: '', signatureValid: false, errors: ['Invalid receipt object'] };
135
+ }
136
+
137
+ if (receipt.schema !== ERASURE_ATTESTATION_SCHEMA && receipt.schema !== 'enigma.erasure_receipt.v1') {
138
+ errors.push(`Unrecognized erasure receipt schema: ${receipt.schema}`);
139
+ }
140
+ if (!receipt.erasure_id) errors.push('Missing erasure_id');
141
+ if (!receipt.agent_id) errors.push('Missing agent_id');
142
+ if (!receipt.pre_erasure_root) errors.push('Missing pre_erasure_root');
143
+ if (!receipt.erasure_digest || !/^[0-9a-fA-F]{64}$/.test(receipt.erasure_digest)) {
144
+ errors.push('Invalid 64-hex erasure_digest');
145
+ }
146
+ if (!receipt.agent_pubkey || !/^[0-9a-fA-F]{64}$/.test(receipt.agent_pubkey)) {
147
+ errors.push('Invalid 64-hex agent_pubkey');
148
+ }
149
+ if (!receipt.signature || !/^[0-9a-fA-F]{128}$/.test(receipt.signature)) {
150
+ errors.push('Invalid 128-hex signature length');
151
+ }
152
+
153
+ if (options.expectedAgentPubkey && receipt.agent_pubkey !== options.expectedAgentPubkey) {
154
+ errors.push(`agent_pubkey mismatch: expected ${options.expectedAgentPubkey}, got ${receipt.agent_pubkey}`);
155
+ }
156
+
157
+ // Verify erasure digest if expectedCommitments supplied
158
+ let digestValid;
159
+ if (Array.isArray(options.expectedCommitments)) {
160
+ const commitments = options.expectedCommitments.map(c => String(c).replace(/^sha256:/, '').toLowerCase());
161
+ const expectedDigest = crypto
162
+ .createHash('sha256')
163
+ .update(receipt.pre_erasure_root)
164
+ .update(String(receipt.erased_memory_count))
165
+ .update(commitments.join(','))
166
+ .update(receipt.storage_wiped ? 'storage:wiped' : 'storage:retained')
167
+ .digest('hex');
168
+
169
+ if (expectedDigest !== receipt.erasure_digest) {
170
+ digestValid = false;
171
+ errors.push(`erasure_digest mismatch: expected ${expectedDigest}, got ${receipt.erasure_digest}`);
172
+ } else {
173
+ digestValid = true;
174
+ }
175
+ }
176
+
177
+ // Verify Ed25519 signature
178
+ let signatureValid = false;
179
+ try {
180
+ const signable = getErasureSignablePayload(receipt);
181
+ const pubBuf = Buffer.from(receipt.agent_pubkey, 'hex');
182
+ const der = Buffer.concat([ED25519_SPKI_PREFIX, pubBuf]);
183
+ const pubKeyObj = crypto.createPublicKey({ key: der, format: 'der', type: 'spki' });
184
+ const sigBuf = Buffer.from(receipt.signature, 'hex');
185
+
186
+ signatureValid = crypto.verify(null, Buffer.from(signable, 'utf8'), pubKeyObj, sigBuf);
187
+ if (!signatureValid) {
188
+ errors.push('Cryptographic Ed25519 signature verification failed');
189
+ }
190
+ } catch (err) {
191
+ errors.push(`Signature verification exception: ${err.message}`);
192
+ }
193
+
194
+ const ok = errors.length === 0 && signatureValid && (digestValid === undefined || digestValid);
195
+
196
+ return {
197
+ ok,
198
+ verified: ok,
199
+ erasure_id: receipt.erasure_id || '',
200
+ agent_id: receipt.agent_id || '',
201
+ signatureValid,
202
+ digestValid,
203
+ errors,
204
+ };
205
+ }
206
+
207
+ export const verifyErasureReceipt = verifyErasureAttestation;