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.
- package/README.md +24 -23
- package/cli.js +431 -1
- package/command-runner.js +224 -0
- package/commands.js +115 -0
- package/index.js +8 -1
- package/lib/stats.json +1 -1
- package/package.json +26 -3
- package/vault/container.js +479 -0
- package/vault/crypto-container.js +278 -0
- package/vault/escrow.js +227 -0
- package/vault/layers.js +254 -0
- package/vault/registry.js +159 -0
- package/vault/seal.js +74 -0
- package/vault/tools-container.js +356 -0
|
@@ -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
|
+
}
|