shogun-core 5.2.0 → 5.2.1
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/dist/browser/defaultVendors-node_modules_hpke_chacha20poly1305_esm_mod_js.shogun-core.js +1220 -0
- package/dist/browser/defaultVendors-node_modules_hpke_chacha20poly1305_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_hpke_hybridkem-x-wing_esm_mod_js.shogun-core.js +844 -0
- package/dist/browser/defaultVendors-node_modules_hpke_hybridkem-x-wing_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_mlkem_esm_mod_js.shogun-core.js +2335 -0
- package/dist/browser/defaultVendors-node_modules_mlkem_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_ciphers_chacha_js.shogun-core.js +999 -0
- package/dist/browser/defaultVendors-node_modules_noble_ciphers_chacha_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_curve_js-node_modules_noble_curves_esm_-1ce4ed.shogun-core.js +1651 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_curve_js-node_modules_noble_curves_esm_-1ce4ed.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_edwards_js-node_modules_noble_curves_es-a82056.shogun-core.js +825 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_abstract_edwards_js-node_modules_noble_curves_es-a82056.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed25519_js.shogun-core.js +508 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed25519_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed448_js.shogun-core.js +747 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_ed448_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_nist_js.shogun-core.js +1608 -0
- package/dist/browser/defaultVendors-node_modules_noble_curves_esm_nist_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_noble_post-quantum_ml-dsa_js.shogun-core.js +2117 -0
- package/dist/browser/defaultVendors-node_modules_noble_post-quantum_ml-dsa_js.shogun-core.js.map +1 -0
- package/dist/browser/defaultVendors-node_modules_openpgp_dist_openpgp_min_mjs.shogun-core.js +86 -0
- package/dist/browser/defaultVendors-node_modules_openpgp_dist_openpgp_min_mjs.shogun-core.js.map +1 -0
- package/dist/browser/node_modules_hpke_ml-kem_esm_mod_js.shogun-core.js +539 -0
- package/dist/browser/node_modules_hpke_ml-kem_esm_mod_js.shogun-core.js.map +1 -0
- package/dist/browser/shogun-core.js +160386 -0
- package/dist/browser/shogun-core.js.map +1 -0
- package/dist/config/simplified-config.js +236 -0
- package/dist/core.js +329 -0
- package/dist/crypto/asymmetric.js +99 -0
- package/dist/crypto/double-ratchet.js +370 -0
- package/dist/crypto/file-encryption.js +213 -0
- package/dist/crypto/hashing.js +87 -0
- package/dist/crypto/index.js +34 -0
- package/dist/crypto/mls-codec.js +202 -0
- package/dist/crypto/mls.js +550 -0
- package/dist/crypto/pgp.js +390 -0
- package/dist/crypto/random-generation.js +341 -0
- package/dist/crypto/sframe.js +350 -0
- package/dist/crypto/signal-protocol.js +376 -0
- package/dist/crypto/symmetric.js +91 -0
- package/dist/crypto/types.js +2 -0
- package/dist/crypto/utils.js +140 -0
- package/dist/examples/auth-test.js +253 -0
- package/dist/examples/crypto-identity-example.js +151 -0
- package/dist/examples/crypto-working-test.js +83 -0
- package/dist/examples/double-ratchet-test.js +155 -0
- package/dist/examples/mls-advanced-example.js +294 -0
- package/dist/examples/mls-sframe-test.js +304 -0
- package/dist/examples/pgp-example.js +200 -0
- package/dist/examples/quick-auth-test.js +61 -0
- package/dist/examples/random-generation-test.js +151 -0
- package/dist/examples/signal-protocol-test.js +38 -0
- package/dist/examples/simple-api-test.js +114 -0
- package/dist/examples/simple-crypto-identity-example.js +84 -0
- package/dist/examples/timeout-test.js +227 -0
- package/dist/examples/zkproof-credentials-example.js +212 -0
- package/dist/examples/zkproof-example.js +201 -0
- package/dist/gundb/api.js +435 -0
- package/dist/gundb/crypto.js +283 -0
- package/dist/gundb/db.js +1946 -0
- package/dist/gundb/derive.js +232 -0
- package/dist/gundb/errors.js +76 -0
- package/dist/gundb/index.js +22 -0
- package/dist/gundb/rxjs.js +447 -0
- package/dist/gundb/types.js +5 -0
- package/dist/index.js +58 -0
- package/dist/interfaces/common.js +2 -0
- package/dist/interfaces/events.js +40 -0
- package/dist/interfaces/plugin.js +2 -0
- package/dist/interfaces/shogun.js +37 -0
- package/dist/managers/AuthManager.js +226 -0
- package/dist/managers/CoreInitializer.js +228 -0
- package/dist/managers/CryptoIdentityManager.js +366 -0
- package/dist/managers/EventManager.js +70 -0
- package/dist/managers/PluginManager.js +299 -0
- package/dist/plugins/base.js +50 -0
- package/dist/plugins/index.js +32 -0
- package/dist/plugins/nostr/index.js +20 -0
- package/dist/plugins/nostr/nostrConnector.js +419 -0
- package/dist/plugins/nostr/nostrConnectorPlugin.js +453 -0
- package/dist/plugins/nostr/nostrSigner.js +319 -0
- package/dist/plugins/nostr/types.js +2 -0
- package/dist/plugins/smartwallet/index.js +18 -0
- package/dist/plugins/smartwallet/smartWalletPlugin.js +511 -0
- package/dist/plugins/smartwallet/types.js +2 -0
- package/dist/plugins/web3/index.js +20 -0
- package/dist/plugins/web3/types.js +2 -0
- package/dist/plugins/web3/web3Connector.js +533 -0
- package/dist/plugins/web3/web3ConnectorPlugin.js +455 -0
- package/dist/plugins/web3/web3Signer.js +314 -0
- package/dist/plugins/webauthn/index.js +19 -0
- package/dist/plugins/webauthn/types.js +14 -0
- package/dist/plugins/webauthn/webauthn.js +496 -0
- package/dist/plugins/webauthn/webauthnPlugin.js +489 -0
- package/dist/plugins/webauthn/webauthnSigner.js +310 -0
- package/dist/plugins/zkproof/index.js +53 -0
- package/dist/plugins/zkproof/types.js +2 -0
- package/dist/plugins/zkproof/zkCredentials.js +213 -0
- package/dist/plugins/zkproof/zkProofConnector.js +198 -0
- package/dist/plugins/zkproof/zkProofPlugin.js +272 -0
- package/dist/storage/storage.js +145 -0
- package/dist/types/config/simplified-config.d.ts +114 -0
- package/dist/types/core.d.ts +305 -0
- package/dist/types/crypto/asymmetric.d.ts +6 -0
- package/dist/types/crypto/double-ratchet.d.ts +22 -0
- package/dist/types/crypto/file-encryption.d.ts +19 -0
- package/dist/types/crypto/hashing.d.ts +9 -0
- package/dist/types/crypto/index.d.ts +13 -0
- package/dist/types/crypto/mls-codec.d.ts +39 -0
- package/dist/types/crypto/mls.d.ts +130 -0
- package/dist/types/crypto/pgp.d.ts +95 -0
- package/dist/types/crypto/random-generation.d.ts +35 -0
- package/dist/types/crypto/sframe.d.ts +102 -0
- package/dist/types/crypto/signal-protocol.d.ts +26 -0
- package/dist/types/crypto/symmetric.d.ts +9 -0
- package/dist/types/crypto/types.d.ts +144 -0
- package/dist/types/crypto/utils.d.ts +22 -0
- package/dist/types/examples/auth-test.d.ts +8 -0
- package/dist/types/examples/crypto-identity-example.d.ts +5 -0
- package/dist/types/examples/crypto-working-test.d.ts +1 -0
- package/dist/types/examples/double-ratchet-test.d.ts +1 -0
- package/dist/types/examples/mls-advanced-example.d.ts +53 -0
- package/dist/types/examples/mls-sframe-test.d.ts +1 -0
- package/dist/types/examples/pgp-example.d.ts +75 -0
- package/dist/types/examples/quick-auth-test.d.ts +8 -0
- package/dist/types/examples/random-generation-test.d.ts +1 -0
- package/dist/types/examples/signal-protocol-test.d.ts +1 -0
- package/dist/types/examples/simple-api-test.d.ts +10 -0
- package/dist/types/examples/simple-crypto-identity-example.d.ts +6 -0
- package/dist/types/examples/timeout-test.d.ts +8 -0
- package/dist/types/examples/zkproof-credentials-example.d.ts +12 -0
- package/dist/types/examples/zkproof-example.d.ts +11 -0
- package/dist/types/gundb/api.d.ts +185 -0
- package/dist/types/gundb/crypto.d.ts +95 -0
- package/dist/types/gundb/db.d.ts +397 -0
- package/dist/types/gundb/derive.d.ts +21 -0
- package/dist/types/gundb/errors.d.ts +42 -0
- package/dist/types/gundb/index.d.ts +3 -0
- package/dist/types/gundb/rxjs.d.ts +110 -0
- package/dist/types/gundb/types.d.ts +255 -0
- package/dist/types/index.d.ts +16 -0
- package/dist/types/interfaces/common.d.ts +85 -0
- package/dist/types/interfaces/events.d.ts +131 -0
- package/dist/types/interfaces/plugin.d.ts +162 -0
- package/dist/types/interfaces/shogun.d.ts +208 -0
- package/dist/types/managers/AuthManager.d.ts +72 -0
- package/dist/types/managers/CoreInitializer.d.ts +40 -0
- package/dist/types/managers/CryptoIdentityManager.d.ts +102 -0
- package/dist/types/managers/EventManager.d.ts +49 -0
- package/dist/types/managers/PluginManager.d.ts +145 -0
- package/dist/types/plugins/base.d.ts +35 -0
- package/dist/types/plugins/index.d.ts +18 -0
- package/dist/types/plugins/nostr/index.d.ts +4 -0
- package/dist/types/plugins/nostr/nostrConnector.d.ts +119 -0
- package/dist/types/plugins/nostr/nostrConnectorPlugin.d.ts +163 -0
- package/dist/types/plugins/nostr/nostrSigner.d.ts +105 -0
- package/dist/types/plugins/nostr/types.d.ts +122 -0
- package/dist/types/plugins/smartwallet/index.d.ts +2 -0
- package/dist/types/plugins/smartwallet/smartWalletPlugin.d.ts +67 -0
- package/dist/types/plugins/smartwallet/types.d.ts +80 -0
- package/dist/types/plugins/web3/index.d.ts +4 -0
- package/dist/types/plugins/web3/types.d.ts +107 -0
- package/dist/types/plugins/web3/web3Connector.d.ts +129 -0
- package/dist/types/plugins/web3/web3ConnectorPlugin.d.ts +160 -0
- package/dist/types/plugins/web3/web3Signer.d.ts +114 -0
- package/dist/types/plugins/webauthn/index.d.ts +3 -0
- package/dist/types/plugins/webauthn/types.d.ts +183 -0
- package/dist/types/plugins/webauthn/webauthn.d.ts +129 -0
- package/dist/types/plugins/webauthn/webauthnPlugin.d.ts +179 -0
- package/dist/types/plugins/webauthn/webauthnSigner.d.ts +91 -0
- package/dist/types/plugins/zkproof/index.d.ts +48 -0
- package/dist/types/plugins/zkproof/types.d.ts +123 -0
- package/dist/types/plugins/zkproof/zkCredentials.d.ts +112 -0
- package/dist/types/plugins/zkproof/zkProofConnector.d.ts +46 -0
- package/dist/types/plugins/zkproof/zkProofPlugin.d.ts +76 -0
- package/dist/types/storage/storage.d.ts +51 -0
- package/dist/types/utils/errorHandler.d.ts +119 -0
- package/dist/types/utils/eventEmitter.d.ts +39 -0
- package/dist/types/utils/seedPhrase.d.ts +50 -0
- package/dist/types/utils/validation.d.ts +27 -0
- package/dist/utils/errorHandler.js +246 -0
- package/dist/utils/eventEmitter.js +79 -0
- package/dist/utils/seedPhrase.js +97 -0
- package/dist/utils/validation.js +81 -0
- package/package.json +10 -1
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MLS (Message Layer Security) Manager
|
|
4
|
+
* RFC 9420 implementation using ts-mls library
|
|
5
|
+
* Provides end-to-end encrypted group messaging with forward secrecy
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.MLSManager = void 0;
|
|
9
|
+
const ts_mls_1 = require("ts-mls");
|
|
10
|
+
// Helper to strip trailing null nodes per RFC 9420
|
|
11
|
+
function stripTrailingNulls(tree) {
|
|
12
|
+
let lastNonNull = tree.length - 1;
|
|
13
|
+
while (lastNonNull >= 0 && tree[lastNonNull] === null) {
|
|
14
|
+
lastNonNull--;
|
|
15
|
+
}
|
|
16
|
+
return tree.slice(0, lastNonNull + 1);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* MLSManager wraps the ts-mls functional API with a class-based interface
|
|
20
|
+
* for easier state management in applications
|
|
21
|
+
*/
|
|
22
|
+
class MLSManager {
|
|
23
|
+
constructor(userId) {
|
|
24
|
+
this.cipherSuite = null;
|
|
25
|
+
this.initialized = false;
|
|
26
|
+
this.groups = new Map();
|
|
27
|
+
this.keyPackage = null;
|
|
28
|
+
this.userId = userId;
|
|
29
|
+
this.credential = {
|
|
30
|
+
credentialType: "basic",
|
|
31
|
+
identity: new TextEncoder().encode(userId),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Initialize the MLS client with a ciphersuite
|
|
36
|
+
*/
|
|
37
|
+
async initialize() {
|
|
38
|
+
if (this.initialized) {
|
|
39
|
+
console.warn("MLS Manager already initialized");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
console.log(`🔐 [MLS] Initializing for user: ${this.userId}`);
|
|
44
|
+
// Use MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 (ID: 1)
|
|
45
|
+
// Using nobleCryptoProvider for compatibility (pure JS implementation)
|
|
46
|
+
const cipherSuiteName = "MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519";
|
|
47
|
+
const cs = (0, ts_mls_1.getCiphersuiteFromName)(cipherSuiteName);
|
|
48
|
+
this.cipherSuite = await ts_mls_1.nobleCryptoProvider.getCiphersuiteImpl(cs);
|
|
49
|
+
console.log(`✅ [MLS] Using ciphersuite: ${cipherSuiteName}`);
|
|
50
|
+
// Mark as initialized before generating key package
|
|
51
|
+
this.initialized = true;
|
|
52
|
+
// Generate initial key package for this user
|
|
53
|
+
await this.generateKeyPackage();
|
|
54
|
+
console.log("✅ [MLS] Initialized successfully");
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error("❌ [MLS] Failed to initialize:", error);
|
|
58
|
+
throw new Error(`MLS initialization failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Generate a new key package for joining groups
|
|
63
|
+
*/
|
|
64
|
+
async generateKeyPackage() {
|
|
65
|
+
this.ensureInitialized();
|
|
66
|
+
try {
|
|
67
|
+
console.log("🔑 [MLS] Generating key package");
|
|
68
|
+
const keyPackageResult = await (0, ts_mls_1.generateKeyPackage)(this.credential, (0, ts_mls_1.defaultCapabilities)(), ts_mls_1.defaultLifetime, [], this.cipherSuite);
|
|
69
|
+
this.keyPackage = {
|
|
70
|
+
...keyPackageResult,
|
|
71
|
+
userId: this.userId,
|
|
72
|
+
};
|
|
73
|
+
console.log("✅ [MLS] Key package generated");
|
|
74
|
+
return this.keyPackage;
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error("❌ [MLS] Failed to generate key package:", error);
|
|
78
|
+
throw new Error(`Key package generation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Get the current key package
|
|
83
|
+
*/
|
|
84
|
+
getKeyPackage() {
|
|
85
|
+
return this.keyPackage;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Create a new MLS group
|
|
89
|
+
*/
|
|
90
|
+
async createGroup(groupId) {
|
|
91
|
+
this.ensureInitialized();
|
|
92
|
+
try {
|
|
93
|
+
console.log(`📝 [MLS] Creating group: ${groupId}`);
|
|
94
|
+
if (!this.keyPackage) {
|
|
95
|
+
throw new Error("No key package available. Call generateKeyPackage() first.");
|
|
96
|
+
}
|
|
97
|
+
const groupIdBytes = new TextEncoder().encode(groupId);
|
|
98
|
+
// Create group using ts-mls
|
|
99
|
+
const groupState = await (0, ts_mls_1.createGroup)(groupIdBytes, this.keyPackage.publicPackage, this.keyPackage.privatePackage, [], this.cipherSuite);
|
|
100
|
+
this.groups.set(groupId, groupState);
|
|
101
|
+
const groupInfo = {
|
|
102
|
+
groupId: groupIdBytes,
|
|
103
|
+
members: [this.userId],
|
|
104
|
+
epoch: groupState.groupContext.epoch,
|
|
105
|
+
};
|
|
106
|
+
console.log(`✅ [MLS] Group created: ${groupId}, epoch: ${groupState.groupContext.epoch}`);
|
|
107
|
+
return groupInfo;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
console.error("❌ [MLS] Failed to create group:", error);
|
|
111
|
+
throw new Error(`Group creation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Add members to an existing group
|
|
116
|
+
*/
|
|
117
|
+
async addMembers(groupId, keyPackages) {
|
|
118
|
+
this.ensureInitialized();
|
|
119
|
+
try {
|
|
120
|
+
console.log(`➕ [MLS] Adding ${keyPackages.length} member(s) to group: ${groupId}`);
|
|
121
|
+
const groupState = this.groups.get(groupId);
|
|
122
|
+
if (!groupState) {
|
|
123
|
+
throw new Error(`Group ${groupId} not found`);
|
|
124
|
+
}
|
|
125
|
+
// Create add proposals for each key package
|
|
126
|
+
const addProposals = keyPackages.map((kp) => ({
|
|
127
|
+
proposalType: "add",
|
|
128
|
+
add: {
|
|
129
|
+
keyPackage: kp.publicPackage,
|
|
130
|
+
},
|
|
131
|
+
}));
|
|
132
|
+
// Create commit with add proposals
|
|
133
|
+
const commitResult = await (0, ts_mls_1.createCommit)({ state: groupState, cipherSuite: this.cipherSuite }, { extraProposals: addProposals });
|
|
134
|
+
// Update group state
|
|
135
|
+
this.groups.set(groupId, commitResult.newState);
|
|
136
|
+
if (!commitResult.welcome) {
|
|
137
|
+
throw new Error("No welcome message generated");
|
|
138
|
+
}
|
|
139
|
+
console.log(`✅ [MLS] Members added, new epoch: ${commitResult.newState.groupContext.epoch}`);
|
|
140
|
+
// Debug: Log the commit structure
|
|
141
|
+
console.group("🔍 [MLS Debug] Commit Structure");
|
|
142
|
+
console.log("commitResult keys:", Object.keys(commitResult));
|
|
143
|
+
console.log("commit:", commitResult.commit);
|
|
144
|
+
console.log("commit.privateMessage:", commitResult.commit?.privateMessage);
|
|
145
|
+
console.groupEnd();
|
|
146
|
+
// RFC 9420 Section 11.2: Commit Distribution
|
|
147
|
+
// ⚠️ IMPORTANT: The returned commit MUST be sent to all existing group members
|
|
148
|
+
// so they can process it with processCommit() to stay synchronized.
|
|
149
|
+
//
|
|
150
|
+
// Distribution flow:
|
|
151
|
+
// 1. Alice adds Bob: addMembers() returns { welcome, commit }
|
|
152
|
+
// 2. Alice sends welcome to Bob (new member)
|
|
153
|
+
// 3. Alice sends commit to existing members (Charlie, David, etc.)
|
|
154
|
+
// 4. All existing members call processCommit(commit) to update their state
|
|
155
|
+
//
|
|
156
|
+
// Without distributing the commit, existing members will remain at old epoch
|
|
157
|
+
// and won't be able to decrypt messages from the updated group.
|
|
158
|
+
// Convert ratchetTree to a real array (it's Uint8Array-like with numeric indices)
|
|
159
|
+
const ratchetTreeArray = Array.from(commitResult.newState.ratchetTree);
|
|
160
|
+
// RFC 9420: Strip trailing null nodes before transmission
|
|
161
|
+
const strippedTree = stripTrailingNulls(ratchetTreeArray);
|
|
162
|
+
console.log(`🔍 [MLS] Ratchet tree stripped: ${ratchetTreeArray.length} -> ${strippedTree.length} nodes`);
|
|
163
|
+
return {
|
|
164
|
+
welcome: commitResult.welcome,
|
|
165
|
+
ratchetTree: strippedTree,
|
|
166
|
+
commit: commitResult.commit,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
console.error("❌ [MLS] Failed to add members:", error);
|
|
171
|
+
throw new Error(`Adding members failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Process a Welcome message to join an MLS group
|
|
176
|
+
*
|
|
177
|
+
* RFC 9420 Compliance:
|
|
178
|
+
* - Interior null nodes represent blank parent nodes (unmerged positions)
|
|
179
|
+
* - These nulls are REQUIRED for proper binary tree structure
|
|
180
|
+
* - Trailing nulls are stripped by sender (per RFC 9420 requirement)
|
|
181
|
+
* - ratchetTree parameter is optional; ts-mls can extract from Welcome extension
|
|
182
|
+
*
|
|
183
|
+
* @param welcome - The Welcome message from group creator
|
|
184
|
+
* @param ratchetTree - Optional ratchet tree (normally provided out-of-band)
|
|
185
|
+
*/
|
|
186
|
+
async processWelcome(welcome, ratchetTree) {
|
|
187
|
+
this.ensureInitialized();
|
|
188
|
+
try {
|
|
189
|
+
console.log("📩 [MLS] Processing welcome message");
|
|
190
|
+
if (!this.keyPackage) {
|
|
191
|
+
throw new Error("No key package available");
|
|
192
|
+
}
|
|
193
|
+
// RFC 9420: Interior null nodes are valid (represent blank parent nodes)
|
|
194
|
+
// Trailing nulls are stripped by sender per RFC requirement
|
|
195
|
+
// Simply pass the tree as-is to ts-mls joinGroup()
|
|
196
|
+
if (ratchetTree && Array.isArray(ratchetTree)) {
|
|
197
|
+
const nullCount = ratchetTree.filter((n) => n === null).length;
|
|
198
|
+
console.log(`🔍 [MLS] Ratchet tree received: ${ratchetTree.length} nodes (${nullCount} interior nulls)`);
|
|
199
|
+
// DEBUG: Log structure of each node
|
|
200
|
+
console.group("🔍 [MLS Debug] Ratchet Tree Structure");
|
|
201
|
+
ratchetTree.forEach((node, i) => {
|
|
202
|
+
if (node === null) {
|
|
203
|
+
console.log(` Node ${i}: NULL`);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(` Node ${i}:`, {
|
|
207
|
+
type: typeof node,
|
|
208
|
+
isObject: typeof node === "object",
|
|
209
|
+
hasNodeType: node && typeof node === "object" && "nodeType" in node,
|
|
210
|
+
nodeType: node?.nodeType,
|
|
211
|
+
keys: node && typeof node === "object"
|
|
212
|
+
? Object.keys(node).slice(0, 5)
|
|
213
|
+
: "n/a",
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
console.groupEnd();
|
|
218
|
+
}
|
|
219
|
+
const groupState = await (0, ts_mls_1.joinGroup)(welcome, this.keyPackage.publicPackage, this.keyPackage.privatePackage, ts_mls_1.emptyPskIndex, this.cipherSuite, ratchetTree);
|
|
220
|
+
const groupId = new TextDecoder().decode(groupState.groupContext.groupId);
|
|
221
|
+
this.groups.set(groupId, groupState);
|
|
222
|
+
// Extract member identities from ratchet tree
|
|
223
|
+
const members = this.extractMembersFromState(groupState);
|
|
224
|
+
const groupInfo = {
|
|
225
|
+
groupId: groupState.groupContext.groupId,
|
|
226
|
+
members,
|
|
227
|
+
epoch: groupState.groupContext.epoch,
|
|
228
|
+
};
|
|
229
|
+
console.log(`✅ [MLS] Welcome processed, joined group: ${groupId}`);
|
|
230
|
+
return groupInfo;
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
console.error("❌ [MLS] Failed to process welcome:", error);
|
|
234
|
+
throw new Error(`Welcome processing failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Encrypt a message for a group
|
|
239
|
+
*/
|
|
240
|
+
async encryptMessage(groupId, plaintext) {
|
|
241
|
+
this.ensureInitialized();
|
|
242
|
+
try {
|
|
243
|
+
console.log(`🔒 [MLS] Encrypting message for group: ${groupId}`);
|
|
244
|
+
const groupState = this.groups.get(groupId);
|
|
245
|
+
if (!groupState) {
|
|
246
|
+
throw new Error(`Group ${groupId} not found`);
|
|
247
|
+
}
|
|
248
|
+
const plaintextBytes = new TextEncoder().encode(plaintext);
|
|
249
|
+
// Create application message
|
|
250
|
+
const result = await (0, ts_mls_1.createApplicationMessage)(groupState, plaintextBytes, this.cipherSuite);
|
|
251
|
+
// Update group state (for key ratcheting)
|
|
252
|
+
this.groups.set(groupId, result.newState);
|
|
253
|
+
// Encode the private message
|
|
254
|
+
const encoded = (0, ts_mls_1.encodeMlsMessage)({
|
|
255
|
+
privateMessage: result.privateMessage,
|
|
256
|
+
wireformat: "mls_private_message",
|
|
257
|
+
version: "mls10",
|
|
258
|
+
});
|
|
259
|
+
const envelope = {
|
|
260
|
+
groupId: new TextEncoder().encode(groupId),
|
|
261
|
+
ciphertext: encoded,
|
|
262
|
+
timestamp: Date.now(),
|
|
263
|
+
};
|
|
264
|
+
console.log("✅ [MLS] Message encrypted");
|
|
265
|
+
return envelope;
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
console.error("❌ [MLS] Failed to encrypt message:", error);
|
|
269
|
+
throw new Error(`Message encryption failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Decrypt a message from a group
|
|
274
|
+
*/
|
|
275
|
+
async decryptMessage(envelope) {
|
|
276
|
+
this.ensureInitialized();
|
|
277
|
+
try {
|
|
278
|
+
const groupId = new TextDecoder().decode(envelope.groupId);
|
|
279
|
+
console.log(`🔓 [MLS] Decrypting message for group: ${groupId}`);
|
|
280
|
+
const groupState = this.groups.get(groupId);
|
|
281
|
+
if (!groupState) {
|
|
282
|
+
throw new Error(`Group ${groupId} not found`);
|
|
283
|
+
}
|
|
284
|
+
// Decode the message
|
|
285
|
+
const decoded = (0, ts_mls_1.decodeMlsMessage)(envelope.ciphertext, 0);
|
|
286
|
+
if (!decoded) {
|
|
287
|
+
throw new Error("Failed to decode message");
|
|
288
|
+
}
|
|
289
|
+
const [decodedMessage] = decoded;
|
|
290
|
+
if (decodedMessage.wireformat !== "mls_private_message") {
|
|
291
|
+
throw new Error("Expected private message");
|
|
292
|
+
}
|
|
293
|
+
// Process the private message
|
|
294
|
+
const result = await (0, ts_mls_1.processPrivateMessage)(groupState, decodedMessage.privateMessage, ts_mls_1.emptyPskIndex, this.cipherSuite);
|
|
295
|
+
// Update group state
|
|
296
|
+
this.groups.set(groupId, result.newState);
|
|
297
|
+
if (result.kind !== "applicationMessage") {
|
|
298
|
+
throw new Error("Expected application message");
|
|
299
|
+
}
|
|
300
|
+
const plaintext = new TextDecoder().decode(result.message);
|
|
301
|
+
console.log("✅ [MLS] Message decrypted");
|
|
302
|
+
return plaintext;
|
|
303
|
+
}
|
|
304
|
+
catch (error) {
|
|
305
|
+
console.error("❌ [MLS] Failed to decrypt message:", error);
|
|
306
|
+
throw new Error(`Decryption failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Update the group keys (key rotation)
|
|
311
|
+
*/
|
|
312
|
+
async updateKey(groupId) {
|
|
313
|
+
this.ensureInitialized();
|
|
314
|
+
try {
|
|
315
|
+
console.log(`🔄 [MLS] Performing key rotation for group: ${groupId}`);
|
|
316
|
+
const groupState = this.groups.get(groupId);
|
|
317
|
+
if (!groupState) {
|
|
318
|
+
throw new Error(`Group ${groupId} not found`);
|
|
319
|
+
}
|
|
320
|
+
// Create update commit (forces path update)
|
|
321
|
+
const commitResult = await (0, ts_mls_1.createCommit)({ state: groupState, cipherSuite: this.cipherSuite }, {});
|
|
322
|
+
// Update group state
|
|
323
|
+
this.groups.set(groupId, commitResult.newState);
|
|
324
|
+
console.log(`✅ [MLS] Key rotation successful, new epoch: ${commitResult.newState.groupContext.epoch}`);
|
|
325
|
+
// Return the raw commit object for other members to process
|
|
326
|
+
return commitResult.commit;
|
|
327
|
+
}
|
|
328
|
+
catch (error) {
|
|
329
|
+
console.error("❌ [MLS] Failed to update key:", error);
|
|
330
|
+
throw new Error(`Key update failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Process a commit message (key rotation, member changes)
|
|
335
|
+
*
|
|
336
|
+
* RFC 9420 Section 12.1.8:
|
|
337
|
+
* - Update commits (key rotation) → PrivateMessage
|
|
338
|
+
* - Add/Remove commits → PublicMessage (for existing group members)
|
|
339
|
+
*
|
|
340
|
+
* This implementation handles both types based on wireformat.
|
|
341
|
+
*/
|
|
342
|
+
async processCommit(groupId, commit) {
|
|
343
|
+
this.ensureInitialized();
|
|
344
|
+
try {
|
|
345
|
+
console.log(`⚙️ [MLS] Processing commit for group: ${groupId}`);
|
|
346
|
+
console.log(`🔍 [MLS Debug] Commit wireformat: ${commit.wireformat}`);
|
|
347
|
+
// DETAILED DEBUG LOGGING
|
|
348
|
+
console.group("🔍 [MLS Debug] Full Commit Structure");
|
|
349
|
+
console.log("commit keys:", Object.keys(commit));
|
|
350
|
+
console.log("commit.wireformat:", commit.wireformat);
|
|
351
|
+
console.log("commit.publicMessage:", commit.publicMessage);
|
|
352
|
+
console.log("commit.privateMessage:", commit.privateMessage);
|
|
353
|
+
// Log proposals if present
|
|
354
|
+
if (commit.publicMessage?.content) {
|
|
355
|
+
console.log("publicMessage.content:", commit.publicMessage.content);
|
|
356
|
+
console.log("publicMessage.content.proposals:", commit.publicMessage.content.proposals);
|
|
357
|
+
if (commit.publicMessage.content.proposals) {
|
|
358
|
+
commit.publicMessage.content.proposals.forEach((prop, i) => {
|
|
359
|
+
console.log(` Proposal ${i}:`, {
|
|
360
|
+
proposalType: prop.proposalType,
|
|
361
|
+
keys: Object.keys(prop),
|
|
362
|
+
full: prop,
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
console.groupEnd();
|
|
368
|
+
const groupState = this.groups.get(groupId);
|
|
369
|
+
if (!groupState) {
|
|
370
|
+
throw new Error(`Group ${groupId} not found`);
|
|
371
|
+
}
|
|
372
|
+
let result;
|
|
373
|
+
// RFC 9420: Route based on message type
|
|
374
|
+
if (commit.wireformat === "mls_public_message") {
|
|
375
|
+
// Public messages (add/remove member commits)
|
|
376
|
+
console.log("🔍 [MLS Debug] Processing as PUBLIC message (add/remove)...");
|
|
377
|
+
const publicMessage = commit.publicMessage || commit;
|
|
378
|
+
result = await (0, ts_mls_1.processPublicMessage)(groupState, publicMessage, ts_mls_1.emptyPskIndex, this.cipherSuite);
|
|
379
|
+
}
|
|
380
|
+
else if (commit.wireformat === "mls_private_message") {
|
|
381
|
+
// Private messages (update/key rotation commits)
|
|
382
|
+
console.log("🔍 [MLS Debug] Processing as PRIVATE message (update)...");
|
|
383
|
+
const privateMessage = commit.privateMessage || commit;
|
|
384
|
+
result = await (0, ts_mls_1.processPrivateMessage)(groupState, privateMessage, ts_mls_1.emptyPskIndex, this.cipherSuite);
|
|
385
|
+
}
|
|
386
|
+
else {
|
|
387
|
+
throw new Error(`Unknown commit wireformat: ${commit.wireformat}`);
|
|
388
|
+
}
|
|
389
|
+
// Update group state
|
|
390
|
+
this.groups.set(groupId, result.newState);
|
|
391
|
+
console.log(`✅ [MLS] Commit processed, epoch: ${result.newState.groupContext.epoch}`);
|
|
392
|
+
}
|
|
393
|
+
catch (error) {
|
|
394
|
+
console.error("❌ [MLS] Failed to process commit:", error);
|
|
395
|
+
console.error("❌ [MLS Debug] Error details:", error instanceof Error ? error.stack : "No stack trace");
|
|
396
|
+
console.error("❌ [MLS Debug] Error message:", error instanceof Error ? error.message : String(error));
|
|
397
|
+
throw new Error(`Commit processing failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Remove members from a group
|
|
402
|
+
*/
|
|
403
|
+
async removeMembers(groupId, memberIndices) {
|
|
404
|
+
this.ensureInitialized();
|
|
405
|
+
try {
|
|
406
|
+
console.log(`➖ [MLS] Removing ${memberIndices.length} member(s) from group: ${groupId}`);
|
|
407
|
+
const groupState = this.groups.get(groupId);
|
|
408
|
+
if (!groupState) {
|
|
409
|
+
throw new Error(`Group ${groupId} not found`);
|
|
410
|
+
}
|
|
411
|
+
// Create remove proposals
|
|
412
|
+
const removeProposals = memberIndices.map((index) => ({
|
|
413
|
+
proposalType: "remove",
|
|
414
|
+
remove: {
|
|
415
|
+
removed: index, // ts-mls expects number, not BigInt
|
|
416
|
+
},
|
|
417
|
+
}));
|
|
418
|
+
// Create commit with remove proposals
|
|
419
|
+
const commitResult = await (0, ts_mls_1.createCommit)({ state: groupState, cipherSuite: this.cipherSuite }, { extraProposals: removeProposals });
|
|
420
|
+
// Update group state
|
|
421
|
+
this.groups.set(groupId, commitResult.newState);
|
|
422
|
+
// Encode the commit
|
|
423
|
+
const encodedCommit = (0, ts_mls_1.encodeMlsMessage)({
|
|
424
|
+
publicMessage: commitResult.publicMessage,
|
|
425
|
+
wireformat: "mls_public_message",
|
|
426
|
+
version: "mls10",
|
|
427
|
+
});
|
|
428
|
+
console.log("✅ [MLS] Members removed");
|
|
429
|
+
return encodedCommit;
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
console.error("❌ [MLS] Failed to remove members:", error);
|
|
433
|
+
throw new Error(`Member removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Get list of groups
|
|
438
|
+
*/
|
|
439
|
+
async getGroups() {
|
|
440
|
+
this.ensureInitialized();
|
|
441
|
+
try {
|
|
442
|
+
const groupIds = Array.from(this.groups.keys()).map((id) => new TextEncoder().encode(id));
|
|
443
|
+
return groupIds;
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
console.error("❌ [MLS] Failed to get groups:", error);
|
|
447
|
+
throw new Error(`Getting groups failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Export group state for persistence
|
|
452
|
+
*/
|
|
453
|
+
async exportGroupState(groupId) {
|
|
454
|
+
this.ensureInitialized();
|
|
455
|
+
try {
|
|
456
|
+
console.log(`💾 [MLS] Exporting state for group: ${groupId}`);
|
|
457
|
+
const groupState = this.groups.get(groupId);
|
|
458
|
+
if (!groupState) {
|
|
459
|
+
throw new Error(`Group ${groupId} not found`);
|
|
460
|
+
}
|
|
461
|
+
// Note: ts-mls ClientState contains non-serializable crypto keys
|
|
462
|
+
// This is a simplified export - in production you'd need proper serialization
|
|
463
|
+
const exportData = {
|
|
464
|
+
groupId,
|
|
465
|
+
epoch: groupState.groupContext.epoch.toString(),
|
|
466
|
+
exported: Date.now(),
|
|
467
|
+
// Add other serializable fields as needed
|
|
468
|
+
};
|
|
469
|
+
console.log("✅ [MLS] Group state exported");
|
|
470
|
+
return exportData;
|
|
471
|
+
}
|
|
472
|
+
catch (error) {
|
|
473
|
+
console.error("❌ [MLS] Failed to export group state:", error);
|
|
474
|
+
throw new Error(`Group state export failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Get user ID
|
|
479
|
+
*/
|
|
480
|
+
getUserId() {
|
|
481
|
+
return this.userId;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Get group information
|
|
485
|
+
*/
|
|
486
|
+
async getGroupKeyInfo(groupId) {
|
|
487
|
+
const groupState = this.groups.get(groupId);
|
|
488
|
+
if (!groupState) {
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
const members = this.extractMembersFromState(groupState);
|
|
492
|
+
return {
|
|
493
|
+
groupId,
|
|
494
|
+
epoch: groupState.groupContext.epoch.toString(),
|
|
495
|
+
members,
|
|
496
|
+
cipherSuite: "MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519",
|
|
497
|
+
treeHash: this.bytesToHex(groupState.groupContext.treeHash).substring(0, 16),
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Clean up resources
|
|
502
|
+
*/
|
|
503
|
+
async destroy() {
|
|
504
|
+
this.groups.clear();
|
|
505
|
+
this.keyPackage = null;
|
|
506
|
+
this.initialized = false;
|
|
507
|
+
console.log("✅ [MLS] Manager destroyed");
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Extract member identities from group state
|
|
511
|
+
*/
|
|
512
|
+
extractMembersFromState(state) {
|
|
513
|
+
const members = [];
|
|
514
|
+
try {
|
|
515
|
+
// Iterate through ratchet tree to find leaf nodes
|
|
516
|
+
for (let i = 0; i < state.ratchetTree.length; i++) {
|
|
517
|
+
const node = state.ratchetTree[i];
|
|
518
|
+
if (node &&
|
|
519
|
+
node.nodeType === "leaf" &&
|
|
520
|
+
node.leaf?.credential) {
|
|
521
|
+
const identity = new TextDecoder().decode(node.leaf.credential.identity);
|
|
522
|
+
members.push(identity);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
catch (error) {
|
|
527
|
+
console.warn("Could not extract members:", error);
|
|
528
|
+
members.push(this.userId); // At least include self
|
|
529
|
+
}
|
|
530
|
+
return members;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Convert bytes to hex string
|
|
534
|
+
*/
|
|
535
|
+
bytesToHex(bytes) {
|
|
536
|
+
return Array.from(bytes)
|
|
537
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
538
|
+
.join("");
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Ensure the manager is initialized
|
|
542
|
+
*/
|
|
543
|
+
ensureInitialized() {
|
|
544
|
+
if (!this.initialized) {
|
|
545
|
+
throw new Error("MLS Manager not initialized. Call initialize() first.");
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
exports.MLSManager = MLSManager;
|
|
550
|
+
exports.default = MLSManager;
|