0nmcp 1.7.0 → 2.0.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,479 @@
1
+ // ============================================================
2
+ // 0nMCP — Vault: Container Assembly
3
+ // ============================================================
4
+ // Binary .0nv container format per patent specification.
5
+ // Assembles/disassembles 7 semantic layers with Seal of Truth
6
+ // and Ed25519 signatures into a portable binary file.
7
+ //
8
+ // Binary Format:
9
+ // [Magic: 0x304E5350 (4B)]
10
+ // [Version: uint8 (1B)]
11
+ // [Transfer ID: UUID v4 (16B)]
12
+ // [Creator PubKey: Ed25519 (32B)]
13
+ // [Timestamp: uint64 BE (8B)]
14
+ // [Seal of Truth: SHA3-256 (32B)]
15
+ // [Layer Count: uint8 (1B)]
16
+ // [Layer Entries: variable]
17
+ // [Escrow Block: variable]
18
+ // [Signature: Ed25519 (64B)]
19
+ //
20
+ // File extension: .0nv (0n Vault)
21
+ //
22
+ // Patent Pending: US Provisional Patent Application #63/990,046
23
+ // ============================================================
24
+
25
+ import { readFileSync, writeFileSync } from "fs";
26
+ import {
27
+ generateTransferId,
28
+ generateSigningKeyPair,
29
+ generateEscrowKeyPair,
30
+ sign,
31
+ verifySignature,
32
+ IV_LENGTH,
33
+ TAG_LENGTH,
34
+ SALT_LENGTH,
35
+ } from "./crypto-container.js";
36
+ import { sealLayer, unsealLayer, sealCredentials, unsealCredentials, LAYER_NAMES } from "./layers.js";
37
+ import { createSeal, verifySeal } from "./seal.js";
38
+ import { createEscrowShares, serializeEscrowBlock, deserializeEscrowBlock, unwrapEscrowShare } from "./escrow.js";
39
+ import { registerTransfer, isTransferUsed } from "./registry.js";
40
+
41
+ // ── Constants ────────────────────────────────────────────────
42
+
43
+ export const MAGIC = Buffer.from([0x30, 0x4E, 0x53, 0x50]); // "0NSP"
44
+ export const VERSION = 1;
45
+ export const FILE_EXTENSION = ".0nv";
46
+
47
+ // ── Container Assembly ───────────────────────────────────────
48
+
49
+ /**
50
+ * Assemble a vault container from layer data.
51
+ *
52
+ * @param {Object} options
53
+ * @param {Object<string, any>} options.layers - { layerName: data }
54
+ * @param {string} options.passphrase - Encryption passphrase
55
+ * @param {Array} [options.escrowParties] - Escrow party definitions
56
+ * @param {Object} [options.accessMatrix] - Per-party layer access
57
+ * @param {Object} [options.metadata] - Additional metadata
58
+ * @returns {Promise<{ buffer: Buffer, transferId: string, sealHex: string, publicKey: Buffer }>}
59
+ */
60
+ export async function assembleContainer(options) {
61
+ const { layers, passphrase, escrowParties = [], accessMatrix = {}, metadata = {} } = options;
62
+
63
+ // Generate identity
64
+ const transferId = generateTransferId();
65
+ const signingPair = generateSigningKeyPair();
66
+ const timestamp = Date.now();
67
+
68
+ // Seal each layer
69
+ const sealedLayers = new Map();
70
+ const layerCiphertexts = [];
71
+
72
+ for (const [name, data] of Object.entries(layers)) {
73
+ if (name === "credentials") {
74
+ const sealed = await sealCredentials(data, passphrase);
75
+ sealedLayers.set(name, { ...sealed, doubleEncrypted: true });
76
+ layerCiphertexts.push(sealed.ciphertext);
77
+ } else {
78
+ const sealed = sealLayer(name, data, passphrase);
79
+ sealedLayers.set(name, sealed);
80
+ layerCiphertexts.push(sealed.ciphertext);
81
+ }
82
+ }
83
+
84
+ // Create Seal of Truth
85
+ const { seal, sealHex } = createSeal(transferId, timestamp, signingPair.publicKey, layerCiphertexts);
86
+
87
+ // Build escrow block
88
+ let escrowBlock = Buffer.alloc(0);
89
+ if (escrowParties.length > 0) {
90
+ const escrowKeyPair = generateEscrowKeyPair();
91
+ const layerKeys = new Map();
92
+ // Generate per-layer keys for escrow (these wrap the passphrase-derived access)
93
+ for (const name of Object.keys(layers)) {
94
+ layerKeys.set(name, Buffer.from(passphrase, "utf8").subarray(0, 32));
95
+ }
96
+ const shares = createEscrowShares(layerKeys, escrowKeyPair.secretKey, escrowParties, accessMatrix);
97
+ escrowBlock = serializeEscrowBlock(shares, escrowKeyPair.publicKey);
98
+ }
99
+
100
+ // Build binary container
101
+ const parts = [];
102
+
103
+ // Header
104
+ parts.push(MAGIC);
105
+ parts.push(Buffer.from([VERSION]));
106
+
107
+ // Transfer ID (as raw bytes from UUID string — store as UTF-8, 36 bytes)
108
+ const tidBuf = Buffer.alloc(36);
109
+ tidBuf.write(transferId, "utf8");
110
+ parts.push(tidBuf);
111
+
112
+ // Creator public key (32 bytes)
113
+ parts.push(signingPair.publicKey);
114
+
115
+ // Timestamp (8 bytes, uint64 BE)
116
+ const tsBuf = Buffer.alloc(8);
117
+ tsBuf.writeBigUInt64BE(BigInt(timestamp));
118
+ parts.push(tsBuf);
119
+
120
+ // Seal of Truth (32 bytes)
121
+ parts.push(seal);
122
+
123
+ // Layer count (1 byte)
124
+ parts.push(Buffer.from([sealedLayers.size]));
125
+
126
+ // Layer entries
127
+ for (const [name, layerInfo] of sealedLayers) {
128
+ // Layer name (2 bytes length + UTF-8)
129
+ const nameBuf = Buffer.from(name, "utf8");
130
+ const nameLen = Buffer.alloc(2);
131
+ nameLen.writeUInt16BE(nameBuf.length);
132
+ parts.push(nameLen, nameBuf);
133
+
134
+ // Flags (1 byte): bit 0 = doubleEncrypted
135
+ const flags = Buffer.alloc(1);
136
+ flags.writeUInt8(layerInfo.doubleEncrypted ? 1 : 0);
137
+ parts.push(flags);
138
+
139
+ // Salt (32 bytes)
140
+ parts.push(layerInfo.salt);
141
+
142
+ // IV (12 bytes)
143
+ parts.push(layerInfo.iv);
144
+
145
+ // Auth tag (16 bytes)
146
+ parts.push(layerInfo.tag);
147
+
148
+ // Argon salt (32 bytes, only for credentials)
149
+ if (layerInfo.doubleEncrypted && layerInfo.argonSalt) {
150
+ parts.push(layerInfo.argonSalt);
151
+ }
152
+
153
+ // Ciphertext (4 bytes length + data)
154
+ const ctLen = Buffer.alloc(4);
155
+ ctLen.writeUInt32BE(layerInfo.ciphertext.length);
156
+ parts.push(ctLen, layerInfo.ciphertext);
157
+ }
158
+
159
+ // Escrow block length (4 bytes) + escrow block
160
+ const escrowLen = Buffer.alloc(4);
161
+ escrowLen.writeUInt32BE(escrowBlock.length);
162
+ parts.push(escrowLen, escrowBlock);
163
+
164
+ // Combine everything except signature
165
+ const unsigned = Buffer.concat(parts);
166
+
167
+ // Ed25519 signature over entire container
168
+ const signature = sign(unsigned, signingPair.secretKey);
169
+ const container = Buffer.concat([unsigned, signature]);
170
+
171
+ // Register transfer
172
+ registerTransfer(transferId, sealHex, {
173
+ layerCount: sealedLayers.size,
174
+ layers: Array.from(sealedLayers.keys()),
175
+ creatorPubKey: signingPair.publicKey.toString("hex"),
176
+ ...metadata,
177
+ });
178
+
179
+ return {
180
+ buffer: container,
181
+ transferId,
182
+ sealHex,
183
+ publicKey: signingPair.publicKey,
184
+ timestamp,
185
+ layerCount: sealedLayers.size,
186
+ layers: Array.from(sealedLayers.keys()),
187
+ };
188
+ }
189
+
190
+ /**
191
+ * Disassemble a vault container and decrypt layers.
192
+ *
193
+ * @param {Buffer} buffer - Container binary data
194
+ * @param {string} passphrase - Decryption passphrase
195
+ * @param {string[]} [onlyLayers] - Optional: only decrypt these layers
196
+ * @returns {Promise<{ layers: Object, metadata: Object, seal: Object }>}
197
+ */
198
+ export async function disassembleContainer(buffer, passphrase, onlyLayers = null) {
199
+ // Validate minimum size
200
+ if (buffer.length < 4 + 1 + 36 + 32 + 8 + 32 + 1 + 64) {
201
+ throw new Error("Invalid container: too short");
202
+ }
203
+
204
+ let offset = 0;
205
+
206
+ // Magic bytes
207
+ const magic = buffer.subarray(offset, offset + 4);
208
+ offset += 4;
209
+ if (Buffer.compare(magic, MAGIC) !== 0) {
210
+ throw new Error("Invalid container: wrong magic bytes (expected 0x304E5350)");
211
+ }
212
+
213
+ // Version
214
+ const version = buffer.readUInt8(offset);
215
+ offset += 1;
216
+ if (version !== VERSION) {
217
+ throw new Error(`Unsupported container version: ${version} (expected ${VERSION})`);
218
+ }
219
+
220
+ // Transfer ID
221
+ const transferId = buffer.subarray(offset, offset + 36).toString("utf8");
222
+ offset += 36;
223
+
224
+ // Creator public key
225
+ const creatorPubKey = buffer.subarray(offset, offset + 32);
226
+ offset += 32;
227
+
228
+ // Timestamp
229
+ const timestamp = Number(buffer.readBigUInt64BE(offset));
230
+ offset += 8;
231
+
232
+ // Seal of Truth
233
+ const seal = buffer.subarray(offset, offset + 32);
234
+ offset += 32;
235
+
236
+ // Layer count
237
+ const layerCount = buffer.readUInt8(offset);
238
+ offset += 1;
239
+
240
+ // Read layers
241
+ const sealedLayers = new Map();
242
+ const layerCiphertexts = [];
243
+
244
+ for (let i = 0; i < layerCount; i++) {
245
+ // Layer name
246
+ const nameLen = buffer.readUInt16BE(offset);
247
+ offset += 2;
248
+ const name = buffer.subarray(offset, offset + nameLen).toString("utf8");
249
+ offset += nameLen;
250
+
251
+ // Flags
252
+ const flags = buffer.readUInt8(offset);
253
+ offset += 1;
254
+ const doubleEncrypted = (flags & 1) !== 0;
255
+
256
+ // Salt
257
+ const salt = Buffer.from(buffer.subarray(offset, offset + SALT_LENGTH));
258
+ offset += SALT_LENGTH;
259
+
260
+ // IV
261
+ const iv = Buffer.from(buffer.subarray(offset, offset + IV_LENGTH));
262
+ offset += IV_LENGTH;
263
+
264
+ // Auth tag
265
+ const tag = Buffer.from(buffer.subarray(offset, offset + TAG_LENGTH));
266
+ offset += TAG_LENGTH;
267
+
268
+ // Argon salt (only for double-encrypted)
269
+ let argonSalt = null;
270
+ if (doubleEncrypted) {
271
+ argonSalt = Buffer.from(buffer.subarray(offset, offset + SALT_LENGTH));
272
+ offset += SALT_LENGTH;
273
+ }
274
+
275
+ // Ciphertext
276
+ const ctLen = buffer.readUInt32BE(offset);
277
+ offset += 4;
278
+ const ciphertext = Buffer.from(buffer.subarray(offset, offset + ctLen));
279
+ offset += ctLen;
280
+
281
+ sealedLayers.set(name, { ciphertext, iv, salt, tag, doubleEncrypted, argonSalt });
282
+ layerCiphertexts.push(ciphertext);
283
+ }
284
+
285
+ // Escrow block
286
+ const escrowLen = buffer.readUInt32BE(offset);
287
+ offset += 4;
288
+ let escrowBlock = null;
289
+ if (escrowLen > 0) {
290
+ escrowBlock = buffer.subarray(offset, offset + escrowLen);
291
+ offset += escrowLen;
292
+ }
293
+
294
+ // Signature (last 64 bytes)
295
+ const signature = buffer.subarray(offset, offset + 64);
296
+ const unsignedData = buffer.subarray(0, offset);
297
+
298
+ // Verify Ed25519 signature
299
+ const sigValid = verifySignature(unsignedData, signature, creatorPubKey);
300
+ if (!sigValid) {
301
+ throw new Error("Invalid container: Ed25519 signature verification failed");
302
+ }
303
+
304
+ // Verify Seal of Truth
305
+ const sealValid = verifySeal(seal, transferId, timestamp, creatorPubKey, layerCiphertexts);
306
+
307
+ // Decrypt requested layers
308
+ const decrypted = {};
309
+ for (const [name, layerInfo] of sealedLayers) {
310
+ if (onlyLayers && !onlyLayers.includes(name)) continue;
311
+
312
+ try {
313
+ if (name === "credentials" && layerInfo.doubleEncrypted) {
314
+ decrypted[name] = await unsealCredentials(
315
+ layerInfo.ciphertext, passphrase,
316
+ layerInfo.iv, layerInfo.salt, layerInfo.tag,
317
+ layerInfo.argonSalt
318
+ );
319
+ } else {
320
+ decrypted[name] = unsealLayer(
321
+ name, layerInfo.ciphertext, passphrase,
322
+ layerInfo.iv, layerInfo.salt, layerInfo.tag
323
+ );
324
+ }
325
+ } catch (err) {
326
+ decrypted[name] = { error: err.message };
327
+ }
328
+ }
329
+
330
+ return {
331
+ layers: decrypted,
332
+ metadata: {
333
+ version,
334
+ transferId,
335
+ timestamp,
336
+ creatorPubKey: creatorPubKey.toString("hex"),
337
+ layerCount,
338
+ layerNames: Array.from(sealedLayers.keys()),
339
+ hasEscrow: escrowBlock !== null,
340
+ },
341
+ seal: {
342
+ hash: seal.toString("hex"),
343
+ valid: sealValid,
344
+ },
345
+ signature: {
346
+ valid: sigValid,
347
+ },
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Inspect a container without decrypting — metadata + seal only.
353
+ *
354
+ * @param {Buffer} buffer
355
+ * @returns {{ metadata: Object, seal: Object, signature: Object }}
356
+ */
357
+ export function inspectContainer(buffer) {
358
+ if (buffer.length < 4 + 1 + 36 + 32 + 8 + 32 + 1 + 64) {
359
+ throw new Error("Invalid container: too short");
360
+ }
361
+
362
+ let offset = 0;
363
+
364
+ const magic = buffer.subarray(offset, offset + 4);
365
+ offset += 4;
366
+ if (Buffer.compare(magic, MAGIC) !== 0) {
367
+ throw new Error("Invalid container: wrong magic bytes");
368
+ }
369
+
370
+ const version = buffer.readUInt8(offset);
371
+ offset += 1;
372
+
373
+ const transferId = buffer.subarray(offset, offset + 36).toString("utf8");
374
+ offset += 36;
375
+
376
+ const creatorPubKey = buffer.subarray(offset, offset + 32);
377
+ offset += 32;
378
+
379
+ const timestamp = Number(buffer.readBigUInt64BE(offset));
380
+ offset += 8;
381
+
382
+ const seal = buffer.subarray(offset, offset + 32);
383
+ offset += 32;
384
+
385
+ const layerCount = buffer.readUInt8(offset);
386
+ offset += 1;
387
+
388
+ // Read layer names only (skip data)
389
+ const layerNames = [];
390
+ const layerCiphertexts = [];
391
+
392
+ for (let i = 0; i < layerCount; i++) {
393
+ const nameLen = buffer.readUInt16BE(offset);
394
+ offset += 2;
395
+ const name = buffer.subarray(offset, offset + nameLen).toString("utf8");
396
+ offset += nameLen;
397
+ layerNames.push(name);
398
+
399
+ const flags = buffer.readUInt8(offset);
400
+ offset += 1;
401
+ const doubleEncrypted = (flags & 1) !== 0;
402
+
403
+ // Skip salt + iv + tag
404
+ offset += SALT_LENGTH + IV_LENGTH + TAG_LENGTH;
405
+
406
+ // Skip argon salt if double-encrypted
407
+ if (doubleEncrypted) offset += SALT_LENGTH;
408
+
409
+ // Skip ciphertext
410
+ const ctLen = buffer.readUInt32BE(offset);
411
+ offset += 4;
412
+ layerCiphertexts.push(buffer.subarray(offset, offset + ctLen));
413
+ offset += ctLen;
414
+ }
415
+
416
+ // Escrow block
417
+ const escrowLen = buffer.readUInt32BE(offset);
418
+ offset += 4;
419
+ let escrowPartyCount = 0;
420
+ if (escrowLen > 0) {
421
+ escrowPartyCount = buffer.readUInt8(offset);
422
+ }
423
+ offset += escrowLen;
424
+
425
+ // Verify signature
426
+ const signature = buffer.subarray(offset, offset + 64);
427
+ const unsignedData = buffer.subarray(0, offset);
428
+ const sigValid = verifySignature(unsignedData, signature, creatorPubKey);
429
+
430
+ // Verify seal
431
+ const sealValid = verifySeal(seal, transferId, timestamp, creatorPubKey, layerCiphertexts);
432
+
433
+ return {
434
+ metadata: {
435
+ version,
436
+ transferId,
437
+ timestamp,
438
+ created: new Date(timestamp).toISOString(),
439
+ creatorPubKey: creatorPubKey.toString("hex"),
440
+ layerCount,
441
+ layerNames,
442
+ hasEscrow: escrowLen > 0,
443
+ escrowPartyCount,
444
+ containerSize: buffer.length,
445
+ },
446
+ seal: {
447
+ hash: seal.toString("hex"),
448
+ valid: sealValid,
449
+ algorithm: "SHA3-256",
450
+ },
451
+ signature: {
452
+ valid: sigValid,
453
+ algorithm: "Ed25519",
454
+ },
455
+ patent: "US Provisional #63/990,046",
456
+ };
457
+ }
458
+
459
+ /**
460
+ * Save container to a .0nv file.
461
+ *
462
+ * @param {Buffer} container - Container buffer
463
+ * @param {string} filePath - Output file path
464
+ */
465
+ export function saveContainer(container, filePath) {
466
+ const path = filePath.endsWith(FILE_EXTENSION) ? filePath : filePath + FILE_EXTENSION;
467
+ writeFileSync(path, container);
468
+ return path;
469
+ }
470
+
471
+ /**
472
+ * Load container from a .0nv file.
473
+ *
474
+ * @param {string} filePath - Input file path
475
+ * @returns {Buffer}
476
+ */
477
+ export function loadContainer(filePath) {
478
+ return readFileSync(filePath);
479
+ }