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.
- package/README.md +23 -13
- package/apps/cli/bin/enigma.mjs +3341 -3151
- 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 -240
- package/docs/blockchain-only-mechanisms.md +400 -388
- package/docs/client-connectors.md +10 -6
- package/docs/demo-proof-network.md +275 -275
- package/docs/developer-ecosystem.md +7 -5
- package/docs/developer-proof-quickstart.md +325 -325
- package/docs/enigma-memory-ready-conformance.md +376 -376
- package/docs/hosted-cloud-product.md +29 -0
- package/docs/install-anywhere.md +16 -12
- package/docs/memory-benchmarks.md +16 -1
- package/docs/memory-drive-health-model.md +690 -649
- package/docs/proof-network-build-notes.md +240 -240
- package/docs/proof-network.md +339 -282
- 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/hosted-cloud/src/index.js +470 -2
- package/packages/mcp-server/src/index.js +1185 -1185
- package/packages/passport/src/index.js +740 -6
- package/packages/proof-network/src/index.js +139 -0
- package/packages/vault/src/index.js +187 -25
- package/scripts/build-benchmark-proof-release.mjs +15 -3
- package/scripts/build-hosted-api-key-lifecycle.mjs +274 -274
- package/scripts/build-hosted-customer-lifecycle.mjs +476 -456
- package/scripts/build-installer-assets.mjs +389 -389
- package/scripts/build-production-unblocker.mjs +409 -409
- 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 -1194
- package/scripts/scan-secrets.mjs +177 -0
- 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
|
|
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
|
}
|
|
@@ -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({
|