Helpers.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. const crypto = require('crypto');
  2. const fs = require("fs").promises;
  3. class Helpers {
  4. static readBigIntFromBuffer(buffer, little = true, signed = false) {
  5. let randBuffer = Buffer.from(buffer);
  6. let bytesNumber = randBuffer.length;
  7. if (little) {
  8. randBuffer = randBuffer.reverse();
  9. }
  10. let bigInt = BigInt("0x" + randBuffer.toString("hex"));
  11. if (signed && Math.floor(bigInt.toString("2").length / 8) >= bytesNumber) {
  12. bigInt -= 2n ** BigInt(bytesNumber * 8);
  13. }
  14. return bigInt;
  15. }
  16. static readBufferFromBigInt(bigInt, bytesNumber, little = true, signed = false) {
  17. let bitLength = bigInt.toString("2").length;
  18. let bytes = Math.ceil(bitLength / 8);
  19. if (bytesNumber < bytes) {
  20. throw new Error("OverflowError: int too big to convert")
  21. } else if (bytesNumber > bytes) {
  22. }
  23. if (!signed && bigInt < 0) {
  24. throw new Error("Cannot convert to unsigned");
  25. }
  26. let below = false;
  27. if (bigInt < 0) {
  28. below = true;
  29. bigInt = -bigInt;
  30. }
  31. let hex = bigInt.toString("16").padStart(bytesNumber * 2, "0");
  32. let l = Buffer.from(hex, "hex");
  33. if (little) {
  34. l = l.reverse();
  35. }
  36. if (signed && below) {
  37. if (little) {
  38. l[0] = 256 - l[0];
  39. for (let i = 1; i < l.length; i++) {
  40. l[i] = 255 - l[i];
  41. }
  42. } else {
  43. l[l.length - 1] = 256 - l[l.length - 1];
  44. for (let i = 0; i < l.length - 1; i++) {
  45. l[i] = 255 - l[i];
  46. }
  47. }
  48. }
  49. return l;
  50. }
  51. /**
  52. * Generates a random long integer (8 bytes), which is optionally signed
  53. * @returns {BigInt}
  54. */
  55. static generateRandomLong(signed = true) {
  56. return this.readBigIntFromBuffer(Helpers.generateRandomBytes(8), true, signed);
  57. }
  58. /**
  59. * .... really javascript
  60. * @param n {number}
  61. * @param m {number}
  62. * @returns {number}
  63. */
  64. static mod(n, m) {
  65. return ((n % m) + m) % m;
  66. }
  67. /**
  68. * Generates a random bytes array
  69. * @param count
  70. * @returns {Buffer}
  71. */
  72. static generateRandomBytes(count) {
  73. return crypto.randomBytes(count);
  74. }
  75. /**
  76. * Loads the user settings located under `api/`
  77. * @param path
  78. * @returns {Promise<void>}
  79. */
  80. static async loadSettings(path = "api/settings") {
  81. let settings = {};
  82. let left, right, value_pair;
  83. let data = await fs.readFile(path, 'utf-8');
  84. for (let line of data.toString().split('\n')) {
  85. value_pair = line.split("=");
  86. if (value_pair.length !== 2) {
  87. break;
  88. }
  89. left = value_pair[0].replace(/ \r?\n|\r/g, '');
  90. right = value_pair[1].replace(/ \r?\n|\r/g, '');
  91. if (!isNaN(right)) {
  92. settings[left] = Number.parseInt(right);
  93. } else {
  94. settings[left] = right;
  95. }
  96. }
  97. return settings;
  98. }
  99. /**
  100. * Calculate the key based on Telegram guidelines, specifying whether it's the client or not
  101. * @param shared_key
  102. * @param msg_key
  103. * @param client
  104. * @returns {{iv: Buffer, key: Buffer}}
  105. */
  106. static calcKey(shared_key, msg_key, client) {
  107. let x = client === true ? 0 : 8;
  108. let iv, key, sha1a, sha1b, sha1c, sha1d;
  109. sha1a = Helpers.sha1(Buffer.concat([
  110. msg_key,
  111. shared_key.slice(x, (x + 32))
  112. ]));
  113. sha1b = Helpers.sha1(Buffer.concat([
  114. shared_key.slice(x + 32, x + 48),
  115. msg_key,
  116. shared_key.slice(x + 48, x + 64)
  117. ]));
  118. sha1c = Helpers.sha1(Buffer.concat([
  119. shared_key.slice(x + 64, x + 96),
  120. msg_key
  121. ]));
  122. sha1d = Helpers.sha1(Buffer.concat([
  123. msg_key,
  124. shared_key.slice((x + 96), (x + 128))
  125. ]));
  126. key = Buffer.concat([
  127. sha1a.slice(0, 8),
  128. sha1b.slice(8, 20),
  129. sha1c.slice(4, 16)]);
  130. iv = Buffer.concat([
  131. sha1a.slice(8, 20),
  132. sha1b.slice(0, 8),
  133. sha1c.slice(16, 20),
  134. sha1d.slice(0, 8)]);
  135. return {key, iv}
  136. }
  137. /**
  138. * Calculates the message key from the given data
  139. * @param data
  140. * @returns {Buffer}
  141. */
  142. static calcMsgKey(data) {
  143. return Helpers.sha1(data).slice(4, 20);
  144. }
  145. /**
  146. * Generates the key data corresponding to the given nonces
  147. * @param serverNonce
  148. * @param newNonce
  149. * @returns {{key: Buffer, iv: Buffer}}
  150. */
  151. static generateKeyDataFromNonce(serverNonce, newNonce) {
  152. serverNonce = Helpers.readBufferFromBigInt(serverNonce, 16, true, true);
  153. newNonce = Helpers.readBufferFromBigInt(newNonce, 32, true, true);
  154. let hash1 = Helpers.sha1(Buffer.concat([newNonce, serverNonce]));
  155. let hash2 = Helpers.sha1(Buffer.concat([serverNonce, newNonce]));
  156. let hash3 = Helpers.sha1(Buffer.concat([newNonce, newNonce]));
  157. let keyBuffer = Buffer.concat([hash1, hash2.slice(0, 12)]);
  158. let ivBuffer = Buffer.concat([hash2.slice(12, 20), hash3, newNonce.slice(0, 4)]);
  159. return {key: keyBuffer, iv: ivBuffer}
  160. }
  161. /**
  162. * Calculates the SHA1 digest for the given data
  163. * @param data
  164. * @returns {Buffer}
  165. */
  166. static sha1(data) {
  167. let shaSum = crypto.createHash('sha1');
  168. shaSum.update(data);
  169. return shaSum.digest();
  170. }
  171. /**
  172. * Calculates the SHA256 digest for the given data
  173. * @param data
  174. * @returns {Buffer}
  175. */
  176. static sha256(data) {
  177. let shaSum = crypto.createHash('sha256');
  178. shaSum.update(data);
  179. return shaSum.digest();
  180. }
  181. /**
  182. * Reads a Telegram-encoded string
  183. * @param buffer {Buffer}
  184. * @param offset {number}
  185. * @returns {{string: string, offset: number}}
  186. */
  187. static tgReadString(buffer, offset) {
  188. let res = Helpers.tgReadByte(buffer, offset);
  189. offset = res.offset;
  190. let string = res.data.toString("utf8");
  191. return {string, offset}
  192. }
  193. /**
  194. *
  195. * @param reader {Buffer}
  196. * @param offset {number}
  197. */
  198. static tgReadObject(reader, offset) {
  199. let constructorId = reader.readUInt32LE(offset);
  200. offset += 4;
  201. let clazz = tlobjects[constructorId];
  202. if (clazz === undefined) {
  203. /**
  204. * The class was None, but there's still a
  205. * chance of it being a manually parsed value like bool!
  206. */
  207. if (constructorId === 0x997275b5) {
  208. return true
  209. } else if (constructorId === 0xbc799737) {
  210. return false
  211. }
  212. throw Error("type not found " + constructorId);
  213. }
  214. return undefined;
  215. }
  216. /**
  217. *
  218. * @param buffer {Buffer}
  219. * @param offset {Number}
  220. * @returns {{data: Buffer, offset: Number}}
  221. */
  222. static tgReadByte(buffer, offset) {
  223. let firstByte = buffer[offset];
  224. offset += 1;
  225. let padding, length;
  226. if (firstByte === 254) {
  227. length = buffer.readInt8(offset) | (buffer.readInt8(offset + 1) << 8) | (buffer.readInt8(offset + 2) << 16);
  228. offset += 3;
  229. padding = length % 4;
  230. } else {
  231. length = firstByte;
  232. padding = (length + 1) % 4;
  233. }
  234. let data = buffer.slice(offset, offset + length);
  235. offset += length;
  236. if (padding > 0) {
  237. padding = 4 - padding;
  238. offset += padding;
  239. }
  240. return {data: data, offset: offset}
  241. }
  242. static tgWriteString(string) {
  243. return Helpers.tgWriteBytes(Buffer.from(string, "utf8"));
  244. }
  245. static tgWriteBytes(data) {
  246. let buffer;
  247. let padding;
  248. if (data.length < 254) {
  249. padding = (data.length + 1) % 4;
  250. if (padding !== 0) {
  251. padding = 4 - padding;
  252. }
  253. buffer = Buffer.concat([Buffer.from([data.length]), data]);
  254. } else {
  255. padding = data.length % 4;
  256. if (padding !== 0) {
  257. padding = 4 - padding;
  258. }
  259. buffer = Buffer.concat([
  260. Buffer.from([254]),
  261. Buffer.from([data.length % 256]),
  262. Buffer.from([(data.length >> 8) % 256]),
  263. Buffer.from([(data.length >> 16) % 256]),
  264. data,
  265. ]);
  266. }
  267. return Buffer.concat([buffer, Buffer.alloc(padding).fill(0)]);
  268. }
  269. /**
  270. * Fast mod pow for RSA calculation. a^b % n
  271. * @param a
  272. * @param b
  273. * @param n
  274. * @returns {bigint}
  275. */
  276. static modExp(a, b, n) {
  277. a = a % n;
  278. let result = 1n;
  279. let x = a;
  280. while (b > 0n) {
  281. let leastSignificantBit = b % 2n;
  282. b = b / 2n;
  283. if (leastSignificantBit === 1n) {
  284. result = result * x;
  285. result = result % n;
  286. }
  287. x = x * x;
  288. x = x % n;
  289. }
  290. return result;
  291. };
  292. /**
  293. * returns a random int from min (inclusive) and max (inclusive)
  294. * @param min
  295. * @param max
  296. * @returns {number}
  297. */
  298. static getRandomInt(min, max) {
  299. min = Math.ceil(min);
  300. max = Math.floor(max);
  301. return Math.floor(Math.random() * (max - min + 1)) + min;
  302. }
  303. /**
  304. * Sleeps a specified amount of time
  305. * @param ms time in milliseconds
  306. * @returns {Promise}
  307. */
  308. static sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
  309. /**
  310. * Checks if the obj is an array
  311. * @param obj
  312. * @returns {boolean}
  313. */
  314. static isArrayLike(obj) {
  315. if (!obj) return false;
  316. let l = obj.length;
  317. if (typeof l != 'number' || l < 0) return false;
  318. if (Math.floor(l) !== l) return false;
  319. // fast check
  320. if (l > 0 && !((l - 1) in obj)) return false;
  321. // more complete check (optional)
  322. for (let i = 0; i < l; ++i) {
  323. if (!(i in obj)) return false;
  324. }
  325. return true;
  326. }
  327. }
  328. module.exports = Helpers;