@mtcute/convert 0.17.0 → 0.18.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 (113) hide show
  1. package/convert/src/dcs.cjs +119 -0
  2. package/convert/src/dcs.js +114 -0
  3. package/convert/src/gramjs/convert.cjs +30 -0
  4. package/convert/src/gramjs/convert.js +25 -0
  5. package/convert/src/gramjs/parse.cjs +33 -0
  6. package/convert/src/gramjs/parse.js +28 -0
  7. package/convert/src/gramjs/serialize.cjs +26 -0
  8. package/convert/src/gramjs/serialize.js +21 -0
  9. package/convert/src/mtkruto/convert.cjs +33 -0
  10. package/convert/src/mtkruto/convert.js +28 -0
  11. package/convert/src/mtkruto/parse.cjs +31 -0
  12. package/convert/src/mtkruto/parse.js +26 -0
  13. package/convert/src/mtkruto/serialize.cjs +18 -0
  14. package/convert/src/mtkruto/serialize.js +13 -0
  15. package/convert/src/pyrogram/convert.cjs +42 -0
  16. package/convert/src/pyrogram/convert.js +37 -0
  17. package/convert/src/pyrogram/parse.cjs +52 -0
  18. package/convert/src/pyrogram/parse.js +47 -0
  19. package/convert/src/pyrogram/serialize.cjs +40 -0
  20. package/convert/src/pyrogram/serialize.js +35 -0
  21. package/convert/src/tdesktop/convert.cjs +66 -0
  22. package/convert/src/tdesktop/convert.js +61 -0
  23. package/convert/src/tdesktop/qt-bundle.cjs +11 -0
  24. package/convert/src/tdesktop/qt-bundle.js +6 -0
  25. package/convert/src/tdesktop/qt-reader.cjs +38 -0
  26. package/convert/src/tdesktop/qt-reader.js +33 -0
  27. package/convert/src/tdesktop/qt-writer.cjs +34 -0
  28. package/convert/src/tdesktop/qt-writer.js +29 -0
  29. package/convert/src/tdesktop/tdata.cjs +359 -0
  30. package/convert/src/tdesktop/tdata.js +332 -0
  31. package/convert/src/telethon/convert.cjs +47 -0
  32. package/convert/src/telethon/convert.js +42 -0
  33. package/convert/src/telethon/parse.cjs +39 -0
  34. package/convert/src/telethon/parse.js +34 -0
  35. package/convert/src/telethon/serialize.cjs +34 -0
  36. package/convert/src/telethon/serialize.js +29 -0
  37. package/convert/src/utils/crypto.cjs +53 -0
  38. package/convert/src/utils/crypto.js +26 -0
  39. package/convert/src/utils/rle.cjs +49 -0
  40. package/convert/src/utils/rle.js +44 -0
  41. package/dcs.d.cts +4 -0
  42. package/gramjs/__fixtures__/session.d.cts +1 -0
  43. package/gramjs/convert.d.cts +4 -0
  44. package/gramjs/convert.test.d.cts +1 -0
  45. package/gramjs/index.d.cts +4 -0
  46. package/gramjs/parse.d.cts +2 -0
  47. package/gramjs/parse.test.d.cts +1 -0
  48. package/gramjs/serialize.d.cts +2 -0
  49. package/gramjs/serialize.test.d.cts +1 -0
  50. package/gramjs/types.d.cts +7 -0
  51. package/index.cjs +41 -488
  52. package/index.d.cts +1 -0
  53. package/index.d.ts +1 -0
  54. package/index.js +20 -467
  55. package/mtkruto/__fixtures__/session.d.cts +1 -0
  56. package/mtkruto/convert.d.cts +4 -0
  57. package/mtkruto/convert.test.d.cts +1 -0
  58. package/mtkruto/index.d.cts +4 -0
  59. package/mtkruto/parse.d.cts +2 -0
  60. package/mtkruto/parse.test.d.cts +1 -0
  61. package/mtkruto/serialize.d.cts +2 -0
  62. package/mtkruto/serialize.test.d.cts +1 -0
  63. package/mtkruto/types.d.cts +5 -0
  64. package/node/src/utils/crypto.cjs +75 -0
  65. package/node/src/utils/crypto.js +69 -0
  66. package/node/src/utils.cjs +34 -0
  67. package/node/src/utils.js +7 -0
  68. package/package.json +12 -9
  69. package/pyrogram/__fixtures__/session.d.cts +1 -0
  70. package/pyrogram/__fixtures__/session_old.d.cts +1 -0
  71. package/pyrogram/convert.d.cts +6 -0
  72. package/pyrogram/convert.test.d.cts +1 -0
  73. package/pyrogram/index.d.cts +4 -0
  74. package/pyrogram/parse.d.cts +2 -0
  75. package/pyrogram/parse.test.d.cts +1 -0
  76. package/pyrogram/serialize.d.cts +2 -0
  77. package/pyrogram/serialize.test.d.cts +1 -0
  78. package/pyrogram/types.d.cts +8 -0
  79. package/tdesktop/convert.d.cts +5 -0
  80. package/tdesktop/convert.d.ts +5 -0
  81. package/tdesktop/index.d.cts +5 -0
  82. package/tdesktop/index.d.ts +5 -0
  83. package/tdesktop/qt-bundle.d.cts +3 -0
  84. package/tdesktop/qt-bundle.d.ts +3 -0
  85. package/tdesktop/qt-reader.d.cts +6 -0
  86. package/tdesktop/qt-reader.d.ts +6 -0
  87. package/tdesktop/qt-writer.d.cts +6 -0
  88. package/tdesktop/qt-writer.d.ts +6 -0
  89. package/tdesktop/tdata.d.cts +65 -0
  90. package/tdesktop/tdata.d.ts +65 -0
  91. package/tdesktop/tdata.test.d.cts +1 -0
  92. package/tdesktop/tdata.test.d.ts +1 -0
  93. package/tdesktop/types.d.cts +22 -0
  94. package/tdesktop/types.d.ts +22 -0
  95. package/telethon/__fixtures__/session.d.cts +1 -0
  96. package/telethon/__fixtures__/session_v6.d.cts +1 -0
  97. package/telethon/convert.d.cts +4 -0
  98. package/telethon/convert.test.d.cts +1 -0
  99. package/telethon/index.d.cts +4 -0
  100. package/telethon/parse.d.cts +2 -0
  101. package/telethon/parse.test.d.cts +1 -0
  102. package/telethon/serialize.d.cts +2 -0
  103. package/telethon/serialize.test.d.cts +1 -0
  104. package/telethon/types.d.cts +7 -0
  105. package/types.d.cts +0 -0
  106. package/utils/crypto.d.cts +9 -0
  107. package/utils/crypto.d.ts +9 -0
  108. package/utils/fs.d.cts +11 -0
  109. package/utils/fs.d.ts +11 -0
  110. package/utils/rle.d.cts +3 -0
  111. package/wasm/src/index.cjs +64 -0
  112. package/wasm/src/index.js +59 -0
  113. package/utils/ip.d.ts +0 -3
