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,278 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// 0nMCP — Vault: Container Crypto Primitives
|
|
3
|
+
// ============================================================
|
|
4
|
+
// AES-256-GCM encryption, Argon2id key derivation, X25519 ECDH,
|
|
5
|
+
// Ed25519 signing, SHA3-256 hashing for the 0nVault container.
|
|
6
|
+
//
|
|
7
|
+
// Patent Pending: US Provisional Patent Application #63/990,046
|
|
8
|
+
// "System and Method for Semantically-Layered Encrypted Digital
|
|
9
|
+
// Business Asset Transfer with Multi-Party Escrow Key Distribution
|
|
10
|
+
// and Public-Verifiable State Certification"
|
|
11
|
+
// ============================================================
|
|
12
|
+
|
|
13
|
+
import { randomBytes, createCipheriv, createDecipheriv, randomUUID, pbkdf2Sync } from "crypto";
|
|
14
|
+
import { createRequire } from "module";
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
|
|
18
|
+
// ── Load optional dependencies ───────────────────────────────
|
|
19
|
+
let naclModule = null;
|
|
20
|
+
let sha3Module = null;
|
|
21
|
+
|
|
22
|
+
try { naclModule = require("tweetnacl"); } catch {}
|
|
23
|
+
try { sha3Module = require("js-sha3"); } catch {}
|
|
24
|
+
|
|
25
|
+
function requireNacl() {
|
|
26
|
+
if (naclModule) return naclModule;
|
|
27
|
+
throw new Error("tweetnacl package required. Install: npm i tweetnacl");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function requireSha3() {
|
|
31
|
+
if (sha3Module) return sha3Module;
|
|
32
|
+
throw new Error("js-sha3 package required. Install: npm i js-sha3");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Constants ────────────────────────────────────────────────
|
|
36
|
+
const ALGORITHM = "aes-256-gcm";
|
|
37
|
+
const KEY_LENGTH = 32; // 256 bits
|
|
38
|
+
const IV_LENGTH = 12; // 96 bits (GCM recommended)
|
|
39
|
+
const SALT_LENGTH = 32; // 256 bits
|
|
40
|
+
const TAG_LENGTH = 16; // 128 bits (GCM auth tag)
|
|
41
|
+
|
|
42
|
+
// Argon2id parameters for credentials double-encryption
|
|
43
|
+
const ARGON2_MEMORY = 65536; // 64MB
|
|
44
|
+
const ARGON2_ITERATIONS = 4;
|
|
45
|
+
const ARGON2_PARALLELISM = 2;
|
|
46
|
+
const ARGON2_HASH_LENGTH = 32; // 256 bits
|
|
47
|
+
|
|
48
|
+
// ── Key Derivation ───────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Derive key from passphrase using PBKDF2-SHA512.
|
|
52
|
+
*/
|
|
53
|
+
function deriveKeyPBKDF2(passphrase, salt) {
|
|
54
|
+
return pbkdf2Sync(passphrase, salt, 100000, KEY_LENGTH, "sha512");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Derive key using Argon2id for credentials double-encryption.
|
|
59
|
+
*
|
|
60
|
+
* @param {string} passphrase
|
|
61
|
+
* @param {Buffer} salt
|
|
62
|
+
* @returns {Promise<Buffer>} 32-byte key
|
|
63
|
+
*/
|
|
64
|
+
export async function deriveKeyArgon2(passphrase, salt) {
|
|
65
|
+
let argon2;
|
|
66
|
+
try { argon2 = await import("argon2"); } catch {
|
|
67
|
+
throw new Error("argon2 package required for credentials layer. Install: npm i argon2");
|
|
68
|
+
}
|
|
69
|
+
const mod = argon2.default || argon2;
|
|
70
|
+
const hash = await mod.hash(passphrase, {
|
|
71
|
+
type: 2, // argon2id
|
|
72
|
+
memoryCost: ARGON2_MEMORY,
|
|
73
|
+
timeCost: ARGON2_ITERATIONS,
|
|
74
|
+
parallelism: ARGON2_PARALLELISM,
|
|
75
|
+
hashLength: ARGON2_HASH_LENGTH,
|
|
76
|
+
salt,
|
|
77
|
+
raw: true,
|
|
78
|
+
});
|
|
79
|
+
return hash;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── AES-256-GCM ──────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Encrypt data with AES-256-GCM using a passphrase-derived key.
|
|
86
|
+
* Each call generates a unique IV and salt.
|
|
87
|
+
*
|
|
88
|
+
* @param {Buffer|string} plaintext - Data to encrypt
|
|
89
|
+
* @param {string} passphrase - Encryption passphrase
|
|
90
|
+
* @returns {{ ciphertext: Buffer, iv: Buffer, salt: Buffer, tag: Buffer }}
|
|
91
|
+
*/
|
|
92
|
+
export function encryptAES(plaintext, passphrase) {
|
|
93
|
+
const salt = randomBytes(SALT_LENGTH);
|
|
94
|
+
const iv = randomBytes(IV_LENGTH);
|
|
95
|
+
const key = deriveKeyPBKDF2(passphrase, salt);
|
|
96
|
+
|
|
97
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
98
|
+
const data = typeof plaintext === "string" ? Buffer.from(plaintext, "utf8") : plaintext;
|
|
99
|
+
const encrypted = Buffer.concat([cipher.update(data), cipher.final()]);
|
|
100
|
+
const tag = cipher.getAuthTag();
|
|
101
|
+
|
|
102
|
+
return { ciphertext: encrypted, iv, salt, tag };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Decrypt AES-256-GCM encrypted data.
|
|
107
|
+
*
|
|
108
|
+
* @param {Buffer} ciphertext - Encrypted data
|
|
109
|
+
* @param {string} passphrase - Decryption passphrase
|
|
110
|
+
* @param {Buffer} iv - Initialization vector
|
|
111
|
+
* @param {Buffer} salt - PBKDF2 salt
|
|
112
|
+
* @param {Buffer} tag - GCM auth tag
|
|
113
|
+
* @returns {Buffer} Decrypted data
|
|
114
|
+
*/
|
|
115
|
+
export function decryptAES(ciphertext, passphrase, iv, salt, tag) {
|
|
116
|
+
const key = deriveKeyPBKDF2(passphrase, salt);
|
|
117
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
118
|
+
decipher.setAuthTag(tag);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
122
|
+
} catch {
|
|
123
|
+
throw new Error("Decryption failed — wrong passphrase or corrupted data");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Encrypt data with a raw 256-bit key (for escrow layer keys).
|
|
129
|
+
*
|
|
130
|
+
* @param {Buffer} plaintext - Data to encrypt
|
|
131
|
+
* @param {Buffer} key - 32-byte AES key
|
|
132
|
+
* @returns {{ ciphertext: Buffer, iv: Buffer, tag: Buffer }}
|
|
133
|
+
*/
|
|
134
|
+
export function encryptAESRaw(plaintext, key) {
|
|
135
|
+
const iv = randomBytes(IV_LENGTH);
|
|
136
|
+
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
137
|
+
const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
138
|
+
const tag = cipher.getAuthTag();
|
|
139
|
+
return { ciphertext: encrypted, iv, tag };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Decrypt with a raw 256-bit key.
|
|
144
|
+
*
|
|
145
|
+
* @param {Buffer} ciphertext
|
|
146
|
+
* @param {Buffer} key - 32-byte AES key
|
|
147
|
+
* @param {Buffer} iv
|
|
148
|
+
* @param {Buffer} tag
|
|
149
|
+
* @returns {Buffer}
|
|
150
|
+
*/
|
|
151
|
+
export function decryptAESRaw(ciphertext, key, iv, tag) {
|
|
152
|
+
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
153
|
+
decipher.setAuthTag(tag);
|
|
154
|
+
try {
|
|
155
|
+
return Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
156
|
+
} catch {
|
|
157
|
+
throw new Error("Decryption failed — wrong key or corrupted data");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Ed25519 Signing ──────────────────────────────────────────
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Generate Ed25519 signing keypair.
|
|
165
|
+
* @returns {{ publicKey: Buffer, secretKey: Buffer }}
|
|
166
|
+
*/
|
|
167
|
+
export function generateSigningKeyPair() {
|
|
168
|
+
const nacl = requireNacl();
|
|
169
|
+
const pair = nacl.sign.keyPair();
|
|
170
|
+
return {
|
|
171
|
+
publicKey: Buffer.from(pair.publicKey),
|
|
172
|
+
secretKey: Buffer.from(pair.secretKey),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Sign data with Ed25519.
|
|
178
|
+
* @param {Buffer} data - Data to sign
|
|
179
|
+
* @param {Buffer} secretKey - 64-byte Ed25519 secret key
|
|
180
|
+
* @returns {Buffer} 64-byte signature
|
|
181
|
+
*/
|
|
182
|
+
export function sign(data, secretKey) {
|
|
183
|
+
const nacl = requireNacl();
|
|
184
|
+
const sig = nacl.sign.detached(new Uint8Array(data), new Uint8Array(secretKey));
|
|
185
|
+
return Buffer.from(sig);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Verify Ed25519 signature.
|
|
190
|
+
* @param {Buffer} data
|
|
191
|
+
* @param {Buffer} signature - 64-byte signature
|
|
192
|
+
* @param {Buffer} publicKey - 32-byte public key
|
|
193
|
+
* @returns {boolean}
|
|
194
|
+
*/
|
|
195
|
+
export function verifySignature(data, signature, publicKey) {
|
|
196
|
+
const nacl = requireNacl();
|
|
197
|
+
return nacl.sign.detached.verify(
|
|
198
|
+
new Uint8Array(data),
|
|
199
|
+
new Uint8Array(signature),
|
|
200
|
+
new Uint8Array(publicKey)
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── X25519 ECDH ──────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Generate X25519 keypair for escrow key exchange.
|
|
208
|
+
* @returns {{ publicKey: Buffer, secretKey: Buffer }}
|
|
209
|
+
*/
|
|
210
|
+
export function generateEscrowKeyPair() {
|
|
211
|
+
const nacl = requireNacl();
|
|
212
|
+
const pair = nacl.box.keyPair();
|
|
213
|
+
return {
|
|
214
|
+
publicKey: Buffer.from(pair.publicKey),
|
|
215
|
+
secretKey: Buffer.from(pair.secretKey),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Compute shared secret via X25519 ECDH.
|
|
221
|
+
* @param {Buffer} mySecretKey - 32-byte secret key
|
|
222
|
+
* @param {Buffer} theirPublicKey - 32-byte public key
|
|
223
|
+
* @returns {Buffer} 32-byte shared secret
|
|
224
|
+
*/
|
|
225
|
+
export function computeSharedSecret(mySecretKey, theirPublicKey) {
|
|
226
|
+
const nacl = requireNacl();
|
|
227
|
+
const shared = nacl.box.before(
|
|
228
|
+
new Uint8Array(theirPublicKey),
|
|
229
|
+
new Uint8Array(mySecretKey)
|
|
230
|
+
);
|
|
231
|
+
return Buffer.from(shared);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ── SHA3-256 ─────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Compute SHA3-256 hash.
|
|
238
|
+
* @param {Buffer|string} data
|
|
239
|
+
* @returns {Buffer} 32-byte hash
|
|
240
|
+
*/
|
|
241
|
+
export function sha3(data) {
|
|
242
|
+
const sha3Lib = requireSha3();
|
|
243
|
+
const input = typeof data === "string" ? data : new Uint8Array(data);
|
|
244
|
+
const hash = sha3Lib.sha3_256.create().update(input).digest();
|
|
245
|
+
return Buffer.from(hash);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Compute SHA3-256 hash and return hex string.
|
|
250
|
+
* @param {Buffer|string} data
|
|
251
|
+
* @returns {string} 64-char hex hash
|
|
252
|
+
*/
|
|
253
|
+
export function sha3Hex(data) {
|
|
254
|
+
return sha3(data).toString("hex");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── UUID ─────────────────────────────────────────────────────
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Generate a UUID v4 transfer ID.
|
|
261
|
+
* @returns {string}
|
|
262
|
+
*/
|
|
263
|
+
export function generateTransferId() {
|
|
264
|
+
return randomUUID();
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Exports ──────────────────────────────────────────────────
|
|
268
|
+
export {
|
|
269
|
+
ALGORITHM,
|
|
270
|
+
KEY_LENGTH,
|
|
271
|
+
IV_LENGTH,
|
|
272
|
+
SALT_LENGTH,
|
|
273
|
+
TAG_LENGTH,
|
|
274
|
+
ARGON2_MEMORY,
|
|
275
|
+
ARGON2_ITERATIONS,
|
|
276
|
+
ARGON2_PARALLELISM,
|
|
277
|
+
ARGON2_HASH_LENGTH,
|
|
278
|
+
};
|
package/vault/escrow.js
ADDED
|
@@ -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 };
|