enigma-memory 0.1.17 → 0.1.18

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 (34) hide show
  1. package/README.md +18 -12
  2. package/apps/cli/bin/enigma.mjs +3341 -3263
  3. package/deploy/SIMULATION.md +14 -9
  4. package/deploy/docker-compose.local-production-simulation.yml +18 -12
  5. package/docs/benchmark-attestation-network.md +487 -487
  6. package/docs/benchmark-reproducibility.md +289 -289
  7. package/docs/blockchain-only-mechanisms.md +400 -400
  8. package/docs/client-connectors.md +1 -1
  9. package/docs/demo-proof-network.md +275 -275
  10. package/docs/developer-proof-quickstart.md +325 -325
  11. package/docs/enigma-memory-ready-conformance.md +376 -376
  12. package/docs/install-anywhere.md +3 -2
  13. package/docs/memory-benchmarks.md +1 -1
  14. package/docs/memory-drive-health-model.md +690 -690
  15. package/docs/proof-network-build-notes.md +240 -240
  16. package/docs/proof-network.md +339 -339
  17. package/docs/sdk-api.md +324 -324
  18. package/docs/solana-proof-rail.md +453 -453
  19. package/package.json +279 -278
  20. package/packages/core/src/index.js +1 -1
  21. package/packages/mcp-server/src/index.js +1185 -1185
  22. package/packages/passport/src/index.js +10 -6
  23. package/packages/vault/src/index.js +187 -25
  24. package/scripts/build-hosted-api-key-lifecycle.mjs +274 -274
  25. package/scripts/build-hosted-customer-lifecycle.mjs +476 -476
  26. package/scripts/build-installer-assets.mjs +389 -389
  27. package/scripts/build-production-unblocker.mjs +1 -1
  28. package/scripts/build-proof-network-packet.mjs +213 -213
  29. package/scripts/check.mjs +8 -1
  30. package/scripts/release-audit.mjs +9 -6
  31. package/scripts/release-provenance.mjs +6 -2
  32. package/scripts/run-standard-memory-benchmarks.mjs +1354 -1352
  33. package/scripts/scan-secrets.mjs +177 -0
  34. package/scripts/simulate-production-env.mjs +64 -8
@@ -134,12 +134,16 @@ function tokenOverlapScore(queryTokens, memoryTokens) {
134
134
  }
135
135
 
136
136
  function strictQueryRelevance(args) {
137
- return args.strict_relevance === true
138
- || args.strictRelevance === true
139
- || args.require_relevance === true
140
- || args.requireRelevance === true
141
- || args.query_relevance === 'strict'
142
- || args.queryRelevance === 'strict';
137
+ if (args.include_unrelated === true || args.includeUnrelated === true) return false;
138
+ if (args.no_strict_relevance === true || args.noStrictRelevance === true) return false;
139
+ if (args.strict_relevance === false || args.strictRelevance === false) return false;
140
+ if (args.require_relevance === false || args.requireRelevance === false) return false;
141
+ if (args.query_relevance === 'fallback' || args.queryRelevance === 'fallback') return false;
142
+ if (args.strict_relevance === true || args.strictRelevance === true) return true;
143
+ if (args.require_relevance === true || args.requireRelevance === true) return true;
144
+ if (args.query_relevance === 'strict' || args.queryRelevance === 'strict') return true;
145
+ const query = typeof args.query === 'string' ? args.query.trim() : String(args.query ?? '').trim();
146
+ return query.length > 0;
143
147
  }
144
148
 
