enigma-memory 0.1.16 → 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 (39) hide show
  1. package/README.md +23 -13
  2. package/apps/cli/bin/enigma.mjs +3341 -3151
  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 -240
  7. package/docs/blockchain-only-mechanisms.md +400 -388
  8. package/docs/client-connectors.md +10 -6
  9. package/docs/demo-proof-network.md +275 -275
  10. package/docs/developer-ecosystem.md +7 -5
  11. package/docs/developer-proof-quickstart.md +325 -325
  12. package/docs/enigma-memory-ready-conformance.md +376 -376
  13. package/docs/hosted-cloud-product.md +29 -0
  14. package/docs/install-anywhere.md +16 -12
  15. package/docs/memory-benchmarks.md +16 -1
  16. package/docs/memory-drive-health-model.md +690 -649
  17. package/docs/proof-network-build-notes.md +240 -240
  18. package/docs/proof-network.md +339 -282
  19. package/docs/sdk-api.md +324 -324
  20. package/docs/solana-proof-rail.md +453 -453
  21. package/package.json +279 -278
  22. package/packages/core/src/index.js +1 -1
  23. package/packages/hosted-cloud/src/index.js +470 -2
  24. package/packages/mcp-server/src/index.js +1185 -1185
  25. package/packages/passport/src/index.js +740 -6
  26. package/packages/proof-network/src/index.js +139 -0
  27. package/packages/vault/src/index.js +187 -25
  28. package/scripts/build-benchmark-proof-release.mjs +15 -3
  29. package/scripts/build-hosted-api-key-lifecycle.mjs +274 -274
  30. package/scripts/build-hosted-customer-lifecycle.mjs +476 -456
  31. package/scripts/build-installer-assets.mjs +389 -389
  32. package/scripts/build-production-unblocker.mjs +409 -409
  33. package/scripts/build-proof-network-packet.mjs +213 -213
  34. package/scripts/check.mjs +8 -1
  35. package/scripts/release-audit.mjs +9 -6
  36. package/scripts/release-provenance.mjs +6 -2
  37. package/scripts/run-standard-memory-benchmarks.mjs +1354 -1194
  38. package/scripts/scan-secrets.mjs +177 -0
  39. package/scripts/simulate-production-env.mjs +64 -8
@@ -5,12 +5,16 @@ export const PROOF_NETWORK_CAPABILITY_GRANT_SCHEMA = 'enigma.proof_network.capab
5
5
  export const PROOF_NETWORK_CAPABILITY_REVOCATION_SCHEMA = 'enigma.proof_network.capability_revocation.v1';
6
6
  export const PROOF_NETWORK_BENCHMARK_ATTESTATION_SCHEMA = 'enigma.proof_network.benchmark_attestation.v1';
7
7
  export const PROOF_NETWORK_PACKET_SCHEMA = 'enigma.proof_network.packet.v1';
8
+ export const PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA = 'enigma.proof_network.registry_entry.v1';
9
+ export const PROOF_NETWORK_REGISTRY_BATCH_SCHEMA = 'enigma.proof_network.registry_batch.v1';
8
10
 
9
11
  export const ANCHOR_BATCH_SCHEMA = PROOF_NETWORK_ANCHOR_BATCH_SCHEMA;
10
12
  export const CAPABILITY_GRANT_SCHEMA = PROOF_NETWORK_CAPABILITY_GRANT_SCHEMA;
11
13
  export const CAPABILITY_REVOCATION_SCHEMA = PROOF_NETWORK_CAPABILITY_REVOCATION_SCHEMA;
12
14
  export const BENCHMARK_ATTESTATION_SCHEMA = PROOF_NETWORK_BENCHMARK_ATTESTATION_SCHEMA;
13
15
  export const PACKET_SCHEMA = PROOF_NETWORK_PACKET_SCHEMA;
16
+ export const REGISTRY_ENTRY_SCHEMA = PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA;
17
+ export const REGISTRY_BATCH_SCHEMA = PROOF_NETWORK_REGISTRY_BATCH_SCHEMA;
14
18
 
