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.
- package/README.md +18 -12
- package/apps/cli/bin/enigma.mjs +3341 -3263
- package/deploy/SIMULATION.md +14 -9
- package/deploy/docker-compose.local-production-simulation.yml +18 -12
- package/docs/benchmark-attestation-network.md +487 -487
- package/docs/benchmark-reproducibility.md +289 -289
- package/docs/blockchain-only-mechanisms.md +400 -400
- package/docs/client-connectors.md +1 -1
- package/docs/demo-proof-network.md +275 -275
- package/docs/developer-proof-quickstart.md +325 -325
- package/docs/enigma-memory-ready-conformance.md +376 -376
- package/docs/install-anywhere.md +3 -2
- package/docs/memory-benchmarks.md +1 -1
- package/docs/memory-drive-health-model.md +690 -690
- package/docs/proof-network-build-notes.md +240 -240
- package/docs/proof-network.md +339 -339
- package/docs/sdk-api.md +324 -324
- package/docs/solana-proof-rail.md +453 -453
- package/package.json +279 -278
- package/packages/core/src/index.js +1 -1
- package/packages/mcp-server/src/index.js +1185 -1185
- package/packages/passport/src/index.js +10 -6
- package/packages/vault/src/index.js +187 -25
- package/scripts/build-hosted-api-key-lifecycle.mjs +274 -274
- package/scripts/build-hosted-customer-lifecycle.mjs +476 -476
- package/scripts/build-installer-assets.mjs +389 -389
- package/scripts/build-production-unblocker.mjs +1 -1
- package/scripts/build-proof-network-packet.mjs +213 -213
- package/scripts/check.mjs +8 -1
- package/scripts/release-audit.mjs +9 -6
- package/scripts/release-provenance.mjs +6 -2
- package/scripts/run-standard-memory-benchmarks.mjs +1354 -1352
- package/scripts/scan-secrets.mjs +177 -0
- 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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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
|
|
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
|
|
597
|
-
addressKey
|
|
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
|
|
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:
|
|
856
|
-
signer:
|
|
857
|
-
publicKey:
|
|
858
|
-
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:
|
|
868
|
-
address_key:
|
|
1028
|
+
vault_key: vaultKey,
|
|
1029
|
+
address_key: addressKey,
|
|
869
1030
|
signingKeyPair: restoredSigningKeyPair,
|
|
870
|
-
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
|
}
|