shadowx-fca 8.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.
Files changed (174) hide show
  1. package/README.md +1066 -0
  2. package/build/messagix.dll +0 -0
  3. package/build/messagix.so +0 -0
  4. package/checkUpdate.js +393 -0
  5. package/config.json +17 -0
  6. package/e2ee.js +563 -0
  7. package/e2eetest.js +356 -0
  8. package/index.js +611 -0
  9. package/lib/index.mjs +1412 -0
  10. package/logger.js +500 -0
  11. package/package.json +65 -0
  12. package/src/GetBotInfo.js +66 -0
  13. package/src/OldMessage.js +182 -0
  14. package/src/Screenshot.js +83 -0
  15. package/src/addExternalModule.js +13 -0
  16. package/src/addUserToGroup.js +33 -0
  17. package/src/approveGroupJoinRequests.js +18 -0
  18. package/src/changeAdminStatus.js +16 -0
  19. package/src/changeArchivedStatus.js +17 -0
  20. package/src/changeAvatar.js +136 -0
  21. package/src/changeAvatarV2.js +86 -0
  22. package/src/changeAvt.js +85 -0
  23. package/src/changeBio.js +76 -0
  24. package/src/changeBlockedStatus.js +20 -0
  25. package/src/changeBlockedStatusMqtt.js +80 -0
  26. package/src/changeCover.js +72 -0
  27. package/src/changeGroupImage.js +16 -0
  28. package/src/changeName.js +79 -0
  29. package/src/changeNickname.js +16 -0
  30. package/src/changeThreadColor.js +15 -0
  31. package/src/changeThreadEmoji.js +15 -0
  32. package/src/changeThreadMemberNickname.js +6 -0
  33. package/src/changeUsername.js +59 -0
  34. package/src/createCommentPost.js +230 -0
  35. package/src/createNewGroup.js +38 -0
  36. package/src/createNote.js +35 -0
  37. package/src/createPoll.js +27 -0
  38. package/src/createPost.js +276 -0
  39. package/src/createThemeAI.js +129 -0
  40. package/src/data/cache/system/data.json +4 -0
  41. package/src/data/cache/system/datahandle.js +21 -0
  42. package/src/data/getThreadInfo.json +1 -0
  43. package/src/deleteComment.js +23 -0
  44. package/src/deleteMessage.js +15 -0
  45. package/src/deleteThread.js +15 -0
  46. package/src/denyGroupJoinRequests.js +18 -0
  47. package/src/e2ee/crypto.js +173 -0
  48. package/src/e2ee/index.js +144 -0
  49. package/src/e2ee/proto/ArmadilloApplication.proto +281 -0
  50. package/src/e2ee/proto/ArmadilloICDC.proto +14 -0
  51. package/src/e2ee/proto/ConsumerApplication.proto +232 -0
  52. package/src/e2ee/proto/MessageApplication.proto +82 -0
  53. package/src/e2ee/proto/MessageTransport.proto +77 -0
  54. package/src/e2ee/proto/WACommon.proto +66 -0
  55. package/src/e2ee/proto/WAMediaTransport.proto +176 -0
  56. package/src/e2ee/proto/proto-writer.ts +76 -0
  57. package/src/e2ee/protocol.js +196 -0
  58. package/src/e2ee/ratchet.js +219 -0
  59. package/src/e2ee/store.js +182 -0
  60. package/src/e2ee.js +8 -0
  61. package/src/editMessage.js +56 -0
  62. package/src/editMessageOld.js +67 -0
  63. package/src/enableReactions.js +24 -0
  64. package/src/follow.js +74 -0
  65. package/src/followUser.js +23 -0
  66. package/src/forwardAttachment.js +16 -0
  67. package/src/friendList.js +98 -0
  68. package/src/getAccess.js +112 -0
  69. package/src/getAppState.js +13 -0
  70. package/src/getAvatarUser.js +11 -0
  71. package/src/getBio.js +24 -0
  72. package/src/getBotInitialData.js +42 -0
  73. package/src/getCtx.js +5 -0
  74. package/src/getCurrentUserID.js +6 -0
  75. package/src/getEmojiUrl.js +29 -0
  76. package/src/getFriendsList.js +36 -0
  77. package/src/getMessage.js +37 -0
  78. package/src/getNotes.js +17 -0
  79. package/src/getOptions.js +5 -0
  80. package/src/getPinnedMessages.js +33 -0
  81. package/src/getPostInfo.js +17 -0
  82. package/src/getProfileInfo.js +17 -0
  83. package/src/getPublicData.js +25 -0
  84. package/src/getRegion.js +7 -0
  85. package/src/getRepInfo.js +17 -0
  86. package/src/getStickerPacks.js +25 -0
  87. package/src/getStickers.js +39 -0
  88. package/src/getStoryReactions.js +18 -0
  89. package/src/getThreadHistory.js +45 -0
  90. package/src/getThreadHistoryDeprecated.js +71 -0
  91. package/src/getThreadInfo.js +73 -0
  92. package/src/getThreadInfoDeprecated.js +56 -0
  93. package/src/getThreadList.js +76 -0
  94. package/src/getThreadListDeprecated.js +46 -0
  95. package/src/getThreadPictures.js +59 -0
  96. package/src/getThreadTheme.js +77 -0
  97. package/src/getUID.js +17 -0
  98. package/src/getUserID.js +17 -0
  99. package/src/getUserInfo.js +28 -0
  100. package/src/handleFriendRequest.js +21 -0
  101. package/src/handleMessageRequest.js +15 -0
  102. package/src/httpGet.js +13 -0
  103. package/src/httpPost.js +12 -0
  104. package/src/httpPostFormData.js +12 -0
  105. package/src/listenE2EE.js +75 -0
  106. package/src/listenMqtt.js +802 -0
  107. package/src/listenNotification.js +85 -0
  108. package/src/logout.js +22 -0
  109. package/src/markAsDelivered.js +17 -0
  110. package/src/markAsRead.js +14 -0
  111. package/src/markAsReadAll.js +15 -0
  112. package/src/markAsSeen.js +15 -0
  113. package/src/metaTheme.js +185 -0
  114. package/src/muteThread.js +52 -0
  115. package/src/note.js +228 -0
  116. package/src/pin.js +53 -0
  117. package/src/pinMessage.js +6 -0
  118. package/src/postComment.js +29 -0
  119. package/src/postFormData.js +46 -0
  120. package/src/reactToComment.js +31 -0
  121. package/src/reactToPost.js +32 -0
  122. package/src/refreshFb_dtsg.js +31 -0
  123. package/src/removeSuspiciousAccount.js +74 -0
  124. package/src/removeUserFromGroup.js +15 -0
  125. package/src/reply.js +442 -0
  126. package/src/resolvePhotoUrl.js +15 -0
  127. package/src/searchForThread.js +20 -0
  128. package/src/searchFriends.js +28 -0
  129. package/src/searchStickers.js +53 -0
  130. package/src/send.js +46 -0
  131. package/src/sendAudio.js +8 -0
  132. package/src/sendBroadcast.js +93 -0
  133. package/src/sendButtons.js +161 -0
  134. package/src/sendComment.js +159 -0
  135. package/src/sendEmoji.js +10 -0
  136. package/src/sendFile.js +9 -0
  137. package/src/sendFriendRequest.js +33 -0
  138. package/src/sendGif.js +24 -0
  139. package/src/sendImage.js +9 -0
  140. package/src/sendLocation.js +9 -0
  141. package/src/sendMessage.js +487 -0
  142. package/src/sendMessage1.js +309 -0
  143. package/src/sendMessageMqtt.js +68 -0
  144. package/src/sendSticker.js +8 -0
  145. package/src/sendTypingIndicator.js +36 -0
  146. package/src/sendTypingIndicatorV2.js +28 -0
  147. package/src/sendVideo.js +9 -0
  148. package/src/sessionGuard.js +130 -0
  149. package/src/setActiveStatus.js +16 -0
  150. package/src/setMessageReaction.js +61 -0
  151. package/src/setMessageReactionMqtt.js +62 -0
  152. package/src/setOptions.js +22 -0
  153. package/src/setPollVote.js +17 -0
  154. package/src/setPostReaction.js +112 -0
  155. package/src/setProfileGuard.js +44 -0
  156. package/src/setProfileLock.js +93 -0
  157. package/src/setStoryReaction.js +129 -0
  158. package/src/setStorySeen.js +99 -0
  159. package/src/setThreadTheme.js +17 -0
  160. package/src/setTitle.js +15 -0
  161. package/src/shareContact.js +33 -0
  162. package/src/shareLink.js +8 -0
  163. package/src/sharePost.js +31 -0
  164. package/src/stopListenMqtt.js +23 -0
  165. package/src/storyManager.js +353 -0
  166. package/src/suggestFriend.js +128 -0
  167. package/src/threadColors.js +131 -0
  168. package/src/unfollowUser.js +23 -0
  169. package/src/unfriend.js +15 -0
  170. package/src/unpinMessage.js +6 -0
  171. package/src/unsendMessage.js +14 -0
  172. package/src/uploadAttachment.js +58 -0
  173. package/src/uploadImageToImgbb.js +29 -0
  174. package/utils.js +2945 -0
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Facebook-specific E2EE wire protocol handling.
5
+ *
6
+ * Facebook Secret Conversations use Signal Protocol (X3DH + Double Ratchet)
7
+ * with their own framing on top. This module handles:
8
+ * - Key bundle upload/download to Facebook's graph endpoint
9
+ * - Message framing (encode/decode SHADOWX_E2EE frame format)
10
+ * - Signed pre-key signatures using HMAC-SHA256 (FB uses Ed25519 in practice;
11
+ * we sign with HMAC for our own inter-device sessions)
12
+ * - E2EE-capable thread detection from message metadata
13
+ */
14
+
15
+ const crypto = require("crypto");
16
+ const { hmacSha256, generateKeyPair, randomBytes } = require("./crypto");
17
+
18
+ // ─── Frame format ──────────────────────────────────────────────────────────────
19
+ // 2 bytes: magic (0x4E58 = "NX")
20
+ // 1 byte: version (0x01)
21
+ // 1 byte: type (0=init, 1=message, 2=reaction, 3=receipt, 4=typing)
22
+ // 4 bytes: sender ID length
23
+ // N bytes: sender ID (utf8)
24
+ // 4 bytes: thread ID length
25
+ // N bytes: thread ID (utf8)
26
+ // 4 bytes: header length [only for type 0,1,2]
27
+ // N bytes: header
28
+ // 4 bytes: payload length
29
+ // N bytes: payload (encrypted or raw for receipt/typing)
30
+
31
+ const MAGIC = 0x4E58;
32
+ const VERSION = 0x01;
33
+ const TYPE = { INIT: 0, MESSAGE: 1, REACTION: 2, RECEIPT: 3, TYPING: 4 };
34
+
35
+ function encodeFrame(type, senderID, threadID, header, payload) {
36
+ const sidBuf = Buffer.from(String(senderID), "utf8");
37
+ const tidBuf = Buffer.from(String(threadID), "utf8");
38
+ const hdrBuf = header || Buffer.alloc(0);
39
+ const payBuf = Buffer.isBuffer(payload) ? payload : Buffer.from(payload || "");
40
+
41
+ const totalSize = 2 + 1 + 1 + 4 + sidBuf.length + 4 + tidBuf.length + 4 + hdrBuf.length + 4 + payBuf.length;
42
+ const buf = Buffer.allocUnsafe(totalSize);
43
+ let offset = 0;
44
+
45
+ buf.writeUInt16BE(MAGIC, offset); offset += 2;
46
+ buf.writeUInt8(VERSION, offset); offset += 1;
47
+ buf.writeUInt8(type, offset); offset += 1;
48
+
49
+ buf.writeUInt32BE(sidBuf.length, offset); offset += 4;
50
+ sidBuf.copy(buf, offset); offset += sidBuf.length;
51
+
52
+ buf.writeUInt32BE(tidBuf.length, offset); offset += 4;
53
+ tidBuf.copy(buf, offset); offset += tidBuf.length;
54
+
55
+ buf.writeUInt32BE(hdrBuf.length, offset); offset += 4;
56
+ hdrBuf.copy(buf, offset); offset += hdrBuf.length;
57
+
58
+ buf.writeUInt32BE(payBuf.length, offset); offset += 4;
59
+ payBuf.copy(buf, offset);
60
+
61
+ return buf;
62
+ }
63
+
64
+ function decodeFrame(buf) {
65
+ if (!Buffer.isBuffer(buf)) buf = Buffer.from(buf);
66
+ if (buf.length < 8) throw new Error("Frame too short");
67
+ let offset = 0;
68
+
69
+ const magic = buf.readUInt16BE(offset); offset += 2;
70
+ if (magic !== MAGIC) throw new Error("Invalid frame magic: " + magic.toString(16));
71
+ const version = buf.readUInt8(offset); offset += 1;
72
+ const type = buf.readUInt8(offset); offset += 1;
73
+
74
+ const sidLen = buf.readUInt32BE(offset); offset += 4;
75
+ const senderID = buf.slice(offset, offset + sidLen).toString("utf8"); offset += sidLen;
76
+
77
+ const tidLen = buf.readUInt32BE(offset); offset += 4;
78
+ const threadID = buf.slice(offset, offset + tidLen).toString("utf8"); offset += tidLen;
79
+
80
+ const hdrLen = buf.readUInt32BE(offset); offset += 4;
81
+ const header = buf.slice(offset, offset + hdrLen); offset += hdrLen;
82
+
83
+ const payLen = buf.readUInt32BE(offset); offset += 4;
84
+ const payload = buf.slice(offset, offset + payLen);
85
+
86
+ return { version, type, senderID, threadID, header, payload };
87
+ }
88
+
89
+ // ─── Key Bundle ────────────────────────────────────────────────────────────────
90
+
91
+ function buildKeyBundle(identityKey, signedPreKey, oneTimePreKeys) {
92
+ return {
93
+ identityKey: identityKey.publicKeyRaw.toString("base64"),
94
+ signedPreKey: {
95
+ keyId: signedPreKey.keyId || 1,
96
+ publicKey: signedPreKey.publicKeyRaw.toString("base64"),
97
+ signature: signedPreKey.signature ? signedPreKey.signature.toString("base64") : ""
98
+ },
99
+ oneTimePreKeys: (oneTimePreKeys || []).map(k => ({
100
+ keyId: k.keyId,
101
+ publicKey: k.publicKeyRaw.toString("base64")
102
+ }))
103
+ };
104
+ }
105
+
106
+ function signKey(identityPrivateKey, keyToSign) {
107
+ return hmacSha256(identityPrivateKey, keyToSign.publicKeyRaw);
108
+ }
109
+
110
+ // ─── Facebook Graph API: key registration/fetching ───────────────────────────
111
+
112
+ async function uploadKeyBundle(defaultFuncs, ctx, keyBundle) {
113
+ try {
114
+ const res = await defaultFuncs.post(
115
+ "https://www.facebook.com/api/graphql/",
116
+ ctx.jar,
117
+ {
118
+ fb_api_req_friendly_name: "MessengerE2EERegistrationMutation",
119
+ fb_api_caller_class: "RelayModern",
120
+ doc_id: "6325661730877783",
121
+ variables: JSON.stringify({
122
+ input: {
123
+ actor_id: ctx.userID,
124
+ client_mutation_id: String(ctx.clientMutationId++),
125
+ identity_key: keyBundle.identityKey,
126
+ signed_pre_key: keyBundle.signedPreKey,
127
+ one_time_pre_keys: keyBundle.oneTimePreKeys
128
+ }
129
+ })
130
+ }
131
+ );
132
+ return res;
133
+ } catch (e) {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ async function fetchRecipientKeyBundle(defaultFuncs, ctx, recipientUserID) {
139
+ try {
140
+ const res = await defaultFuncs.post(
141
+ "https://www.facebook.com/api/graphql/",
142
+ ctx.jar,
143
+ {
144
+ fb_api_req_friendly_name: "MessengerE2EEKeyBundleQuery",
145
+ fb_api_caller_class: "RelayModern",
146
+ doc_id: "6325661730877784",
147
+ variables: JSON.stringify({ user_id: String(recipientUserID) })
148
+ }
149
+ );
150
+ return res;
151
+ } catch (e) {
152
+ return null;
153
+ }
154
+ }
155
+
156
+ // ─── Message payload builders ─────────────────────────────────────────────────
157
+
158
+ function buildInitPayload(senderEphemeralPub, senderOneTimeKeyId, recipientOneTimeKeyId) {
159
+ return JSON.stringify({
160
+ type: "x3dh_init",
161
+ ek: senderEphemeralPub.toString("base64"),
162
+ skid: senderOneTimeKeyId || null,
163
+ opk_id: recipientOneTimeKeyId || null,
164
+ ts: Date.now()
165
+ });
166
+ }
167
+
168
+ function parseInitPayload(buf) {
169
+ const str = Buffer.isBuffer(buf) ? buf.toString("utf8") : buf;
170
+ return JSON.parse(str);
171
+ }
172
+
173
+ function buildMessagePayload(text, attachments, replyToMessageId) {
174
+ return JSON.stringify({
175
+ v: 1,
176
+ text: text || null,
177
+ attachments: attachments || [],
178
+ reply_to: replyToMessageId || null,
179
+ ts: Date.now(),
180
+ nonce: randomBytes(8).toString("hex")
181
+ });
182
+ }
183
+
184
+ function parseMessagePayload(buf) {
185
+ const str = Buffer.isBuffer(buf) ? buf.toString("utf8") : buf;
186
+ return JSON.parse(str);
187
+ }
188
+
189
+ module.exports = {
190
+ MAGIC, VERSION, TYPE,
191
+ encodeFrame, decodeFrame,
192
+ buildKeyBundle, signKey,
193
+ uploadKeyBundle, fetchRecipientKeyBundle,
194
+ buildInitPayload, parseInitPayload,
195
+ buildMessagePayload, parseMessagePayload
196
+ };
@@ -0,0 +1,219 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Double Ratchet Algorithm (Signal Protocol)
5
+ * Based on the Signal Protocol specification:
6
+ * https://signal.org/docs/specifications/doubleratchet/
7
+ */
8
+
9
+ const { generateKeyPair, dh, hkdf, hkdfExtract, hkdfExpand, encrypt, decrypt, hmacSha256 } = require("./crypto");
10
+
11
+ const MAX_SKIP = 1000;
12
+ const INFO_RATCHET = Buffer.from("WhisperRatchet", "utf8");
13
+ const INFO_MSG = Buffer.from("WhisperMessageKeys", "utf8");
14
+
15
+ function kdfCK(ck) {
16
+ const ck2 = hmacSha256(ck, Buffer.from([0x02]));
17
+ const mk = hmacSha256(ck, Buffer.from([0x01]));
18
+ return { ck: ck2, mk };
19
+ }
20
+
21
+ function kdfRK(rk, dh_out) {
22
+ const salt = rk;
23
+ const out = hkdf(dh_out, 64, salt, INFO_RATCHET);
24
+ return { rk: out.slice(0, 32), ck: out.slice(32, 64) };
25
+ }
26
+
27
+ function deriveMessageKeys(mk) {
28
+ const out = hkdf(mk, 80, Buffer.alloc(32, 0), INFO_MSG);
29
+ return {
30
+ encKey: out.slice(0, 32),
31
+ authKey: out.slice(32, 64),
32
+ iv: out.slice(64, 80)
33
+ };
34
+ }
35
+
36
+ class DoubleRatchetSession {
37
+ constructor() {
38
+ this.DHs = null; // Our DH ratchet key pair
39
+ this.DHr = null; // Their DH ratchet public key (raw 32 bytes)
40
+ this.RK = null; // Root key (32 bytes)
41
+ this.CKs = null; // Sending chain key
42
+ this.CKr = null; // Receiving chain key
43
+ this.Ns = 0; // Send message number
44
+ this.Nr = 0; // Receive message number
45
+ this.PN = 0; // Previous sending chain length
46
+ this.MKSKIPPED = new Map(); // Skipped message keys
47
+ }
48
+
49
+ /**
50
+ * Initialize as sender (Alice) after X3DH
51
+ */
52
+ initAsSender(sk, recipientRatchetPublicKey) {
53
+ this.DHs = generateKeyPair();
54
+ this.DHr = Buffer.from(recipientRatchetPublicKey);
55
+ const dhOut = dh(this.DHs.privateKey, this.DHr);
56
+ const { rk, ck } = kdfRK(sk, dhOut);
57
+ this.RK = rk;
58
+ this.CKs = ck;
59
+ this.CKr = null;
60
+ this.Ns = 0; this.Nr = 0; this.PN = 0;
61
+ }
62
+
63
+ /**
64
+ * Initialize as receiver (Bob) after X3DH
65
+ */
66
+ initAsReceiver(sk, ourRatchetKeyPair) {
67
+ this.DHs = ourRatchetKeyPair;
68
+ this.DHr = null;
69
+ this.RK = sk;
70
+ this.CKs = null;
71
+ this.CKr = null;
72
+ this.Ns = 0; this.Nr = 0; this.PN = 0;
73
+ }
74
+
75
+ /**
76
+ * Encrypt a message
77
+ * Returns { header, ciphertext }
78
+ */
79
+ encrypt(plaintext, aad) {
80
+ const { ck, mk } = kdfCK(this.CKs);
81
+ this.CKs = ck;
82
+ const header = this._buildHeader(this.DHs.publicKeyRaw, this.PN, this.Ns);
83
+ this.Ns++;
84
+ const keys = deriveMessageKeys(mk);
85
+ const aadFull = aad ? Buffer.concat([aad, header]) : header;
86
+ const ciphertext = encrypt(keys.encKey, Buffer.isBuffer(plaintext) ? plaintext : Buffer.from(plaintext, "utf8"), aadFull);
87
+ return { header, ciphertext };
88
+ }
89
+
90
+ /**
91
+ * Decrypt a message
92
+ * @param {Buffer} header - message header
93
+ * @param {Buffer} ciphertext - encrypted payload
94
+ * @param {Buffer} [aad] - additional auth data
95
+ * @returns {Buffer} decrypted plaintext
96
+ */
97
+ decrypt(header, ciphertext, aad) {
98
+ const { dh_pub, pn, n } = this._parseHeader(header);
99
+ const aadFull = aad ? Buffer.concat([aad, header]) : header;
100
+
101
+ // Check skipped keys first
102
+ const skipKey = `${dh_pub.toString("hex")}:${n}`;
103
+ if (this.MKSKIPPED.has(skipKey)) {
104
+ const mk = this.MKSKIPPED.get(skipKey);
105
+ this.MKSKIPPED.delete(skipKey);
106
+ const keys = deriveMessageKeys(mk);
107
+ return decrypt(keys.encKey, ciphertext, aadFull);
108
+ }
109
+
110
+ // Check if we need to ratchet
111
+ const needsRatchet = !this.DHr || !dh_pub.equals(this.DHr);
112
+ if (needsRatchet) {
113
+ this._skipMessageKeys(pn);
114
+ this._dhRatchetStep(dh_pub);
115
+ }
116
+
117
+ this._skipMessageKeys(n);
118
+ const { ck, mk } = kdfCK(this.CKr);
119
+ this.CKr = ck;
120
+ this.Nr++;
121
+
122
+ const keys = deriveMessageKeys(mk);
123
+ return decrypt(keys.encKey, ciphertext, aadFull);
124
+ }
125
+
126
+ _skipMessageKeys(until) {
127
+ if (this.Nr + MAX_SKIP < until) throw new Error("Too many skipped messages: " + until);
128
+ if (this.CKr) {
129
+ while (this.Nr < until) {
130
+ const { ck, mk } = kdfCK(this.CKr);
131
+ this.CKr = ck;
132
+ const key = `${this.DHr.toString("hex")}:${this.Nr}`;
133
+ this.MKSKIPPED.set(key, mk);
134
+ this.Nr++;
135
+ }
136
+ }
137
+ }
138
+
139
+ _dhRatchetStep(remoteDhPub) {
140
+ this.PN = this.Ns;
141
+ this.Ns = 0;
142
+ this.Nr = 0;
143
+ this.DHr = remoteDhPub;
144
+
145
+ // Receive ratchet
146
+ const dhOut1 = dh(this.DHs.privateKey, this.DHr);
147
+ const { rk: rk1, ck: ckr } = kdfRK(this.RK, dhOut1);
148
+ this.RK = rk1;
149
+ this.CKr = ckr;
150
+
151
+ // Send ratchet
152
+ this.DHs = generateKeyPair();
153
+ const dhOut2 = dh(this.DHs.privateKey, this.DHr);
154
+ const { rk: rk2, ck: cks } = kdfRK(this.RK, dhOut2);
155
+ this.RK = rk2;
156
+ this.CKs = cks;
157
+ }
158
+
159
+ _buildHeader(dhPub, pn, n) {
160
+ const buf = Buffer.allocUnsafe(32 + 4 + 4);
161
+ Buffer.from(dhPub).copy(buf, 0);
162
+ buf.writeUInt32BE(pn, 32);
163
+ buf.writeUInt32BE(n, 36);
164
+ return buf;
165
+ }
166
+
167
+ _parseHeader(header) {
168
+ if (header.length < 40) throw new Error("Invalid ratchet header length");
169
+ return {
170
+ dh_pub: header.slice(0, 32),
171
+ pn: header.readUInt32BE(32),
172
+ n: header.readUInt32BE(36)
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Serialize session state for persistence
178
+ */
179
+ serialize() {
180
+ return JSON.stringify({
181
+ DHs_priv: this.DHs ? this.DHs.privateKey.toString("base64") : null,
182
+ DHs_pub: this.DHs ? this.DHs.publicKeyRaw.toString("base64") : null,
183
+ DHr: this.DHr ? this.DHr.toString("base64") : null,
184
+ RK: this.RK ? this.RK.toString("base64") : null,
185
+ CKs: this.CKs ? this.CKs.toString("base64") : null,
186
+ CKr: this.CKr ? this.CKr.toString("base64") : null,
187
+ Ns: this.Ns,
188
+ Nr: this.Nr,
189
+ PN: this.PN,
190
+ MKSKIPPED: Array.from(this.MKSKIPPED.entries()).map(([k, v]) => [k, v.toString("base64")])
191
+ });
192
+ }
193
+
194
+ /**
195
+ * Restore session state from serialized string
196
+ */
197
+ static deserialize(json) {
198
+ const d = JSON.parse(json);
199
+ const session = new DoubleRatchetSession();
200
+ if (d.DHs_priv) {
201
+ session.DHs = {
202
+ privateKey: Buffer.from(d.DHs_priv, "base64"),
203
+ publicKey: null,
204
+ publicKeyRaw: Buffer.from(d.DHs_pub, "base64")
205
+ };
206
+ }
207
+ session.DHr = d.DHr ? Buffer.from(d.DHr, "base64") : null;
208
+ session.RK = d.RK ? Buffer.from(d.RK, "base64") : null;
209
+ session.CKs = d.CKs ? Buffer.from(d.CKs, "base64") : null;
210
+ session.CKr = d.CKr ? Buffer.from(d.CKr, "base64") : null;
211
+ session.Ns = d.Ns || 0;
212
+ session.Nr = d.Nr || 0;
213
+ session.PN = d.PN || 0;
214
+ session.MKSKIPPED = new Map((d.MKSKIPPED || []).map(([k, v]) => [k, Buffer.from(v, "base64")]));
215
+ return session;
216
+ }
217
+ }
218
+
219
+ module.exports = { DoubleRatchetSession, kdfCK, kdfRK, deriveMessageKeys };
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * E2EE Device/Session Store
5
+ * Persists key material to a JSON file (encrypted with a device secret).
6
+ * Each E2EE session (thread) is stored separately.
7
+ */
8
+
9
+ const fs = require("fs");
10
+ const path = require("path");
11
+ const crypto = require("crypto");
12
+
13
+ class E2EEStore {
14
+ constructor(storePath, userID) {
15
+ this.storePath = storePath;
16
+ this.userID = userID;
17
+ this.data = {
18
+ identityKey: null,
19
+ signedPreKey: null,
20
+ oneTimePreKeys: [],
21
+ sessions: {}, // threadID -> serialized DoubleRatchetSession
22
+ deviceSecret: null // 32-byte random secret for local encryption
23
+ };
24
+ this._loaded = false;
25
+ }
26
+
27
+ _storeFile() {
28
+ return path.join(this.storePath, `shadowx_e2ee_${this.userID}.json`);
29
+ }
30
+
31
+ _salt() {
32
+ return Buffer.from(this.userID.padEnd(16, "0").slice(0, 16), "utf8");
33
+ }
34
+
35
+ _localEncrypt(data) {
36
+ if (!this.data.deviceSecret) return data;
37
+ const key = Buffer.from(this.data.deviceSecret, "base64");
38
+ if (key.length !== 32) return data;
39
+ const iv = crypto.randomBytes(12);
40
+ const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
41
+ const enc = Buffer.concat([cipher.update(Buffer.from(data, "utf8")), cipher.final()]);
42
+ const tag = cipher.getAuthTag();
43
+ return Buffer.concat([iv, tag, enc]).toString("base64");
44
+ }
45
+
46
+ _localDecrypt(data) {
47
+ if (!this.data.deviceSecret) return data;
48
+ try {
49
+ const key = Buffer.from(this.data.deviceSecret, "base64");
50
+ if (key.length !== 32) return data;
51
+ const buf = Buffer.from(data, "base64");
52
+ const iv = buf.slice(0, 12);
53
+ const tag = buf.slice(12, 28);
54
+ const enc = buf.slice(28);
55
+ const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
56
+ decipher.setAuthTag(tag);
57
+ return Buffer.concat([decipher.update(enc), decipher.final()]).toString("utf8");
58
+ } catch (_) {
59
+ return data;
60
+ }
61
+ }
62
+
63
+ load() {
64
+ const file = this._storeFile();
65
+ if (!fs.existsSync(file)) return false;
66
+ try {
67
+ const raw = fs.readFileSync(file, "utf8");
68
+ const parsed = JSON.parse(raw);
69
+ if (parsed.deviceSecret) this.data.deviceSecret = parsed.deviceSecret;
70
+ const decrypted = this._localDecrypt(parsed.payload || "{}");
71
+ const payload = JSON.parse(decrypted);
72
+ this.data.identityKey = payload.identityKey || null;
73
+ this.data.signedPreKey = payload.signedPreKey || null;
74
+ this.data.oneTimePreKeys = payload.oneTimePreKeys || [];
75
+ this.data.sessions = payload.sessions || {};
76
+ this._loaded = true;
77
+ return true;
78
+ } catch (_) {
79
+ return false;
80
+ }
81
+ }
82
+
83
+ save() {
84
+ if (!fs.existsSync(this.storePath)) {
85
+ fs.mkdirSync(this.storePath, { recursive: true });
86
+ }
87
+ if (!this.data.deviceSecret) {
88
+ this.data.deviceSecret = crypto.randomBytes(32).toString("base64");
89
+ }
90
+ const payload = JSON.stringify({
91
+ identityKey: this.data.identityKey,
92
+ signedPreKey: this.data.signedPreKey,
93
+ oneTimePreKeys: this.data.oneTimePreKeys,
94
+ sessions: this.data.sessions
95
+ });
96
+ const encrypted = this._localEncrypt(payload);
97
+ fs.writeFileSync(this._storeFile(), JSON.stringify({ deviceSecret: this.data.deviceSecret, payload: encrypted }, null, 2), "utf8");
98
+ }
99
+
100
+ hasIdentityKey() { return !!this.data.identityKey; }
101
+
102
+ getIdentityKey() {
103
+ const ik = this.data.identityKey;
104
+ if (!ik) return null;
105
+ return {
106
+ privateKey: Buffer.from(ik.privateKey, "base64"),
107
+ publicKey: Buffer.from(ik.publicKey, "base64"),
108
+ publicKeyRaw: Buffer.from(ik.publicKeyRaw, "base64")
109
+ };
110
+ }
111
+
112
+ setIdentityKey(keyPair) {
113
+ this.data.identityKey = {
114
+ privateKey: keyPair.privateKey.toString("base64"),
115
+ publicKey: keyPair.publicKey.toString("base64"),
116
+ publicKeyRaw: keyPair.publicKeyRaw.toString("base64")
117
+ };
118
+ }
119
+
120
+ getSignedPreKey() {
121
+ const spk = this.data.signedPreKey;
122
+ if (!spk) return null;
123
+ return {
124
+ privateKey: Buffer.from(spk.privateKey, "base64"),
125
+ publicKey: Buffer.from(spk.publicKey, "base64"),
126
+ publicKeyRaw: Buffer.from(spk.publicKeyRaw, "base64"),
127
+ signature: Buffer.from(spk.signature, "base64"),
128
+ keyId: spk.keyId
129
+ };
130
+ }
131
+
132
+ setSignedPreKey(keyPair) {
133
+ this.data.signedPreKey = {
134
+ privateKey: keyPair.privateKey.toString("base64"),
135
+ publicKey: keyPair.publicKey.toString("base64"),
136
+ publicKeyRaw: keyPair.publicKeyRaw.toString("base64"),
137
+ signature: keyPair.signature ? keyPair.signature.toString("base64") : "",
138
+ keyId: keyPair.keyId || 1
139
+ };
140
+ }
141
+
142
+ getOneTimePreKeys() {
143
+ return (this.data.oneTimePreKeys || []).map(k => ({
144
+ privateKey: Buffer.from(k.privateKey, "base64"),
145
+ publicKey: Buffer.from(k.publicKey, "base64"),
146
+ publicKeyRaw: Buffer.from(k.publicKeyRaw, "base64"),
147
+ keyId: k.keyId
148
+ }));
149
+ }
150
+
151
+ addOneTimePreKey(keyPair) {
152
+ this.data.oneTimePreKeys = this.data.oneTimePreKeys || [];
153
+ this.data.oneTimePreKeys.push({
154
+ privateKey: keyPair.privateKey.toString("base64"),
155
+ publicKey: keyPair.publicKey.toString("base64"),
156
+ publicKeyRaw: keyPair.publicKeyRaw.toString("base64"),
157
+ keyId: keyPair.keyId
158
+ });
159
+ }
160
+
161
+ consumeOneTimePreKey(keyId) {
162
+ const idx = (this.data.oneTimePreKeys || []).findIndex(k => k.keyId === keyId);
163
+ if (idx < 0) return null;
164
+ const k = this.data.oneTimePreKeys.splice(idx, 1)[0];
165
+ return {
166
+ privateKey: Buffer.from(k.privateKey, "base64"),
167
+ publicKey: Buffer.from(k.publicKey, "base64"),
168
+ publicKeyRaw: Buffer.from(k.publicKeyRaw, "base64"),
169
+ keyId: k.keyId
170
+ };
171
+ }
172
+
173
+ hasSession(threadID) { return !!this.data.sessions[threadID]; }
174
+
175
+ getSession(threadID) { return this.data.sessions[threadID] || null; }
176
+
177
+ setSession(threadID, serializedSession) { this.data.sessions[threadID] = serializedSession; }
178
+
179
+ deleteSession(threadID) { delete this.data.sessions[threadID]; }
180
+ }
181
+
182
+ module.exports = { E2EEStore };
package/src/e2ee.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * SHADOWX E2EE — own implementation, zero external packages.
5
+ * Delegates to ./e2ee/index.js which uses only Node.js built-in crypto.
6
+ */
7
+
8
+ module.exports = require("./e2ee/index");
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+
3
+ var utils = require("../utils");
4
+
5
+ module.exports = function (defaultFuncs, api, ctx) {
6
+ return function editMessage(text, messageID, callback) {
7
+ var { promise, callback: cb } = utils.wrapCallback(callback);
8
+
9
+ var mqttClient = ctx.mqttClient || global.mqttClient;
10
+
11
+ if (mqttClient) {
12
+ ctx.wsReqNumber += 1;
13
+ ctx.wsTaskNumber += 1;
14
+
15
+ var queryPayload = {
16
+ message_id: messageID,
17
+ text: text
18
+ };
19
+
20
+ var query = {
21
+ failure_count: null,
22
+ label: "742",
23
+ payload: JSON.stringify(queryPayload),
24
+ queue_name: "edit_message",
25
+ task_id: ctx.wsTaskNumber
26
+ };
27
+
28
+ var context = {
29
+ app_id: "2220391788200892",
30
+ payload: JSON.stringify({
31
+ data_trace_id: null,
32
+ epoch_id: String((BigInt(Date.now()) << 22n)),
33
+ tasks: [query],
34
+ version_id: "6903494529735864"
35
+ }),
36
+ request_id: ctx.wsReqNumber,
37
+ type: 3
38
+ };
39
+
40
+ mqttClient.publish("/ls_req", JSON.stringify(context), { qos: 1, retain: false }, function (err) {
41
+ if (err) return cb(err);
42
+ cb(null, true);
43
+ });
44
+ } else {
45
+ defaultFuncs.post("https://www.facebook.com/messaging/edit_message/", ctx.jar, {
46
+ message_id: messageID,
47
+ body: text
48
+ })
49
+ .then(utils.parseAndCheckLogin(ctx, defaultFuncs))
50
+ .then(function (resData) { if (resData.error) throw resData; cb(null, true); })
51
+ .catch(function (err) { cb(err); });
52
+ }
53
+
54
+ return promise;
55
+ };
56
+ };