0nmcp 1.7.0 → 2.1.0

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.
@@ -0,0 +1,227 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Multi-Party Escrow System
3
+ // ============================================================
4
+ // X25519 ECDH key agreement for up to 8 escrow parties.
5
+ // Each party receives encrypted shares for only the layers
6
+ // they're authorized to access via the access matrix.
7
+ //
8
+ // Patent Pending: US Provisional Patent Application #63/990,046
9
+ // ============================================================
10
+
11
+ import { randomBytes } from "crypto";
12
+ import {
13
+ generateEscrowKeyPair,
14
+ computeSharedSecret,
15
+ encryptAESRaw,
16
+ decryptAESRaw,
17
+ KEY_LENGTH,
18
+ } from "./crypto-container.js";
19
+ import { LAYER_NAMES, isValidLayer } from "./layers.js";
20
+
21
+ const MAX_PARTIES = 8;
22
+
23
+ /**
24
+ * Generate escrow keypair for a party.
25
+ *
26
+ * @returns {{ publicKey: Buffer, secretKey: Buffer, partyId: string }}
27
+ */
28
+ export function generatePartyKeys() {
29
+ const pair = generateEscrowKeyPair();
30
+ const partyId = randomBytes(8).toString("hex");
31
+ return { ...pair, partyId };
32
+ }
33
+
34
+ /**
35
+ * Create per-layer encryption keys (one random 256-bit key per layer).
36
+ *
37
+ * @returns {Map<string, Buffer>} layerName → 32-byte AES key
38
+ */
39
+ export function generateLayerKeys() {
40
+ const keys = new Map();
41
+ for (const name of LAYER_NAMES) {
42
+ keys.set(name, randomBytes(KEY_LENGTH));
43
+ }
44
+ return keys;
45
+ }
46
+
47
+ /**
48
+ * Create escrow shares for each party based on access matrix.
49
+ * Each party's share is encrypted with a shared secret derived via X25519.
50
+ *
51
+ * @param {Map<string, Buffer>} layerKeys - Per-layer encryption keys
52
+ * @param {Buffer} creatorSecretKey - Creator's X25519 secret key
53
+ * @param {Array<{ partyId: string, publicKey: Buffer }>} parties - Escrow parties
54
+ * @param {Object<string, Object<string, boolean>>} accessMatrix - Per-party, per-layer access
55
+ * @returns {Array<{ partyId: string, encryptedShare: Buffer, iv: Buffer, tag: Buffer, authorizedLayers: string[] }>}
56
+ */
57
+ export function createEscrowShares(layerKeys, creatorSecretKey, parties, accessMatrix) {
58
+ if (parties.length > MAX_PARTIES) {
59
+ throw new Error(`Maximum ${MAX_PARTIES} escrow parties allowed`);
60
+ }
61
+
62
+ const shares = [];
63
+
64
+ for (const party of parties) {
65
+ const partyAccess = accessMatrix[party.partyId];
66
+ if (!partyAccess) {
67
+ throw new Error(`No access matrix entry for party: ${party.partyId}`);
68
+ }
69
+
70
+ // Collect only the layer keys this party is authorized for
71
+ const authorizedLayers = [];
72
+ const shareData = {};
73
+
74
+ for (const [layerName, hasAccess] of Object.entries(partyAccess)) {
75
+ if (hasAccess && layerKeys.has(layerName)) {
76
+ authorizedLayers.push(layerName);
77
+ shareData[layerName] = layerKeys.get(layerName).toString("base64");
78
+ }
79
+ }
80
+
81
+ if (authorizedLayers.length === 0) {
82
+ continue; // Skip parties with no access
83
+ }
84
+
85
+ // Compute shared secret via X25519
86
+ const sharedSecret = computeSharedSecret(creatorSecretKey, party.publicKey);
87
+
88
+ // Encrypt the share data with the shared secret
89
+ const plaintext = Buffer.from(JSON.stringify(shareData), "utf8");
90
+ const { ciphertext, iv, tag } = encryptAESRaw(plaintext, sharedSecret);
91
+
92
+ shares.push({
93
+ partyId: party.partyId,
94
+ encryptedShare: ciphertext,
95
+ iv,
96
+ tag,
97
+ authorizedLayers,
98
+ });
99
+ }
100
+
101
+ return shares;
102
+ }
103
+
104
+ /**
105
+ * Unwrap an escrow share using a party's secret key.
106
+ *
107
+ * @param {{ encryptedShare: Buffer, iv: Buffer, tag: Buffer }} share - Encrypted share
108
+ * @param {Buffer} partySecretKey - Party's X25519 secret key
109
+ * @param {Buffer} creatorPublicKey - Creator's X25519 public key
110
+ * @returns {Object<string, Buffer>} layerName → decrypted AES key
111
+ */
112
+ export function unwrapEscrowShare(share, partySecretKey, creatorPublicKey) {
113
+ // Compute shared secret via X25519 (reverse direction)
114
+ const sharedSecret = computeSharedSecret(partySecretKey, creatorPublicKey);
115
+
116
+ // Decrypt the share
117
+ const decrypted = decryptAESRaw(share.encryptedShare, sharedSecret, share.iv, share.tag);
118
+ const shareData = JSON.parse(decrypted.toString("utf8"));
119
+
120
+ // Convert base64 keys back to Buffers
121
+ const layerKeys = {};
122
+ for (const [layerName, keyB64] of Object.entries(shareData)) {
123
+ layerKeys[layerName] = Buffer.from(keyB64, "base64");
124
+ }
125
+
126
+ return layerKeys;
127
+ }
128
+
129
+ /**
130
+ * Serialize escrow block for binary container format.
131
+ *
132
+ * @param {Array} shares - Escrow shares from createEscrowShares
133
+ * @param {Buffer} creatorPublicKey - Creator's X25519 public key (for unwrap)
134
+ * @returns {Buffer} Serialized escrow block
135
+ */
136
+ export function serializeEscrowBlock(shares, creatorPublicKey) {
137
+ const parts = [];
138
+
139
+ // Party count (1 byte)
140
+ const countBuf = Buffer.alloc(1);
141
+ countBuf.writeUInt8(shares.length);
142
+ parts.push(countBuf);
143
+
144
+ // Creator escrow public key (32 bytes)
145
+ parts.push(creatorPublicKey);
146
+
147
+ for (const share of shares) {
148
+ // Party ID length (1 byte) + Party ID
149
+ const partyIdBuf = Buffer.from(share.partyId, "utf8");
150
+ const partyIdLen = Buffer.alloc(1);
151
+ partyIdLen.writeUInt8(partyIdBuf.length);
152
+ parts.push(partyIdLen, partyIdBuf);
153
+
154
+ // Authorized layer count (1 byte) + layer indices
155
+ const layerCount = Buffer.alloc(1);
156
+ layerCount.writeUInt8(share.authorizedLayers.length);
157
+ parts.push(layerCount);
158
+
159
+ for (const layerName of share.authorizedLayers) {
160
+ const idx = Buffer.alloc(1);
161
+ idx.writeUInt8(LAYER_NAMES.indexOf(layerName));
162
+ parts.push(idx);
163
+ }
164
+
165
+ // IV (12 bytes) + Tag (16 bytes) + Share length (4 bytes) + Share ciphertext
166
+ parts.push(share.iv);
167
+ parts.push(share.tag);
168
+
169
+ const shareLen = Buffer.alloc(4);
170
+ shareLen.writeUInt32BE(share.encryptedShare.length);
171
+ parts.push(shareLen, share.encryptedShare);
172
+ }
173
+
174
+ return Buffer.concat(parts);
175
+ }
176
+
177
+ /**
178
+ * Deserialize escrow block from binary.
179
+ *
180
+ * @param {Buffer} data - Serialized escrow block
181
+ * @returns {{ creatorPublicKey: Buffer, shares: Array }}
182
+ */
183
+ export function deserializeEscrowBlock(data) {
184
+ let offset = 0;
185
+
186
+ const partyCount = data.readUInt8(offset);
187
+ offset += 1;
188
+
189
+ const creatorPublicKey = data.subarray(offset, offset + 32);
190
+ offset += 32;
191
+
192
+ const shares = [];
193
+
194
+ for (let i = 0; i < partyCount; i++) {
195
+ // Party ID
196
+ const partyIdLen = data.readUInt8(offset);
197
+ offset += 1;
198
+ const partyId = data.subarray(offset, offset + partyIdLen).toString("utf8");
199
+ offset += partyIdLen;
200
+
201
+ // Authorized layers
202
+ const layerCount = data.readUInt8(offset);
203
+ offset += 1;
204
+ const authorizedLayers = [];
205
+ for (let j = 0; j < layerCount; j++) {
206
+ const idx = data.readUInt8(offset);
207
+ offset += 1;
208
+ authorizedLayers.push(LAYER_NAMES[idx]);
209
+ }
210
+
211
+ // IV (12) + Tag (16) + Share
212
+ const iv = data.subarray(offset, offset + 12);
213
+ offset += 12;
214
+ const tag = data.subarray(offset, offset + 16);
215
+ offset += 16;
216
+ const shareLen = data.readUInt32BE(offset);
217
+ offset += 4;
218
+ const encryptedShare = data.subarray(offset, offset + shareLen);
219
+ offset += shareLen;
220
+
221
+ shares.push({ partyId, authorizedLayers, encryptedShare, iv, tag });
222
+ }
223
+
224
+ return { creatorPublicKey, shares };
225
+ }
226
+
227
+ export { MAX_PARTIES };
@@ -0,0 +1,254 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Semantic Layer Manager
3
+ // ============================================================
4
+ // 7 named layers, each independently encrypted with AES-256-GCM.
5
+ // The credentials layer (layer 2) uses double-encryption via
6
+ // Argon2id for maximum protection of API keys and secrets.
7
+ //
8
+ // Patent Pending: US Provisional Patent Application #63/990,046
9
+ // ============================================================
10
+
11
+ import { encryptAES, decryptAES, deriveKeyArgon2, encryptAESRaw, decryptAESRaw, SALT_LENGTH } from "./crypto-container.js";
12
+ import { randomBytes } from "crypto";
13
+
14
+ // ── Layer Definitions ────────────────────────────────────────
15
+
16
+ export const LAYER_NAMES = [
17
+ "workflows", // 1: .0n workflow definitions
18
+ "credentials", // 2: API keys, tokens, secrets (double-encrypted)
19
+ "env_vars", // 3: Environment variables
20
+ "mcp_configs", // 4: MCP server configurations
21
+ "site_profiles", // 5: CRO9/site configs
22
+ "ai_brain", // 6: AI agent data, prompts, memory
23
+ "audit_trail", // 7: Execution logs, timestamps (append-only)
24
+ ];
25
+
26
+ export const LAYER_COUNT = LAYER_NAMES.length;
27
+
28
+ /**
29
+ * Check if a layer name is valid.
30
+ * @param {string} name
31
+ * @returns {boolean}
32
+ */
33
+ export function isValidLayer(name) {
34
+ return LAYER_NAMES.includes(name);
35
+ }
36
+
37
+ /**
38
+ * Get the index of a layer by name.
39
+ * @param {string} name
40
+ * @returns {number} 0-based index, or -1 if not found
41
+ */
42
+ export function getLayerIndex(name) {
43
+ return LAYER_NAMES.indexOf(name);
44
+ }
45
+
46
+ // ── Layer Encryption ─────────────────────────────────────────
47
+
48
+ /**
49
+ * Seal (encrypt) a single layer's data.
50
+ *
51
+ * @param {string} name - Layer name
52
+ * @param {any} data - Data to encrypt (will be JSON.stringify'd)
53
+ * @param {string} passphrase - Encryption passphrase
54
+ * @returns {{ name: string, ciphertext: Buffer, iv: Buffer, salt: Buffer, tag: Buffer }}
55
+ */
56
+ export function sealLayer(name, data, passphrase) {
57
+ if (!isValidLayer(name)) {
58
+ throw new Error(`Invalid layer name: "${name}". Valid: ${LAYER_NAMES.join(", ")}`);
59
+ }
60
+
61
+ const plaintext = JSON.stringify(data);
62
+ const result = encryptAES(plaintext, passphrase);
63
+
64
+ return {
65
+ name,
66
+ ciphertext: result.ciphertext,
67
+ iv: result.iv,
68
+ salt: result.salt,
69
+ tag: result.tag,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Unseal (decrypt) a single layer's data.
75
+ *
76
+ * @param {string} name - Layer name
77
+ * @param {Buffer} ciphertext
78
+ * @param {string} passphrase
79
+ * @param {Buffer} iv
80
+ * @param {Buffer} salt
81
+ * @param {Buffer} tag
82
+ * @returns {any} Parsed JSON data
83
+ */
84
+ export function unsealLayer(name, ciphertext, passphrase, iv, salt, tag) {
85
+ if (!isValidLayer(name)) {
86
+ throw new Error(`Invalid layer name: "${name}"`);
87
+ }
88
+
89
+ const decrypted = decryptAES(ciphertext, passphrase, iv, salt, tag);
90
+ return JSON.parse(decrypted.toString("utf8"));
91
+ }
92
+
93
+ // ── Credentials Double-Encryption ────────────────────────────
94
+
95
+ /**
96
+ * Seal credentials with double encryption:
97
+ * 1. First layer: AES-256-GCM with passphrase
98
+ * 2. Second layer: AES-256-GCM with Argon2id-derived key
99
+ *
100
+ * @param {any} data - Credentials data
101
+ * @param {string} passphrase - Encryption passphrase
102
+ * @returns {Promise<{ ciphertext: Buffer, iv: Buffer, salt: Buffer, tag: Buffer, argonSalt: Buffer, innerIv: Buffer, innerTag: Buffer }>}
103
+ */
104
+ export async function sealCredentials(data, passphrase) {
105
+ const plaintext = Buffer.from(JSON.stringify(data), "utf8");
106
+
107
+ // Inner layer: Argon2id-derived key
108
+ const argonSalt = randomBytes(SALT_LENGTH);
109
+ const argonKey = await deriveKeyArgon2(passphrase, argonSalt);
110
+ const inner = encryptAESRaw(plaintext, argonKey);
111
+
112
+ // Outer layer: standard AES-256-GCM with passphrase
113
+ // Combine inner ciphertext + iv + tag for outer encryption
114
+ const innerPayload = Buffer.concat([inner.iv, inner.tag, inner.ciphertext]);
115
+ const outer = encryptAES(innerPayload, passphrase);
116
+
117
+ return {
118
+ ciphertext: outer.ciphertext,
119
+ iv: outer.iv,
120
+ salt: outer.salt,
121
+ tag: outer.tag,
122
+ argonSalt,
123
+ innerIv: inner.iv,
124
+ innerTag: inner.tag,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Unseal double-encrypted credentials.
130
+ *
131
+ * @param {Buffer} ciphertext
132
+ * @param {string} passphrase
133
+ * @param {Buffer} iv
134
+ * @param {Buffer} salt
135
+ * @param {Buffer} tag
136
+ * @param {Buffer} argonSalt
137
+ * @returns {Promise<any>} Parsed credentials
138
+ */
139
+ export async function unsealCredentials(ciphertext, passphrase, iv, salt, tag, argonSalt) {
140
+ // Outer layer: decrypt with passphrase
141
+ const innerPayload = decryptAES(ciphertext, passphrase, iv, salt, tag);
142
+
143
+ // Extract inner components (iv:12 + tag:16 + ciphertext:rest)
144
+ const innerIv = innerPayload.subarray(0, 12);
145
+ const innerTag = innerPayload.subarray(12, 28);
146
+ const innerCiphertext = innerPayload.subarray(28);
147
+
148
+ // Inner layer: Argon2id-derived key
149
+ const argonKey = await deriveKeyArgon2(passphrase, argonSalt);
150
+ const decrypted = decryptAESRaw(innerCiphertext, argonKey, innerIv, innerTag);
151
+
152
+ return JSON.parse(decrypted.toString("utf8"));
153
+ }
154
+
155
+ // ── Bulk Layer Operations ────────────────────────────────────
156
+
157
+ /**
158
+ * Seal multiple layers at once.
159
+ *
160
+ * @param {Object<string, any>} layerData - { layerName: data }
161
+ * @param {string} passphrase
162
+ * @returns {Promise<Map<string, object>>} Sealed layers
163
+ */
164
+ export async function sealAllLayers(layerData, passphrase) {
165
+ const sealed = new Map();
166
+
167
+ for (const [name, data] of Object.entries(layerData)) {
168
+ if (!isValidLayer(name)) {
169
+ throw new Error(`Invalid layer: "${name}"`);
170
+ }
171
+
172
+ if (name === "credentials") {
173
+ const result = await sealCredentials(data, passphrase);
174
+ sealed.set(name, { ...result, doubleEncrypted: true });
175
+ } else {
176
+ const result = sealLayer(name, data, passphrase);
177
+ sealed.set(name, result);
178
+ }
179
+ }
180
+
181
+ return sealed;
182
+ }
183
+
184
+ /**
185
+ * Unseal multiple layers at once.
186
+ *
187
+ * @param {Map<string, object>} sealedLayers
188
+ * @param {string} passphrase
189
+ * @param {string[]} [onlyLayers] - Optional filter: only unseal these layers
190
+ * @returns {Promise<Object<string, any>>} Unsealed layer data
191
+ */
192
+ export async function unsealAllLayers(sealedLayers, passphrase, onlyLayers = null) {
193
+ const result = {};
194
+
195
+ for (const [name, layerInfo] of sealedLayers) {
196
+ if (onlyLayers && !onlyLayers.includes(name)) continue;
197
+
198
+ if (name === "credentials" && layerInfo.doubleEncrypted) {
199
+ result[name] = await unsealCredentials(
200
+ layerInfo.ciphertext, passphrase,
201
+ layerInfo.iv, layerInfo.salt, layerInfo.tag,
202
+ layerInfo.argonSalt
203
+ );
204
+ } else {
205
+ result[name] = unsealLayer(
206
+ name, layerInfo.ciphertext, passphrase,
207
+ layerInfo.iv, layerInfo.salt, layerInfo.tag
208
+ );
209
+ }
210
+ }
211
+
212
+ return result;
213
+ }
214
+
215
+ // ── Layer Access Matrix ──────────────────────────────────────
216
+
217
+ /**
218
+ * Create a layer access matrix for escrow parties.
219
+ *
220
+ * @param {Object<string, string[]>} partyAccess - { partyId: [layerNames] }
221
+ * @returns {Object<string, Object<string, boolean>>} Full matrix
222
+ */
223
+ export function createAccessMatrix(partyAccess) {
224
+ const matrix = {};
225
+
226
+ for (const [partyId, layers] of Object.entries(partyAccess)) {
227
+ matrix[partyId] = {};
228
+ for (const layerName of LAYER_NAMES) {
229
+ matrix[partyId][layerName] = layers.includes(layerName);
230
+ }
231
+ }
232
+
233
+ return matrix;
234
+ }
235
+
236
+ /**
237
+ * Validate an access matrix.
238
+ *
239
+ * @param {Object} matrix
240
+ * @returns {{ valid: boolean, errors: string[] }}
241
+ */
242
+ export function validateAccessMatrix(matrix) {
243
+ const errors = [];
244
+
245
+ for (const [partyId, access] of Object.entries(matrix)) {
246
+ for (const layerName of Object.keys(access)) {
247
+ if (!isValidLayer(layerName)) {
248
+ errors.push(`Party "${partyId}": invalid layer "${layerName}"`);
249
+ }
250
+ }
251
+ }
252
+
253
+ return { valid: errors.length === 0, errors };
254
+ }
@@ -0,0 +1,159 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Transfer Registry
3
+ // ============================================================
4
+ // In-memory + file-backed registry for tracking vault transfers.
5
+ // Prevents replay attacks by rejecting duplicate transfer IDs.
6
+ //
7
+ // Patent Pending: US Provisional Patent Application #63/990,046
8
+ // ============================================================
9
+
10
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
11
+ import { join } from "path";
12
+ import { homedir } from "os";
13
+
14
+ const VAULT_DIR = join(homedir(), ".0n", "vault");
15
+ const REGISTRY_PATH = join(VAULT_DIR, "transfers.json");
16
+
17
+ // In-memory cache
18
+ let registry = null;
19
+
20
+ /**
21
+ * Ensure vault directory exists and load registry from disk.
22
+ */
23
+ function ensureRegistry() {
24
+ if (registry !== null) return;
25
+
26
+ if (!existsSync(VAULT_DIR)) {
27
+ mkdirSync(VAULT_DIR, { recursive: true });
28
+ }
29
+
30
+ if (existsSync(REGISTRY_PATH)) {
31
+ try {
32
+ registry = JSON.parse(readFileSync(REGISTRY_PATH, "utf-8"));
33
+ } catch {
34
+ registry = { transfers: {} };
35
+ }
36
+ } else {
37
+ registry = { transfers: {} };
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Save registry to disk.
43
+ */
44
+ function saveRegistry() {
45
+ if (!existsSync(VAULT_DIR)) {
46
+ mkdirSync(VAULT_DIR, { recursive: true });
47
+ }
48
+ writeFileSync(REGISTRY_PATH, JSON.stringify(registry, null, 2));
49
+ }
50
+
51
+ /**
52
+ * Register a new transfer.
53
+ *
54
+ * @param {string} transferId - UUID v4
55
+ * @param {string} sealHex - Seal of Truth hex string
56
+ * @param {Object} metadata - Additional metadata
57
+ * @returns {{ success: boolean, error?: string }}
58
+ */
59
+ export function registerTransfer(transferId, sealHex, metadata = {}) {
60
+ ensureRegistry();
61
+
62
+ // Replay prevention
63
+ if (registry.transfers[transferId]) {
64
+ return {
65
+ success: false,
66
+ error: `Transfer ID "${transferId}" already registered — replay rejected`,
67
+ };
68
+ }
69
+
70
+ registry.transfers[transferId] = {
71
+ seal: sealHex,
72
+ registered_at: new Date().toISOString(),
73
+ status: "active",
74
+ ...metadata,
75
+ };
76
+
77
+ saveRegistry();
78
+
79
+ return { success: true, transferId };
80
+ }
81
+
82
+ /**
83
+ * Look up a transfer by ID.
84
+ *
85
+ * @param {string} transferId
86
+ * @returns {{ found: boolean, transfer?: Object }}
87
+ */
88
+ export function lookupTransfer(transferId) {
89
+ ensureRegistry();
90
+
91
+ const transfer = registry.transfers[transferId];
92
+ if (!transfer) {
93
+ return { found: false };
94
+ }
95
+
96
+ return { found: true, transfer: { transferId, ...transfer } };
97
+ }
98
+
99
+ /**
100
+ * Revoke a transfer.
101
+ *
102
+ * @param {string} transferId
103
+ * @returns {{ success: boolean, error?: string }}
104
+ */
105
+ export function revokeTransfer(transferId) {
106
+ ensureRegistry();
107
+
108
+ if (!registry.transfers[transferId]) {
109
+ return { success: false, error: `Transfer ID "${transferId}" not found` };
110
+ }
111
+
112
+ if (registry.transfers[transferId].status === "revoked") {
113
+ return { success: false, error: `Transfer "${transferId}" is already revoked` };
114
+ }
115
+
116
+ registry.transfers[transferId].status = "revoked";
117
+ registry.transfers[transferId].revoked_at = new Date().toISOString();
118
+
119
+ saveRegistry();
120
+
121
+ return { success: true, transferId, message: `Transfer "${transferId}" has been revoked` };
122
+ }
123
+
124
+ /**
125
+ * List all transfers.
126
+ *
127
+ * @param {string} [status] - Optional filter: "active", "revoked"
128
+ * @returns {Array<Object>}
129
+ */
130
+ export function listTransfers(status = null) {
131
+ ensureRegistry();
132
+
133
+ const transfers = [];
134
+ for (const [id, data] of Object.entries(registry.transfers)) {
135
+ if (status && data.status !== status) continue;
136
+ transfers.push({ transferId: id, ...data });
137
+ }
138
+
139
+ return transfers;
140
+ }
141
+
142
+ /**
143
+ * Check if a transfer ID has been used (for replay prevention).
144
+ *
145
+ * @param {string} transferId
146
+ * @returns {boolean}
147
+ */
148
+ export function isTransferUsed(transferId) {
149
+ ensureRegistry();
150
+ return !!registry.transfers[transferId];
151
+ }
152
+
153
+ /**
154
+ * Reset registry (for testing only).
155
+ */
156
+ export function resetRegistry() {
157
+ registry = { transfers: {} };
158
+ saveRegistry();
159
+ }
package/vault/seal.js ADDED
@@ -0,0 +1,74 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Seal of Truth
3
+ // ============================================================
4
+ // SHA3-256 based content certification hash.
5
+ // Publicly verifiable — anyone can confirm container integrity
6
+ // without decrypting any layer data.
7
+ //
8
+ // Formula:
9
+ // SHA3-256(transfer_id || timestamp || pubkey || SHA3-256(concat(all_ciphertexts)))
10
+ //
11
+ // Patent Pending: US Provisional Patent Application #63/990,046
12
+ // ============================================================
13
+
14
+ import { sha3, sha3Hex } from "./crypto-container.js";
15
+
16
+ /**
17
+ * Create a Seal of Truth for a vault container.
18
+ *
19
+ * @param {string} transferId - UUID v4 transfer ID
20
+ * @param {number} timestamp - Unix timestamp (ms)
21
+ * @param {Buffer} pubkey - Ed25519 public key (32 bytes)
22
+ * @param {Buffer[]} layerCiphertexts - Array of encrypted layer data
23
+ * @returns {{ seal: Buffer, sealHex: string }}
24
+ */
25
+ export function createSeal(transferId, timestamp, pubkey, layerCiphertexts) {
26
+ // Hash all ciphertexts together
27
+ const contentHash = sha3(Buffer.concat(layerCiphertexts));
28
+
29
+ // Combine components: transferId || timestamp || pubkey || contentHash
30
+ const transferIdBuf = Buffer.from(transferId, "utf8");
31
+ const timestampBuf = Buffer.alloc(8);
32
+ timestampBuf.writeBigUInt64BE(BigInt(timestamp));
33
+
34
+ const sealInput = Buffer.concat([transferIdBuf, timestampBuf, pubkey, contentHash]);
35
+ const seal = sha3(sealInput);
36
+
37
+ return {
38
+ seal,
39
+ sealHex: seal.toString("hex"),
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Verify a Seal of Truth.
45
+ *
46
+ * @param {Buffer} seal - 32-byte seal hash
47
+ * @param {string} transferId
48
+ * @param {number} timestamp
49
+ * @param {Buffer} pubkey
50
+ * @param {Buffer[]} layerCiphertexts
51
+ * @returns {boolean} True if seal matches
52
+ */
53
+ export function verifySeal(seal, transferId, timestamp, pubkey, layerCiphertexts) {
54
+ const expected = createSeal(transferId, timestamp, pubkey, layerCiphertexts);
55
+ return Buffer.compare(seal, expected.seal) === 0;
56
+ }
57
+
58
+ /**
59
+ * Generate a human-readable seal summary.
60
+ *
61
+ * @param {Buffer} seal
62
+ * @param {string} transferId
63
+ * @param {number} timestamp
64
+ * @returns {string}
65
+ */
66
+ export function sealSummary(seal, transferId, timestamp) {
67
+ return [
68
+ `Seal of Truth: ${seal.toString("hex")}`,
69
+ `Transfer ID: ${transferId}`,
70
+ `Timestamp: ${new Date(timestamp).toISOString()}`,
71
+ `Algorithm: SHA3-256`,
72
+ `Status: Verified ✓`,
73
+ ].join("\n");
74
+ }