enigma-memory 0.1.17 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -27
- package/apps/cli/bin/enigma-desktop.mjs +140 -0
- package/apps/cli/bin/enigma-terminal.mjs +78 -0
- package/apps/cli/bin/enigma.mjs +4979 -3263
- package/apps/desktop/electron-main.cjs +217 -0
- package/apps/desktop/package.json +12 -0
- package/apps/desktop/src/app.js +264 -7
- package/apps/desktop/src/index.html +3514 -1373
- package/apps/desktop/src/launch-electron.mjs +51 -0
- package/apps/desktop/src/server.mjs +2914 -0
- package/apps/desktop/src/styles.css +2972 -260
- package/apps/desktop/src/zk-browser-prove.mjs +53 -0
- package/apps/desktop/src/zk-state.mjs +1789 -0
- package/apps/gateway/bin/enigma-gateway.mjs +102 -5
- package/apps/gateway/src/server.mjs +271 -8
- package/apps/ios/EnigmaCore/Package.swift +12 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/EnigmaAPIClient.swift +227 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/Models.swift +278 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PKCE.swift +96 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/PrivacyMinimizer.swift +187 -0
- package/apps/ios/EnigmaCore/Sources/EnigmaCore/ToolModels.swift +129 -0
- package/apps/ios/EnigmaCore/Tests/EnigmaCoreTests/EnigmaCoreTests.swift +42 -0
- package/apps/ios/EnigmaIOS/Enigma/AppModel.swift +346 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AccentColor.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/Contents.json +11 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/AppIcon.appiconset/EnigmaAppIcon.png +0 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/Contents.json +3 -0
- package/apps/ios/EnigmaIOS/Enigma/Assets.xcassets/LaunchBackground.colorset/Contents.json +12 -0
- package/apps/ios/EnigmaIOS/Enigma/ChatView.swift +181 -0
- package/apps/ios/EnigmaIOS/Enigma/CouncilView.swift +78 -0
- package/apps/ios/EnigmaIOS/Enigma/CreateView.swift +152 -0
- package/apps/ios/EnigmaIOS/Enigma/EnigmaApp.swift +52 -0
- package/apps/ios/EnigmaIOS/Enigma/Info.plist +52 -0
- package/apps/ios/EnigmaIOS/Enigma/NaturalLanguagePrivacyTagger.swift +26 -0
- package/apps/ios/EnigmaIOS/Enigma/OAuthClient.swift +321 -0
- package/apps/ios/EnigmaIOS/Enigma/OnboardingView.swift +105 -0
- package/apps/ios/EnigmaIOS/Enigma/PrivateVaultView.swift +275 -0
- package/apps/ios/EnigmaIOS/Enigma/SecureStore.swift +76 -0
- package/apps/ios/EnigmaIOS/Enigma/SettingsView.swift +60 -0
- package/apps/ios/EnigmaIOS/Enigma/Theme.swift +80 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/project.pbxproj +211 -0
- package/apps/ios/EnigmaIOS/EnigmaIOS.xcodeproj/xcshareddata/xcschemes/Enigma.xcscheme +23 -0
- package/apps/native-host/README.md +19 -8
- package/apps/native-host/bin/enigma-native-host.mjs +229 -13
- package/apps/relay/bin/enigma-relay.mjs +103 -5
- package/apps/relay/src/federation-runtime.mjs +618 -0
- package/apps/relay/src/server.mjs +310 -9
- package/apps/verifier/bin/enigma-verify.mjs +327 -11
- package/cortex-v3/circuits/build/intent_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/sale_vk_bytes.json +35 -0
- package/cortex-v3/circuits/build/vk_bytes.json +32 -0
- package/cortex-v3/proving-assets.json +64 -0
- package/cortex-v3/zk/BUILD-CONTRACT.md +87 -0
- package/cortex-v3/zk/action-transition-vk.json +119 -0
- package/cortex-v3/zk/alias-adversarial.test.mjs +220 -0
- package/cortex-v3/zk/groth16-verify-child.mjs +17 -0
- package/cortex-v3/zk/intent-witness.mjs +365 -0
- package/cortex-v3/zk/intent-witness.test.mjs +485 -0
- package/cortex-v3/zk/proving-assets.mjs +203 -0
- package/cortex-v3/zk/sale-witness.mjs +783 -0
- package/cortex-v3/zk/sale-witness.test.mjs +784 -0
- package/cortex-v3/zk/sealed-sale-release-vk.json +119 -0
- package/cortex-v3/zk/settlement-evidence.mjs +722 -0
- package/cortex-v3/zk/setup-intent.mjs +688 -0
- package/cortex-v3/zk/setup-sale.mjs +666 -0
- package/cortex-v3/zk/setup.mjs +594 -0
- package/cortex-v3/zk/witness.mjs +184 -0
- package/cortex-v3/zk/zk-codec.mjs +232 -0
- package/cortex-v3/zk/zk-codec.test.mjs +293 -0
- package/cortex-v3/zk/zk-settle.mjs +370 -0
- package/cortex-v3/zk/zk-tree.mjs +256 -0
- package/cortex-v3/zk/zk-tree.test.mjs +419 -0
- package/deploy/SIMULATION.md +14 -9
- package/deploy/docker-compose.local-production-simulation.yml +54 -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/browser-extension-install.md +8 -6
- package/docs/client-connectors.md +16 -12
- package/docs/demo-proof-network.md +275 -275
- package/docs/developer-ecosystem.md +15 -13
- package/docs/developer-proof-quickstart.md +325 -325
- package/docs/enigma-memory-ready-conformance.md +378 -376
- package/docs/install-anywhere.md +64 -30
- package/docs/installers-and-desktop.md +8 -7
- package/docs/memory-benchmarks.md +1 -1
- package/docs/memory-drive-health-model.md +690 -690
- package/docs/novelty-invention-candidates.md +161 -161
- package/docs/proof-network-build-notes.md +240 -240
- package/docs/proof-network-claim-boundaries.md +320 -318
- package/docs/proof-network.md +339 -339
- package/docs/sdk-api.md +324 -324
- package/docs/solana-proof-rail.md +453 -453
- package/examples/01-quickstart-agent/index.mjs +49 -0
- package/examples/01_agent_memory_quickstart.mjs +57 -0
- package/examples/02-multi-agent-swarm/index.mjs +57 -0
- package/examples/02_cross_model_passport.mjs +64 -0
- package/examples/03-langchain-memory/index.mjs +41 -0
- package/examples/03_poseidon_commitment_verification.mjs +71 -0
- package/examples/04-python-trading-agent/trader.py +49 -0
- package/examples/README.md +27 -0
- package/examples/ci/github-actions.yml +7 -2
- package/package.json +410 -278
- package/packages/adapters/PACKAGE_CONTRACT.md +1 -1
- package/packages/connectors/src/index.js +196 -4
- package/packages/connectors/swarm-router.mjs +168 -0
- package/packages/core/src/index.js +249 -2
- package/packages/core/src/version.mjs +7 -0
- package/packages/dev-tools/package.json +19 -0
- package/packages/dev-tools/src/index.js +4 -0
- package/packages/dev-tools/src/memory-benchmark-suite.js +112 -0
- package/packages/dev-tools/src/swarm-simulator.js +101 -0
- package/packages/dev-tools/src/vault-inspector.js +114 -0
- package/packages/dev-tools/src/vector-benchmark.js +100 -0
- package/packages/developer-platform/src/access-credentials.js +341 -0
- package/packages/developer-platform/src/http.js +132 -0
- package/packages/developer-platform/src/index.js +4 -0
- package/packages/developer-platform/src/usage-http.js +60 -0
- package/packages/developer-platform/src/usage.js +295 -0
- package/packages/enclave-runtime/attestation.mjs +159 -0
- package/packages/enclave-runtime/index.mjs +47 -0
- package/packages/enclave-runtime/session-manager.mjs +253 -0
- package/packages/enclave-runtime/zeroization-proof.mjs +227 -0
- package/packages/enigma-reflex/package.json +14 -0
- package/packages/enigma-reflex/src/index.js +204 -0
- package/packages/enigma-reflex/training/generate-dataset.mjs +40 -0
- package/packages/enigma-reflex/training/requirements.txt +8 -0
- package/packages/enigma-reflex/training/train.py +314 -0
- package/packages/enigma-weave/LICENSE +22 -0
- package/packages/enigma-weave/UPSTREAM.json +21 -0
- package/packages/enigma-weave/package.json +14 -0
- package/packages/enigma-weave/src/index.js +286 -0
- package/packages/hosted-cloud/src/index.js +80 -5
- package/packages/importers/src/index.js +432 -0
- package/packages/inference-runtime/src/browser.js +401 -0
- package/packages/inference-runtime/src/chat.js +265 -0
- package/packages/inference-runtime/src/code.js +407 -0
- package/packages/inference-runtime/src/contracts.js +162 -0
- package/packages/inference-runtime/src/http.js +232 -0
- package/packages/inference-runtime/src/image.js +186 -0
- package/packages/inference-runtime/src/index.js +10 -0
- package/packages/inference-runtime/src/model-router.js +320 -0
- package/packages/inference-runtime/src/platform.js +125 -0
- package/packages/inference-runtime/src/privacy.js +400 -0
- package/packages/inference-runtime/src/video.js +253 -0
- package/packages/mcp-server/README.md +22 -6
- package/packages/mcp-server/bin/enigma-mcp.mjs +2 -1
- package/packages/mcp-server/src/index.js +2498 -1185
- package/packages/mcp-server/src/oauth.js +561 -0
- package/packages/mcp-server/src/private-handoff.js +84 -0
- package/packages/mcp-server/src/remote-http.js +273 -0
- package/packages/mcp-server/src/remote-policy.js +72 -0
- package/packages/mcp-server/swarm-bridge.mjs +361 -0
- package/packages/mesh/index.d.ts +283 -0
- package/packages/mesh/package.json +23 -0
- package/packages/mesh/src/crypto.js +189 -0
- package/packages/mesh/src/federation-packets.js +353 -0
- package/packages/mesh/src/gossip.js +311 -0
- package/packages/mesh/src/index.js +6 -0
- package/packages/mesh/src/protocol.js +255 -0
- package/packages/mesh/src/router.js +279 -0
- package/packages/mesh/src/transport.js +306 -0
- package/packages/passport/src/index.js +436 -7
- package/packages/private-economy/src/credits-http.js +100 -0
- package/packages/private-economy/src/credits.js +447 -0
- package/packages/private-economy/src/index.js +5 -0
- package/packages/private-economy/src/payments-http.js +120 -0
- package/packages/private-economy/src/payments.js +509 -0
- package/packages/private-economy/src/x402.js +346 -0
- package/packages/proof-network/PACKAGE_CONTRACT.md +21 -0
- package/packages/rag/index.d.ts +182 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/THIRD_PARTY_LICENSES.txt +207 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/config.json +25 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/onnx/model_quantized.onnx +0 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/sha256-manifest.json +28 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer.json +30686 -0
- package/packages/rag/models/Xenova/all-MiniLM-L6-v2/tokenizer_config.json +15 -0
- package/packages/rag/package.json +27 -0
- package/packages/rag/src/blinded-search.js +109 -0
- package/packages/rag/src/bm25.js +169 -0
- package/packages/rag/src/embeddings.js +459 -0
- package/packages/rag/src/hybrid.js +76 -0
- package/packages/rag/src/index.js +38 -0
- package/packages/rag/src/reranker.js +61 -0
- package/packages/rag/src/research.js +107 -0
- package/packages/rag/src/vector-store.js +430 -0
- package/packages/rag/src/verify-model-artifacts.mjs +4 -0
- package/packages/sdk/index.d.ts +760 -0
- package/packages/sdk/package.json +33 -0
- package/packages/sdk/python/README.md +24 -0
- package/packages/sdk/python/enigma_sdk.py +250 -0
- package/packages/sdk/python/pyproject.toml +34 -0
- package/packages/sdk/python/requirements.txt +1 -0
- package/packages/sdk/python/setup.py +20 -0
- package/packages/sdk/src/federation/capability-grant.js +389 -0
- package/packages/sdk/src/federation/federation-router.js +360 -0
- package/packages/sdk/src/federation/ghostmesh-bridge.js +497 -0
- package/packages/sdk/src/federation/index.js +3 -0
- package/packages/sdk/src/index.js +1796 -0
- package/packages/sdk/src/intelligence/contradiction.js +337 -0
- package/packages/sdk/src/intelligence/decision-engine.js +155 -0
- package/packages/sdk/src/intelligence/index.js +4 -0
- package/packages/sdk/src/intelligence/ontology.js +122 -0
- package/packages/sdk/src/intelligence/temporal.js +123 -0
- package/packages/sdk/src/market-client.js +142 -0
- package/packages/sdk/src/mesh-client.js +110 -0
- package/packages/sdk/src/middleware/index.js +3 -0
- package/packages/sdk/src/middleware/langchain.js +159 -0
- package/packages/sdk/src/middleware/llamaindex.js +101 -0
- package/packages/sdk/src/middleware/vercel-ai.js +112 -0
- package/packages/sdk/src/rag-client.js +85 -0
- package/packages/sdk/src/swarm-orchestrator.js +260 -0
- package/packages/settlement/PACKAGE_CONTRACT.md +1 -1
- package/packages/snapcompact/THIRD_PARTY_LICENSES.txt +40 -0
- package/packages/snapcompact/assets/8x13-latin1.bdf +3837 -0
- package/packages/snapcompact/index.d.ts +284 -0
- package/packages/snapcompact/package.json +25 -0
- package/packages/snapcompact/src/index.js +716 -0
- package/packages/storage/PACKAGE_CONTRACT.md +1 -1
- package/packages/terminal-console/animations.mjs +240 -0
- package/packages/terminal-console/auto-anchor.mjs +220 -0
- package/packages/terminal-console/banner.mjs +91 -0
- package/packages/terminal-console/commands.mjs +459 -0
- package/packages/terminal-console/delegation.mjs +152 -0
- package/packages/terminal-console/index.mjs +5 -0
- package/packages/terminal-console/outbox.mjs +143 -0
- package/packages/terminal-console/phantom-bridge.mjs +637 -0
- package/packages/terminal-console/repl.mjs +136 -0
- package/packages/terminal-console/signer-store.mjs +130 -0
- package/packages/terminal-console/solana-rpc.mjs +214 -0
- package/packages/terminal-console/solana-transport.mjs +189 -0
- package/packages/terminal-tui/dashboard.mjs +214 -0
- package/packages/terminal-tui/index.mjs +28 -0
- package/packages/terminal-tui/merkle-tree-renderer.mjs +268 -0
- package/packages/terminal-tui/telemetry-hud.mjs +137 -0
- package/packages/vault/index.d.ts +449 -0
- package/packages/vault/package.json +27 -0
- package/packages/vault/src/e2ee.mjs +393 -0
- package/packages/vault/src/enclave.js +481 -0
- package/packages/vault/src/erasure.js +207 -0
- package/packages/vault/src/index.js +1150 -125
- package/packages/vault/src/persistence.js +307 -0
- package/packages/vault/src/poseidon.js +354 -0
- package/packages/vault/src/receipt.js +459 -0
- package/scripts/benchmark-optical-context.mjs +166 -0
- package/scripts/bootstrap-enigma.mjs +502 -0
- package/scripts/build-edge-backend-workers.mjs +20 -5
- package/scripts/build-goal-completion-audit.mjs +72 -25
- package/scripts/build-hosted-api-key-lifecycle.mjs +292 -274
- package/scripts/build-hosted-customer-lifecycle.mjs +493 -476
- package/scripts/build-hosted-probe-worker.mjs +19 -4
- package/scripts/build-installer-assets.mjs +409 -389
- package/scripts/build-operator-evidence-starter.mjs +59 -1
- package/scripts/build-production-backend-env-kit.mjs +2 -0
- package/scripts/build-production-unblocker.mjs +4 -1
- package/scripts/build-proof-network-packet.mjs +213 -213
- package/scripts/check.mjs +25 -4
- package/scripts/collect-hosted-backend-live-evidence.mjs +49 -12
- package/scripts/install-enigma-local.mjs +18 -5
- package/scripts/release-audit.mjs +74 -115
- package/scripts/release-provenance.mjs +12 -2
- package/scripts/run-backend-readiness-smoke.mjs +112 -10
- package/scripts/run-standard-memory-benchmarks.mjs +1354 -1352
- package/scripts/scan-secrets.mjs +178 -0
- package/scripts/simulate-production-env.mjs +71 -10
- package/scripts/validate-hosted-backend-live.mjs +112 -1
- package/specs/antibody-pack-v1.schema.json +95 -0
- package/specs/antigen-envelope-v1.schema.json +81 -0
- package/specs/boundary-manifest-v1.schema.json +35 -35
- package/specs/capsule-v1.schema.json +55 -55
- package/specs/claim-boundary-manifest-v1.schema.json +22 -22
- package/specs/claim-ledger-v1.schema.json +291 -0
- package/specs/context-passport-v1.schema.json +59 -0
- package/specs/deletion-tombstone-v1.schema.json +26 -26
- package/specs/evidence-packet-v1.schema.json +177 -0
- package/specs/hosted-backend-live-evidence-v1.schema.json +72 -3
- package/specs/immune-scan-report-v1.schema.json +112 -0
- package/specs/lifecycle-receipt-log-v1.schema.json +67 -0
- package/specs/memory-atom-v1.schema.json +59 -0
- package/specs/memory-event-v1.schema.json +42 -42
- package/specs/passport-v1.schema.json +50 -50
- package/specs/proof-of-non-use-v1.schema.json +65 -0
- package/specs/quarantine-record-v1.schema.json +126 -0
- package/specs/receipt-v1.schema.json +61 -61
- package/specs/state-checkpoint-v1.schema.json +37 -37
- package/specs/trust-bundle-v1.schema.json +56 -56
- package/specs/trust-card-v1.schema.json +119 -0
- package/docs/proof-network-launch-plan.md +0 -421
- package/packages/metering/PACKAGE_CONTRACT.md +0 -20
- package/scripts/build-ai-orchestration-plan.mjs +0 -248
|
@@ -3,24 +3,32 @@ 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,
|
|
12
|
-
createMemoryAddress,
|
|
13
16
|
createReceipt,
|
|
14
17
|
generateSigningKeyPair,
|
|
15
18
|
MerkleSet,
|
|
16
19
|
hmacSha256Hex,
|
|
17
20
|
receiptHash,
|
|
18
21
|
sha256Hex,
|
|
22
|
+
verifyReceiptChain,
|
|
19
23
|
} from '../../core/src/index.js';
|
|
20
24
|
|
|
21
|
-
const VAULT_SCHEMA = 'enigma.vault.local.v1';
|
|
22
|
-
const
|
|
23
|
-
const
|
|
25
|
+
export const VAULT_SCHEMA = 'enigma.vault.local.v1';
|
|
26
|
+
export const VAULT_BUNDLE_SCHEMA = 'enigma.vault_bundle.v1';
|
|
27
|
+
export const MEMORY_OBJECT_SCHEMA = 'enigma.memory_object.v1';
|
|
28
|
+
export const EVENT_SCHEMA = 'enigma.memory_event.v1';
|
|
29
|
+
const MEMORY_DOMAIN = 'enigma.memory.v2';
|
|
30
|
+
const CONTENT_AAD_DOMAIN = 'enigma.memory.content_aad.v1';
|
|
31
|
+
const PRIVATE_MEMORY_SCHEMA = 'enigma.memory_private.v1';
|
|
24
32
|
const ZERO_ROOT = new MerkleSet().root();
|
|
25
33
|
const RECEIPT_PLAINTEXT_KEYS = new Set(['content', 'plaintext', 'text', 'memory', 'prompt', 'response']);
|
|
26
34
|
const PUBLIC_PLAINTEXT_KEYS = new Set([
|
|
@@ -45,7 +53,26 @@ const PUBLIC_PLAINTEXT_KEYS = new Set([
|
|
|
45
53
|
]);
|
|
46
54
|
const SAFE_METADATA_DIGEST_RE = /^(?:sha256:)?[a-f0-9]{64}$|^hmac-sha256:[a-f0-9]{64}$/;
|
|
47
55
|
const SOURCE_REF_ROOT_RE = /^sha256:[a-f0-9]{64}$/;
|
|
48
|
-
|
|
56
|
+
export const KEYRING_ENCRYPTED_TYPE = 'local_secret_encrypted';
|
|
57
|
+
export const KEYRING_PLAINTEXT_TYPE = 'local_secret';
|
|
58
|
+
export const DEFAULT_KDF_SPEC = {
|
|
59
|
+
algorithm: 'scrypt',
|
|
60
|
+
n: 16384,
|
|
61
|
+
r: 8,
|
|
62
|
+
p: 1,
|
|
63
|
+
maxmem: 64 * 1024 * 1024,
|
|
64
|
+
};
|
|
65
|
+
export const PBKDF2_KDF_SPEC = {
|
|
66
|
+
algorithm: 'pbkdf2',
|
|
67
|
+
iterations: 600000,
|
|
68
|
+
hash: 'sha256',
|
|
69
|
+
};
|
|
70
|
+
const DERIVED_KEY_LENGTH = 128; // 32 bytes each: KEK, vaultKey, addressKey, signingSeed
|
|
71
|
+
const SALT_LENGTH = 16;
|
|
72
|
+
const IV_LENGTH = 12;
|
|
73
|
+
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.';
|
|
74
|
+
const ENCRYPTED_KEYRING_WARNING = 'Keyring is encrypted with AES-256-GCM; passphrase required on import.';
|
|
75
|
+
const DEPRECATION_PLAINTEXT_KEYRING = 'Plaintext keyrings are deprecated; use --passphrase to encrypt new bundles.';
|
|
49
76
|
function normalizedPublicKey(key) {
|
|
50
77
|
return String(key).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
51
78
|
}
|
|
@@ -87,44 +114,54 @@ function isSafeMetadataPrimitive(key, value) {
|
|
|
87
114
|
return false;
|
|
88
115
|
}
|
|
89
116
|
|
|
90
|
-
function
|
|
91
|
-
|
|
117
|
+
function privateValueCommitment(key, value) {
|
|
118
|
+
const encoded = typeof value === 'string' ? value : canonical(value);
|
|
119
|
+
return key ? `sha256:${asHmacSha256(key, encoded)}` : asSha256(encoded);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function commitMetadataValue(out, key, value, commitmentKey) {
|
|
123
|
+
out[metadataCommitmentKey(key)] = privateValueCommitment(
|
|
124
|
+
commitmentKey,
|
|
125
|
+
isMetadataPrimitive(value) ? String(value) : value,
|
|
126
|
+
);
|
|
92
127
|
}
|
|
93
128
|
|
|
94
|
-
function sanitizeSourceRefValue(value) {
|
|
129
|
+
function sanitizeSourceRefValue(value, commitmentKey) {
|
|
95
130
|
if (value === undefined || value === null) return undefined;
|
|
96
|
-
if (typeof value === 'string')
|
|
131
|
+
if (typeof value === 'string') {
|
|
132
|
+
return SAFE_METADATA_DIGEST_RE.test(value) ? value : privateValueCommitment(commitmentKey, value);
|
|
133
|
+
}
|
|
97
134
|
if (['number', 'boolean'].includes(typeof value)) return value;
|
|
98
|
-
if (Array.isArray(value)) return value.map(sanitizeSourceRefValue).filter((item) => item !== undefined);
|
|
135
|
+
if (Array.isArray(value)) return value.map((item) => sanitizeSourceRefValue(item, commitmentKey)).filter((item) => item !== undefined);
|
|
99
136
|
if (typeof value !== 'object') return undefined;
|
|
100
137
|
|
|
101
138
|
const out = {};
|
|
102
139
|
for (const [key, item] of Object.entries(value)) {
|
|
103
140
|
if (isSourceRefPlaintextLikeKey(key)) {
|
|
104
141
|
if (item !== undefined && item !== null && ['string', 'number', 'boolean'].includes(typeof item)) {
|
|
105
|
-
out[privateCommitmentKey(key)] =
|
|
142
|
+
out[privateCommitmentKey(key)] = privateValueCommitment(commitmentKey, String(item));
|
|
106
143
|
}
|
|
107
144
|
continue;
|
|
108
145
|
}
|
|
109
|
-
if (key === 'source_hash' && typeof item === 'string' &&
|
|
146
|
+
if (key === 'source_hash' && typeof item === 'string' && SAFE_METADATA_DIGEST_RE.test(item)) {
|
|
110
147
|
out[key] = item;
|
|
111
148
|
continue;
|
|
112
149
|
}
|
|
113
|
-
const sanitized = sanitizeSourceRefValue(item);
|
|
150
|
+
const sanitized = sanitizeSourceRefValue(item, commitmentKey);
|
|
114
151
|
if (sanitized !== undefined) out[key] = sanitized;
|
|
115
152
|
}
|
|
116
153
|
return Object.keys(out).length > 0 ? out : undefined;
|
|
117
154
|
}
|
|
118
155
|
|
|
119
|
-
function sanitizeSourceRefs(refs) {
|
|
156
|
+
function sanitizeSourceRefs(refs, commitmentKey) {
|
|
120
157
|
if (!Array.isArray(refs)) return [];
|
|
121
158
|
const out = [];
|
|
122
159
|
for (const ref of refs) {
|
|
123
160
|
if (typeof ref === 'string') {
|
|
124
|
-
out.push({ source_hash:
|
|
161
|
+
out.push({ source_hash: privateValueCommitment(commitmentKey, ref) });
|
|
125
162
|
continue;
|
|
126
163
|
}
|
|
127
|
-
const sanitized = sanitizeSourceRefValue(ref);
|
|
164
|
+
const sanitized = sanitizeSourceRefValue(ref, commitmentKey);
|
|
128
165
|
if (sanitized !== undefined) out.push(sanitized);
|
|
129
166
|
}
|
|
130
167
|
return out;
|
|
@@ -175,12 +212,15 @@ function receiptEvent(receipt) {
|
|
|
175
212
|
function deriveReceiptLifecycle(receipts) {
|
|
176
213
|
const active = new Set();
|
|
177
214
|
const tombstones = new Map();
|
|
215
|
+
let activeRoot = computeSetRoot(active);
|
|
178
216
|
for (const receipt of receipts) {
|
|
179
217
|
const event = receiptEvent(receipt);
|
|
180
218
|
if (!event) continue;
|
|
219
|
+
let activeChanged = false;
|
|
181
220
|
if (event.operation === 'create' && event.memory_addr) {
|
|
182
221
|
active.add(event.memory_addr);
|
|
183
222
|
tombstones.delete(event.memory_addr);
|
|
223
|
+
activeChanged = true;
|
|
184
224
|
} else if (event.operation === 'update' && event.memory_addr) {
|
|
185
225
|
if (event.source_addr) {
|
|
186
226
|
active.delete(event.source_addr);
|
|
@@ -195,6 +235,7 @@ function deriveReceiptLifecycle(receipts) {
|
|
|
195
235
|
}
|
|
196
236
|
active.add(event.memory_addr);
|
|
197
237
|
tombstones.delete(event.memory_addr);
|
|
238
|
+
activeChanged = true;
|
|
198
239
|
} else if (event.operation === 'delete' && event.memory_addr) {
|
|
199
240
|
active.delete(event.memory_addr);
|
|
200
241
|
tombstones.set(event.memory_addr, {
|
|
@@ -204,8 +245,14 @@ function deriveReceiptLifecycle(receipts) {
|
|
|
204
245
|
tombstoned_at: event.timestamp,
|
|
205
246
|
reason: 'deleted',
|
|
206
247
|
});
|
|
248
|
+
activeChanged = true;
|
|
249
|
+
} else if (event.operation === 'key_reset') {
|
|
250
|
+
active.clear();
|
|
251
|
+
tombstones.clear();
|
|
252
|
+
activeChanged = true;
|
|
207
253
|
}
|
|
208
|
-
if (
|
|
254
|
+
if (activeChanged) activeRoot = computeSetRoot(active);
|
|
255
|
+
if (typeof receipt.active_set_root === 'string' && receipt.active_set_root !== activeRoot) {
|
|
209
256
|
throw new Error('importBundle receipt active_set_root mismatch');
|
|
210
257
|
}
|
|
211
258
|
}
|
|
@@ -313,7 +360,169 @@ function keyBytes(key, label) {
|
|
|
313
360
|
throw new Error(`${label} must be a Buffer or base64 string`);
|
|
314
361
|
}
|
|
315
362
|
|
|
316
|
-
function
|
|
363
|
+
function buildEd25519Pkcs8(seed) {
|
|
364
|
+
if (!Buffer.isBuffer(seed) || seed.length !== 32) throw new Error('Ed25519 seed must be 32 bytes');
|
|
365
|
+
// PKCS#8 PrivateKeyInfo for Ed25519 (OID 1.3.101.112): SEQUENCE { INTEGER 0, AlgorithmIdentifier, OCTET STRING { OCTET STRING seed } }
|
|
366
|
+
return Buffer.from([
|
|
367
|
+
0x30, 0x2e,
|
|
368
|
+
0x02, 0x01, 0x00,
|
|
369
|
+
0x30, 0x05,
|
|
370
|
+
0x06, 0x03, 0x2b, 0x65, 0x70,
|
|
371
|
+
0x04, 0x22,
|
|
372
|
+
0x04, 0x20,
|
|
373
|
+
...seed,
|
|
374
|
+
]);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function validateKdfSpec(kdfSpec = DEFAULT_KDF_SPEC) {
|
|
378
|
+
const algorithm = kdfSpec?.algorithm ?? 'scrypt';
|
|
379
|
+
if (algorithm === 'scrypt') {
|
|
380
|
+
const n = kdfSpec?.n ?? 16384;
|
|
381
|
+
const r = kdfSpec?.r ?? 8;
|
|
382
|
+
const p = kdfSpec?.p ?? 1;
|
|
383
|
+
const maxmem = kdfSpec?.maxmem ?? 64 * 1024 * 1024;
|
|
384
|
+
if (!Number.isInteger(n) || n < 16384 || n > 131072 || (n & (n - 1)) !== 0) {
|
|
385
|
+
throw new Error('scrypt n must be a power of two from 16384 through 131072');
|
|
386
|
+
}
|
|
387
|
+
if (!Number.isInteger(r) || r < 1 || r > 32) throw new Error('scrypt r must be an integer from 1 through 32');
|
|
388
|
+
if (!Number.isInteger(p) || p < 1 || p > 8) throw new Error('scrypt p must be an integer from 1 through 8');
|
|
389
|
+
const requiredMemory = 128 * n * r + 1024 * 1024;
|
|
390
|
+
if (!Number.isInteger(maxmem) || maxmem < requiredMemory || maxmem > 256 * 1024 * 1024) {
|
|
391
|
+
throw new Error(`scrypt maxmem must be an integer from ${requiredMemory} through 268435456`);
|
|
392
|
+
}
|
|
393
|
+
return { ...kdfSpec, algorithm, n, r, p, maxmem };
|
|
394
|
+
}
|
|
395
|
+
if (algorithm === 'pbkdf2') {
|
|
396
|
+
const iterations = kdfSpec?.iterations ?? 600000;
|
|
397
|
+
const hash = kdfSpec?.hash ?? 'sha256';
|
|
398
|
+
if (!Number.isInteger(iterations) || iterations < 100000 || iterations > 2000000) {
|
|
399
|
+
throw new Error('PBKDF2 iterations must be an integer from 100000 through 2000000');
|
|
400
|
+
}
|
|
401
|
+
if (!['sha256', 'sha512'].includes(hash)) throw new Error('PBKDF2 hash must be sha256 or sha512');
|
|
402
|
+
return { ...kdfSpec, algorithm, iterations, hash };
|
|
403
|
+
}
|
|
404
|
+
throw new Error(`Unsupported KDF algorithm: ${algorithm}`);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function deriveMasterKeyFromPassphrase(passphrase, salt, kdfSpec = DEFAULT_KDF_SPEC) {
|
|
408
|
+
const normalized = Buffer.from(String(passphrase), 'utf8');
|
|
409
|
+
if (typeof salt === 'string' && salt.length > 128) throw new Error('Encoded salt is too large');
|
|
410
|
+
const saltBuf = typeof salt === 'string' ? Buffer.from(salt, 'base64') : Buffer.from(salt);
|
|
411
|
+
if (saltBuf.length < 8 || saltBuf.length > 64) throw new Error('Salt must be from 8 through 64 bytes');
|
|
412
|
+
const validated = validateKdfSpec(kdfSpec);
|
|
413
|
+
if (validated.algorithm === 'scrypt') {
|
|
414
|
+
return scryptSync(normalized, saltBuf, DERIVED_KEY_LENGTH, {
|
|
415
|
+
N: validated.n,
|
|
416
|
+
r: validated.r,
|
|
417
|
+
p: validated.p,
|
|
418
|
+
maxmem: validated.maxmem,
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return pbkdf2Sync(normalized, saltBuf, validated.iterations, DERIVED_KEY_LENGTH, validated.hash);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export function deriveKeysFromPassphrase(passphrase, salt, kdfSpec = DEFAULT_KDF_SPEC) {
|
|
425
|
+
const master = deriveMasterKeyFromPassphrase(passphrase, salt, kdfSpec);
|
|
426
|
+
return {
|
|
427
|
+
kek: master.subarray(0, 32),
|
|
428
|
+
vaultKey: master.subarray(32, 64),
|
|
429
|
+
addressKey: master.subarray(64, 96),
|
|
430
|
+
signingSeed: master.subarray(96, 128),
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export function canonicalKeyringAad(meta = {}) {
|
|
435
|
+
const aad = {
|
|
436
|
+
schema: meta.schema ?? 'enigma.vault_keyring.v2',
|
|
437
|
+
type: meta.type ?? KEYRING_ENCRYPTED_TYPE,
|
|
438
|
+
kdf: meta.kdf ?? DEFAULT_KDF_SPEC,
|
|
439
|
+
salt: String(meta.salt ?? ''),
|
|
440
|
+
vault_schema: meta.vault_schema ?? VAULT_SCHEMA,
|
|
441
|
+
vault_id: meta.vault_id ?? null,
|
|
442
|
+
active_key_epoch: meta.active_key_epoch ?? 1,
|
|
443
|
+
key_epochs: Array.isArray(meta.key_epochs) ? meta.key_epochs : [],
|
|
444
|
+
old_epoch_read_policy: meta.old_epoch_read_policy ?? 'retained_keys_allowed',
|
|
445
|
+
signer: meta.signer ?? 'ed25519',
|
|
446
|
+
publicKey: meta.publicKey ?? null,
|
|
447
|
+
};
|
|
448
|
+
return Buffer.from(canonical(aad), 'utf8');
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function encryptKeyring(plaintextKeyring, kek, aadMeta = undefined) {
|
|
452
|
+
const iv = randomBytes(IV_LENGTH);
|
|
453
|
+
const cipher = createCipheriv('aes-256-gcm', kek, iv);
|
|
454
|
+
if (aadMeta) {
|
|
455
|
+
cipher.setAAD(canonicalKeyringAad(aadMeta));
|
|
456
|
+
}
|
|
457
|
+
const plaintext = Buffer.from(JSON.stringify(plaintextKeyring), 'utf8');
|
|
458
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
459
|
+
const tag = cipher.getAuthTag();
|
|
460
|
+
return {
|
|
461
|
+
iv: iv.toString('base64'),
|
|
462
|
+
tag: tag.toString('base64'),
|
|
463
|
+
ciphertext: ciphertext.toString('base64'),
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function zeroDerivedKeys(keys) {
|
|
468
|
+
for (const value of Object.values(keys ?? {})) value?.fill?.(0);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export function decryptKeyring(keyring, passphrase) {
|
|
472
|
+
if (keyring?.type !== KEYRING_ENCRYPTED_TYPE) throw new Error('Keyring is not encrypted');
|
|
473
|
+
if (passphrase === undefined || passphrase === null || String(passphrase).trim().length === 0) {
|
|
474
|
+
throw new Error('Encrypted keyring requires passphrase');
|
|
475
|
+
}
|
|
476
|
+
const kdf = keyring.kdf || DEFAULT_KDF_SPEC;
|
|
477
|
+
const salt = keyring.kdf?.salt ?? keyring.salt;
|
|
478
|
+
const derived = deriveKeysFromPassphrase(passphrase, salt, kdf);
|
|
479
|
+
try {
|
|
480
|
+
if (keyring.kdf) {
|
|
481
|
+
const decipher = createDecipheriv('aes-256-gcm', derived.kek, Buffer.from(keyring.iv, 'base64'));
|
|
482
|
+
decipher.setAuthTag(Buffer.from(keyring.tag, 'base64'));
|
|
483
|
+
decipher.setAAD(canonicalKeyringAad(keyring));
|
|
484
|
+
const plaintext = Buffer.concat([
|
|
485
|
+
decipher.update(Buffer.from(keyring.ciphertext, 'base64')),
|
|
486
|
+
decipher.final(),
|
|
487
|
+
]);
|
|
488
|
+
try {
|
|
489
|
+
return JSON.parse(plaintext.toString('utf8'));
|
|
490
|
+
} finally {
|
|
491
|
+
plaintext.fill(0);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const legacyDecipher = createDecipheriv('aes-256-gcm', derived.kek, Buffer.from(keyring.iv, 'base64'));
|
|
496
|
+
legacyDecipher.setAuthTag(Buffer.from(keyring.tag, 'base64'));
|
|
497
|
+
const plaintext = Buffer.concat([
|
|
498
|
+
legacyDecipher.update(Buffer.from(keyring.ciphertext, 'base64')),
|
|
499
|
+
legacyDecipher.final(),
|
|
500
|
+
]);
|
|
501
|
+
try {
|
|
502
|
+
return JSON.parse(plaintext.toString('utf8'));
|
|
503
|
+
} finally {
|
|
504
|
+
plaintext.fill(0);
|
|
505
|
+
}
|
|
506
|
+
} finally {
|
|
507
|
+
zeroDerivedKeys(derived);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function signingPair(seed) {
|
|
512
|
+
if (seed !== undefined && seed !== null) {
|
|
513
|
+
try {
|
|
514
|
+
const privateKey = createPrivateKey({ key: buildEd25519Pkcs8(seed), format: 'der', type: 'pkcs8' });
|
|
515
|
+
const publicKey = createPublicKey(privateKey);
|
|
516
|
+
const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
|
517
|
+
return {
|
|
518
|
+
key_id: `ed25519:${sha256Hex(publicKeyPem).slice(0, 32)}`,
|
|
519
|
+
publicKey,
|
|
520
|
+
privateKey,
|
|
521
|
+
};
|
|
522
|
+
} catch {
|
|
523
|
+
// Fall through to random signing pair if deterministic construction is unsupported.
|
|
524
|
+
}
|
|
525
|
+
}
|
|
317
526
|
try {
|
|
318
527
|
const pair = generateSigningKeyPair();
|
|
319
528
|
if (pair && typeof pair.then !== 'function') return pair;
|
|
@@ -367,7 +576,7 @@ function nowIso(now) {
|
|
|
367
576
|
return new Date().toISOString();
|
|
368
577
|
}
|
|
369
578
|
|
|
370
|
-
function metadata(input) {
|
|
579
|
+
function metadata(input, commitmentKey) {
|
|
371
580
|
const out = {};
|
|
372
581
|
if (!input || typeof input !== 'object') return out;
|
|
373
582
|
for (const [key, value] of Object.entries(input)) {
|
|
@@ -376,12 +585,12 @@ function metadata(input) {
|
|
|
376
585
|
if (isSafeMetadataPrimitive(key, value)) {
|
|
377
586
|
out[key] = value;
|
|
378
587
|
} else {
|
|
379
|
-
commitMetadataValue(out, key, value);
|
|
588
|
+
commitMetadataValue(out, key, value, commitmentKey);
|
|
380
589
|
}
|
|
381
590
|
continue;
|
|
382
591
|
}
|
|
383
592
|
if (Array.isArray(value) || typeof value === 'object') {
|
|
384
|
-
commitMetadataValue(out, key, value);
|
|
593
|
+
commitMetadataValue(out, key, value, commitmentKey);
|
|
385
594
|
}
|
|
386
595
|
}
|
|
387
596
|
return out;
|
|
@@ -389,7 +598,7 @@ function metadata(input) {
|
|
|
389
598
|
|
|
390
599
|
function pruneUndefined(value) {
|
|
391
600
|
if (Array.isArray(value)) return value.map((item) => pruneUndefined(item));
|
|
392
|
-
if (value && typeof value === 'object') {
|
|
601
|
+
if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype) {
|
|
393
602
|
const out = {};
|
|
394
603
|
for (const [key, item] of Object.entries(value)) {
|
|
395
604
|
if (item !== undefined) out[key] = pruneUndefined(item);
|
|
@@ -469,7 +678,7 @@ function appendEvent(vault, fields) {
|
|
|
469
678
|
sequence,
|
|
470
679
|
policy_id: fields.policy_id ?? vault.policy_id,
|
|
471
680
|
decision: fields.decision,
|
|
472
|
-
metadata: metadata(fields.metadata),
|
|
681
|
+
metadata: metadata(fields.metadata, vault.addressKey),
|
|
473
682
|
});
|
|
474
683
|
const eventCanonical = canonical(event);
|
|
475
684
|
const eventHash = asSha256(eventCanonical);
|
|
@@ -481,45 +690,75 @@ function appendEvent(vault, fields) {
|
|
|
481
690
|
vault.receipts.push(receipt);
|
|
482
691
|
vault.sequence += 1;
|
|
483
692
|
vault.updated_at = event.timestamp;
|
|
484
|
-
vault
|
|
485
|
-
vault.receipt_log_root = computeReceiptLogRootFrom(vault.receipts);
|
|
693
|
+
Object.assign(vault, lifecycleRoots(vault));
|
|
486
694
|
return { event, receipt };
|
|
487
695
|
}
|
|
488
696
|
|
|
489
|
-
function
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
});
|
|
506
|
-
if (typeof addr === 'string' && addr.length > 0) return addr;
|
|
507
|
-
} catch {
|
|
508
|
-
// Local HMAC construction below follows the protocol domain separation.
|
|
509
|
-
}
|
|
697
|
+
function contentCommitment(vault, plaintext) {
|
|
698
|
+
return `hmac-sha256:${asHmacSha256(vault.addressKey, canonical({
|
|
699
|
+
domain: `${MEMORY_DOMAIN}/content_commitment`,
|
|
700
|
+
vault_id: vault.vault_id,
|
|
701
|
+
content: String(plaintext),
|
|
702
|
+
}))}`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function memoryAddress(vault, subjectId, memoryId, commitment) {
|
|
706
|
+
const payload = canonical({
|
|
707
|
+
domain: MEMORY_DOMAIN,
|
|
708
|
+
vault_id: vault.vault_id,
|
|
709
|
+
subject_id: subjectId,
|
|
710
|
+
memory_id: memoryId,
|
|
711
|
+
content_commitment: commitment,
|
|
712
|
+
});
|
|
510
713
|
return `mem:${asHmacSha256(vault.addressKey, payload)}`;
|
|
511
714
|
}
|
|
512
715
|
|
|
513
|
-
function
|
|
716
|
+
function canonicalContentAad(vault, memoryAddr, epoch, keyId) {
|
|
717
|
+
return canonical({
|
|
718
|
+
domain: CONTENT_AAD_DOMAIN,
|
|
719
|
+
schema: MEMORY_OBJECT_SCHEMA,
|
|
720
|
+
vault_id: vault.vault_id,
|
|
721
|
+
memory_addr: memoryAddr,
|
|
722
|
+
key_epoch: epoch,
|
|
723
|
+
key_id: keyId,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
function normalizedPrivateTags(value) {
|
|
728
|
+
if (!Array.isArray(value)) return [];
|
|
729
|
+
return value.map((tag) => String(tag)).filter((tag) => tag.length > 0);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
function setPrivateTags(record, tags) {
|
|
733
|
+
Object.defineProperty(record, 'purpose_tags', {
|
|
734
|
+
value: normalizedPrivateTags(tags),
|
|
735
|
+
enumerable: false,
|
|
736
|
+
configurable: true,
|
|
737
|
+
writable: true,
|
|
738
|
+
});
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function privateTags(record) {
|
|
742
|
+
const descriptor = Object.getOwnPropertyDescriptor(record, 'purpose_tags');
|
|
743
|
+
return descriptor && descriptor.enumerable === false ? normalizedPrivateTags(descriptor.value) : [];
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function encryptContent(vault, memoryAddr, plaintext, epochEntry = activeContentEpoch(vault), privateFields = {}) {
|
|
514
747
|
const iv = randomBytes(12);
|
|
515
|
-
const
|
|
516
|
-
cipher
|
|
517
|
-
|
|
748
|
+
const aad = canonicalContentAad(vault, memoryAddr, epochEntry.epoch, epochEntry.key_id);
|
|
749
|
+
const cipher = createCipheriv('aes-256-gcm', epochEntry.key, iv);
|
|
750
|
+
cipher.setAAD(Buffer.from(aad, 'utf8'));
|
|
751
|
+
const privatePayload = JSON.stringify({
|
|
752
|
+
schema: PRIVATE_MEMORY_SCHEMA,
|
|
753
|
+
content: String(plaintext),
|
|
754
|
+
purpose_tags: normalizedPrivateTags(privateFields.purpose_tags),
|
|
755
|
+
});
|
|
756
|
+
const ciphertext = Buffer.concat([cipher.update(privatePayload, 'utf8'), cipher.final()]);
|
|
518
757
|
const tag = cipher.getAuthTag();
|
|
519
758
|
return {
|
|
520
759
|
alg: 'AES-256-GCM',
|
|
521
760
|
iv: iv.toString('base64'),
|
|
522
|
-
aad
|
|
761
|
+
aad,
|
|
523
762
|
ciphertext: ciphertext.toString('base64'),
|
|
524
763
|
tag: tag.toString('base64'),
|
|
525
764
|
};
|
|
@@ -546,16 +785,31 @@ function getPlaintextCache(record) {
|
|
|
546
785
|
}
|
|
547
786
|
|
|
548
787
|
function decryptContent(vault, record) {
|
|
788
|
+
if (!record) throw new Error('memory record is unavailable');
|
|
789
|
+
const epochEntry = contentKeyForRecord(vault, record);
|
|
549
790
|
const cached = getPlaintextCache(record);
|
|
550
791
|
if (cached !== undefined) return cached;
|
|
551
792
|
const encrypted = record.content_ciphertext;
|
|
552
|
-
|
|
553
|
-
|
|
793
|
+
if (!encrypted || encrypted.alg !== 'AES-256-GCM') throw new Error('memory ciphertext envelope is invalid');
|
|
794
|
+
const expectedAad = canonicalContentAad(vault, record.memory_addr, epochEntry.epoch, epochEntry.key_id);
|
|
795
|
+
if (encrypted.aad !== expectedAad) throw new Error('memory ciphertext AAD mismatch');
|
|
796
|
+
const decipher = createDecipheriv('aes-256-gcm', epochEntry.key, Buffer.from(encrypted.iv, 'base64'));
|
|
797
|
+
decipher.setAAD(Buffer.from(expectedAad, 'utf8'));
|
|
554
798
|
decipher.setAuthTag(Buffer.from(encrypted.tag, 'base64'));
|
|
555
|
-
const
|
|
799
|
+
const decrypted = Buffer.concat([
|
|
556
800
|
decipher.update(Buffer.from(encrypted.ciphertext, 'base64')),
|
|
557
801
|
decipher.final(),
|
|
558
802
|
]).toString('utf8');
|
|
803
|
+
let plaintext = decrypted;
|
|
804
|
+
try {
|
|
805
|
+
const payload = JSON.parse(decrypted);
|
|
806
|
+
if (payload?.schema === PRIVATE_MEMORY_SCHEMA && typeof payload.content === 'string') {
|
|
807
|
+
plaintext = payload.content;
|
|
808
|
+
setPrivateTags(record, payload.purpose_tags);
|
|
809
|
+
}
|
|
810
|
+
} catch {
|
|
811
|
+
// Pre-private-envelope ciphertext stores the content string directly.
|
|
812
|
+
}
|
|
559
813
|
return setPlaintextCache(record, plaintext);
|
|
560
814
|
}
|
|
561
815
|
|
|
@@ -563,25 +817,315 @@ function publicRecord(record) {
|
|
|
563
817
|
return pruneUndefined(sanitizePublicValue(record));
|
|
564
818
|
}
|
|
565
819
|
|
|
820
|
+
function requireVault(input, functionName) {
|
|
821
|
+
const vault = input?.vault ?? input;
|
|
822
|
+
if (!vault) throw new Error(`${functionName} requires a vault`);
|
|
823
|
+
return vault;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function signerRef(signer) {
|
|
827
|
+
if (!signer || typeof signer !== 'object') return undefined;
|
|
828
|
+
return pruneUndefined({
|
|
829
|
+
key_id: signer.key_id ?? signer.keyId,
|
|
830
|
+
alg: signer.alg,
|
|
831
|
+
publicKey: signer.publicKey,
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
function signatureRef(signature) {
|
|
836
|
+
if (!signature || typeof signature !== 'object') return undefined;
|
|
837
|
+
return pruneUndefined({
|
|
838
|
+
alg: signature.alg,
|
|
839
|
+
value: signature.value,
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function sortedMapValues(mapLike) {
|
|
844
|
+
if (!mapLike) return [];
|
|
845
|
+
if (typeof mapLike.values === 'function') return [...mapLike.values()];
|
|
846
|
+
if (Array.isArray(mapLike)) return mapLike;
|
|
847
|
+
return [];
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function quarantineAddresses(vault) {
|
|
851
|
+
const out = new Set(vault.quarantineAddresses ?? []);
|
|
852
|
+
for (const [memoryAddr, record] of vault.memories ?? []) {
|
|
853
|
+
if (record?.state === 'quarantine' || record?.state === 'quarantined') out.add(memoryAddr);
|
|
854
|
+
}
|
|
855
|
+
return sortedStrings(out);
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
function publicTombstone(tombstone) {
|
|
859
|
+
if (!tombstone || typeof tombstone !== 'object') return undefined;
|
|
860
|
+
return pruneUndefined({
|
|
861
|
+
schema: tombstone.schema ?? 'enigma.deletion_tombstone.v1',
|
|
862
|
+
memory_addr: tombstone.memory_addr,
|
|
863
|
+
operation: tombstone.operation,
|
|
864
|
+
successor_addr: tombstone.successor_addr,
|
|
865
|
+
tombstoned_at: tombstone.tombstoned_at,
|
|
866
|
+
reason_hash: tombstone.reason ? asSha256(String(tombstone.reason)) : undefined,
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function tombstoneRootItems(vault) {
|
|
871
|
+
return sortedMapValues(vault.tombstones)
|
|
872
|
+
.map(publicTombstone)
|
|
873
|
+
.filter(Boolean)
|
|
874
|
+
.sort((a, b) => String(a.memory_addr).localeCompare(String(b.memory_addr)))
|
|
875
|
+
.map((tombstone) => asSha256(canonical(tombstone)));
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function lifecycleRoots(vault) {
|
|
879
|
+
const active = sortedStrings(vault.activeAddresses ?? []);
|
|
880
|
+
const quarantine = quarantineAddresses(vault);
|
|
881
|
+
const tombstones = tombstoneRootItems(vault);
|
|
882
|
+
const activeSetRoot = computeSetRoot(active);
|
|
883
|
+
const quarantineSetRoot = computeSetRoot(quarantine);
|
|
884
|
+
const tombstoneSetRoot = computeSetRoot(tombstones);
|
|
885
|
+
const receiptLogRoot = computeReceiptLogRootFrom(vault.receipts ?? []);
|
|
886
|
+
return {
|
|
887
|
+
active_set_root: activeSetRoot,
|
|
888
|
+
quarantine_set_root: quarantineSetRoot,
|
|
889
|
+
tombstone_set_root: tombstoneSetRoot,
|
|
890
|
+
receipt_log_root: receiptLogRoot,
|
|
891
|
+
lifecycle_root: computeSetRoot([activeSetRoot, quarantineSetRoot, tombstoneSetRoot, receiptLogRoot]),
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function applyLifecycleRoots(vault) {
|
|
896
|
+
return Object.assign(vault, lifecycleRoots(vault));
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function projectLifecycleReceipt(receipt) {
|
|
900
|
+
if (!receipt || typeof receipt !== 'object') return undefined;
|
|
901
|
+
return pruneUndefined({
|
|
902
|
+
schema: 'enigma.lifecycle_receipt_projection.v1',
|
|
903
|
+
receipt_id: receipt.receipt_id,
|
|
904
|
+
receipt_hash: hashReceipt(receipt),
|
|
905
|
+
event_hash: receipt.event_hash,
|
|
906
|
+
operation: receipt.operation,
|
|
907
|
+
tenant_id: receipt.tenant_id,
|
|
908
|
+
subject_id: receipt.subject_id,
|
|
909
|
+
memory_addr: receipt.memory_addr,
|
|
910
|
+
source_addr: receipt.source_addr,
|
|
911
|
+
policy_id: receipt.policy_id,
|
|
912
|
+
sequence: receipt.sequence,
|
|
913
|
+
previous_receipt_hash: receipt.previous_receipt_hash,
|
|
914
|
+
active_set_root: receipt.active_set_root,
|
|
915
|
+
receipt_log_root: receipt.receipt_log_root,
|
|
916
|
+
timestamp: receipt.timestamp,
|
|
917
|
+
signer: signerRef(receipt.signer),
|
|
918
|
+
signature: signatureRef(receipt.signature),
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function lifecycleStateForReceipt(receipt) {
|
|
923
|
+
if (receipt?.operation === 'delete') return 'tombstone';
|
|
924
|
+
if (receipt?.operation === 'quarantine') return 'quarantine';
|
|
925
|
+
return 'active';
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function projectLifecycleReceiptRef(receipt) {
|
|
929
|
+
if (!receipt || typeof receipt !== 'object') return undefined;
|
|
930
|
+
const receiptHash = hashReceipt(receipt);
|
|
931
|
+
return pruneUndefined({
|
|
932
|
+
sequence: receipt.sequence,
|
|
933
|
+
receipt_ref: receipt.receipt_id ?? receiptHash,
|
|
934
|
+
receipt_hash: receiptHash,
|
|
935
|
+
previous_receipt_hash: receipt.previous_receipt_hash,
|
|
936
|
+
lifecycle_state: lifecycleStateForReceipt(receipt),
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function lifecycleLogId(vault, roots, issuedAt) {
|
|
941
|
+
return `log_${sha256Hex(canonical({
|
|
942
|
+
schema: 'enigma.lifecycle_receipt_log.id.v1',
|
|
943
|
+
vault_id: vault.vault_id,
|
|
944
|
+
receipt_log_root: roots.receipt_log_root,
|
|
945
|
+
lifecycle_root: roots.lifecycle_root,
|
|
946
|
+
issued_at: issuedAt,
|
|
947
|
+
})).slice(0, 32)}`;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function refHash(value) {
|
|
951
|
+
return asSha256(canonical(value));
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
|
|
955
|
+
export function buildLifecycleRootSummary(args = {}) {
|
|
956
|
+
const vault = requireVault(args, 'buildLifecycleRootSummary');
|
|
957
|
+
const roots = lifecycleRoots(vault);
|
|
958
|
+
const active = sortedStrings(vault.activeAddresses ?? []);
|
|
959
|
+
const quarantine = quarantineAddresses(vault);
|
|
960
|
+
const tombstones = tombstoneRootItems(vault);
|
|
961
|
+
return pruneUndefined({
|
|
962
|
+
schema: 'enigma.lifecycle_root_summary.v1',
|
|
963
|
+
vault_id: vault.vault_id,
|
|
964
|
+
tenant_id: vault.tenant_id,
|
|
965
|
+
subject_id: vault.subject_id,
|
|
966
|
+
policy_id: vault.policy_id,
|
|
967
|
+
sequence: vault.sequence,
|
|
968
|
+
active_count: active.length,
|
|
969
|
+
quarantine_count: quarantine.length,
|
|
970
|
+
tombstone_count: tombstones.length,
|
|
971
|
+
receipt_count: Array.isArray(vault.receipts) ? vault.receipts.length : 0,
|
|
972
|
+
active_set_root: roots.active_set_root,
|
|
973
|
+
quarantine_set_root: roots.quarantine_set_root,
|
|
974
|
+
tombstone_set_root: roots.tombstone_set_root,
|
|
975
|
+
active_root: roots.active_set_root,
|
|
976
|
+
quarantine_root: roots.quarantine_set_root,
|
|
977
|
+
tombstone_root: roots.tombstone_set_root,
|
|
978
|
+
receipt_log_root: roots.receipt_log_root,
|
|
979
|
+
lifecycle_root: roots.lifecycle_root,
|
|
980
|
+
updated_at: vault.updated_at,
|
|
981
|
+
signer: signerRef(vault.signer),
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
export function buildMemoryBoundaryTransactionSummary(args = {}) {
|
|
986
|
+
const vault = requireVault(args, 'buildMemoryBoundaryTransactionSummary');
|
|
987
|
+
const receipt = args.receipt ?? args.transaction ?? vault.receipts?.[vault.receipts.length - 1];
|
|
988
|
+
const projection = projectLifecycleReceipt(receipt);
|
|
989
|
+
if (!projection) throw new Error('buildMemoryBoundaryTransactionSummary requires at least one receipt');
|
|
990
|
+
return pruneUndefined({
|
|
991
|
+
schema: 'enigma.memory_boundary_transaction_summary.v1',
|
|
992
|
+
vault_id: vault.vault_id,
|
|
993
|
+
tenant_id: vault.tenant_id,
|
|
994
|
+
subject_id: vault.subject_id,
|
|
995
|
+
operation: projection.operation,
|
|
996
|
+
sequence: projection.sequence,
|
|
997
|
+
timestamp: projection.timestamp,
|
|
998
|
+
memory_addr: projection.memory_addr,
|
|
999
|
+
source_addr: projection.source_addr,
|
|
1000
|
+
policy_id: projection.policy_id,
|
|
1001
|
+
event_hash: projection.event_hash,
|
|
1002
|
+
receipt_hash: projection.receipt_hash,
|
|
1003
|
+
previous_receipt_hash: projection.previous_receipt_hash,
|
|
1004
|
+
active_set_root: projection.active_set_root,
|
|
1005
|
+
receipt_log_root: projection.receipt_log_root,
|
|
1006
|
+
lifecycle_root: buildLifecycleRootSummary({ vault }).lifecycle_root,
|
|
1007
|
+
signer: projection.signer,
|
|
1008
|
+
signature: projection.signature,
|
|
1009
|
+
public_safe: true,
|
|
1010
|
+
});
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export function buildPublicLifecycleReceiptLog(args = {}) {
|
|
1014
|
+
const vault = requireVault(args, 'buildPublicLifecycleReceiptLog');
|
|
1015
|
+
const receiptRefs = (vault.receipts ?? []).map(projectLifecycleReceiptRef).filter(Boolean);
|
|
1016
|
+
if (receiptRefs.length === 0) throw new Error('buildPublicLifecycleReceiptLog requires at least one receipt');
|
|
1017
|
+
const roots = buildLifecycleRootSummary({ vault });
|
|
1018
|
+
const issuedAt = args.now ? nowIso(args.now) : vault.updated_at;
|
|
1019
|
+
const latestReceipt = vault.receipts?.[vault.receipts.length - 1];
|
|
1020
|
+
const logId = lifecycleLogId(vault, roots, issuedAt);
|
|
1021
|
+
return pruneUndefined({
|
|
1022
|
+
schema: 'enigma.lifecycle_receipt_log.v1',
|
|
1023
|
+
log_id: logId,
|
|
1024
|
+
log_ref: refHash({ schema: 'enigma.lifecycle_receipt_log.ref.v1', log_id: logId, receipt_log_root: roots.receipt_log_root }),
|
|
1025
|
+
subject_ref: refHash({ schema: 'enigma.subject_ref.v1', subject_id: vault.subject_id }),
|
|
1026
|
+
tenant_ref: refHash({ schema: 'enigma.tenant_ref.v1', tenant_id: vault.tenant_id }),
|
|
1027
|
+
epoch: vault.sequence,
|
|
1028
|
+
previous_log_root: latestReceipt?.receipt_log_root ?? ZERO_ROOT,
|
|
1029
|
+
receipt_refs: receiptRefs,
|
|
1030
|
+
active_root: roots.active_set_root,
|
|
1031
|
+
quarantine_root: roots.quarantine_set_root,
|
|
1032
|
+
tombstone_root: roots.tombstone_set_root,
|
|
1033
|
+
receipt_log_root: roots.receipt_log_root,
|
|
1034
|
+
issued_at: issuedAt,
|
|
1035
|
+
signer_ref: refHash(signerRef(vault.signer) ?? { key_id: vault.signer?.key_id ?? null }),
|
|
1036
|
+
signature_ref: latestReceipt?.signature ? refHash(signatureRef(latestReceipt.signature)) : undefined,
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
|
|
566
1041
|
function attachInternals(vault) {
|
|
567
1042
|
Object.defineProperties(vault, {
|
|
568
1043
|
__recordEvent: { value: (fields) => appendEvent(vault, fields), enumerable: false },
|
|
569
1044
|
__getRecord: { value: (memoryAddr) => vault.memories.get(memoryAddr), enumerable: false },
|
|
570
1045
|
__getPlaintext: { value: (memoryAddr) => decryptContent(vault, vault.memories.get(memoryAddr)), enumerable: false },
|
|
571
1046
|
__computeRoots: {
|
|
572
|
-
value: () => (
|
|
573
|
-
active_set_root: computeSetRoot(vault.activeAddresses),
|
|
574
|
-
receipt_log_root: computeReceiptLogRootFrom(vault.receipts),
|
|
575
|
-
}),
|
|
1047
|
+
value: () => lifecycleRoots(vault),
|
|
576
1048
|
enumerable: false,
|
|
577
1049
|
},
|
|
578
1050
|
});
|
|
579
1051
|
return vault;
|
|
580
1052
|
}
|
|
581
1053
|
|
|
1054
|
+
function normalizeKeyEpoch(entry, fallbackKey, fallbackKeyId, createdAt) {
|
|
1055
|
+
const epoch = Number(entry?.epoch ?? 1);
|
|
1056
|
+
if (!Number.isInteger(epoch) || epoch < 1) throw new Error('content key epoch must be a positive integer');
|
|
1057
|
+
const key = keyBytes(entry?.key ?? entry?.key_b64 ?? fallbackKey, `content key for epoch ${epoch}`);
|
|
1058
|
+
const keyId = entry?.key_id ?? entry?.keyId ?? fallbackKeyId;
|
|
1059
|
+
if (typeof keyId !== 'string' || keyId.length === 0) throw new Error(`content key epoch ${epoch} requires key_id`);
|
|
1060
|
+
const status = entry?.status ?? 'active';
|
|
1061
|
+
if (!['active', 'retained', 'sealed'].includes(status)) throw new Error(`unsupported content key epoch status: ${status}`);
|
|
1062
|
+
return {
|
|
1063
|
+
epoch,
|
|
1064
|
+
key_id: keyId,
|
|
1065
|
+
key,
|
|
1066
|
+
status,
|
|
1067
|
+
created_at: entry?.created_at ?? createdAt,
|
|
1068
|
+
sealed_at: entry?.sealed_at,
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function activeContentEpoch(vault) {
|
|
1073
|
+
const entry = vault.keyEpochs.get(vault.activeKeyEpoch);
|
|
1074
|
+
if (!entry) throw new Error(`active content key epoch ${vault.activeKeyEpoch} is unavailable`);
|
|
1075
|
+
if (entry.status === 'sealed') throw new Error(`content key epoch ${entry.epoch} is sealed and rejects writes`);
|
|
1076
|
+
if (entry.status !== 'active') throw new Error(`content key epoch ${entry.epoch} is not active`);
|
|
1077
|
+
return entry;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
function contentKeyForRecord(vault, record) {
|
|
1081
|
+
const epoch = Number(record.key_epoch ?? 1);
|
|
1082
|
+
const entry = vault.keyEpochs.get(epoch);
|
|
1083
|
+
if (!entry || !entry.key) throw new Error(`content key epoch ${epoch} has been destroyed or is unavailable`);
|
|
1084
|
+
if (entry.key_id !== record.key_id) throw new Error(`content key id mismatch for epoch ${epoch}`);
|
|
1085
|
+
if (epoch !== vault.activeKeyEpoch && vault.allowOldEpochReads !== true) {
|
|
1086
|
+
throw new Error(`read policy rejects old content key epoch ${epoch}`);
|
|
1087
|
+
}
|
|
1088
|
+
return entry;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
582
1091
|
export function createVault(args = {}) {
|
|
583
1092
|
const createdAt = nowIso(args.now);
|
|
584
|
-
const
|
|
1093
|
+
const passphrase = args.passphrase;
|
|
1094
|
+
const salt = args.salt ?? randomBytes(SALT_LENGTH).toString('base64');
|
|
1095
|
+
let vaultKey;
|
|
1096
|
+
let addressKey;
|
|
1097
|
+
let signingSeed;
|
|
1098
|
+
let keyringType = KEYRING_PLAINTEXT_TYPE;
|
|
1099
|
+
if (passphrase !== undefined && passphrase !== null) {
|
|
1100
|
+
const rawPass = String(passphrase);
|
|
1101
|
+
if (rawPass.length === 0 || rawPass.trim().length === 0) {
|
|
1102
|
+
throw new Error('Passphrase provided to createVault cannot be empty.');
|
|
1103
|
+
}
|
|
1104
|
+
const kdf = args.kdf || DEFAULT_KDF_SPEC;
|
|
1105
|
+
const derived = deriveKeysFromPassphrase(rawPass, salt, kdf);
|
|
1106
|
+
vaultKey = derived.vaultKey;
|
|
1107
|
+
addressKey = derived.addressKey;
|
|
1108
|
+
signingSeed = derived.signingSeed;
|
|
1109
|
+
keyringType = KEYRING_ENCRYPTED_TYPE;
|
|
1110
|
+
} else {
|
|
1111
|
+
vaultKey = keyBytes(args.vault_key ?? args.vaultKey, 'vault key');
|
|
1112
|
+
addressKey = keyBytes(args.address_key ?? args.addressKey, 'address key');
|
|
1113
|
+
}
|
|
1114
|
+
const signingKeyPair = args.signingKeyPair ?? signingPair(signingSeed);
|
|
1115
|
+
const activeKeyEpoch = Number(args.active_key_epoch ?? args.activeKeyEpoch ?? 1);
|
|
1116
|
+
const initialKeyId = args.key_id ?? args.keyId ?? `vault-key-${randomUUID()}`;
|
|
1117
|
+
const suppliedEpochs = args.key_epochs ?? args.keyEpochs;
|
|
1118
|
+
const epochEntries = Array.isArray(suppliedEpochs) && suppliedEpochs.length > 0
|
|
1119
|
+
? suppliedEpochs.map((entry) => normalizeKeyEpoch(entry, undefined, undefined, createdAt))
|
|
1120
|
+
: [normalizeKeyEpoch({ epoch: activeKeyEpoch, key_id: initialKeyId, key: vaultKey, status: 'active' }, undefined, undefined, createdAt)];
|
|
1121
|
+
const keyEpochs = new Map(epochEntries.map((entry) => [entry.epoch, entry]));
|
|
1122
|
+
if (keyEpochs.size !== epochEntries.length) throw new Error('duplicate content key epoch');
|
|
1123
|
+
const activeEpochEntry = keyEpochs.get(activeKeyEpoch);
|
|
1124
|
+
if (!activeEpochEntry) throw new Error(`active content key epoch ${activeKeyEpoch} is missing`);
|
|
1125
|
+
if (!['active', 'sealed'].includes(activeEpochEntry.status)) {
|
|
1126
|
+
throw new Error(`active content key epoch ${activeKeyEpoch} cannot have ${activeEpochEntry.status} status`);
|
|
1127
|
+
}
|
|
1128
|
+
vaultKey = activeEpochEntry.key;
|
|
585
1129
|
const vault = {
|
|
586
1130
|
schema: VAULT_SCHEMA,
|
|
587
1131
|
vault_id: args.vault_id ?? args.vaultId ?? `vault_${randomUUID()}`,
|
|
@@ -592,9 +1136,12 @@ export function createVault(args = {}) {
|
|
|
592
1136
|
created_at: createdAt,
|
|
593
1137
|
updated_at: createdAt,
|
|
594
1138
|
encryption: 'local_aead',
|
|
595
|
-
key_id:
|
|
596
|
-
|
|
597
|
-
|
|
1139
|
+
key_id: activeEpochEntry.key_id,
|
|
1140
|
+
activeKeyEpoch,
|
|
1141
|
+
keyEpochs,
|
|
1142
|
+
allowOldEpochReads: args.allow_old_epoch_reads ?? args.allowOldEpochReads ?? true,
|
|
1143
|
+
vaultKey,
|
|
1144
|
+
addressKey,
|
|
598
1145
|
signingKeyPair,
|
|
599
1146
|
signer: args.signer ?? signerFor(signingKeyPair),
|
|
600
1147
|
memories: new Map(),
|
|
@@ -602,13 +1149,134 @@ export function createVault(args = {}) {
|
|
|
602
1149
|
receipts: [],
|
|
603
1150
|
activeAddresses: new Set(),
|
|
604
1151
|
tombstones: new Map(),
|
|
1152
|
+
quarantineAddresses: new Set(),
|
|
605
1153
|
sequence: 0,
|
|
606
1154
|
active_set_root: ZERO_ROOT,
|
|
1155
|
+
quarantine_set_root: ZERO_ROOT,
|
|
1156
|
+
tombstone_set_root: ZERO_ROOT,
|
|
607
1157
|
receipt_log_root: ZERO_ROOT,
|
|
1158
|
+
lifecycle_root: ZERO_ROOT,
|
|
1159
|
+
keyringType,
|
|
608
1160
|
};
|
|
1161
|
+
applyLifecycleRoots(vault);
|
|
609
1162
|
return attachInternals(vault);
|
|
610
1163
|
}
|
|
611
1164
|
|
|
1165
|
+
export function rotateContentKey(args = {}) {
|
|
1166
|
+
const vault = requireVault(args, 'rotateContentKey');
|
|
1167
|
+
const previous = vault.keyEpochs.get(vault.activeKeyEpoch);
|
|
1168
|
+
if (previous.status === 'active') previous.status = 'retained';
|
|
1169
|
+
const epoch = Math.max(...vault.keyEpochs.keys()) + 1;
|
|
1170
|
+
const createdAt = nowIso(args.now);
|
|
1171
|
+
const next = normalizeKeyEpoch({
|
|
1172
|
+
epoch,
|
|
1173
|
+
key_id: args.key_id ?? args.keyId ?? `vault-key-${randomUUID()}`,
|
|
1174
|
+
key: args.key,
|
|
1175
|
+
status: 'active',
|
|
1176
|
+
created_at: createdAt,
|
|
1177
|
+
}, undefined, undefined, createdAt);
|
|
1178
|
+
vault.keyEpochs.set(epoch, next);
|
|
1179
|
+
vault.activeKeyEpoch = epoch;
|
|
1180
|
+
vault.key_id = next.key_id;
|
|
1181
|
+
vault.vaultKey = next.key;
|
|
1182
|
+
const { event, receipt } = appendEvent(vault, {
|
|
1183
|
+
operation: 'key_rotate',
|
|
1184
|
+
actor_id: args.actor_id ?? args.actorId,
|
|
1185
|
+
now: args.now,
|
|
1186
|
+
metadata: { prior_epoch: previous.epoch, new_epoch: epoch, prior_key_id: previous.key_id, new_key_id: next.key_id },
|
|
1187
|
+
});
|
|
1188
|
+
return {
|
|
1189
|
+
epoch,
|
|
1190
|
+
key_id: next.key_id,
|
|
1191
|
+
prior_epoch: previous.epoch,
|
|
1192
|
+
prior_key_status: previous.status,
|
|
1193
|
+
old_key_retained: true,
|
|
1194
|
+
external_copies_destroyed: false,
|
|
1195
|
+
event,
|
|
1196
|
+
receipt,
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
export function sealContentKeyEpoch(args = {}) {
|
|
1201
|
+
const vault = requireVault(args, 'sealContentKeyEpoch');
|
|
1202
|
+
const epoch = Number(args.epoch ?? vault.activeKeyEpoch);
|
|
1203
|
+
const entry = vault.keyEpochs.get(epoch);
|
|
1204
|
+
if (!entry) throw new Error(`unknown content key epoch ${epoch}`);
|
|
1205
|
+
if (entry.status === 'sealed') throw new Error(`content key epoch ${epoch} is already sealed`);
|
|
1206
|
+
entry.status = 'sealed';
|
|
1207
|
+
entry.sealed_at = nowIso(args.now);
|
|
1208
|
+
const { event, receipt } = appendEvent(vault, {
|
|
1209
|
+
operation: 'key_seal',
|
|
1210
|
+
actor_id: args.actor_id ?? args.actorId,
|
|
1211
|
+
now: args.now,
|
|
1212
|
+
metadata: { epoch, key_id: entry.key_id, key_retained: true },
|
|
1213
|
+
});
|
|
1214
|
+
return {
|
|
1215
|
+
epoch,
|
|
1216
|
+
key_id: entry.key_id,
|
|
1217
|
+
status: entry.status,
|
|
1218
|
+
key_retained_for_reads: true,
|
|
1219
|
+
cryptographic_erasure_performed: false,
|
|
1220
|
+
external_copies_destroyed: false,
|
|
1221
|
+
event,
|
|
1222
|
+
receipt,
|
|
1223
|
+
};
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
export function resetVaultContentKeys(args = {}) {
|
|
1227
|
+
const vault = requireVault(args, 'resetVaultContentKeys');
|
|
1228
|
+
const destroyedEpochs = [...vault.keyEpochs.keys()].sort((a, b) => a - b);
|
|
1229
|
+
const oldEpochEntries = [...vault.keyEpochs.values()];
|
|
1230
|
+
const removedRecords = vault.memories.size;
|
|
1231
|
+
const nextEpoch = Math.max(0, ...destroyedEpochs) + 1;
|
|
1232
|
+
const createdAt = nowIso(args.now);
|
|
1233
|
+
const next = normalizeKeyEpoch({
|
|
1234
|
+
epoch: nextEpoch,
|
|
1235
|
+
key_id: args.key_id ?? args.keyId ?? `vault-key-${randomUUID()}`,
|
|
1236
|
+
key: args.key,
|
|
1237
|
+
status: 'active',
|
|
1238
|
+
created_at: createdAt,
|
|
1239
|
+
}, undefined, undefined, createdAt);
|
|
1240
|
+
const oldAddressKey = vault.addressKey;
|
|
1241
|
+
vault.memories.clear();
|
|
1242
|
+
vault.activeAddresses.clear();
|
|
1243
|
+
vault.tombstones.clear();
|
|
1244
|
+
vault.quarantineAddresses.clear();
|
|
1245
|
+
vault.keyEpochs = new Map([[nextEpoch, next]]);
|
|
1246
|
+
vault.activeKeyEpoch = nextEpoch;
|
|
1247
|
+
vault.key_id = next.key_id;
|
|
1248
|
+
vault.vaultKey = next.key;
|
|
1249
|
+
vault.addressKey = keyBytes(args.address_key ?? args.addressKey, 'address key');
|
|
1250
|
+
applyLifecycleRoots(vault);
|
|
1251
|
+
const { event, receipt } = appendEvent(vault, {
|
|
1252
|
+
operation: 'key_reset',
|
|
1253
|
+
actor_id: args.actor_id ?? args.actorId,
|
|
1254
|
+
now: args.now,
|
|
1255
|
+
metadata: { destroyed_epoch_count: destroyedEpochs.length, removed_record_count: removedRecords, new_epoch: nextEpoch, new_key_id: next.key_id },
|
|
1256
|
+
});
|
|
1257
|
+
for (const entry of oldEpochEntries) entry.key?.fill?.(0);
|
|
1258
|
+
oldAddressKey?.fill?.(0);
|
|
1259
|
+
return {
|
|
1260
|
+
destroyed_epochs: destroyedEpochs,
|
|
1261
|
+
removed_record_count: removedRecords,
|
|
1262
|
+
epoch: nextEpoch,
|
|
1263
|
+
key_id: next.key_id,
|
|
1264
|
+
software_key_material_zeroized_in_process: true,
|
|
1265
|
+
process_memory_overwrite_guaranteed: false,
|
|
1266
|
+
backups_and_external_copies_destroyed: false,
|
|
1267
|
+
physical_media_erasure_guaranteed: false,
|
|
1268
|
+
event,
|
|
1269
|
+
receipt,
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function normalizeImportance(value) {
|
|
1274
|
+
if (value === undefined || value === null) return undefined;
|
|
1275
|
+
const num = Number(value);
|
|
1276
|
+
if (!Number.isFinite(num)) return undefined;
|
|
1277
|
+
return Math.max(0, Math.min(1, num));
|
|
1278
|
+
}
|
|
1279
|
+
|
|
612
1280
|
export function remember(args = {}) {
|
|
613
1281
|
const vault = args.vault;
|
|
614
1282
|
if (!vault) throw new Error('remember requires a vault');
|
|
@@ -618,29 +1286,38 @@ export function remember(args = {}) {
|
|
|
618
1286
|
const memoryId = args.memory_id ?? args.memoryId ?? `mem_${randomUUID()}`;
|
|
619
1287
|
const subjectId = args.subject_id ?? args.subjectId ?? vault.subject_id;
|
|
620
1288
|
const createdAt = nowIso(args.now);
|
|
621
|
-
const
|
|
622
|
-
const
|
|
1289
|
+
const epochEntry = activeContentEpoch(vault);
|
|
1290
|
+
const requestedEpoch = args.key_epoch ?? args.keyEpoch;
|
|
1291
|
+
if (requestedEpoch !== undefined && Number(requestedEpoch) !== epochEntry.epoch) {
|
|
1292
|
+
throw new Error(`writes require active content key epoch ${epochEntry.epoch}`);
|
|
1293
|
+
}
|
|
1294
|
+
const commitment = contentCommitment(vault, plaintext);
|
|
1295
|
+
const memoryAddr = memoryAddress(vault, subjectId, memoryId, commitment);
|
|
623
1296
|
if (vault.tombstones.has(memoryAddr)) throw new Error(`memory address is tombstoned: ${memoryAddr}`);
|
|
1297
|
+
const rawPurposeTags = normalizedPrivateTags(args.purpose_tags ?? args.purposeTags);
|
|
624
1298
|
|
|
625
1299
|
const record = pruneUndefined({
|
|
626
|
-
schema:
|
|
1300
|
+
schema: MEMORY_OBJECT_SCHEMA,
|
|
627
1301
|
memory_id: memoryId,
|
|
628
1302
|
memory_addr: memoryAddr,
|
|
629
1303
|
subject_id: subjectId,
|
|
630
|
-
kind: args.kind
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
1304
|
+
kind: ['fact', 'preference', 'goal', 'instruction', 'episode'].includes(args.kind) ? args.kind : 'fact',
|
|
1305
|
+
key_epoch: epochEntry.epoch,
|
|
1306
|
+
key_id: epochEntry.key_id,
|
|
1307
|
+
content_ciphertext: encryptContent(vault, memoryAddr, plaintext, epochEntry, { purpose_tags: rawPurposeTags }),
|
|
1308
|
+
content_commitment: commitment,
|
|
1309
|
+
source_refs: sanitizeSourceRefs(args.source_refs ?? args.sourceRefs, vault.addressKey),
|
|
1310
|
+
confidence: ['user_confirmed', 'inferred', 'verified'].includes(args.confidence) ? args.confidence : 'user_confirmed',
|
|
1311
|
+
sensitivity: ['normal', 'sensitive', 'restricted'].includes(args.sensitivity) ? args.sensitivity : 'normal',
|
|
1312
|
+
purpose_tag_commitments: rawPurposeTags.map((tag) => privateValueCommitment(vault.addressKey, tag)),
|
|
1313
|
+
retention: ['durable', 'session', 'ephemeral'].includes(args.retention) ? args.retention : 'durable',
|
|
1314
|
+
importance: normalizeImportance(args.importance),
|
|
639
1315
|
state: 'active',
|
|
640
|
-
metadata: metadata(args.metadata),
|
|
1316
|
+
metadata: metadata(args.metadata, vault.addressKey),
|
|
641
1317
|
created_at: createdAt,
|
|
642
1318
|
updated_at: createdAt,
|
|
643
1319
|
});
|
|
1320
|
+
setPrivateTags(record, rawPurposeTags);
|
|
644
1321
|
|
|
645
1322
|
vault.memories.set(memoryAddr, record);
|
|
646
1323
|
vault.activeAddresses.add(memoryAddr);
|
|
@@ -651,12 +1328,13 @@ export function remember(args = {}) {
|
|
|
651
1328
|
actor_id: args.actor_id ?? args.actorId,
|
|
652
1329
|
policy_id: args.policy_id ?? args.policyId,
|
|
653
1330
|
now: args.now,
|
|
654
|
-
metadata: {
|
|
1331
|
+
metadata: pruneUndefined({
|
|
655
1332
|
memory_id: memoryId,
|
|
656
1333
|
kind: record.kind,
|
|
657
1334
|
sensitivity: record.sensitivity,
|
|
1335
|
+
importance: record.importance,
|
|
658
1336
|
content_commitment: record.content_commitment,
|
|
659
|
-
},
|
|
1337
|
+
}),
|
|
660
1338
|
});
|
|
661
1339
|
|
|
662
1340
|
return { memory: publicRecord(record), memory_addr: memoryAddr, event, receipt };
|
|
@@ -699,22 +1377,34 @@ export function updateMemory(args = {}) {
|
|
|
699
1377
|
|
|
700
1378
|
const oldRecord = vault.memories.get(oldAddr);
|
|
701
1379
|
if (!oldRecord) throw new Error(`unknown memory: ${oldAddr}`);
|
|
1380
|
+
const epochEntry = activeContentEpoch(vault);
|
|
1381
|
+
const requestedEpoch = args.key_epoch ?? args.keyEpoch;
|
|
1382
|
+
if (requestedEpoch !== undefined && Number(requestedEpoch) !== epochEntry.epoch) {
|
|
1383
|
+
throw new Error(`writes require active content key epoch ${epochEntry.epoch}`);
|
|
1384
|
+
}
|
|
1385
|
+
decryptContent(vault, oldRecord);
|
|
1386
|
+
const rawPurposeTags = args.purpose_tags !== undefined || args.purposeTags !== undefined
|
|
1387
|
+
? normalizedPrivateTags(args.purpose_tags ?? args.purposeTags)
|
|
1388
|
+
: privateTags(oldRecord);
|
|
702
1389
|
oldRecord.state = 'superseded';
|
|
703
1390
|
oldRecord.updated_at = nowIso(args.now);
|
|
704
1391
|
vault.activeAddresses.delete(oldAddr);
|
|
705
1392
|
|
|
706
|
-
const
|
|
707
|
-
const newAddr = memoryAddress(vault, oldRecord.subject_id, oldRecord.memory_id,
|
|
1393
|
+
const commitment = contentCommitment(vault, plaintext);
|
|
1394
|
+
const newAddr = memoryAddress(vault, oldRecord.subject_id, oldRecord.memory_id, commitment);
|
|
708
1395
|
const updatedAt = nowIso(args.now);
|
|
709
1396
|
const newRecord = pruneUndefined({
|
|
710
1397
|
...publicRecord(oldRecord),
|
|
711
1398
|
memory_addr: newAddr,
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1399
|
+
key_epoch: epochEntry.epoch,
|
|
1400
|
+
key_id: epochEntry.key_id,
|
|
1401
|
+
content_ciphertext: encryptContent(vault, newAddr, plaintext, epochEntry, { purpose_tags: rawPurposeTags }),
|
|
1402
|
+
content_commitment: commitment,
|
|
1403
|
+
purpose_tag_commitments: rawPurposeTags.map((tag) => privateValueCommitment(vault.addressKey, tag)),
|
|
715
1404
|
state: 'active',
|
|
716
1405
|
updated_at: updatedAt,
|
|
717
1406
|
});
|
|
1407
|
+
setPrivateTags(newRecord, rawPurposeTags);
|
|
718
1408
|
|
|
719
1409
|
vault.memories.set(newAddr, newRecord);
|
|
720
1410
|
vault.activeAddresses.add(newAddr);
|
|
@@ -788,6 +1478,86 @@ export function deleteMemory(args = {}) {
|
|
|
788
1478
|
return { memory_addr: memoryAddr, tombstone, event, receipt };
|
|
789
1479
|
}
|
|
790
1480
|
|
|
1481
|
+
function publicKeyEpochs(vault) {
|
|
1482
|
+
return [...vault.keyEpochs.values()]
|
|
1483
|
+
.sort((left, right) => left.epoch - right.epoch)
|
|
1484
|
+
.map((entry) => pruneUndefined({
|
|
1485
|
+
epoch: entry.epoch,
|
|
1486
|
+
key_id: entry.key_id,
|
|
1487
|
+
status: entry.status,
|
|
1488
|
+
created_at: entry.created_at,
|
|
1489
|
+
sealed_at: entry.sealed_at,
|
|
1490
|
+
}));
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
function privateKeyEpochs(vault) {
|
|
1494
|
+
return [...vault.keyEpochs.values()]
|
|
1495
|
+
.sort((left, right) => left.epoch - right.epoch)
|
|
1496
|
+
.map((entry) => ({
|
|
1497
|
+
epoch: entry.epoch,
|
|
1498
|
+
key_id: entry.key_id,
|
|
1499
|
+
status: entry.status,
|
|
1500
|
+
created_at: entry.created_at,
|
|
1501
|
+
sealed_at: entry.sealed_at,
|
|
1502
|
+
key_b64: entry.key.toString('base64'),
|
|
1503
|
+
}));
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
function buildKeyring(vault, passphrase, options = {}) {
|
|
1507
|
+
const signer = vault.signer;
|
|
1508
|
+
const publicKey = exportPublicKey(vault.signingKeyPair);
|
|
1509
|
+
const keyEpochs = publicKeyEpochs(vault);
|
|
1510
|
+
const common = {
|
|
1511
|
+
schema: 'enigma.vault_keyring.v2',
|
|
1512
|
+
vault_schema: vault.schema,
|
|
1513
|
+
vault_id: vault.vault_id,
|
|
1514
|
+
active_key_epoch: vault.activeKeyEpoch,
|
|
1515
|
+
key_epochs: keyEpochs,
|
|
1516
|
+
old_epoch_read_policy: vault.allowOldEpochReads === true ? 'retained_keys_allowed' : 'active_epoch_only',
|
|
1517
|
+
signer,
|
|
1518
|
+
publicKey,
|
|
1519
|
+
};
|
|
1520
|
+
if (passphrase !== undefined && passphrase !== null) {
|
|
1521
|
+
const rawPass = String(passphrase);
|
|
1522
|
+
if (rawPass.length === 0 || rawPass.trim().length === 0) {
|
|
1523
|
+
throw new Error('Passphrase provided to buildKeyring cannot be empty.');
|
|
1524
|
+
}
|
|
1525
|
+
const salt = randomBytes(SALT_LENGTH).toString('base64');
|
|
1526
|
+
const kdf = { ...(options.kdf || DEFAULT_KDF_SPEC), salt };
|
|
1527
|
+
const derived = deriveKeysFromPassphrase(rawPass, salt, kdf);
|
|
1528
|
+
const aadMeta = { ...common, type: KEYRING_ENCRYPTED_TYPE, kdf, salt };
|
|
1529
|
+
const plaintextKeyring = {
|
|
1530
|
+
vault_key_b64: vault.vaultKey.toString('base64'),
|
|
1531
|
+
address_key_b64: vault.addressKey.toString('base64'),
|
|
1532
|
+
privateKey: exportPrivateKey(vault.signingKeyPair),
|
|
1533
|
+
content_keys: privateKeyEpochs(vault),
|
|
1534
|
+
};
|
|
1535
|
+
let encrypted;
|
|
1536
|
+
try {
|
|
1537
|
+
encrypted = encryptKeyring(plaintextKeyring, derived.kek, aadMeta);
|
|
1538
|
+
} finally {
|
|
1539
|
+
zeroDerivedKeys(derived);
|
|
1540
|
+
}
|
|
1541
|
+
return {
|
|
1542
|
+
...aadMeta,
|
|
1543
|
+
type: KEYRING_ENCRYPTED_TYPE,
|
|
1544
|
+
warning: ENCRYPTED_KEYRING_WARNING,
|
|
1545
|
+
iv: encrypted.iv,
|
|
1546
|
+
tag: encrypted.tag,
|
|
1547
|
+
ciphertext: encrypted.ciphertext,
|
|
1548
|
+
};
|
|
1549
|
+
}
|
|
1550
|
+
return {
|
|
1551
|
+
...common,
|
|
1552
|
+
type: KEYRING_PLAINTEXT_TYPE,
|
|
1553
|
+
unencrypted: true,
|
|
1554
|
+
warning: PLAINTEXT_KEYRING_WARNING,
|
|
1555
|
+
vault_key_b64: vault.vaultKey.toString('base64'),
|
|
1556
|
+
address_key_b64: vault.addressKey.toString('base64'),
|
|
1557
|
+
content_keys: privateKeyEpochs(vault),
|
|
1558
|
+
privateKey: exportPrivateKey(vault.signingKeyPair),
|
|
1559
|
+
};
|
|
1560
|
+
}
|
|
791
1561
|
export function exportBundle(args = {}) {
|
|
792
1562
|
const vault = args.vault;
|
|
793
1563
|
if (!vault) throw new Error('exportBundle requires a vault');
|
|
@@ -805,7 +1575,7 @@ export function exportBundle(args = {}) {
|
|
|
805
1575
|
|
|
806
1576
|
const roots = vault.__computeRoots();
|
|
807
1577
|
const bundle = {
|
|
808
|
-
schema:
|
|
1578
|
+
schema: VAULT_BUNDLE_SCHEMA,
|
|
809
1579
|
exported_at: nowIso(args.now),
|
|
810
1580
|
vault: {
|
|
811
1581
|
schema: vault.schema,
|
|
@@ -818,19 +1588,15 @@ export function exportBundle(args = {}) {
|
|
|
818
1588
|
updated_at: vault.updated_at,
|
|
819
1589
|
encryption: vault.encryption,
|
|
820
1590
|
key_id: vault.key_id,
|
|
1591
|
+
active_key_epoch: vault.activeKeyEpoch,
|
|
1592
|
+
key_epochs: publicKeyEpochs(vault),
|
|
1593
|
+
old_epoch_read_policy: vault.allowOldEpochReads === true ? 'retained_keys_allowed' : 'active_epoch_only',
|
|
1594
|
+
key_erasure_scope: 'software_process_and_persisted_bundle_only',
|
|
821
1595
|
active_set_root: roots.active_set_root,
|
|
822
1596
|
receipt_log_root: roots.receipt_log_root,
|
|
823
1597
|
sequence: vault.sequence,
|
|
824
1598
|
},
|
|
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
|
-
},
|
|
1599
|
+
keyring: buildKeyring(vault, args.passphrase, { kdf: args.kdf }),
|
|
834
1600
|
memory_objects: [...vault.memories.values()].map(publicRecord),
|
|
835
1601
|
active_memory_addresses: [...vault.activeAddresses].sort(),
|
|
836
1602
|
tombstones: [...vault.tombstones.values()].sort((a, b) => a.memory_addr.localeCompare(b.memory_addr)),
|
|
@@ -846,59 +1612,318 @@ export function exportBundle(args = {}) {
|
|
|
846
1612
|
return bundle;
|
|
847
1613
|
}
|
|
848
1614
|
|
|
1615
|
+
function isPlainObject(value) {
|
|
1616
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
function assertExactKeys(value, allowed, label) {
|
|
1620
|
+
if (!isPlainObject(value)) throw new Error(`${label} must be an object`);
|
|
1621
|
+
for (const key of Object.keys(value)) {
|
|
1622
|
+
if (!allowed.has(key)) throw new Error(`${label} contains unsupported field: ${key}`);
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
function assertNonEmptyString(value, label) {
|
|
1627
|
+
if (typeof value !== 'string' || value.length === 0) throw new Error(`${label} must be a non-empty string`);
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
function derivedPublicKeyPem(privateKey) {
|
|
1631
|
+
return createPublicKey(createPrivateKey(privateKey)).export({ type: 'spki', format: 'pem' }).trim();
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
function validateRecordCiphertext(bundle, record, epochEntries) {
|
|
1635
|
+
if (record?.schema !== MEMORY_OBJECT_SCHEMA) throw new Error(`unsupported memory object schema: ${record?.schema ?? '<missing>'}`);
|
|
1636
|
+
assertNonEmptyString(record.memory_addr, 'memory_object.memory_addr');
|
|
1637
|
+
assertNonEmptyString(record.key_id, 'memory_object.key_id');
|
|
1638
|
+
if (Object.prototype.hasOwnProperty.call(record, 'content_hash')) throw new Error('memory object exposes a forbidden unkeyed content hash');
|
|
1639
|
+
for (const key of Object.keys(record)) {
|
|
1640
|
+
if (isPlaintextLikeKey(key)) throw new Error(`memory object exposes forbidden plaintext field: ${key}`);
|
|
1641
|
+
}
|
|
1642
|
+
if (!/^hmac-sha256:[a-f0-9]{64}$/.test(record.content_commitment ?? '')) {
|
|
1643
|
+
throw new Error('memory object content commitment must be keyed');
|
|
1644
|
+
}
|
|
1645
|
+
if (!Number.isInteger(record.key_epoch) || record.key_epoch < 1) throw new Error('memory_object.key_epoch must be a positive integer');
|
|
1646
|
+
const epoch = epochEntries.get(record.key_epoch);
|
|
1647
|
+
if (!epoch || epoch.key_id !== record.key_id) throw new Error(`memory object references unknown key epoch/id: ${record.key_epoch}/${record.key_id}`);
|
|
1648
|
+
const encrypted = record.content_ciphertext;
|
|
1649
|
+
if (!isPlainObject(encrypted) || encrypted.alg !== 'AES-256-GCM') throw new Error('memory object ciphertext envelope is invalid');
|
|
1650
|
+
const expectedAad = canonicalContentAad({ vault_id: bundle.vault.vault_id }, record.memory_addr, record.key_epoch, record.key_id);
|
|
1651
|
+
if (encrypted.aad !== expectedAad) throw new Error(`memory object AAD mismatch: ${record.memory_addr}`);
|
|
1652
|
+
let plaintext;
|
|
1653
|
+
try {
|
|
1654
|
+
const decipher = createDecipheriv('aes-256-gcm', epoch.key, Buffer.from(encrypted.iv, 'base64'));
|
|
1655
|
+
decipher.setAAD(Buffer.from(expectedAad, 'utf8'));
|
|
1656
|
+
decipher.setAuthTag(Buffer.from(encrypted.tag, 'base64'));
|
|
1657
|
+
plaintext = Buffer.concat([
|
|
1658
|
+
decipher.update(Buffer.from(encrypted.ciphertext, 'base64')),
|
|
1659
|
+
decipher.final(),
|
|
1660
|
+
]);
|
|
1661
|
+
} catch {
|
|
1662
|
+
throw new Error(`memory object authentication failed: ${record.memory_addr}`);
|
|
1663
|
+
} finally {
|
|
1664
|
+
plaintext?.fill(0);
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function validateStrictBundle(bundle, keyring, decrypted, epochEntries) {
|
|
1669
|
+
assertExactKeys(bundle, new Set([
|
|
1670
|
+
'schema', 'exported_at', 'vault', 'keyring', 'memory_objects',
|
|
1671
|
+
'active_memory_addresses', 'tombstones', 'events', 'receipts', 'export_receipt',
|
|
1672
|
+
]), 'vault bundle');
|
|
1673
|
+
assertExactKeys(bundle.vault, new Set([
|
|
1674
|
+
'schema', 'vault_id', 'tenant_id', 'subject_id', 'actor_id', 'policy_id',
|
|
1675
|
+
'created_at', 'updated_at', 'encryption', 'key_id', 'active_key_epoch',
|
|
1676
|
+
'key_epochs', 'old_epoch_read_policy', 'key_erasure_scope', 'active_set_root',
|
|
1677
|
+
'receipt_log_root', 'sequence',
|
|
1678
|
+
]), 'vault bundle manifest');
|
|
1679
|
+
const encryptedKeyringFields = new Set([
|
|
1680
|
+
'schema', 'vault_schema', 'vault_id', 'active_key_epoch', 'key_epochs',
|
|
1681
|
+
'old_epoch_read_policy', 'signer', 'publicKey', 'type', 'warning', 'kdf', 'salt', 'iv', 'tag', 'ciphertext',
|
|
1682
|
+
]);
|
|
1683
|
+
const plaintextKeyringFields = new Set([
|
|
1684
|
+
'schema', 'vault_schema', 'vault_id', 'active_key_epoch', 'key_epochs',
|
|
1685
|
+
'old_epoch_read_policy', 'signer', 'publicKey', 'type', 'warning', 'unencrypted', 'vault_key_b64',
|
|
1686
|
+
'address_key_b64', 'content_keys', 'privateKey',
|
|
1687
|
+
]);
|
|
1688
|
+
assertExactKeys(
|
|
1689
|
+
keyring,
|
|
1690
|
+
keyring.type === KEYRING_ENCRYPTED_TYPE ? encryptedKeyringFields : plaintextKeyringFields,
|
|
1691
|
+
'vault keyring',
|
|
1692
|
+
);
|
|
1693
|
+
assertExactKeys(
|
|
1694
|
+
decrypted,
|
|
1695
|
+
keyring.type === KEYRING_ENCRYPTED_TYPE
|
|
1696
|
+
? new Set(['vault_key_b64', 'address_key_b64', 'privateKey', 'content_keys'])
|
|
1697
|
+
: new Set(['vault_key_b64', 'address_key_b64', 'privateKey', 'content_keys']),
|
|
1698
|
+
'decrypted vault keyring',
|
|
1699
|
+
);
|
|
1700
|
+
if (bundle.vault.schema !== VAULT_SCHEMA) throw new Error('vault manifest schema mismatch');
|
|
1701
|
+
if (bundle.vault.encryption !== 'local_aead') throw new Error('vault manifest encryption mismatch');
|
|
1702
|
+
assertNonEmptyString(bundle.vault.vault_id, 'vault.vault_id');
|
|
1703
|
+
if (!Number.isInteger(bundle.vault.active_key_epoch) || bundle.vault.active_key_epoch < 1) {
|
|
1704
|
+
throw new Error('vault.active_key_epoch must be a positive integer');
|
|
1705
|
+
}
|
|
1706
|
+
if (!Number.isInteger(bundle.vault.sequence) || bundle.vault.sequence < 0) throw new Error('vault.sequence must be a non-negative integer');
|
|
1707
|
+
if (keyring.schema !== 'enigma.vault_keyring.v2') throw new Error('keyring schema mismatch');
|
|
1708
|
+
if (keyring.vault_schema !== bundle.vault.schema || keyring.vault_id !== bundle.vault.vault_id) {
|
|
1709
|
+
throw new Error('keyring vault binding mismatch');
|
|
1710
|
+
}
|
|
1711
|
+
if (keyring.active_key_epoch !== bundle.vault.active_key_epoch) throw new Error('keyring active key epoch mismatch');
|
|
1712
|
+
if (keyring.old_epoch_read_policy !== bundle.vault.old_epoch_read_policy) {
|
|
1713
|
+
throw new Error('keyring old-epoch read policy mismatch');
|
|
1714
|
+
}
|
|
1715
|
+
const decryptedEpochDescriptors = [...epochEntries.values()]
|
|
1716
|
+
.sort((left, right) => left.epoch - right.epoch)
|
|
1717
|
+
.map((entry) => pruneUndefined({
|
|
1718
|
+
epoch: entry.epoch,
|
|
1719
|
+
key_id: entry.key_id,
|
|
1720
|
+
status: entry.status,
|
|
1721
|
+
created_at: entry.created_at,
|
|
1722
|
+
sealed_at: entry.sealed_at,
|
|
1723
|
+
}));
|
|
1724
|
+
if (canonical(decryptedEpochDescriptors) !== canonical(keyring.key_epochs)) {
|
|
1725
|
+
throw new Error('decrypted content keys do not match signed key epoch descriptors');
|
|
1726
|
+
}
|
|
1727
|
+
if (bundle.vault.key_id !== epochEntries.get(bundle.vault.active_key_epoch)?.key_id) throw new Error('vault active key id mismatch');
|
|
1728
|
+
if (canonical(keyring.key_epochs) !== canonical(bundle.vault.key_epochs)) throw new Error('key epoch manifest mismatch');
|
|
1729
|
+
if (derivedPublicKeyPem(decrypted.privateKey) !== String(keyring.publicKey).trim()) throw new Error('keyring public/private signing key mismatch');
|
|
1730
|
+
if (keyring.signer?.key_id === undefined) throw new Error('keyring signer key id is missing');
|
|
1731
|
+
if (!Array.isArray(keyring.key_epochs) || keyring.key_epochs.length !== epochEntries.size) {
|
|
1732
|
+
throw new Error('keyring key epoch descriptor count mismatch');
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
if (!Array.isArray(bundle.receipts) || !Array.isArray(bundle.events)) throw new Error('bundle receipts and events must be arrays');
|
|
1736
|
+
if (bundle.receipts.length !== bundle.events.length || bundle.vault.sequence !== bundle.receipts.length) {
|
|
1737
|
+
throw new Error('bundle event/receipt sequence length mismatch');
|
|
1738
|
+
}
|
|
1739
|
+
for (let index = 0; index < bundle.events.length; index += 1) {
|
|
1740
|
+
const entry = bundle.events[index];
|
|
1741
|
+
assertExactKeys(entry, new Set(['event', 'canonical', 'hash']), `event entry ${index}`);
|
|
1742
|
+
if (entry.event?.schema !== EVENT_SCHEMA || entry.event?.sequence !== index) throw new Error(`event ${index} schema or sequence mismatch`);
|
|
1743
|
+
const expectedCanonical = canonical(entry.event);
|
|
1744
|
+
const expectedHash = asSha256(expectedCanonical);
|
|
1745
|
+
if (entry.canonical !== expectedCanonical || entry.hash !== expectedHash) throw new Error(`event ${index} canonical/hash mismatch`);
|
|
1746
|
+
const receipt = bundle.receipts[index];
|
|
1747
|
+
if (receipt?.tenant_id !== bundle.vault.tenant_id || receipt?.subject_id !== bundle.vault.subject_id) {
|
|
1748
|
+
throw new Error(`receipt ${index} vault identity binding mismatch`);
|
|
1749
|
+
}
|
|
1750
|
+
if (receipt?.event_hash !== expectedHash || receipt?.operation !== entry.event.operation) throw new Error(`event ${index} receipt binding mismatch`);
|
|
1751
|
+
}
|
|
1752
|
+
const chain = verifyReceiptChain({
|
|
1753
|
+
receipts: bundle.receipts,
|
|
1754
|
+
publicKey: keyring.publicKey,
|
|
1755
|
+
expectedSignerKeyId: keyring.signer.key_id,
|
|
1756
|
+
expectedReceiptLogRoot: bundle.vault.receipt_log_root,
|
|
1757
|
+
expectedActiveSetRoot: bundle.vault.active_set_root,
|
|
1758
|
+
verifyEmbeddedReceiptLogRoot: false,
|
|
1759
|
+
});
|
|
1760
|
+
if (!chain.ok) throw new Error(`receipt chain verification failed: ${chain.errors.join('; ')}`);
|
|
1761
|
+
if (bundle.receipts.length === 0 || canonical(bundle.export_receipt) !== canonical(bundle.receipts.at(-1))) {
|
|
1762
|
+
throw new Error('export receipt does not match the signed receipt chain');
|
|
1763
|
+
}
|
|
1764
|
+
if (computeSetRoot(bundle.active_memory_addresses ?? []) !== bundle.vault.active_set_root) {
|
|
1765
|
+
throw new Error('active memory address root mismatch');
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
if (!Array.isArray(bundle.memory_objects) || !Array.isArray(bundle.active_memory_addresses) || !Array.isArray(bundle.tombstones)) {
|
|
1769
|
+
throw new Error('bundle lifecycle collections must be arrays');
|
|
1770
|
+
}
|
|
1771
|
+
const recordByAddress = new Map();
|
|
1772
|
+
const activeAddressSet = new Set(bundle.active_memory_addresses);
|
|
1773
|
+
if (activeAddressSet.size !== bundle.active_memory_addresses.length) throw new Error('duplicate active memory address');
|
|
1774
|
+
for (const record of bundle.memory_objects) {
|
|
1775
|
+
if (recordByAddress.has(record.memory_addr)) throw new Error(`duplicate memory address: ${record.memory_addr}`);
|
|
1776
|
+
recordByAddress.set(record.memory_addr, record);
|
|
1777
|
+
validateRecordCiphertext(bundle, record, epochEntries);
|
|
1778
|
+
}
|
|
1779
|
+
for (const address of bundle.active_memory_addresses) {
|
|
1780
|
+
if (!recordByAddress.has(address)) throw new Error(`active memory address has no record: ${address}`);
|
|
1781
|
+
const record = recordByAddress.get(address);
|
|
1782
|
+
if (record?.state !== 'active') throw new Error(`active memory address references non-active record: ${address}`);
|
|
1783
|
+
}
|
|
1784
|
+
for (const record of bundle.memory_objects) {
|
|
1785
|
+
if (record.state === 'active' && !activeAddressSet.has(record.memory_addr)) {
|
|
1786
|
+
throw new Error(`active memory record is absent from active address set: ${record.memory_addr}`);
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
|
|
849
1791
|
export function importBundle(args = {}) {
|
|
850
|
-
const source = args.schema ===
|
|
1792
|
+
const source = args.schema === VAULT_BUNDLE_SCHEMA ? args : (args.bundle?.bundle ?? args.bundle ?? args);
|
|
851
1793
|
const bundle = typeof source === 'string' ? JSON.parse(source) : source;
|
|
852
|
-
if (!bundle || bundle.schema !==
|
|
853
|
-
const
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
1794
|
+
if (!bundle || bundle.schema !== VAULT_BUNDLE_SCHEMA) throw new Error(`importBundle requires an ${VAULT_BUNDLE_SCHEMA} bundle`);
|
|
1795
|
+
const strict = args.strict === true;
|
|
1796
|
+
const insecureDevMode = args.insecureDevMode === true || args.insecure_dev_mode === true;
|
|
1797
|
+
const warnings = [];
|
|
1798
|
+
const keyring = bundle.keyring;
|
|
1799
|
+
let decrypted;
|
|
1800
|
+
if (keyring?.type === KEYRING_ENCRYPTED_TYPE) {
|
|
1801
|
+
if (args.passphrase === undefined || args.passphrase === null || String(args.passphrase).trim().length === 0) {
|
|
1802
|
+
throw new Error('Encrypted keyring requires passphrase');
|
|
1803
|
+
}
|
|
1804
|
+
decrypted = decryptKeyring(keyring, args.passphrase);
|
|
1805
|
+
} else if (keyring?.type === KEYRING_PLAINTEXT_TYPE) {
|
|
1806
|
+
if (strict && !insecureDevMode) throw new Error('Strict vault import rejects plaintext keyrings outside explicit insecure-dev mode');
|
|
1807
|
+
warnings.push(DEPRECATION_PLAINTEXT_KEYRING);
|
|
1808
|
+
decrypted = {
|
|
1809
|
+
vault_key_b64: keyring.vault_key_b64,
|
|
1810
|
+
address_key_b64: keyring.address_key_b64,
|
|
1811
|
+
privateKey: keyring.privateKey,
|
|
1812
|
+
content_keys: keyring.content_keys,
|
|
1813
|
+
};
|
|
1814
|
+
} else {
|
|
1815
|
+
throw new Error(`Unsupported vault keyring type: ${keyring?.type ?? '<missing>'}`);
|
|
1816
|
+
}
|
|
1817
|
+
assertNonEmptyString(decrypted?.address_key_b64, 'keyring address key');
|
|
1818
|
+
assertNonEmptyString(decrypted?.privateKey, 'keyring private signing key');
|
|
1819
|
+
keyBytes(decrypted.address_key_b64, 'address key');
|
|
1820
|
+
|
|
1821
|
+
const activeKeyEpoch = Number(bundle.vault?.active_key_epoch ?? keyring.active_key_epoch ?? 1);
|
|
1822
|
+
const rawContentKeys = Array.isArray(decrypted.content_keys) && decrypted.content_keys.length > 0
|
|
1823
|
+
? decrypted.content_keys
|
|
1824
|
+
: [{
|
|
1825
|
+
epoch: activeKeyEpoch,
|
|
1826
|
+
key_id: bundle.vault?.key_id,
|
|
1827
|
+
status: 'active',
|
|
1828
|
+
created_at: bundle.vault?.created_at,
|
|
1829
|
+
key_b64: decrypted.vault_key_b64,
|
|
1830
|
+
}];
|
|
1831
|
+
const epochs = rawContentKeys.map((entry) => normalizeKeyEpoch(entry, undefined, undefined, bundle.vault?.created_at));
|
|
1832
|
+
const epochEntries = new Map(epochs.map((entry) => [entry.epoch, entry]));
|
|
1833
|
+
if (epochEntries.size !== epochs.length) throw new Error('duplicate content key epoch');
|
|
1834
|
+
if (!epochEntries.has(activeKeyEpoch)) throw new Error(`active content key epoch ${activeKeyEpoch} is unavailable`);
|
|
1835
|
+
if (decrypted.vault_key_b64 !== undefined
|
|
1836
|
+
&& decrypted.vault_key_b64 !== epochEntries.get(activeKeyEpoch).key.toString('base64')) {
|
|
1837
|
+
throw new Error('active vault key alias does not match the active content key epoch');
|
|
1838
|
+
}
|
|
1839
|
+
if (strict) validateStrictBundle(bundle, keyring, decrypted, epochEntries);
|
|
1840
|
+
|
|
1841
|
+
const restoredSigningKeyPair = {
|
|
1842
|
+
key_id: keyring.signer?.key_id,
|
|
1843
|
+
signer: keyring.signer,
|
|
1844
|
+
publicKey: keyring.publicKey,
|
|
1845
|
+
privateKey: decrypted.privateKey,
|
|
1846
|
+
};
|
|
861
1847
|
const vault = createVault({
|
|
862
1848
|
vault_id: bundle.vault?.vault_id,
|
|
863
1849
|
tenant_id: bundle.vault?.tenant_id,
|
|
864
1850
|
subject_id: bundle.vault?.subject_id,
|
|
865
1851
|
actor_id: args.actor_id ?? args.actorId ?? bundle.vault?.actor_id,
|
|
866
1852
|
policy_id: bundle.vault?.policy_id,
|
|
867
|
-
vault_key:
|
|
868
|
-
address_key:
|
|
1853
|
+
vault_key: epochEntries.get(activeKeyEpoch).key,
|
|
1854
|
+
address_key: decrypted.address_key_b64,
|
|
1855
|
+
key_epochs: epochs,
|
|
1856
|
+
active_key_epoch: activeKeyEpoch,
|
|
1857
|
+
allow_old_epoch_reads: args.allowOldEpochReads ?? args.allow_old_epoch_reads
|
|
1858
|
+
?? (keyring.old_epoch_read_policy ?? bundle.vault?.old_epoch_read_policy) !== 'active_epoch_only',
|
|
869
1859
|
signingKeyPair: restoredSigningKeyPair,
|
|
870
|
-
signer:
|
|
1860
|
+
signer: keyring.signer,
|
|
871
1861
|
now: bundle.vault?.created_at,
|
|
872
1862
|
});
|
|
873
1863
|
vault.created_at = bundle.vault?.created_at ?? vault.created_at;
|
|
874
1864
|
vault.updated_at = bundle.vault?.updated_at ?? vault.updated_at;
|
|
875
1865
|
vault.sequence = Number.isInteger(bundle.vault?.sequence) ? bundle.vault.sequence : 0;
|
|
876
|
-
vault.events = Array.isArray(bundle.events)
|
|
1866
|
+
vault.events = Array.isArray(bundle.events)
|
|
1867
|
+
? (strict
|
|
1868
|
+
? bundle.events.map((entry) => ({ ...entry, event: { ...entry.event } }))
|
|
1869
|
+
: bundle.events.map(sanitizeImportedEventEntry).filter(Boolean))
|
|
1870
|
+
: [];
|
|
877
1871
|
vault.receipts = Array.isArray(bundle.receipts) ? bundle.receipts.map((receipt) => ({
|
|
878
|
-
...sanitizePublicValue(receipt),
|
|
879
|
-
signer: receipt.signer ? { ...sanitizePublicValue(receipt.signer) } : receipt.signer,
|
|
880
|
-
signature: receipt.signature ? { ...sanitizePublicValue(receipt.signature) } : receipt.signature,
|
|
1872
|
+
...(strict ? receipt : sanitizePublicValue(receipt)),
|
|
1873
|
+
signer: receipt.signer ? { ...(strict ? receipt.signer : sanitizePublicValue(receipt.signer)) } : receipt.signer,
|
|
1874
|
+
signature: receipt.signature ? { ...(strict ? receipt.signature : sanitizePublicValue(receipt.signature)) } : receipt.signature,
|
|
881
1875
|
})) : [];
|
|
882
1876
|
vault.memories = new Map();
|
|
883
1877
|
for (const record of bundle.memory_objects ?? []) {
|
|
884
|
-
|
|
1878
|
+
const imported = strict ? record : publicRecord(record);
|
|
1879
|
+
vault.memories.set(record.memory_addr, { ...imported, content_ciphertext: { ...imported.content_ciphertext } });
|
|
885
1880
|
}
|
|
886
1881
|
const lifecycle = reconcileImportedLifecycle(bundle, vault.memories, vault.receipts);
|
|
887
1882
|
vault.activeAddresses = lifecycle.active;
|
|
888
1883
|
vault.tombstones = lifecycle.tombstones;
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
return {
|
|
1884
|
+
applyLifecycleRoots(vault);
|
|
1885
|
+
if (args.recordImport !== false) {
|
|
1886
|
+
appendEvent(vault, {
|
|
1887
|
+
operation: 'import',
|
|
1888
|
+
actor_id: args.actor_id ?? args.actorId,
|
|
1889
|
+
policy_id: args.policy_id ?? args.policyId,
|
|
1890
|
+
now: args.now,
|
|
1891
|
+
metadata: {
|
|
1892
|
+
imported_memory_count: vault.memories.size,
|
|
1893
|
+
imported_active_count: vault.activeAddresses.size,
|
|
1894
|
+
imported_tombstone_count: vault.tombstones.size,
|
|
1895
|
+
},
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
return {
|
|
1899
|
+
vault,
|
|
1900
|
+
active_memory_addresses: [...vault.activeAddresses].sort(),
|
|
1901
|
+
tombstones: [...vault.tombstones.values()],
|
|
1902
|
+
warnings,
|
|
1903
|
+
verified: strict,
|
|
1904
|
+
};
|
|
904
1905
|
}
|
|
1906
|
+
|
|
1907
|
+
export * from './poseidon.js';
|
|
1908
|
+
export * from './enclave.js';
|
|
1909
|
+
// Named (not star) export: e2ee.mjs also defines poseidon(), which would conflict
|
|
1910
|
+
// with poseidon.js under export *.
|
|
1911
|
+
export {
|
|
1912
|
+
E2EE_SCHEMA,
|
|
1913
|
+
PAYLOAD_COMMIT_DOMAIN,
|
|
1914
|
+
HPKE_WRAP_INFO,
|
|
1915
|
+
BN254_SCALAR_FIELD,
|
|
1916
|
+
canonicalJson,
|
|
1917
|
+
normalizeFieldElement32,
|
|
1918
|
+
deriveScopeCommit,
|
|
1919
|
+
deriveNoteContext,
|
|
1920
|
+
deriveAad,
|
|
1921
|
+
generateRecipientKeyPair,
|
|
1922
|
+
derivePayloadCommit,
|
|
1923
|
+
deriveFinalNote,
|
|
1924
|
+
e2eeEncrypt,
|
|
1925
|
+
e2eeDecrypt,
|
|
1926
|
+
} from './e2ee.mjs';
|
|
1927
|
+
export * from './receipt.js';
|
|
1928
|
+
export * from './erasure.js';
|
|
1929
|
+
export * from './persistence.js';
|