@nexustechpro/baileys 1.1.3 → 1.1.5
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/LICENSE +21 -0
- package/README.md +306 -261
- package/WAProto/fix-imports.js +71 -19
- package/WAProto/index.js +65468 -137440
- package/engine-requirements.js +10 -0
- package/lib/Defaults/index.js +145 -115
- package/lib/Socket/Client/websocket.js +35 -131
- package/lib/Socket/communities.js +1 -1
- package/lib/Socket/messages-recv.js +364 -1003
- package/lib/Socket/messages-send.js +310 -648
- package/lib/Socket/newsletter.js +141 -293
- package/lib/Socket/socket.js +162 -644
- package/lib/Types/Newsletter.js +29 -31
- package/lib/Utils/chat-utils.js +1 -1
- package/lib/Utils/crypto.js +21 -47
- package/lib/Utils/generics.js +267 -383
- package/lib/Utils/identity-chnage-handler.js +55 -0
- package/lib/Utils/index.js +1 -0
- package/lib/Utils/lt-hash.js +2 -2
- package/lib/Utils/messages-media.js +200 -15
- package/lib/Utils/messages.js +331 -286
- package/lib/Utils/noise-handler.js +200 -166
- package/lib/index.js +2 -2
- package/package.json +137 -121
|
@@ -3,220 +3,254 @@ import { proto } from '../../WAProto/index.js';
|
|
|
3
3
|
import { NOISE_MODE, WA_CERT_DETAILS } from '../Defaults/index.js';
|
|
4
4
|
import { decodeBinaryNode } from '../WABinary/index.js';
|
|
5
5
|
import { aesDecryptGCM, aesEncryptGCM, Curve, hkdf, sha256 } from './crypto.js';
|
|
6
|
+
|
|
7
|
+
const IV_LENGTH = 12;
|
|
8
|
+
const EMPTY_BUFFER = Buffer.alloc(0);
|
|
9
|
+
|
|
6
10
|
const generateIV = (counter) => {
|
|
7
|
-
const iv = new ArrayBuffer(
|
|
11
|
+
const iv = new ArrayBuffer(IV_LENGTH);
|
|
8
12
|
new DataView(iv).setUint32(8, counter);
|
|
9
13
|
return new Uint8Array(iv);
|
|
10
14
|
};
|
|
11
|
-
|
|
15
|
+
|
|
16
|
+
class TransportState {
|
|
17
|
+
constructor(encKey, decKey) {
|
|
18
|
+
this.encKey = encKey;
|
|
19
|
+
this.decKey = decKey;
|
|
20
|
+
this.readCounter = 0;
|
|
21
|
+
this.writeCounter = 0;
|
|
22
|
+
this.iv = new Uint8Array(IV_LENGTH);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
encrypt(plaintext) {
|
|
26
|
+
const c = this.writeCounter++;
|
|
27
|
+
this.iv[8] = (c >>> 24) & 0xff;
|
|
28
|
+
this.iv[9] = (c >>> 16) & 0xff;
|
|
29
|
+
this.iv[10] = (c >>> 8) & 0xff;
|
|
30
|
+
this.iv[11] = c & 0xff;
|
|
31
|
+
|
|
32
|
+
return aesEncryptGCM(plaintext, this.encKey, this.iv, EMPTY_BUFFER);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
decrypt(ciphertext) {
|
|
36
|
+
const c = this.readCounter++;
|
|
37
|
+
this.iv[8] = (c >>> 24) & 0xff;
|
|
38
|
+
this.iv[9] = (c >>> 16) & 0xff;
|
|
39
|
+
this.iv[10] = (c >>> 8) & 0xff;
|
|
40
|
+
this.iv[11] = c & 0xff;
|
|
41
|
+
|
|
42
|
+
return aesDecryptGCM(ciphertext, this.decKey, this.iv, EMPTY_BUFFER);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const makeNoiseHandler = ({
|
|
47
|
+
keyPair: { private: privateKey, public: publicKey },
|
|
48
|
+
NOISE_HEADER,
|
|
49
|
+
logger,
|
|
50
|
+
routingInfo
|
|
51
|
+
}) => {
|
|
12
52
|
logger = logger.child({ class: 'ns' });
|
|
53
|
+
|
|
54
|
+
const data = Buffer.from(NOISE_MODE);
|
|
55
|
+
let hash = data.byteLength === 32 ? data : sha256(data);
|
|
56
|
+
let salt = hash;
|
|
57
|
+
let encKey = hash;
|
|
58
|
+
let decKey = hash;
|
|
59
|
+
let counter = 0;
|
|
60
|
+
let sentIntro = false;
|
|
61
|
+
|
|
62
|
+
let inBytes = Buffer.alloc(0);
|
|
63
|
+
|
|
64
|
+
let transport = null;
|
|
65
|
+
let isWaitingForTransport = false;
|
|
66
|
+
let pendingOnFrame = null;
|
|
67
|
+
|
|
68
|
+
let introHeader;
|
|
69
|
+
if (routingInfo) {
|
|
70
|
+
introHeader = Buffer.alloc(7 + routingInfo.byteLength + NOISE_HEADER.length);
|
|
71
|
+
introHeader.write('ED', 0, 'utf8');
|
|
72
|
+
introHeader.writeUint8(0, 2);
|
|
73
|
+
introHeader.writeUint8(1, 3);
|
|
74
|
+
introHeader.writeUint8(routingInfo.byteLength >> 16, 4);
|
|
75
|
+
introHeader.writeUint16BE(routingInfo.byteLength & 65535, 5);
|
|
76
|
+
introHeader.set(routingInfo, 7);
|
|
77
|
+
introHeader.set(NOISE_HEADER, 7 + routingInfo.byteLength);
|
|
78
|
+
} else {
|
|
79
|
+
introHeader = Buffer.from(NOISE_HEADER);
|
|
80
|
+
}
|
|
81
|
+
|
|
13
82
|
const authenticate = (data) => {
|
|
14
|
-
if (!
|
|
83
|
+
if (!transport) {
|
|
15
84
|
hash = sha256(Buffer.concat([hash, data]));
|
|
16
85
|
}
|
|
17
86
|
};
|
|
87
|
+
|
|
18
88
|
const encrypt = (plaintext) => {
|
|
19
|
-
|
|
20
|
-
|
|
89
|
+
if (transport) {
|
|
90
|
+
return transport.encrypt(plaintext);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = aesEncryptGCM(plaintext, encKey, generateIV(counter++), hash);
|
|
21
94
|
authenticate(result);
|
|
22
95
|
return result;
|
|
23
96
|
};
|
|
97
|
+
|
|
24
98
|
const decrypt = (ciphertext) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const iv = generateIV(counter);
|
|
28
|
-
const result = aesDecryptGCM(ciphertext, decKey, iv, hash);
|
|
29
|
-
return { success: true, result, counter };
|
|
30
|
-
} catch {
|
|
31
|
-
return { success: false };
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// Try with current counter
|
|
36
|
-
const currentCounter = isFinished ? readCounter : writeCounter;
|
|
37
|
-
let attempt = attemptDecrypt(currentCounter);
|
|
38
|
-
|
|
39
|
-
if (attempt.success) {
|
|
40
|
-
if (isFinished) {
|
|
41
|
-
readCounter += 1;
|
|
42
|
-
} else {
|
|
43
|
-
writeCounter += 1;
|
|
99
|
+
if (transport) {
|
|
100
|
+
return transport.decrypt(ciphertext);
|
|
44
101
|
}
|
|
102
|
+
|
|
103
|
+
const result = aesDecryptGCM(ciphertext, decKey, generateIV(counter++), hash);
|
|
45
104
|
authenticate(ciphertext);
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (attempt.success) {
|
|
54
|
-
logger.info('Decryption succeeded with alternate counter - syncing');
|
|
55
|
-
if (isFinished) {
|
|
56
|
-
readCounter = currentCounter + 2; // Skip ahead
|
|
57
|
-
} else {
|
|
58
|
-
writeCounter = currentCounter + 2;
|
|
59
|
-
}
|
|
60
|
-
authenticate(ciphertext);
|
|
61
|
-
return attempt.result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Try previous counter
|
|
65
|
-
if (currentCounter > 0) {
|
|
66
|
-
attempt = attemptDecrypt(currentCounter - 1);
|
|
67
|
-
|
|
68
|
-
if (attempt.success) {
|
|
69
|
-
logger.info('Decryption succeeded with previous counter - syncing');
|
|
70
|
-
if (isFinished) {
|
|
71
|
-
readCounter = currentCounter;
|
|
72
|
-
} else {
|
|
73
|
-
writeCounter = currentCounter;
|
|
74
|
-
}
|
|
75
|
-
authenticate(ciphertext);
|
|
76
|
-
return attempt.result;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// All attempts failed - throw so frame gets skipped
|
|
81
|
-
logger.warn({ currentCounter, isFinished }, 'All decryption attempts failed - frame will be skipped');
|
|
82
|
-
throw new Error('Decryption failed - counter mismatch');
|
|
83
|
-
};
|
|
84
|
-
const localHKDF = async (data) => {
|
|
85
|
-
const key = await hkdf(Buffer.from(data), 64, { salt, info: '' });
|
|
86
|
-
return [key.slice(0, 32), key.slice(32)];
|
|
105
|
+
return result;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const localHKDF = (data) => {
|
|
109
|
+
const key = hkdf(Buffer.from(data), 64, { salt, info: '' });
|
|
110
|
+
return [key.subarray(0, 32), key.subarray(32)];
|
|
87
111
|
};
|
|
88
|
-
|
|
89
|
-
|
|
112
|
+
|
|
113
|
+
const mixIntoKey = (data) => {
|
|
114
|
+
const [write, read] = localHKDF(data);
|
|
90
115
|
salt = write;
|
|
91
116
|
encKey = read;
|
|
92
117
|
decKey = read;
|
|
93
|
-
|
|
94
|
-
writeCounter = 0;
|
|
118
|
+
counter = 0;
|
|
95
119
|
};
|
|
120
|
+
|
|
96
121
|
const finishInit = async () => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
122
|
+
isWaitingForTransport = true;
|
|
123
|
+
const [write, read] = localHKDF(new Uint8Array(0));
|
|
124
|
+
transport = new TransportState(write, read);
|
|
125
|
+
isWaitingForTransport = false;
|
|
126
|
+
|
|
127
|
+
logger.trace('Noise handler transitioned to Transport state');
|
|
128
|
+
|
|
129
|
+
if (pendingOnFrame) {
|
|
130
|
+
logger.trace({ length: inBytes.length }, 'Flushing buffered frames after transport ready');
|
|
131
|
+
await processData(pendingOnFrame);
|
|
132
|
+
pendingOnFrame = null;
|
|
133
|
+
}
|
|
104
134
|
};
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
135
|
+
|
|
136
|
+
const processData = async (onFrame) => {
|
|
137
|
+
let size;
|
|
138
|
+
|
|
139
|
+
while (true) {
|
|
140
|
+
if (inBytes.length < 3) return;
|
|
141
|
+
|
|
142
|
+
size = (inBytes[0] << 16) | (inBytes[1] << 8) | inBytes[2];
|
|
143
|
+
|
|
144
|
+
if (inBytes.length < size + 3) return;
|
|
145
|
+
|
|
146
|
+
let frame = inBytes.subarray(3, size + 3);
|
|
147
|
+
inBytes = inBytes.subarray(size + 3);
|
|
148
|
+
|
|
149
|
+
if (transport) {
|
|
150
|
+
const result = transport.decrypt(frame);
|
|
151
|
+
frame = await decodeBinaryNode(result);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (logger.level === 'trace') {
|
|
155
|
+
logger.trace({ msg: frame?.attrs?.id }, 'recv frame');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
onFrame(frame);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
115
162
|
authenticate(NOISE_HEADER);
|
|
116
163
|
authenticate(publicKey);
|
|
164
|
+
|
|
117
165
|
return {
|
|
118
166
|
encrypt,
|
|
119
167
|
decrypt,
|
|
120
168
|
authenticate,
|
|
121
169
|
mixIntoKey,
|
|
122
170
|
finishInit,
|
|
123
|
-
processHandshake:
|
|
171
|
+
processHandshake: ({ serverHello }, noiseKey) => {
|
|
124
172
|
authenticate(serverHello.ephemeral);
|
|
125
|
-
|
|
173
|
+
mixIntoKey(Curve.sharedKey(privateKey, serverHello.ephemeral));
|
|
174
|
+
|
|
126
175
|
const decStaticContent = decrypt(serverHello.static);
|
|
127
|
-
|
|
176
|
+
mixIntoKey(Curve.sharedKey(privateKey, decStaticContent));
|
|
177
|
+
|
|
128
178
|
const certDecoded = decrypt(serverHello.payload);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
179
|
+
|
|
180
|
+
const { intermediate: certIntermediate, leaf } = proto.CertChain.decode(certDecoded);
|
|
181
|
+
|
|
182
|
+
// Leaf certificate validation
|
|
183
|
+
if (!leaf?.details || !leaf?.signature) {
|
|
184
|
+
throw new Boom('invalid noise leaf certificate', { statusCode: 400 });
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!certIntermediate?.details || !certIntermediate?.signature) {
|
|
188
|
+
throw new Boom('invalid noise intermediate certificate', { statusCode: 400 });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const details = proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
|
|
192
|
+
const { issuerSerial } = details;
|
|
193
|
+
|
|
194
|
+
const verify = Curve.verify(details.key, leaf.details, leaf.signature);
|
|
195
|
+
const verifyIntermediate = Curve.verify(
|
|
196
|
+
WA_CERT_DETAILS.PUBLIC_KEY,
|
|
197
|
+
certIntermediate.details,
|
|
198
|
+
certIntermediate.signature
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!verify) {
|
|
202
|
+
throw new Boom('noise certificate signature invalid', { statusCode: 400 });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!verifyIntermediate) {
|
|
206
|
+
throw new Boom('noise intermediate certificate signature invalid', { statusCode: 400 });
|
|
207
|
+
}
|
|
208
|
+
|
|
132
209
|
if (issuerSerial !== WA_CERT_DETAILS.SERIAL) {
|
|
133
210
|
throw new Boom('certification match failed', { statusCode: 400 });
|
|
134
211
|
}
|
|
212
|
+
|
|
135
213
|
const keyEnc = encrypt(noiseKey.public);
|
|
136
|
-
|
|
214
|
+
mixIntoKey(Curve.sharedKey(noiseKey.private, serverHello.ephemeral));
|
|
215
|
+
|
|
137
216
|
return keyEnc;
|
|
138
217
|
},
|
|
139
218
|
encodeFrame: (data) => {
|
|
140
|
-
if (
|
|
141
|
-
data = encrypt(data);
|
|
142
|
-
}
|
|
143
|
-
let header;
|
|
144
|
-
if (routingInfo) {
|
|
145
|
-
header = Buffer.alloc(7);
|
|
146
|
-
header.write('ED', 0, 'utf8');
|
|
147
|
-
header.writeUint8(0, 2);
|
|
148
|
-
header.writeUint8(1, 3);
|
|
149
|
-
header.writeUint8(routingInfo.byteLength >> 16, 4);
|
|
150
|
-
header.writeUint16BE(routingInfo.byteLength & 65535, 5);
|
|
151
|
-
header = Buffer.concat([header, routingInfo, NOISE_HEADER]);
|
|
152
|
-
}
|
|
153
|
-
else {
|
|
154
|
-
header = Buffer.from(NOISE_HEADER);
|
|
219
|
+
if (transport) {
|
|
220
|
+
data = transport.encrypt(data);
|
|
155
221
|
}
|
|
156
|
-
|
|
157
|
-
const
|
|
222
|
+
|
|
223
|
+
const dataLen = data.byteLength;
|
|
224
|
+
const introSize = sentIntro ? 0 : introHeader.length;
|
|
225
|
+
const frame = Buffer.allocUnsafe(introSize + 3 + dataLen);
|
|
226
|
+
|
|
158
227
|
if (!sentIntro) {
|
|
159
|
-
frame.set(
|
|
228
|
+
frame.set(introHeader);
|
|
160
229
|
sentIntro = true;
|
|
161
230
|
}
|
|
162
|
-
|
|
163
|
-
frame
|
|
231
|
+
|
|
232
|
+
frame[introSize] = (dataLen >>> 16) & 0xff;
|
|
233
|
+
frame[introSize + 1] = (dataLen >>> 8) & 0xff;
|
|
234
|
+
frame[introSize + 2] = dataLen & 0xff;
|
|
235
|
+
|
|
164
236
|
frame.set(data, introSize + 3);
|
|
237
|
+
|
|
165
238
|
return frame;
|
|
166
239
|
},
|
|
167
|
-
decodeFrame: async (newData, onFrame) => {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
inBytes = Buffer.concat([inBytes, newData]);
|
|
176
|
-
logger.trace(`recv ${newData.length} bytes, total recv ${inBytes.length} bytes`);
|
|
177
|
-
|
|
178
|
-
let size = getBytesSize();
|
|
179
|
-
|
|
180
|
-
while (size && inBytes.length >= size + 3) {
|
|
181
|
-
let frame = inBytes.slice(3, size + 3);
|
|
182
|
-
inBytes = inBytes.slice(size + 3);
|
|
183
|
-
|
|
184
|
-
try {
|
|
185
|
-
if (isFinished) {
|
|
186
|
-
try {
|
|
187
|
-
const result = decrypt(frame);
|
|
188
|
-
frame = await decodeBinaryNode(result);
|
|
189
|
-
} catch (decryptError) {
|
|
190
|
-
logger.warn({
|
|
191
|
-
error: decryptError.message,
|
|
192
|
-
frameSize: frame.length
|
|
193
|
-
}, 'Frame decryption failed - skipping this frame');
|
|
194
|
-
|
|
195
|
-
// Just skip and move to next frame - never stop processing
|
|
196
|
-
size = getBytesSize();
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
logger.trace({ msg: frame?.attrs?.id }, 'recv frame');
|
|
202
|
-
onFrame(frame);
|
|
203
|
-
} catch (frameError) {
|
|
204
|
-
logger.warn({
|
|
205
|
-
error: frameError.message,
|
|
206
|
-
frameSize: frame?.length
|
|
207
|
-
}, 'Frame processing error - skipping');
|
|
208
|
-
// Continue to next frame
|
|
240
|
+
decodeFrame: async (newData, onFrame) => {
|
|
241
|
+
if (isWaitingForTransport) {
|
|
242
|
+
inBytes = Buffer.concat([inBytes, newData]);
|
|
243
|
+
pendingOnFrame = onFrame;
|
|
244
|
+
return;
|
|
209
245
|
}
|
|
210
|
-
|
|
211
|
-
|
|
246
|
+
|
|
247
|
+
if (inBytes.length === 0) {
|
|
248
|
+
inBytes = Buffer.from(newData);
|
|
249
|
+
} else {
|
|
250
|
+
inBytes = Buffer.concat([inBytes, newData]);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await processData(onFrame);
|
|
212
254
|
}
|
|
213
|
-
} catch (outerError) {
|
|
214
|
-
// Even on outer errors, just log and continue
|
|
215
|
-
logger.warn({ error: outerError.message }, 'Buffer processing error - resetting buffer and continuing');
|
|
216
|
-
inBytes = Buffer.alloc(0);
|
|
217
|
-
// Don't throw - just continue processing next data
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
255
|
};
|
|
221
|
-
};
|
|
222
|
-
//# sourceMappingURL=noise-handler.js.map
|
|
256
|
+
};
|
package/lib/index.js
CHANGED
|
@@ -31,8 +31,8 @@ const banner = `
|
|
|
31
31
|
const info = `
|
|
32
32
|
┌───────────────────────────────────────────────────────────────────────┐
|
|
33
33
|
│ 📦 Package: @nexustechpro/baileys │
|
|
34
|
-
│ 🔖 Version: 1.1.
|
|
35
|
-
│ ⚡ Status: Production Ready
|
|
34
|
+
│ 🔖 Version: 1.1.5 │
|
|
35
|
+
│ ⚡ Status: Production Ready │
|
|
36
36
|
├───────────────────────────────────────────────────────────────────────┤
|
|
37
37
|
│ 🚀 Advanced WhatsApp Web API Client │
|
|
38
38
|
│ ✨ Interactive Buttons • Products • Events • Media │
|