15
19
  export const PROOF_NETWORK_SCHEMAS = Object.freeze({
16
20
  anchor_batch: PROOF_NETWORK_ANCHOR_BATCH_SCHEMA,
@@ -18,6 +22,8 @@ export const PROOF_NETWORK_SCHEMAS = Object.freeze({
18
22
  capability_revocation: PROOF_NETWORK_CAPABILITY_REVOCATION_SCHEMA,
19
23
  benchmark_attestation: PROOF_NETWORK_BENCHMARK_ATTESTATION_SCHEMA,
20
24
  packet: PROOF_NETWORK_PACKET_SCHEMA,
25
+ registry_entry: PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA,
26
+ registry_batch: PROOF_NETWORK_REGISTRY_BATCH_SCHEMA,
21
27
  });
22
28
 
23
29
  const SHA256_PREFIX = 'sha256:';
@@ -36,6 +42,14 @@ const SAFE_FIELD_NAMES = new Set(Object.keys(SAFE_BOOLEAN_BOUNDARIES));
36
42
  const FORBIDDEN_KEY_RE = /(?:^|_)(?:raw|plaintext|plain_text|prompt|prompts|message|messages|text|content|document|documents|transcript|transcripts|completion|completions|embedding|embeddings|acl|acl_body|access_control_list|provider_response|provider_responses|response_body|credential|credentials|api_key|secret|password|private_key|seed|seed_phrase|mnemonic|tenant_name|customer_name|organization_name|org_name)(?:$|_)/iu;
37
43
  const SECRET_VALUE_RE = /(?:Bearer\s+[A-Za-z0-9._~+/=-]{12,}|Basic\s+[A-Za-z0-9+/=-]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|https?:\/\/[^\s/@]+:[^\s/@]+@|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|(?:seed phrase|mnemonic phrase|raw memory|private prompt|full transcript|provider response|embedding vector))/iu;
38
44
  const SUPPORTED_ARTIFACT_SCHEMAS = new Set(Object.values(PROOF_NETWORK_SCHEMAS));
45
+ const REGISTRY_ENTRY_TYPES = Object.freeze(new Set([
46
+ 'anchor_batch',
47
+ 'benchmark_attestation',
48
+ 'connector_conformance',
49
+ 'health_report',
50
+ 'operator_receipt',
51
+ 'settlement_job',
52
+ ]));
39
53
 
40
54
  function isPlainObject(value) {
41
55
  return value !== null && typeof value === 'object' && !Array.isArray(value);
@@ -296,6 +310,10 @@ function artifactHash(artifact, index) {
296
310
  return requiredString(artifact.benchmark_attestation_hash, `artifacts[${index}].benchmark_attestation_hash`);
297
311
  case PROOF_NETWORK_PACKET_SCHEMA:
298
312
  return requiredString(artifact.proof_network_packet_hash, `artifacts[${index}].proof_network_packet_hash`);
313
+ case PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA:
314
+ return requiredString(artifact.registry_entry_hash, `artifacts[${index}].registry_entry_hash`);
315
+ case PROOF_NETWORK_REGISTRY_BATCH_SCHEMA:
316
+ return requiredString(artifact.registry_batch_hash, `artifacts[${index}].registry_batch_hash`);
299
317
  default:
300
318
  throw new TypeError(`artifacts[${index}] has unsupported schema`);
301
319
  }
@@ -313,6 +331,10 @@ function validateSupportedArtifact(artifact) {
313
331
  return validateBenchmarkAttestation(artifact);
314
332
  case PROOF_NETWORK_PACKET_SCHEMA:
315
333
  return validateProofNetworkPacket(artifact);
334
+ case PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA:
335
+ return validateRegistryEntry(artifact);
336
+ case PROOF_NETWORK_REGISTRY_BATCH_SCHEMA:
337
+ return validateRegistryBatch(artifact);
316
338
  default:
317
339
  return Object.freeze({ ok: false, valid: false, errors: Object.freeze(['unsupported artifact schema']) });
318
340
  }
@@ -568,3 +590,120 @@ export function validateProofNetworkPacket(packet) {
568
590
  validateIdentity(packet, errors, 'proof_network_packet_id', 'proof_network_packet_hash', 'pnp');
569
591
  });
570
592
  }
593
+
594
+ function registryEntryType(value, field = 'entry_type') {
595
+ const type = optionalString(value, undefined, field);
596
+ if (type === undefined || !REGISTRY_ENTRY_TYPES.has(type)) {
597
+ throw new TypeError(`${field} must be one of ${[...REGISTRY_ENTRY_TYPES].join(', ')}`);
598
+ }
599
+ return type;
600
+ }
601
+
602
+ export function createRegistryEntry(input = {}) {
603
+ requiredObject(input, 'input');
604
+ assertNoPrivateProofPayload(input, 'input');
605
+ const entryType = registryEntryType(input.entry_type ?? input.entryType ?? input.type, 'entry_type');
606
+ const artifactHash = digestRef(input.artifact_hash ?? input.artifactHash ?? input.digest_ref ?? input.digestRef, 'artifact_hash');
607
+ const artifactSchemaRef = publicRef(input.artifact_schema_ref ?? input.artifactSchemaRef ?? input.schema_ref ?? input.schemaRef, 'artifact_schema_ref');
608
+ const digestRefs = digestArray(input.digest_refs ?? input.digestRefs ?? input.digest_roots ?? input.digestRoots ?? input.roots ?? artifactHash, 'digest_refs', { min: 1, max: 64 });
609
+ const signerRefs = publicRefArray(input.signer_refs ?? input.signerRefs ?? input.signer_ref ?? input.signerRef, 'signer_refs', { min: 0, max: 64 });
610
+ const entryRef = optionalPublicRef(input.entry_ref ?? input.entryRef, refFromDigest('registry-entry', artifactHash), 'entry_ref');
611
+ const body = {
612
+ schema: PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA,
613
+ registered_at: isoTimestamp(input.registered_at ?? input.registeredAt ?? input.created_at ?? input.createdAt ?? input.generated_at ?? input.generatedAt, 'registered_at'),
614
+ entry_type: entryType,
615
+ entry_ref: entryRef,
616
+ registry_ref: optionalPublicRef(input.registry_ref ?? input.registryRef ?? input.marketplace_ref ?? input.marketplaceRef, 'registry:memory-drive-marketplace', 'registry_ref'),
617
+ artifact_schema_ref: artifactSchemaRef,
618
+ artifact_hash: artifactHash,
619
+ digest_root: sha256Json(digestRefs),
620
+ digest_refs: digestRefs,
621
+ signer_refs: signerRefs,
622
+ entry_count: nonNegativeInteger(input.entry_count ?? input.entryCount ?? input.count, 'entry_count', 1),
623
+ ...((input.signature_ref ?? input.signatureRef) ? { signature_ref: signatureRef(input.signature_ref ?? input.signatureRef, 'signature_ref') } : {}),
624
+ ...SAFE_BOOLEAN_BOUNDARIES,
625
+ };
626
+ return freezeArtifact(body, 'registry_entry_id', 'registry_entry_hash', 'pnrg');
627
+ }
628
+
629
+ export function validateRegistryEntry(entry) {
630
+ return collectValidation((errors) => {
631
+ requiredObject(entry, 'entry');
632
+ assertNoPrivateProofPayload(entry, 'entry');
633
+ if (entry.schema !== PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA) errors.push(`schema must be ${PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA}`);
634
+ requireSafeBoundaries(entry, errors, 'entry');
635
+ isoTimestamp(entry.registered_at, 'registered_at');
636
+ if (!REGISTRY_ENTRY_TYPES.has(entry.entry_type)) errors.push(`entry_type must be one of ${[...REGISTRY_ENTRY_TYPES].join(', ')}`);
637
+ publicRef(entry.entry_ref, 'entry_ref');
638
+ publicRef(entry.registry_ref, 'registry_ref');
639
+ publicRef(entry.artifact_schema_ref, 'artifact_schema_ref');
640
+ digestRef(entry.artifact_hash, 'artifact_hash');
641
+ const digestRefs = digestArray(entry.digest_refs, 'digest_refs', { min: 1, max: 64 });
642
+ if (entry.digest_root !== sha256Json(digestRefs)) errors.push('digest_root mismatch');
643
+ publicRefArray(entry.signer_refs, 'signer_refs', { min: 0, max: 64 });
644
+ nonNegativeInteger(entry.entry_count, 'entry_count');
645
+ if (entry.signature_ref !== undefined) signatureRef(entry.signature_ref, 'signature_ref');
646
+ validateIdentity(entry, errors, 'registry_entry_id', 'registry_entry_hash', 'pnrg');
647
+ });
648
+ }
649
+
650
+ export function createRegistryBatch(input = {}) {
651
+ requiredObject(input, 'input');
652
+ assertNoPrivateProofPayload(input, 'input');
653
+ const entries = Object.freeze(arrayInput(input.entries ?? input.registry_entries ?? input.registryEntries).map((entry, index) => {
654
+ requiredObject(entry, `entries[${index}]`);
655
+ if (entry.schema !== PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA) throw new TypeError(`entries[${index}] must be a ${PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA}`);
656
+ const validation = validateRegistryEntry(entry);
657
+ if (!validation.ok) throw new TypeError(`entries[${index}] is invalid: ${validation.errors.join('; ')}`);
658
+ return Object.freeze(entry);
659
+ }));
660
+ if (entries.length === 0) throw new TypeError('entries must be non-empty');
661
+ if (entries.length > 128) throw new TypeError('entries must contain at most 128 entries');
662
+ const entryHashes = Object.freeze(entries.map((entry) => requiredString(entry.registry_entry_hash, 'entries.registry_entry_hash')).sort());
663
+ const registryRoot = sha256Json(entryHashes);
664
+ const body = {
665
+ schema: PROOF_NETWORK_REGISTRY_BATCH_SCHEMA,
666
+ created_at: isoTimestamp(input.created_at ?? input.createdAt ?? input.generated_at ?? input.generatedAt, 'created_at'),
667
+ registry_ref: optionalPublicRef(input.registry_ref ?? input.registryRef ?? input.marketplace_ref ?? input.marketplaceRef, refFromDigest('registry-batch', registryRoot), 'registry_ref'),
668
+ entry_count: entries.length,
669
+ registry_root: registryRoot,
670
+ entry_hashes: entryHashes,
671
+ entries,
672
+ ...SAFE_BOOLEAN_BOUNDARIES,
673
+ };
674
+ return freezeArtifact(body, 'registry_batch_id', 'registry_batch_hash', 'pnrb');
675
+ }
676
+
677
+ export function validateRegistryBatch(batch) {
678
+ return collectValidation((errors) => {
679
+ requiredObject(batch, 'batch');
680
+ assertNoPrivateProofPayload(batch, 'batch');
681
+ if (batch.schema !== PROOF_NETWORK_REGISTRY_BATCH_SCHEMA) errors.push(`schema must be ${PROOF_NETWORK_REGISTRY_BATCH_SCHEMA}`);
682
+ requireSafeBoundaries(batch, errors, 'batch');
683
+ isoTimestamp(batch.created_at, 'created_at');
684
+ publicRef(batch.registry_ref, 'registry_ref');
685
+ const entries = Array.isArray(batch.entries) ? batch.entries : [];
686
+ if (entries.length === 0 || entries.length > 128) errors.push('entries must contain 1-128 entries');
687
+ const entryHashes = [];
688
+ for (const [index, entry] of entries.entries()) {
689
+ requiredObject(entry, `entries[${index}]`);
690
+ if (entry.schema !== PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA) {
691
+ errors.push(`entries[${index}] must be a ${PROOF_NETWORK_REGISTRY_ENTRY_SCHEMA}`);
692
+ continue;
693
+ }
694
+ const validation = validateRegistryEntry(entry);
695
+ if (!validation.ok) errors.push(`entries[${index}] is invalid: ${validation.errors.join('; ')}`);
696
+ entryHashes.push(requiredString(entry.registry_entry_hash, `entries[${index}].registry_entry_hash`));
697
+ }
698
+ entryHashes.sort();
699
+ if (batch.entry_count !== entries.length) errors.push('entry_count mismatch');
700
+ if (batch.registry_root !== sha256Json(entryHashes)) errors.push('registry_root mismatch');
701
+ if (JSON.stringify(batch.entry_hashes) !== JSON.stringify(entryHashes)) errors.push('entry_hashes mismatch');
702
+ validateIdentity(batch, errors, 'registry_batch_id', 'registry_batch_hash', 'pnrb');
703
+ });
704
+ }
705
+
706
+ export const createProofRegistryEntry = createRegistryEntry;
707
+ export const validateProofRegistryEntry = validateRegistryEntry;
708
+ export const createProofRegistryBatch = createRegistryBatch;
709
+ export const validateProofRegistryBatch = validateRegistryBatch;
@@ -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
  }