@@ -0,0 +1,359 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __copyProps = (to, from, except, desc) => {
8
+ if (from && typeof from === "object" || typeof from === "function") {
9
+ for (let key of __getOwnPropNames(from))
10
+ if (!__hasOwnProp.call(to, key) && key !== except)
11
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
12
+ }
13
+ return to;
14
+ };
15
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
16
+ // If the importer is in node compatibility mode or this is not an ESM
17
+ // file that has been converted to a CommonJS file using a Babel-
18
+ // compatible transform (i.e. "__esModule" has not been set), then set
19
+ // "default" to the CommonJS "module.exports" for node compatibility.
20
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
21
+ mod
22
+ ));
23
+ if (typeof globalThis !== "undefined" && !globalThis._MTCUTE_CJS_DEPRECATION_WARNED) {
24
+ globalThis._MTCUTE_CJS_DEPRECATION_WARNED = true;
25
+ console.warn("[mtcute-workspace] CommonJS support is deprecated and will be removed in 0.20.0. Please consider switching to ESM, it's " + (/* @__PURE__ */ new Date()).getFullYear() + " already.");
26
+ console.warn("[mtcute-workspace] Learn more about switching to ESM: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c");
27
+ }
28
+ "use strict";
29
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
30
+ const posix = require("node:path/posix");
31
+ const io = require("@fuman/io");
32
+ const utils = require("@fuman/utils");
33
+ const core = require("@mtcute/core");
34
+ const utils_js = require("@mtcute/core/utils.js");
35
+ const crypto = require("../utils/crypto.cjs");
36
+ const qtReader = require("./qt-reader.cjs");
37
+ const qtWriter = require("./qt-writer.cjs");
38
+ const TDF_MAGIC = /* @__PURE__ */ utils.utf8.encoder.encode("TDF$");
39
+ const TDF_VERSION = 5008003;
40
+ const MTP_AUTHORIZATION_BLOCK = 75;
41
+ const HEX_ALPHABET = "0123456789ABCDEF";
42
+ function toFilePart(key) {
43
+ let str = "";
44
+ for (let i = 0; i < 8; i++) {
45
+ const b = key[i];
46
+ const low = b & 15;
47
+ const high = b >> 4;
48
+ str += HEX_ALPHABET[low] + HEX_ALPHABET[high];
49
+ }
50
+ return str;
51
+ }
52
+ class Tdata {
53
+ constructor(options, fs, crypto2) {
54
+ this.options = options;
55
+ this.fs = fs;
56
+ this.crypto = crypto2;
57
+ }
58
+ keyData;
59
+ static async open(options) {
60
+ const fs = options.fs ?? await import("node:fs/promises");
61
+ const crypto$1 = options.crypto ?? await crypto.getDefaultCryptoProvider();
62
+ await crypto$1.initialize?.();
63
+ const tdata = new Tdata(options, fs, crypto$1);
64
+ tdata.keyData = await tdata.readKeyData();
65
+ return tdata;
66
+ }
67
+ static async create(options) {
68
+ const fs = options.fs ?? await import("node:fs/promises");
69
+ const crypto$1 = options.crypto ?? await crypto.getDefaultCryptoProvider();
70
+ await crypto$1.initialize?.();
71
+ const tdata = new Tdata(options, fs, crypto$1);
72
+ const keyData = {
73
+ ...options.keyData,
74
+ localKey: options.keyData.localKey ?? crypto$1.randomBytes(256),
75
+ version: options.keyData.version ?? TDF_VERSION
76
+ };
77
+ tdata.keyData = keyData;
78
+ await tdata.writeKeyData(keyData);
79
+ return tdata;
80
+ }
81
+ #readInt32(buf) {
82
+ return this.options.le ?? true ? io.read.int32le(buf) : io.read.int32be(buf);
83
+ }
84
+ #writeInt32(buf, val) {
85
+ if (this.options.le ?? true) {
86
+ io.write.int32le(buf, val);
87
+ } else {
88
+ io.write.int32be(buf, val);
89
+ }
90
+ return buf;
91
+ }
92
+ getDataName(idx) {
93
+ let res = this.options.dataKey ?? "data";
94
+ if (idx > 0) {
95
+ res += `#${idx + 1}`;
96
+ }
97
+ return res;
98
+ }
99
+ async readFile(filename) {
100
+ const order = [];
101
+ const modern = `${filename}s`;
102
+ if (await this.fs.stat(posix.join(this.options.path, modern))) {
103
+ order.push(modern);
104
+ } else {
105
+ const try0 = `${filename}0`;
106
+ const try1 = `${filename}1`;
107
+ const try0s = await this.fs.stat(posix.join(this.options.path, try0));
108
+ const try1s = await this.fs.stat(posix.join(this.options.path, try1));
109
+ if (try0s) {
110
+ order.push(try0);
111
+ if (try1s) {
112
+ order.push(try1);
113
+ if (try0s.lastModified < try1s.lastModified) {
114
+ order.reverse();
115
+ }
116
+ }
117
+ } else if (try1s) {
118
+ order.push(try1);
119
+ }
120
+ }
121
+ let lastError = "file not found";
122
+ for (const file of order) {
123
+ const data = await this.fs.readFile(posix.join(this.options.path, file));
124
+ const magic = data.subarray(0, 4);
125
+ if (!utils.typed.equal(magic, TDF_MAGIC)) {
126
+ lastError = "invalid magic";
127
+ continue;
128
+ }
129
+ const versionBytes = data.subarray(4, 8);
130
+ const version = this.#readInt32(versionBytes);
131
+ if (version > TDF_VERSION && !this.options.ignoreVersion) {
132
+ lastError = `Unsupported version: ${version}`;
133
+ continue;
134
+ }
135
+ const dataSize = data.length - 24;
136
+ const bytes = data.subarray(8, dataSize + 8);
137
+ const md5 = await this.crypto.createHash("md5");
138
+ await md5.update(bytes);
139
+ await md5.update(this.#writeInt32(utils.u8.alloc(4), dataSize));
140
+ await md5.update(versionBytes);
141
+ await md5.update(magic);
142
+ const hash = await md5.digest();
143
+ if (!utils.typed.equal(hash, data.subarray(dataSize + 8))) {
144
+ lastError = "md5 mismatch";
145
+ continue;
146
+ }
147
+ return [version, bytes];
148
+ }
149
+ throw new Error(`failed to read ${filename}, last error: ${lastError}`);
150
+ }
151
+ async writeFile(filename, data, mkdir = false) {
152
+ filename = posix.join(this.options.path, `${filename}s`);
153
+ const version = this.#writeInt32(utils.u8.alloc(4), TDF_VERSION);
154
+ const dataSize = this.#writeInt32(utils.u8.alloc(4), data.length);
155
+ const md5 = await this.crypto.createHash("md5");
156
+ await md5.update(data);
157
+ await md5.update(dataSize);
158
+ await md5.update(version);
159
+ await md5.update(TDF_MAGIC);
160
+ if (mkdir) {
161
+ await this.fs.mkdir(posix.dirname(filename), { recursive: true });
162
+ }
163
+ await this.fs.writeFile(
164
+ filename,
165
+ utils.u8.concat([TDF_MAGIC, version, data, await md5.digest()])
166
+ );
167
+ }
168
+ async createLocalKey(salt, passcode = this.options.passcode ?? "") {
169
+ const hasher = await this.crypto.createHash("sha512");
170
+ await hasher.update(salt);
171
+ await hasher.update(utils.utf8.encoder.encode(passcode));
172
+ await hasher.update(salt);
173
+ const hash = await hasher.digest();
174
+ return this.crypto.pbkdf2(
175
+ hash,
176
+ salt,
177
+ passcode === "" ? 1 : 1e5,
178
+ 256,
179
+ "sha512"
180
+ );
181
+ }
182
+ async decryptLocal(encrypted, key) {
183
+ const encryptedKey = encrypted.subarray(0, 16);
184
+ const encryptedData = encrypted.subarray(16);
185
+ const ige = utils_js.createAesIgeForMessageOld(
186
+ this.crypto,
187
+ key,
188
+ encryptedKey,
189
+ false
190
+ );
191
+ const decrypted = ige.decrypt(encryptedData);
192
+ if (!utils.typed.equal(
193
+ this.crypto.sha1(decrypted).subarray(0, 16),
194
+ encryptedKey
195
+ )) {
196
+ throw new Error("Failed to decrypt, invalid password?");
197
+ }
198
+ const fullLen = encryptedData.length;
199
+ const dataLen = this.#readInt32(decrypted);
200
+ if (dataLen > decrypted.length || dataLen <= fullLen - 16 || dataLen < 4) {
201
+ throw new Error("Failed to decrypt, invalid data length");
202
+ }
203
+ return decrypted.subarray(4, dataLen);
204
+ }
205
+ async encryptLocal(data, key) {
206
+ const dataSize = data.length + 4;
207
+ const padding = dataSize & 15 ? this.crypto.randomBytes(16 - (dataSize & 15)) : utils.u8.empty;
208
+ const toEncrypt = utils.u8.alloc(dataSize + padding.length);
209
+ this.#writeInt32(toEncrypt, dataSize);
210
+ toEncrypt.set(data, 4);
211
+ toEncrypt.set(padding, dataSize);
212
+ const encryptedKey = this.crypto.sha1(toEncrypt).subarray(0, 16);
213
+ const ige = utils_js.createAesIgeForMessageOld(
214
+ this.crypto,
215
+ key,
216
+ encryptedKey,
217
+ false
218
+ );
219
+ const encryptedData = ige.encrypt(toEncrypt);
220
+ return utils.u8.concat2(encryptedKey, encryptedData);
221
+ }
222
+ async readKeyData() {
223
+ const [version, data] = await this.readFile(`key_${this.options.dataKey ?? "data"}`);
224
+ const bytes = io.Bytes.from(data);
225
+ const salt = qtReader.readQByteArray(bytes);
226
+ const keyEncrypted = qtReader.readQByteArray(bytes);
227
+ const infoEncrypted = qtReader.readQByteArray(bytes);
228
+ const passcodeKey = await this.createLocalKey(salt);
229
+ const keyInnerData = await this.decryptLocal(keyEncrypted, passcodeKey);
230
+ const infoDecrypted = await this.decryptLocal(
231
+ infoEncrypted,
232
+ keyInnerData
233
+ );
234
+ const info = io.Bytes.from(infoDecrypted);
235
+ const localKey = keyInnerData;
236
+ const count = io.read.int32be(info);
237
+ const order = [...new Array(count)].map(() => io.read.int32be(info));
238
+ const active = io.read.int32be(info);
239
+ return {
240
+ version,
241
+ localKey,
242
+ count,
243
+ order,
244
+ active
245
+ };
246
+ }
247
+ async writeKeyData(keyData) {
248
+ const info = io.Bytes.alloc();
249
+ io.write.int32be(info, keyData.count);
250
+ keyData.order.forEach((i) => io.write.int32be(info, i));
251
+ io.write.int32be(info, keyData.active);
252
+ const infoDecrypted = info.result();
253
+ const infoEncrypted = await this.encryptLocal(infoDecrypted, keyData.localKey);
254
+ const salt = this.crypto.randomBytes(32);
255
+ const passcodeKey = await this.createLocalKey(salt);
256
+ const keyEncrypted = await this.encryptLocal(keyData.localKey, passcodeKey);
257
+ const data = io.Bytes.alloc();
258
+ qtWriter.writeQByteArray(data, salt);
259
+ qtWriter.writeQByteArray(data, keyEncrypted);
260
+ qtWriter.writeQByteArray(data, infoEncrypted);
261
+ await this.writeFile(`key_${this.options.dataKey ?? "data"}`, data.result(), true);
262
+ }
263
+ async computeDataNameKey(accountIdx) {
264
+ const md5 = await this.crypto.createHash("md5");
265
+ await md5.update(utils.utf8.encoder.encode(this.getDataName(accountIdx)));
266
+ const r = await md5.digest();
267
+ return r.subarray(0, 8);
268
+ }
269
+ async computeDataNameKeyHex(accountIdx) {
270
+ return toFilePart(await this.computeDataNameKey(accountIdx));
271
+ }
272
+ async readEncryptedFile(filename) {
273
+ const [version, data] = await this.readFile(filename);
274
+ const encrypted = qtReader.readQByteArray(io.Bytes.from(data));
275
+ const decrypted = await this.decryptLocal(encrypted, this.keyData.localKey);
276
+ return [version, decrypted];
277
+ }
278
+ async writeEncryptedFile(filename, data, mkdir = false) {
279
+ const encryptedInner = await this.encryptLocal(data, this.keyData.localKey);
280
+ const writer = io.Bytes.alloc(data.length + 4);
281
+ qtWriter.writeQByteArray(writer, encryptedInner);
282
+ await this.writeFile(filename, writer.result(), mkdir);
283
+ }
284
+ async readMtpAuthorization(accountIdx = 0) {
285
+ const [, mtpData] = await this.readEncryptedFile(
286
+ await this.computeDataNameKeyHex(accountIdx)
287
+ );
288
+ let bytes = io.Bytes.from(mtpData);
289
+ const header = io.read.int32be(bytes);
290
+ if (header !== MTP_AUTHORIZATION_BLOCK) {
291
+ throw new core.MtUnsupportedError(`expected first setting to be mtp auth data, got 0x${header.toString(16)}`);
292
+ }
293
+ const mtpAuthBlock = qtReader.readQByteArray(bytes);
294
+ bytes = io.Bytes.from(mtpAuthBlock);
295
+ const legacyUserId = io.read.int32be(bytes);
296
+ const legacyMainDcId = io.read.int32be(bytes);
297
+ let userId, mainDcId;
298
+ if (legacyMainDcId === -1 && legacyMainDcId === -1) {
299
+ userId = qtReader.readLong(bytes);
300
+ mainDcId = io.read.int32be(bytes);
301
+ } else {
302
+ userId = core.Long.fromInt(legacyUserId);
303
+ mainDcId = legacyMainDcId;
304
+ }
305
+ function readKeys(target) {
306
+ const count = io.read.uint32be(bytes);
307
+ for (let i = 0; i < count; i++) {
308
+ const dcId = io.read.int32be(bytes);
309
+ const key = io.read.exactly(bytes, 256);
310
+ target.push({ dcId, key });
311
+ }
312
+ }
313
+ const authKeys = [];
314
+ const authKeysToDestroy = [];
315
+ readKeys(authKeys);
316
+ readKeys(authKeysToDestroy);
317
+ return {
318
+ userId,
319
+ mainDcId,
320
+ authKeys,
321
+ authKeysToDestroy
322
+ };
323
+ }
324
+ async writeMtpAuthorization(auth, accountIdx = 0) {
325
+ const bytes = io.Bytes.alloc();
326
+ io.write.int32be(bytes, -1);
327
+ io.write.int32be(bytes, -1);
328
+ qtWriter.writeLong(bytes, auth.userId);
329
+ io.write.int32be(bytes, auth.mainDcId);
330
+ function writeKeys(keys) {
331
+ io.write.uint32be(bytes, keys.length);
332
+ keys.forEach((k) => {
333
+ io.write.int32be(bytes, k.dcId);
334
+ io.write.bytes(bytes, k.key);
335
+ });
336
+ }
337
+ writeKeys(auth.authKeys);
338
+ writeKeys(auth.authKeysToDestroy);
339
+ const file = io.Bytes.alloc();
340
+ io.write.int32be(file, MTP_AUTHORIZATION_BLOCK);
341
+ qtWriter.writeQByteArray(file, bytes.result());
342
+ await this.writeEncryptedFile(
343
+ await this.computeDataNameKeyHex(accountIdx),
344
+ file.result()
345
+ );
346
+ }
347
+ async writeEmptyMapFile(accountIdx) {
348
+ const writer = io.Bytes.alloc();
349
+ qtWriter.writeQByteArray(writer, utils.u8.empty);
350
+ qtWriter.writeQByteArray(writer, utils.u8.empty);
351
+ qtWriter.writeQByteArray(writer, await this.encryptLocal(utils.u8.empty, this.keyData.localKey));
352
+ await this.writeFile(
353
+ posix.join(await this.computeDataNameKeyHex(accountIdx), "map"),
354
+ writer.result(),
355
+ true
356
+ );
357
+ }
358
+ }
359
+ exports.Tdata = Tdata;
@@ -0,0 +1,332 @@
1
+ import { join, dirname } from "node:path/posix";
2
+ import { read, write, Bytes } from "@fuman/io";
3
+ import { typed, utf8, u8 } from "@fuman/utils";
4
+ import { MtUnsupportedError, Long } from "@mtcute/core";
5
+ import { createAesIgeForMessageOld } from "@mtcute/core/utils.js";
6
+ import { getDefaultCryptoProvider } from "../utils/crypto.js";
7
+ import { readQByteArray, readLong } from "./qt-reader.js";
8
+ import { writeQByteArray, writeLong } from "./qt-writer.js";
9
+ const TDF_MAGIC = /* @__PURE__ */ utf8.encoder.encode("TDF$");
10
+ const TDF_VERSION = 5008003;
11
+ const MTP_AUTHORIZATION_BLOCK = 75;
12
+ const HEX_ALPHABET = "0123456789ABCDEF";
13
+ function toFilePart(key) {
14
+ let str = "";
15
+ for (let i = 0; i < 8; i++) {
16
+ const b = key[i];
17
+ const low = b & 15;
18
+ const high = b >> 4;
19
+ str += HEX_ALPHABET[low] + HEX_ALPHABET[high];
20
+ }
21
+ return str;
22
+ }
23
+ class Tdata {
24
+ constructor(options, fs, crypto) {
25
+ this.options = options;
26
+ this.fs = fs;
27
+ this.crypto = crypto;
28
+ }
29
+ keyData;
30
+ static async open(options) {
31
+ const fs = options.fs ?? await import("node:fs/promises");
32
+ const crypto = options.crypto ?? await getDefaultCryptoProvider();
33
+ await crypto.initialize?.();
34
+ const tdata = new Tdata(options, fs, crypto);
35
+ tdata.keyData = await tdata.readKeyData();
36
+ return tdata;
37
+ }
38
+ static async create(options) {
39
+ const fs = options.fs ?? await import("node:fs/promises");
40
+ const crypto = options.crypto ?? await getDefaultCryptoProvider();
41
+ await crypto.initialize?.();
42
+ const tdata = new Tdata(options, fs, crypto);
43
+ const keyData = {
44
+ ...options.keyData,
45
+ localKey: options.keyData.localKey ?? crypto.randomBytes(256),
46
+ version: options.keyData.version ?? TDF_VERSION
47
+ };
48
+ tdata.keyData = keyData;
49
+ await tdata.writeKeyData(keyData);
50
+ return tdata;
51
+ }
52
+ #readInt32(buf) {
53
+ return this.options.le ?? true ? read.int32le(buf) : read.int32be(buf);
54
+ }
55
+ #writeInt32(buf, val) {
56
+ if (this.options.le ?? true) {
57
+ write.int32le(buf, val);
58
+ } else {
59
+ write.int32be(buf, val);
60
+ }
61
+ return buf;
62
+ }
63
+ getDataName(idx) {
64
+ let res = this.options.dataKey ?? "data";
65
+ if (idx > 0) {
66
+ res += `#${idx + 1}`;
67
+ }
68
+ return res;
69
+ }
70
+ async readFile(filename) {
71
+ const order = [];
72
+ const modern = `${filename}s`;
73
+ if (await this.fs.stat(join(this.options.path, modern))) {
74
+ order.push(modern);
75
+ } else {
76
+ const try0 = `${filename}0`;
77
+ const try1 = `${filename}1`;
78
+ const try0s = await this.fs.stat(join(this.options.path, try0));
79
+ const try1s = await this.fs.stat(join(this.options.path, try1));
80
+ if (try0s) {
81
+ order.push(try0);
82
+ if (try1s) {
83
+ order.push(try1);
84
+ if (try0s.lastModified < try1s.lastModified) {
85
+ order.reverse();
86
+ }
87
+ }
88
+ } else if (try1s) {
89
+ order.push(try1);
90
+ }
91
+ }
92
+ let lastError = "file not found";
93
+ for (const file of order) {
94
+ const data = await this.fs.readFile(join(this.options.path, file));
95
+ const magic = data.subarray(0, 4);
96
+ if (!typed.equal(magic, TDF_MAGIC)) {
97
+ lastError = "invalid magic";
98
+ continue;
99
+ }
100
+ const versionBytes = data.subarray(4, 8);
101
+ const version = this.#readInt32(versionBytes);
102
+ if (version > TDF_VERSION && !this.options.ignoreVersion) {
103
+ lastError = `Unsupported version: ${version}`;
104
+ continue;
105
+ }
106
+ const dataSize = data.length - 24;
107
+ const bytes = data.subarray(8, dataSize + 8);
108
+ const md5 = await this.crypto.createHash("md5");
109
+ await md5.update(bytes);
110
+ await md5.update(this.#writeInt32(u8.alloc(4), dataSize));
111
+ await md5.update(versionBytes);
112
+ await md5.update(magic);
113
+ const hash = await md5.digest();
114
+ if (!typed.equal(hash, data.subarray(dataSize + 8))) {
115
+ lastError = "md5 mismatch";
116
+ continue;
117
+ }
118
+ return [version, bytes];
119
+ }
120
+ throw new Error(`failed to read ${filename}, last error: ${lastError}`);
121
+ }
122
+ async writeFile(filename, data, mkdir = false) {
123
+ filename = join(this.options.path, `${filename}s`);
124
+ const version = this.#writeInt32(u8.alloc(4), TDF_VERSION);
125
+ const dataSize = this.#writeInt32(u8.alloc(4), data.length);
126
+ const md5 = await this.crypto.createHash("md5");
127
+ await md5.update(data);
128
+ await md5.update(dataSize);
129
+ await md5.update(version);
130
+ await md5.update(TDF_MAGIC);
131
+ if (mkdir) {
132
+ await this.fs.mkdir(dirname(filename), { recursive: true });
133
+ }
134
+ await this.fs.writeFile(
135
+ filename,
136
+ u8.concat([TDF_MAGIC, version, data, await md5.digest()])
137
+ );
138
+ }
139
+ async createLocalKey(salt, passcode = this.options.passcode ?? "") {
140
+ const hasher = await this.crypto.createHash("sha512");
141
+ await hasher.update(salt);
142
+ await hasher.update(utf8.encoder.encode(passcode));
143
+ await hasher.update(salt);
144
+ const hash = await hasher.digest();
145
+ return this.crypto.pbkdf2(
146
+ hash,
147
+ salt,
148
+ passcode === "" ? 1 : 1e5,
149
+ 256,
150
+ "sha512"
151
+ );
152
+ }
153
+ async decryptLocal(encrypted, key) {
154
+ const encryptedKey = encrypted.subarray(0, 16);
155
+ const encryptedData = encrypted.subarray(16);
156
+ const ige = createAesIgeForMessageOld(
157
+ this.crypto,
158
+ key,
159
+ encryptedKey,
160
+ false
161
+ );
162
+ const decrypted = ige.decrypt(encryptedData);
163
+ if (!typed.equal(
164
+ this.crypto.sha1(decrypted).subarray(0, 16),
165
+ encryptedKey
166
+ )) {
167
+ throw new Error("Failed to decrypt, invalid password?");
168
+ }
169
+ const fullLen = encryptedData.length;
170
+ const dataLen = this.#readInt32(decrypted);
171
+ if (dataLen > decrypted.length || dataLen <= fullLen - 16 || dataLen < 4) {
172
+ throw new Error("Failed to decrypt, invalid data length");
173
+ }
174
+ return decrypted.subarray(4, dataLen);
175
+ }
176
+ async encryptLocal(data, key) {
177
+ const dataSize = data.length + 4;
178
+ const padding = dataSize & 15 ? this.crypto.randomBytes(16 - (dataSize & 15)) : u8.empty;
179
+ const toEncrypt = u8.alloc(dataSize + padding.length);
180
+ this.#writeInt32(toEncrypt, dataSize);
181
+ toEncrypt.set(data, 4);
182
+ toEncrypt.set(padding, dataSize);
183
+ const encryptedKey = this.crypto.sha1(toEncrypt).subarray(0, 16);
184
+ const ige = createAesIgeForMessageOld(
185
+ this.crypto,
186
+ key,
187
+ encryptedKey,
188
+ false
189
+ );
190
+ const encryptedData = ige.encrypt(toEncrypt);
191
+ return u8.concat2(encryptedKey, encryptedData);
192
+ }
193
+ async readKeyData() {
194
+ const [version, data] = await this.readFile(`key_${this.options.dataKey ?? "data"}`);
195
+ const bytes = Bytes.from(data);
196
+ const salt = readQByteArray(bytes);
197
+ const keyEncrypted = readQByteArray(bytes);
198
+ const infoEncrypted = readQByteArray(bytes);
199
+ const passcodeKey = await this.createLocalKey(salt);
200
+ const keyInnerData = await this.decryptLocal(keyEncrypted, passcodeKey);
201
+ const infoDecrypted = await this.decryptLocal(
202
+ infoEncrypted,
203
+ keyInnerData
204
+ );
205
+ const info = Bytes.from(infoDecrypted);
206
+ const localKey = keyInnerData;
207
+ const count = read.int32be(info);
208
+ const order = [...new Array(count)].map(() => read.int32be(info));
209
+ const active = read.int32be(info);
210
+ return {
211
+ version,
212
+ localKey,
213
+ count,
214
+ order,
215
+ active
216
+ };
217
+ }
218
+ async writeKeyData(keyData) {
219
+ const info = Bytes.alloc();
220
+ write.int32be(info, keyData.count);
221
+ keyData.order.forEach((i) => write.int32be(info, i));
222
+ write.int32be(info, keyData.active);
223
+ const infoDecrypted = info.result();
224
+ const infoEncrypted = await this.encryptLocal(infoDecrypted, keyData.localKey);
225
+ const salt = this.crypto.randomBytes(32);
226
+ const passcodeKey = await this.createLocalKey(salt);
227
+ const keyEncrypted = await this.encryptLocal(keyData.localKey, passcodeKey);
228
+ const data = Bytes.alloc();
229
+ writeQByteArray(data, salt);
230
+ writeQByteArray(data, keyEncrypted);
231
+ writeQByteArray(data, infoEncrypted);
232
+ await this.writeFile(`key_${this.options.dataKey ?? "data"}`, data.result(), true);
233
+ }
234
+ async computeDataNameKey(accountIdx) {
235
+ const md5 = await this.crypto.createHash("md5");
236
+ await md5.update(utf8.encoder.encode(this.getDataName(accountIdx)));
237
+ const r = await md5.digest();
238
+ return r.subarray(0, 8);
239
+ }
240
+ async computeDataNameKeyHex(accountIdx) {
241
+ return toFilePart(await this.computeDataNameKey(accountIdx));
242
+ }
243
+ async readEncryptedFile(filename) {
244
+ const [version, data] = await this.readFile(filename);
245
+ const encrypted = readQByteArray(Bytes.from(data));
246
+ const decrypted = await this.decryptLocal(encrypted, this.keyData.localKey);
247
+ return [version, decrypted];
248
+ }
249
+ async writeEncryptedFile(filename, data, mkdir = false) {
250
+ const encryptedInner = await this.encryptLocal(data, this.keyData.localKey);
251
+ const writer = Bytes.alloc(data.length + 4);
252
+ writeQByteArray(writer, encryptedInner);
253
+ await this.writeFile(filename, writer.result(), mkdir);
254
+ }
255
+ async readMtpAuthorization(accountIdx = 0) {
256
+ const [, mtpData] = await this.readEncryptedFile(
257
+ await this.computeDataNameKeyHex(accountIdx)
258
+ );
259
+ let bytes = Bytes.from(mtpData);
260
+ const header = read.int32be(bytes);
261
+ if (header !== MTP_AUTHORIZATION_BLOCK) {
262
+ throw new MtUnsupportedError(`expected first setting to be mtp auth data, got 0x${header.toString(16)}`);
263
+ }
264
+ const mtpAuthBlock = readQByteArray(bytes);
265
+ bytes = Bytes.from(mtpAuthBlock);
266
+ const legacyUserId = read.int32be(bytes);
267
+ const legacyMainDcId = read.int32be(bytes);
268
+ let userId, mainDcId;
269
+ if (legacyMainDcId === -1 && legacyMainDcId === -1) {
270
+ userId = readLong(bytes);
271
+ mainDcId = read.int32be(bytes);
272
+ } else {
273
+ userId = Long.fromInt(legacyUserId);
274
+ mainDcId = legacyMainDcId;
275
+ }
276
+ function readKeys(target) {
277
+ const count = read.uint32be(bytes);
278
+ for (let i = 0; i < count; i++) {
279
+ const dcId = read.int32be(bytes);
280
+ const key = read.exactly(bytes, 256);
281
+ target.push({ dcId, key });
282
+ }
283
+ }
284
+ const authKeys = [];
285
+ const authKeysToDestroy = [];
286
+ readKeys(authKeys);
287
+ readKeys(authKeysToDestroy);
288
+ return {
289
+ userId,
290
+ mainDcId,
291
+ authKeys,
292
+ authKeysToDestroy
293
+ };
294
+ }
295
+ async writeMtpAuthorization(auth, accountIdx = 0) {
296
+ const bytes = Bytes.alloc();
297
+ write.int32be(bytes, -1);
298
+ write.int32be(bytes, -1);
299
+ writeLong(bytes, auth.userId);
300
+ write.int32be(bytes, auth.mainDcId);
301
+ function writeKeys(keys) {
302
+ write.uint32be(bytes, keys.length);
303
+ keys.forEach((k) => {
304
+ write.int32be(bytes, k.dcId);
305
+ write.bytes(bytes, k.key);
306
+ });
307
+ }
308
+ writeKeys(auth.authKeys);
309
+ writeKeys(auth.authKeysToDestroy);
310
+ const file = Bytes.alloc();
311
+ write.int32be(file, MTP_AUTHORIZATION_BLOCK);
312
+ writeQByteArray(file, bytes.result());
313
+ await this.writeEncryptedFile(
314
+ await this.computeDataNameKeyHex(accountIdx),
315
+ file.result()
316
+ );
317
+ }
318
+ async writeEmptyMapFile(accountIdx) {
319
+ const writer = Bytes.alloc();
320
+ writeQByteArray(writer, u8.empty);
321
+ writeQByteArray(writer, u8.empty);
322
+ writeQByteArray(writer, await this.encryptLocal(u8.empty, this.keyData.localKey));
323
+ await this.writeFile(
324
+ join(await this.computeDataNameKeyHex(accountIdx), "map"),
325
+ writer.result(),
326
+ true
327
+ );
328
+ }
329
+ }
330
+ export {
331
+ Tdata
332
+ };