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,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
+ };
@@ -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 };