@@ -23,6 +23,7 @@ const SHA256_PREFIX = 'sha256:';
23
23
  const PUBLIC_REF_RE = /^[a-z0-9][a-z0-9._:/@+-]{2,191}$/u;
24
24
  const SCORE_KEY_RE = /^[a-z][a-z0-9_.:-]{1,63}$/u;
25
25
  const PRIVATE_REPORT_KEY_RE = /(?:^|_)(?:raw_memory|memory_plaintext|plaintext|plain_text|prompt|prompts|conversation|conversations|message|messages|content|body|payload|payloads|document|documents|transcript|transcripts|completion|completions|embedding|embeddings|provider_response|provider_responses|response_body|credential|credentials|api_key|secret|password|private_key|seed|seed_phrase|mnemonic|tenant_name|customer_name|organization_name|org_name|account_id)(?:$|_)/iu;
26
+ const RAW_ANSWER_REPORT_KEY_RE = /(?:^|_)(?:raw_answer|raw_answers|answer_text|answer_texts|answer_body|answer_content|generated_answer|generated_answers|final_answer|final_answers|model_answer|model_answers|llm_answer|llm_answers|provider_answer|provider_answers)(?:$|_)/iu;
26
27
  const ALLOWED_FALSE_REPORT_KEYS = new Set([
27
28
  'raw_private_memory_plaintext_included',
28
29
  'raw_question_text_included',
@@ -55,6 +56,9 @@ const ALLOWED_PUBLIC_REPORT_KEYS = new Set([
55
56
  'estimated_prompt_tokens',
56
57
  'baseline_prompt_tokens',
57
58
  'optimized_prompt_tokens',
59
+ 'prompt_refs',
60
+ 'same_prompts_for_all_rows',
61
+ 'prompts_fixed',
58
62
  ]);
59
63
  const SECRET_VALUE_RE = /(?:Bearer\s+[A-Za-z0-9._~+/=-]{12,}|Basic\s+[A-Za-z0-9+/=-]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY-----|https?:\/\/[^\s/@]+:[^\s/@]+@|sk-[A-Za-z0-9_-]{16,}|AKIA[0-9A-Z]{16}|(?:seed phrase|mnemonic phrase|raw memory|private prompt|full transcript|provider response|embedding vector))/iu;
60
64
  const ABSOLUTE_LOCAL_PATH_RE = /^(?:[A-Za-z]:[\\/]|\\\\|\/(?:Users|home|tmp|var|etc|mnt|Volumes)\b)/u;
@@ -62,6 +66,7 @@ const CLAIM_SCORE_KEY_RE = /(?:provider|competitor|roi|profit|savings|solana|tra
62
66
  const PROOF_COMPATIBLE_REPORT_SCHEMAS = new Set([
63
67
  'enigma.memory_benchmark_suite.v1',
64
68
  'enigma.standard_memory_benchmark_suite.v1',
69
+ 'enigma.standard_memory_benchmark_protocol_plan.v1',
65
70
  ]);
66
71
  const REQUIRED_FALSE_BOUNDARY_KEYS = Object.freeze([
67
72
  'external_provider_calls',
@@ -172,7 +177,7 @@ export function parseArgs(argv = process.argv.slice(2)) {
172
177
  export function usage() {
173
178
  return `Usage: node scripts/build-benchmark-proof-release.mjs --report <path> --dataset-ref <ref> --runner-ref <ref> --package-ref <ref> [--score key=value ...] [--out-dir <dir>]
174
179
 
175
- Builds a dependency-free, local benchmark proof release from an existing public-safe benchmark report. The report must use a compatible benchmark schema and explicit offline benchmark boundaries. The report file is parsed for public-safety checks and hashed, but its body and local path are never copied into the attestation or proof packet. The generated artifacts are local benchmark attestation/proof only: no API calls, provider answer-accuracy claims, Mem0 or competitor performance claims, Solana submissions, hosted SaaS claims, ROI/profit/savings claims, raw memory, prompts, transcripts, embeddings, credentials, account ids, private keys, or provider responses are written.
180
+ Builds a dependency-free, local benchmark proof release from an existing public-safe benchmark report. The report must use a compatible benchmark schema (a scored retrieval proxy report such as enigma.standard_memory_benchmark_suite.v1, or a full-answer protocol plan such as enigma.standard_memory_benchmark_protocol_plan.v1) and explicit offline benchmark boundaries. The report file is parsed for public-safety checks and hashed, but its body and local path are never copied into the attestation or proof packet. The generated artifacts are local benchmark attestation/proof only: no API calls, provider answer-accuracy claims, Mem0 or competitor performance claims, Solana submissions, hosted SaaS claims, ROI/profit/savings claims, raw memory, prompts, transcripts, embeddings, credentials, account ids, private keys, or provider responses are written. A protocol-plan report is accepted as protocol-readiness evidence only and is rejected if it contains raw answers, prompts, provider responses, or competitor scores.
176
181
  `;
177
182
  }
178
183
 
@@ -205,7 +210,7 @@ function assertPublicReportPayload(value, path = 'report') {
205
210
  }
206
211
  if (!isPlainObject(value)) return;
207
212
  for (const [key, child] of Object.entries(value)) {
208
- if (PRIVATE_REPORT_KEY_RE.test(key) && !ALLOWED_PUBLIC_REPORT_KEYS.has(key)) {
213
+ if ((PRIVATE_REPORT_KEY_RE.test(key) || RAW_ANSWER_REPORT_KEY_RE.test(key)) && !ALLOWED_PUBLIC_REPORT_KEYS.has(key)) {
209
214
  if (!ALLOWED_FALSE_REPORT_KEYS.has(key) || child !== false) throw new UsageError(`${path}.${key} is not allowed in public benchmark proof artifacts`);
210
215
  }
211
216
  assertPublicReportPayload(child, `${path}.${key}`);
@@ -275,6 +280,13 @@ function assertProofCompatibleBenchmarkBoundaries(report) {
275
280
  if (report.schema === 'enigma.memory_benchmark_suite.v1') {
276
281
  if (boundaries.local_only !== true) throw new UsageError('local memory benchmark report must set benchmark_boundaries.local_only true');
277
282
  }
283
+ if (report.schema === 'enigma.standard_memory_benchmark_protocol_plan.v1') {
284
+ const protocolBoundaries = report.protocol_boundaries;
285
+ if (!isPlainObject(protocolBoundaries)) throw new UsageError('protocol plan report must include protocol_boundaries');
286
+ for (const key of ['network_required', 'provider_calls_made', 'answers_generated', 'judged']) {
287
+ requireFalseField(protocolBoundaries, key, 'report.protocol_boundaries');
288
+ }
289
+ }
278
290
  assertCommandBoundaries(report);
279
291
  assertExternalAdaptersUnscored(report);
280
292
  }
@@ -404,7 +416,7 @@ export async function buildBenchmarkProofRelease(args, options = {}) {
404
416
  const schema = reportSchema(report);
405
417
  const sampleCount = sampleCountFromReport(report);
406
418
  const datasetCount = Array.isArray(report.datasets) ? report.datasets.length : 0;
407
- const commitments = scoreCommitments(args.scores, reportHash);
419
+ const commitments = scoreCommitments(Array.isArray(args.scores) ? args.scores : [], reportHash);
408
420
  const metricRoots = commitments.map((score) => score.score_hash);
409
421
 
410
422
  const attestation = createBenchmarkAttestation({