145
149
  function relevanceCandidateSet(args, candidates) {
@@ -3,9 +3,13 @@ import {
3
3
  createDecipheriv,
4
4
  createHash,
5
5
  createHmac,
6
+ createPrivateKey,
7
+ createPublicKey,
6
8
  generateKeyPairSync,
9
+ pbkdf2Sync,
7
10
  randomBytes,
8
11
  randomUUID,
12
+ scryptSync,
9
13
  } from 'node:crypto';
10
14
  import {
11
15
  canonicalize,
@@ -46,6 +50,14 @@ const PUBLIC_PLAINTEXT_KEYS = new Set([
46
50
  const SAFE_METADATA_DIGEST_RE = /^(?:sha256:)?[a-f0-9]{64}$|^hmac-sha256:[a-f0-9]{64}$/;
47
51
  const SOURCE_REF_ROOT_RE = /^sha256:[a-f0-9]{64}$/;
48
52
 
53
+ export const KEYRING_ENCRYPTED_TYPE = 'local_secret_encrypted';
54
+ export const KEYRING_PLAINTEXT_TYPE = 'local_secret';
55
+ const DERIVED_KEY_LENGTH = 128; // 32 bytes each: KEK, vaultKey, addressKey, signingSeed
56
+ const SALT_LENGTH = 16;
57
+ const IV_LENGTH = 12;
58
+ const PLAINTEXT_KEYRING_WARNING = 'WARNING: This bundle stores vault keys in plaintext. Anyone with file access can decrypt memory contents. Use --passphrase to encrypt the keyring.';
59
+ const ENCRYPTED_KEYRING_WARNING = 'Keyring is encrypted with AES-256-GCM; passphrase required on import.';
60
+ const DEPRECATION_PLAINTEXT_KEYRING = 'Plaintext keyrings are deprecated; use --passphrase to encrypt new bundles.';
49
61
  function normalizedPublicKey(key) {
50
62
  return String(key).toLowerCase().replace(/[^a-z0-9]/g, '');
51
63
  }
@@ -313,7 +325,84 @@ function keyBytes(key, label) {
313
325
  throw new Error(`${label} must be a Buffer or base64 string`);
314
326
  }
315
327
 
316
- function signingPair() {
328
+ function buildEd25519Pkcs8(seed) {
329
+ if (!Buffer.isBuffer(seed) || seed.length !== 32) throw new Error('Ed25519 seed must be 32 bytes');
330
+ // PKCS#8 PrivateKeyInfo for Ed25519 (OID 1.3.101.112): SEQUENCE { INTEGER 0, AlgorithmIdentifier, OCTET STRING { OCTET STRING seed } }
331
+ return Buffer.from([
332
+ 0x30, 0x2e,
333
+ 0x02, 0x01, 0x00,
334
+ 0x30, 0x05,
335
+ 0x06, 0x03, 0x2b, 0x65, 0x70,
336
+ 0x04, 0x22,
337
+ 0x04, 0x20,
338
+ ...seed,
339
+ ]);
340
+ }
341
+
342
+ function deriveMasterKeyFromPassphrase(passphrase, salt) {
343
+ const normalized = Buffer.from(String(passphrase), 'utf8');
344
+ const saltBuf = typeof salt === 'string' ? Buffer.from(salt, 'base64') : Buffer.from(salt);
345
+ if (saltBuf.length < 8) throw new Error('Salt must be at least 8 bytes');
346
+ try {
347
+ return scryptSync(normalized, saltBuf, DERIVED_KEY_LENGTH);
348
+ } catch {
349
+ return pbkdf2Sync(normalized, saltBuf, 600000, DERIVED_KEY_LENGTH, 'sha256');
350
+ }
351
+ }
352
+
353
+ function deriveKeysFromPassphrase(passphrase, salt) {
354
+ const master = deriveMasterKeyFromPassphrase(passphrase, salt);
355
+ return {
356
+ kek: master.subarray(0, 32),
357
+ vaultKey: master.subarray(32, 64),
358
+ addressKey: master.subarray(64, 96),
359
+ signingSeed: master.subarray(96, 128),
360
+ };
361
+ }
362
+
363
+ function encryptKeyring(plaintextKeyring, kek) {
364
+ const iv = randomBytes(IV_LENGTH);
365
+ const cipher = createCipheriv('aes-256-gcm', kek, iv);
366
+ const plaintext = Buffer.from(JSON.stringify(plaintextKeyring), 'utf8');
367
+ const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
368
+ const tag = cipher.getAuthTag();
369
+ return {
370
+ iv: iv.toString('base64'),
371
+ tag: tag.toString('base64'),
372
+ ciphertext: ciphertext.toString('base64'),
373
+ };
374
+ }
375
+
376
+ export function decryptKeyring(keyring, passphrase) {
377
+ if (keyring?.type !== KEYRING_ENCRYPTED_TYPE) throw new Error('Keyring is not encrypted');
378
+ if (passphrase === undefined || passphrase === null || String(passphrase).length === 0) {
379
+ throw new Error('Encrypted keyring requires passphrase');
380
+ }
381
+ const { kek } = deriveKeysFromPassphrase(passphrase, keyring.salt);
382
+ const decipher = createDecipheriv('aes-256-gcm', kek, Buffer.from(keyring.iv, 'base64'));
383
+ decipher.setAuthTag(Buffer.from(keyring.tag, 'base64'));
384
+ const plaintext = Buffer.concat([
385
+ decipher.update(Buffer.from(keyring.ciphertext, 'base64')),
386
+ decipher.final(),
387
+ ]).toString('utf8');
388
+ return JSON.parse(plaintext);
389
+ }
390
+
391
+ function signingPair(seed) {
392
+ if (seed !== undefined && seed !== null) {
393
+ try {
394
+ const privateKey = createPrivateKey({ key: buildEd25519Pkcs8(seed), format: 'der', type: 'pkcs8' });
395
+ const publicKey = createPublicKey(privateKey);
396
+ const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
397
+ return {
398
+ key_id: `ed25519:${sha256Hex(publicKeyPem).slice(0, 32)}`,
399
+ publicKey,
400
+ privateKey,
401
+ };
402
+ } catch {
403
+ // Fall through to random signing pair if deterministic construction is unsupported.
404
+ }
405
+ }
317
406
  try {
318
407
  const pair = generateSigningKeyPair();
319
408
  if (pair && typeof pair.then !== 'function') return pair;
@@ -389,7 +478,7 @@ function metadata(input) {
389
478
 
390
479
  function pruneUndefined(value) {
391
480
  if (Array.isArray(value)) return value.map((item) => pruneUndefined(item));
392
- if (value && typeof value === 'object') {
481
+ if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
393
482
  const out = {};
394
483
  for (const [key, item] of Object.entries(value)) {
395
484
  if (item !== undefined) out[key] = pruneUndefined(item);
@@ -581,7 +670,23 @@ function attachInternals(vault) {
581
670
 
582
671
  export function createVault(args = {}) {
583
672
  const createdAt = nowIso(args.now);
584
- const signingKeyPair = args.signingKeyPair ?? signingPair();
673
+ const passphrase = args.passphrase;
674
+ const salt = args.salt ?? randomBytes(SALT_LENGTH).toString('base64');
675
+ let vaultKey;
676
+ let addressKey;
677
+ let signingSeed;
678
+ let keyringType = KEYRING_PLAINTEXT_TYPE;
679
+ if (passphrase !== undefined && passphrase !== null && String(passphrase).length > 0) {
680
+ const derived = deriveKeysFromPassphrase(passphrase, salt);
681
+ vaultKey = derived.vaultKey;
682
+ addressKey = derived.addressKey;
683
+ signingSeed = derived.signingSeed;
684
+ keyringType = KEYRING_ENCRYPTED_TYPE;
685
+ } else {
686
+ vaultKey = keyBytes(args.vault_key ?? args.vaultKey, 'vault key');
687
+ addressKey = keyBytes(args.address_key ?? args.addressKey, 'address key');
688
+ }
689
+ const signingKeyPair = args.signingKeyPair ?? signingPair(signingSeed);
585
690
  const vault = {
586
691
  schema: VAULT_SCHEMA,
587
692
  vault_id: args.vault_id ?? args.vaultId ?? `vault_${randomUUID()}`,
@@ -593,8 +698,8 @@ export function createVault(args = {}) {
593
698
  updated_at: createdAt,
594
699
  encryption: 'local_aead',
595
700
  key_id: args.key_id ?? args.keyId ?? `vault-key-${randomUUID()}`,
596
- vaultKey: keyBytes(args.vault_key ?? args.vaultKey, 'vault key'),
597
- addressKey: keyBytes(args.address_key ?? args.addressKey, 'address key'),
701
+ vaultKey,
702
+ addressKey,
598
703
  signingKeyPair,
599
704
  signer: args.signer ?? signerFor(signingKeyPair),
600
705
  memories: new Map(),
@@ -605,10 +710,18 @@ export function createVault(args = {}) {
605
710
  sequence: 0,
606
711
  active_set_root: ZERO_ROOT,
607
712
  receipt_log_root: ZERO_ROOT,
713
+ keyringType,
608
714
  };
609
715
  return attachInternals(vault);
610
716
  }
611
717
 
718
+ function normalizeImportance(value) {
719
+ if (value === undefined || value === null) return undefined;
720
+ const num = Number(value);
721
+ if (!Number.isFinite(num)) return undefined;
722
+ return Math.max(0, Math.min(1, num));
723
+ }
724
+
612
725
  export function remember(args = {}) {
613
726
  const vault = args.vault;
614
727
  if (!vault) throw new Error('remember requires a vault');
@@ -636,6 +749,7 @@ export function remember(args = {}) {
636
749
  sensitivity: args.sensitivity ?? 'normal',
637
750
  purpose_tags: Array.isArray(args.purpose_tags ?? args.purposeTags) ? (args.purpose_tags ?? args.purposeTags) : [],
638
751
  retention: args.retention ?? 'durable',
752
+ importance: normalizeImportance(args.importance),
639
753
  state: 'active',
640
754
  metadata: metadata(args.metadata),
641
755
  created_at: createdAt,
@@ -651,12 +765,13 @@ export function remember(args = {}) {
651
765
  actor_id: args.actor_id ?? args.actorId,
652
766
  policy_id: args.policy_id ?? args.policyId,
653
767
  now: args.now,
654
- metadata: {
768
+ metadata: pruneUndefined({
655
769
  memory_id: memoryId,
656
770
  kind: record.kind,
657
771
  sensitivity: record.sensitivity,
772
+ importance: record.importance,
658
773
  content_commitment: record.content_commitment,
659
- },
774
+ }),
660
775
  });
661
776
 
662
777
  return { memory: publicRecord(record), memory_addr: memoryAddr, event, receipt };
@@ -788,6 +903,39 @@ export function deleteMemory(args = {}) {
788
903
  return { memory_addr: memoryAddr, tombstone, event, receipt };
789
904
  }
790
905
 
906
+ function buildKeyring(vault, passphrase) {
907
+ if (passphrase !== undefined && passphrase !== null && String(passphrase).length > 0) {
908
+ const salt = randomBytes(SALT_LENGTH).toString('base64');
909
+ const { kek } = deriveKeysFromPassphrase(passphrase, salt);
910
+ const plaintextKeyring = {
911
+ vault_key_b64: vault.vaultKey.toString('base64'),
912
+ address_key_b64: vault.addressKey.toString('base64'),
913
+ privateKey: exportPrivateKey(vault.signingKeyPair),
914
+ };
915
+ const encrypted = encryptKeyring(plaintextKeyring, kek);
916
+ return {
917
+ type: KEYRING_ENCRYPTED_TYPE,
918
+ warning: ENCRYPTED_KEYRING_WARNING,
919
+ salt,
920
+ iv: encrypted.iv,
921
+ tag: encrypted.tag,
922
+ ciphertext: encrypted.ciphertext,
923
+ signer: vault.signer,
924
+ publicKey: exportPublicKey(vault.signingKeyPair),
925
+ };
926
+ }
927
+ return {
928
+ type: KEYRING_PLAINTEXT_TYPE,
929
+ unencrypted: true,
930
+ warning: PLAINTEXT_KEYRING_WARNING,
931
+ vault_key_b64: vault.vaultKey.toString('base64'),
932
+ address_key_b64: vault.addressKey.toString('base64'),
933
+ signer: vault.signer,
934
+ publicKey: exportPublicKey(vault.signingKeyPair),
935
+ privateKey: exportPrivateKey(vault.signingKeyPair),
936
+ };
937
+ }
938
+
791
939
  export function exportBundle(args = {}) {
792
940
  const vault = args.vault;
793
941
  if (!vault) throw new Error('exportBundle requires a vault');
@@ -822,15 +970,7 @@ export function exportBundle(args = {}) {
822
970
  receipt_log_root: roots.receipt_log_root,
823
971
  sequence: vault.sequence,
824
972
  },
825
- keyring: {
826
- type: 'local_secret',
827
- warning: 'Contains local vault keys for offline import; memory payloads remain AES-256-GCM encrypted.',
828
- vault_key_b64: vault.vaultKey.toString('base64'),
829
- address_key_b64: vault.addressKey.toString('base64'),
830
- signer: vault.signer,
831
- publicKey: exportPublicKey(vault.signingKeyPair),
832
- privateKey: exportPrivateKey(vault.signingKeyPair),
833
- },
973
+ keyring: buildKeyring(vault, args.passphrase),
834
974
  memory_objects: [...vault.memories.values()].map(publicRecord),
835
975
  active_memory_addresses: [...vault.activeAddresses].sort(),
836
976
  tombstones: [...vault.tombstones.values()].sort((a, b) => a.memory_addr.localeCompare(b.memory_addr)),
@@ -850,12 +990,33 @@ export function importBundle(args = {}) {
850
990
  const source = args.schema === 'enigma.vault_bundle.v1' ? args : (args.bundle?.bundle ?? args.bundle ?? args);
851
991
  const bundle = typeof source === 'string' ? JSON.parse(source) : source;
852
992
  if (!bundle || bundle.schema !== 'enigma.vault_bundle.v1') throw new Error('importBundle requires an enigma.vault_bundle.v1 bundle');
853
- const restoredSigningKeyPair = bundle.keyring?.privateKey
993
+ const warnings = [];
994
+ let vaultKey;
995
+ let addressKey;
996
+ let privateKey;
997
+ const keyring = bundle.keyring;
998
+ if (keyring?.type === KEYRING_ENCRYPTED_TYPE) {
999
+ if (args.passphrase === undefined || args.passphrase === null || String(args.passphrase).length === 0) {
1000
+ throw new Error('Encrypted keyring requires passphrase');
1001
+ }
1002
+ const decrypted = decryptKeyring(keyring, args.passphrase);
1003
+ vaultKey = decrypted.vault_key_b64;
1004
+ addressKey = decrypted.address_key_b64;
1005
+ privateKey = decrypted.privateKey;
1006
+ } else {
1007
+ if (keyring?.type === KEYRING_PLAINTEXT_TYPE) {
1008
+ warnings.push(DEPRECATION_PLAINTEXT_KEYRING);
1009
+ }
1010
+ vaultKey = keyring?.vault_key_b64;
1011
+ addressKey = keyring?.address_key_b64;
1012
+ privateKey = keyring?.privateKey;
1013
+ }
1014
+ const restoredSigningKeyPair = privateKey
854
1015
  ? {
855
- key_id: bundle.keyring?.signer?.key_id,
856
- signer: bundle.keyring?.signer,
857
- publicKey: bundle.keyring?.publicKey,
858
- privateKey: bundle.keyring?.privateKey,
1016
+ key_id: keyring?.signer?.key_id,
1017
+ signer: keyring?.signer,
1018
+ publicKey: keyring?.publicKey,
1019
+ privateKey,
859
1020
  }
860
1021
  : undefined;
861
1022
  const vault = createVault({
@@ -864,10 +1025,10 @@ export function importBundle(args = {}) {
864
1025
  subject_id: bundle.vault?.subject_id,
865
1026
  actor_id: args.actor_id ?? args.actorId ?? bundle.vault?.actor_id,
866
1027
  policy_id: bundle.vault?.policy_id,
867
- vault_key: bundle.keyring?.vault_key_b64,
868
- address_key: bundle.keyring?.address_key_b64,
1028
+ vault_key: vaultKey,
1029
+ address_key: addressKey,
869
1030
  signingKeyPair: restoredSigningKeyPair,
870
- signer: bundle.keyring?.signer,
1031
+ signer: keyring?.signer,
871
1032
  now: bundle.vault?.created_at,
872
1033
  });
873
1034
  vault.created_at = bundle.vault?.created_at ?? vault.created_at;
@@ -900,5 +1061,6 @@ export function importBundle(args = {}) {
900
1061
  imported_tombstone_count: vault.tombstones.size,
901
1062
  },
902
1063
  });
903
- return { vault, active_memory_addresses: [...vault.activeAddresses].sort(), tombstones: [...vault.tombstones.values()] };
1064
+ return { vault, active_memory_addresses: [...vault.activeAddresses].sort(), tombstones: [...vault.tombstones.values()], warnings };
1065
+
904
1066
  }