Browse Source

GramJS : Various fixes (#55)

* GramJS : Various fixes

* Some refactoring for downloading files

* Remove `modPowLeemon`; Rename `FactorizatorJSBI` to `Factorizator`, add proxy to `FactorizatorLeemon`; Remove `FactorizatorNative`
painor 5 years ago
parent
commit
29b0ed9f64

+ 13 - 17
src/api/gramjs/client.ts

@@ -42,13 +42,7 @@ export async function init(sessionId: string) {
       code: onRequestCode,
       password: onRequestPassword,
     } as any);
-    for (let x = 1; x <= 5; x++) {
-      if (x !== client.session.dcId) {
-        // Todo Better logic
-        // eslint-disable-next-line no-underscore-dangle
-        // await client._borrowExportedSender(x);
-      }
-    }
+
     const newSessionId = stringSession.save();
 
     if (DEBUG) {
@@ -67,15 +61,6 @@ export async function init(sessionId: string) {
   }
 }
 
-export function downloadFile(id: number, fileLocation: ApiFileLocation, args = {
-  partSizeKb: null,
-  fileSize: null,
-  progressCallback: null,
-  dcId: null,
-}) {
-  return client.downloadFile(buildInputPeerPhotoFileLocation({ id, fileLocation }), Buffer, args);
-}
-
 export async function invokeRequest(data: InvokeRequestPayload) {
   const { namespace, name, args } = data;
 
@@ -112,6 +97,14 @@ export async function invokeRequest(data: InvokeRequestPayload) {
   return result;
 }
 
+export function downloadFile(id: number, fileLocation: ApiFileLocation, dcId?: number) {
+  return client.downloadFile(
+    buildInputPeerPhotoFileLocation({ id, fileLocation }),
+    true,
+    { dcId },
+  );
+}
+
 function postProcess(name: string, anyResult: any, args: AnyLiteral) {
   switch (name) {
     case 'GetDialogsRequest': {
@@ -146,7 +139,10 @@ function postProcess(name: string, anyResult: any, args: AnyLiteral) {
 
       const updates = result.hasOwnProperty('updates') ? result.updates as MTP.Updates[] : [result as MTP.Updates];
 
-      const originRequest = { name, args };
+      const originRequest = {
+        name,
+        args,
+      };
       updates.forEach((update) => onGramJsUpdate(update, originRequest));
     }
   }

+ 3 - 3
src/api/gramjs/connectors/files.ts

@@ -6,9 +6,9 @@ export function init() {
 }
 
 export async function loadFile(id: any, fileLocation: ApiFileLocation): Promise<string | null> {
-  const result = null; // await downloadFile(id, fileLocation);
-  // eslint-disable-next-line no-underscore-dangle
-  return result ? bytesToUrl(result) : null;
+  const fileBuffer = await downloadFile(id, fileLocation);
+
+  return fileBuffer ? bytesToUrl(fileBuffer) : null;
 }
 
 function bytesToUrl(bytes: Uint8Array, mimeType?: string) {

+ 17 - 4
src/lib/gramjs/Helpers.js

@@ -171,16 +171,16 @@ function sha256(data) {
  * @param a
  * @param b
  * @param n
- * @returns {BigInteger}
+ * @returns {bigInt.BigInteger}
  */
 function modExp(a, b, n) {
     a = a.remainder(n)
-    let result = BigInt(1)
+    let result = BigInt.one
     let x = a
-    while (b.greater(BigInt(0))) {
+    while (b.greater(BigInt.zero)) {
         const leastSignificantBit = b.remainder(BigInt(2))
         b = b.divide(BigInt(2))
-        if (leastSignificantBit.eq(BigInt(1))) {
+        if (leastSignificantBit.eq(BigInt.one)) {
             result = result.multiply(x)
             result = result.remainder(n)
         }
@@ -190,6 +190,18 @@ function modExp(a, b, n) {
     return result
 }
 
+
+/**
+ * Gets the arbitrary-length byte array corresponding to the given integer
+ * @param integer {number,BigInteger}
+ * @param signed {boolean}
+ * @returns {Buffer}
+ */
+function getByteArray(integer, signed = false) {
+    const bits = integer.toString(2).length
+    const byteLength = Math.floor((bits + 8 - 1) / 8)
+    return readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
+}
 /**
  * returns a random int from min (inclusive) and max (inclusive)
  * @param min
@@ -243,5 +255,6 @@ module.exports = {
     modExp,
     getRandomInt,
     sleep,
+    getByteArray,
     isArrayLike,
 }

+ 1 - 1
src/lib/gramjs/Password.js

@@ -1,4 +1,4 @@
-const Factorizator = require('./crypto/FactorizatorJSBI')
+const Factorizator = require('./crypto/Factorizator')
 const { types } = require('./tl')
 const { readBigIntFromBuffer, readBufferFromBigInt, sha256, modExp, generateRandomBytes } = require('./Helpers')
 const crypto = require('crypto')

+ 4 - 3
src/lib/gramjs/Utils.js

@@ -3,6 +3,8 @@ const { types } = require('./tl')
 const USERNAME_RE = new RegExp('@|(?:https?:\\/\\/)?(?:www\\.)?' +
     '(?:telegram\\.(?:me|dog)|t\\.me)\\/(@|joinchat\\/)?')
 
+const JPEG_HEADER = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex')
+const JPEG_FOOTER = Buffer.from('ffd9', 'hex')
 
 const TG_JOIN_RE = new RegExp('tg:\\/\\/(join)\\?invite=')
 
@@ -233,11 +235,10 @@ function strippedPhotoToJpg(stripped) {
     if (stripped.length < 3 || stripped[0] !== 1) {
         return stripped
     }
-    const header = Buffer.from('ffd8ffe000104a46494600010100000100010000ffdb004300281c1e231e19282321232d2b28303c64413c37373c7b585d4964918099968f808c8aa0b4e6c3a0aadaad8a8cc8ffcbdaeef5ffffff9bc1fffffffaffe6fdfff8ffdb0043012b2d2d3c353c76414176f8a58ca5f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8f8ffc00011080000000003012200021101031101ffc4001f0000010501010101010100000000000000000102030405060708090a0bffc400b5100002010303020403050504040000017d01020300041105122131410613516107227114328191a1082342b1c11552d1f02433627282090a161718191a25262728292a3435363738393a434445464748494a535455565758595a636465666768696a737475767778797a838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae1e2e3e4e5e6e7e8e9eaf1f2f3f4f5f6f7f8f9faffc4001f0100030101010101010101010000000000000102030405060708090a0bffc400b51100020102040403040705040400010277000102031104052131061241510761711322328108144291a1b1c109233352f0156272d10a162434e125f11718191a262728292a35363738393a434445464748494a535455565758595a636465666768696a737475767778797a82838485868788898a92939495969798999aa2a3a4a5a6a7a8a9aab2b3b4b5b6b7b8b9bac2c3c4c5c6c7c8c9cad2d3d4d5d6d7d8d9dae2e3e4e5e6e7e8e9eaf2f3f4f5f6f7f8f9faffda000c03010002110311003f00', 'hex')
-    const footer = Buffer.from('ffd9', 'hex')
+    const header = Buffer.from(JPEG_HEADER)
     header[164] = stripped[1]
     header[166] = stripped[2]
-    return Buffer.concat([header, stripped.slice(3), footer])
+    return Buffer.concat([header, stripped.slice(3), JPEG_FOOTER])
 }
 
 

+ 123 - 87
src/lib/gramjs/client/TelegramClient.js

@@ -129,8 +129,7 @@ class TelegramClient {
 
         })
         this.phoneCodeHashes = []
-        this._borrowedSenders = {}
-        this._updatesHandle = null
+        this._borrowedSenderPromises = {}
     }
 
 
@@ -153,7 +152,7 @@ class TelegramClient {
         await this._sender.send(this._initWith(
             new GetConfigRequest(),
         ))
-        this._updatesHandle = this._updateLoop()
+        this._updateLoop()
     }
 
     async _updateLoop() {
@@ -220,71 +219,98 @@ class TelegramClient {
     // endregion
     // export region
 
-    async _borrowExportedSender(dcId) {
-        let sender = this._borrowedSenders[dcId]
+    _onAuth() {
+        this._setupAdditionalDcConnections()
+    }
+
+    _setupAdditionalDcConnections() {
+        for (let i = 1; i <= 5; i++) {
+            if (i !== this.session.dcId) {
+                this._borrowExportedSender(i)
+            }
+        }
+    }
+
+    async _borrowExportedSender(dcId, retries = 5) {
+        let sender = this._borrowedSenderPromises[dcId]
         if (!sender) {
-            sender = await this._createExportedSender(dcId)
-            sender.dcId = dcId
-            this._borrowedSenders[dcId] = sender
+            sender = this._createExportedSender(dcId, retries)
+            this._borrowedSenderPromises[dcId] = sender
         }
         return sender
     }
 
-    async _createExportedSender(dcId) {
+    async _createExportedSender(dcId, retries) {
         const dc = await this._getDC(dcId)
         const sender = new MTProtoSender(null, { logger: this._log })
-        await sender.connect(new this._connection(
-            dc.ipAddress,
-            dc.port,
-            dcId,
-            this._log,
-        ))
-        this._log.info(`Exporting authorization for data center ${dc.ipAddress}`)
-        const auth = await this.invoke(new functions.auth.ExportAuthorizationRequest({ dcId: dcId }))
-        const req = this._initWith(new functions.auth.ImportAuthorizationRequest({
-                id: auth.id, bytes: auth.bytes,
-            },
-        ))
-        await sender.send(req)
-        return sender
+        for (let i = 0; i < retries; i++) {
+            try {
+                await sender.connect(new this._connection(
+                    dc.ipAddress,
+                    dc.port,
+                    dcId,
+                    this._log,
+                ))
+                this._log.info(`Exporting authorization for data center ${dc.ipAddress}`)
+                const auth = await this.invoke(new functions.auth.ExportAuthorizationRequest({ dcId: dcId }))
+                const req = this._initWith(new functions.auth.ImportAuthorizationRequest({
+                        id: auth.id, bytes: auth.bytes,
+                    },
+                ))
+                await sender.send(req)
+                sender.dcId = dcId
+                return sender
+            } catch (e) {
+                console.log(e)
+                await sender.disconnect()
+            }
+        }
+        return null
     }
 
     // end region
 
     // download region
 
+    /**
+     * Complete flow to download a file.
+     * @param inputLocation {types.InputFileLocation}
+     * @param [saveToMemory=true {boolean}] Whether or not to return a buffer.
+     * @param [args[partSizeKb] {number}]
+     * @param [args[fileSize] {number}]
+     * @param [args[progressCallback] {Function}]
+     * @param [args[dcId] {number}]
+     * @returns {Promise<Buffer>}
+     */
+    async downloadFile(inputLocation, saveToMemory = true, args = {}) {
+        let { partSizeKb, fileSize } = args
+        const { dcId } = args
 
-    async downloadFile(inputLocation, file, args = {
-        partSizeKb: null,
-        fileSize: null,
-        progressCallback: null,
-        dcId: null,
-    }) {
-        if (!args.partSizeKb) {
-            if (!args.fileSize) {
-                args.partSizeKb = 64
+        if (!partSizeKb) {
+            if (!fileSize) {
+                partSizeKb = 64
             } else {
-                args.partSizeKb = utils.getAppropriatedPartSize(args.fileSize)
+                partSizeKb = utils.getAppropriatedPartSize(partSizeKb)
             }
         }
-        const partSize = parseInt(args.partSizeKb * 1024)
+        const partSize = parseInt(partSizeKb * 1024)
         if (partSize % MIN_CHUNK_SIZE !== 0) {
             throw new Error('The part size must be evenly divisible by 4096')
         }
-        const inMemory = !file || file === Buffer
+
         let f
-        if (inMemory) {
+        if (saveToMemory) {
             f = new BinaryWriter(Buffer.alloc(0))
         } else {
             throw new Error('not supported')
         }
         const res = utils.getInputLocation(inputLocation)
-        let exported = args.dcId && this.session.dcId !== args.dcId
+        let exported = dcId && this.session.dcId !== dcId
 
         let sender
         if (exported) {
             try {
-                sender = await this._borrowExportedSender(args.dcId)
+                sender = await this._borrowExportedSender(dcId)
             } catch (e) {
                 if (e instanceof errors.DcIdInvalidError) {
                     // Can't export a sender for the ID we are currently in
@@ -326,25 +352,32 @@ class TelegramClient {
                 }
                 offset += partSize
 
-                if (!result.bytes.length) {
-                    if (inMemory) {
+                if (result.bytes.length) {
+                    this._log.debug(`Saving ${result.bytes.length} more bytes`)
+
+                    f.write(result.bytes)
+
+                    if (args.progressCallback) {
+                        await args.progressCallback(f.getValue().length, fileSize)
+                    }
+                }
+
+                // Last chunk.
+                if (result.bytes.length < partSize) {
+                    if (saveToMemory) {
                         return f.getValue()
                     } else {
                         // Todo implement
+                        throw new Error('Saving to files is not implemented yet')
                     }
                 }
-                this._log.debug(`Saving ${result.bytes.length} more bytes`)
-                f.write(result.bytes)
-                if (args.progressCallback) {
-                    await args.progressCallback(f.getValue().length, args.fileSize)
-                }
             }
         } finally {
             // TODO
         }
     }
 
-    async downloadMedia(message, file, args = {
+    async downloadMedia(message, saveToMemory, args = {
         thumb: null,
         progressCallback: null,
     }) {
@@ -367,19 +400,19 @@ class TelegramClient {
             }
         }
         if (media instanceof types.MessageMediaPhoto || media instanceof types.Photo) {
-            return await this._downloadPhoto(media, file, date, args.thumb, args.progressCallback)
+            return await this._downloadPhoto(media, saveToMemory, date, args.thumb, args.progressCallback)
         } else if (media instanceof types.MessageMediaDocument || media instanceof types.Document) {
-            return await this._downloadDocument(media, file, date, args.thumb, args.progressCallback, media.dcId)
+            return await this._downloadDocument(media, saveToMemory, date, args.thumb, args.progressCallback, media.dcId)
         } else if (media instanceof types.MessageMediaContact && args.thumb == null) {
-            return this._downloadContact(media, file)
+            return this._downloadContact(media, saveToMemory)
         } else if ((media instanceof types.WebDocument || media instanceof types.WebDocumentNoProxy) && args.thumb == null) {
-            return await this._downloadWebDocument(media, file, args.progressCallback)
+            return await this._downloadWebDocument(media, saveToMemory, args.progressCallback)
         }
     }
 
-    async downloadProfilePhoto(entity, file, downloadBig = false) {
+    async downloadProfilePhoto(entity, saveToMemory, downloadBig = false) {
         // ('User', 'Chat', 'UserFull', 'ChatFull')
-        const ENTITIES = [0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697]
+        const ENTITIES = [ 0x2da17977, 0xc5af5d94, 0x1f4661b9, 0xd49a2697 ]
         // ('InputPeer', 'InputUser', 'InputChannel')
         // const INPUTS = [0xc91c90b6, 0xe669bf46, 0x40f202fd]
         // Todo account for input methods
@@ -395,7 +428,7 @@ class TelegramClient {
                 }
 
                 return await this._downloadPhoto(
-                    entity.chatPhoto, file, null, thumb, null,
+                    entity.chatPhoto, saveToMemory, null, thumb, null,
                 )
             }
             photo = entity.photo
@@ -421,9 +454,8 @@ class TelegramClient {
             // media which should be done with `download_media` instead.
             return null
         }
-        file = file ? file : Buffer
         try {
-            const result = await this.downloadFile(loc, file, {
+            const result = await this.downloadFile(loc, saveToMemory, {
                 dcId: dcId,
             })
             return result
@@ -434,7 +466,7 @@ class TelegramClient {
                     const full = await this.invoke(new functions.channels.GetFullChannelRequest({
                         channel: ie,
                     }))
-                    return await this._downloadPhoto(full.fullChat.chatPhoto, file, null, null, thumb)
+                    return await this._downloadPhoto(full.fullChat.chatPhoto, saveToMemory, null, null, thumb)
                 } else {
                     return null
                 }
@@ -460,7 +492,7 @@ class TelegramClient {
         }
     }
 
-    _downloadCachedPhotoSize(size, file) {
+    _downloadCachedPhotoSize(size, saveToMemory) {
         // No need to download anything, simply write the bytes
         let data
         if (size instanceof types.PhotoStrippedSize) {
@@ -471,7 +503,7 @@ class TelegramClient {
         return data
     }
 
-    async _downloadPhoto(photo, file, date, thumb, progressCallback) {
+    async _downloadPhoto(photo, saveToMemory, date, thumb, progressCallback) {
         if (photo instanceof types.MessageMediaPhoto) {
             photo = photo.photo
         }
@@ -483,9 +515,9 @@ class TelegramClient {
             return
         }
 
-        file = file ? file : Buffer
+
         if (size instanceof types.PhotoCachedSize || size instanceof types.PhotoStrippedSize) {
-            return this._downloadCachedPhotoSize(size, file)
+            return this._downloadCachedPhotoSize(size, saveToMemory)
         }
 
         const result = await this.downloadFile(
@@ -495,7 +527,7 @@ class TelegramClient {
                 fileReference: photo.fileReference,
                 thumbSize: size.type,
             }),
-            file,
+            saveToMemory,
             {
                 dcId: photo.dcId,
                 fileSize: size.size,
@@ -505,34 +537,33 @@ class TelegramClient {
         return result
     }
 
-    async _downloadDocument(document, file, date, thumb, progressCallback, dcId) {
-        if (document instanceof types.MessageMediaPhoto) {
-            document = document.document
+    async _downloadDocument(doc, saveToMemory, date, thumb, progressCallback, dcId) {
+        if (doc instanceof types.MessageMediaPhoto) {
+            doc = document.document
         }
-        if (!(document instanceof types.Document)) {
+        if (!(doc instanceof types.Document)) {
             return
         }
         let size
-        file = file ? file : Buffer
 
         if (thumb === null || thumb === undefined) {
             size = null
         } else {
-            size = this._getThumb(document.thumbs, thumb)
+            size = this._getThumb(doc.thumbs, thumb)
             if (size instanceof types.PhotoCachedSize || size instanceof types.PhotoStrippedSize) {
-                return this._downloadCachedPhotoSize(size, file)
+                return this._downloadCachedPhotoSize(size, saveToMemory)
             }
         }
         const result = await this.downloadFile(
             new types.InputDocumentFileLocation({
-                id: document.id,
-                accessHash: document.accessHash,
-                fileReference: document.fileReference,
+                id: doc.id,
+                accessHash: doc.accessHash,
+                fileReference: doc.fileReference,
                 thumbSize: size ? size.type : '',
             }),
-            file,
+            saveToMemory,
             {
-                fileSize: size ? size.size : document.size,
+                fileSize: size ? size.size : doc.size,
                 progressCallback: progressCallback,
                 dcId,
             },
@@ -540,11 +571,11 @@ class TelegramClient {
         return result
     }
 
-    _downloadContact(media, file) {
+    _downloadContact(media, saveToMemory) {
         throw new Error('not implemented')
     }
 
-    async _downloadWebDocument(media, file, progressCallback) {
+    async _downloadWebDocument(media, saveToMemory, progressCallback) {
         throw new Error('not implemented')
     }
 
@@ -653,7 +684,7 @@ class TelegramClient {
 
     async getMe() {
         const me = (await this.invoke(new functions.users
-            .GetUsersRequest({ id: [new types.InputUserSelf()] })))[0]
+            .GetUsersRequest({ id: [ new types.InputUserSelf() ] })))[0]
         return me
     }
 
@@ -673,6 +704,8 @@ class TelegramClient {
             await this.connect()
         }
         if (await this.isUserAuthorized()) {
+            this._onAuth()
+
             return this
         }
         if (args.code == null && !args.botToken) {
@@ -779,6 +812,9 @@ class TelegramClient {
         }
         const name = utils.getDisplayName(me)
         console.log('Signed in successfully as', name)
+
+        this._onAuth()
+
         return this
     }
 
@@ -793,7 +829,7 @@ class TelegramClient {
         if (args.phone && !args.code && !args.password) {
             return await this.sendCodeRequest(args.phone)
         } else if (args.code) {
-            const [phone, phoneCodeHash] =
+            const [ phone, phoneCodeHash ] =
                 this._parsePhoneAndHash(args.phone, args.phoneCodeHash)
             // May raise PhoneCodeEmptyError, PhoneCodeExpiredError,
             // PhoneCodeHashEmptyError or PhoneCodeInvalidError.
@@ -841,7 +877,7 @@ class TelegramClient {
             throw new Error('You also need to provide a phone_code_hash.')
         }
 
-        return [phone, phoneHash]
+        return [ phone, phoneHash ]
     }
 
     // endregion
@@ -914,7 +950,7 @@ class TelegramClient {
 
     // event region
     addEventHandler(callback, event) {
-        this._eventBuilders.push([event, callback])
+        this._eventBuilders.push([ event, callback ])
     }
 
     _handleUpdate(update) {
@@ -924,7 +960,7 @@ class TelegramClient {
         if (update instanceof types.Updates || update instanceof types.UpdatesCombined) {
             // TODO deal with entities
             const entities = {}
-            for (const x of [...update.users, ...update.chats]) {
+            for (const x of [ ...update.users, ...update.chats ]) {
                 entities[utils.getPeerId(x)] = x
             }
             for (const u of update.updates) {
@@ -984,7 +1020,7 @@ class TelegramClient {
                 }
                 throw e
             }
-        } else if (['me', 'this'].includes(string.toLowerCase())) {
+        } else if ([ 'me', 'this' ].includes(string.toLowerCase())) {
             return await this.getMe()
         } else {
             const { username, isJoinChat } = utils.parseUsername(string)
@@ -1117,7 +1153,7 @@ class TelegramClient {
         } catch (e) {
         }
         // Then come known strings that take precedence
-        if (['me', 'this'].includes(peer)) {
+        if ([ 'me', 'this' ].includes(peer)) {
             return new types.InputPeerSelf()
         }
         // No InputPeer, cached peer, or known string. Fetch from disk cache
@@ -1137,9 +1173,9 @@ class TelegramClient {
         peer = utils.getPeer(peer)
         if (peer instanceof types.PeerUser) {
             const users = await this.invoke(new functions.users.GetUsersRequest({
-                id: [new types.InputUser({
+                id: [ new types.InputUser({
                     userId: peer.userId, accessHash: 0,
-                })],
+                }) ],
             }))
             if (users && !(users[0] instanceof types.UserEmpty)) {
                 // If the user passed a valid ID they expect to work for
@@ -1158,10 +1194,10 @@ class TelegramClient {
         } else if (peer instanceof types.PeerChannel) {
             try {
                 const channels = await this.invoke(new functions.channels.GetChannelsRequest({
-                    id: [new types.InputChannel({
+                    id: [ new types.InputChannel({
                         channelId: peer.channelId,
                         accessHash: 0,
-                    })],
+                    }) ],
                 }))
 
                 return utils.getInputPeer(channels.chats[0])
@@ -1187,7 +1223,7 @@ class TelegramClient {
         channelId: null,
         ptsDate: null,
     }) {
-        for (const [builder, callback] of this._eventBuilders) {
+        for (const [ builder, callback ] of this._eventBuilders) {
             const event = builder.build(args.update)
             if (event) {
                 await callback(event)

+ 88 - 0
src/lib/gramjs/crypto/Factorizator.js

@@ -0,0 +1,88 @@
+const BigInt = require('big-integer')
+const { getByteArray, readBigIntFromBuffer, modExp } = require('../Helpers')
+const FactorizatorLeemon = require('./FactorizatorLeemon');
+
+class Factorizator {
+
+    /**
+     * Calculates the greatest common divisor
+     * @param a {BigInteger}
+     * @param b {BigInteger}
+     * @returns {BigInteger}
+     */
+    static gcd(a, b) {
+        while (b.neq(BigInt.zero)) {
+            let temp = b
+            b = a.remainder(b)
+            a = temp
+        }
+        return a
+    }
+
+    /**
+     * Factorizes the given number and returns both the divisor and the number divided by the divisor
+     * @param pq {BigInteger}
+     * @returns {{p: *, q: *}}
+     */
+    static factorize(pq) {
+        const pqBuffer = getByteArray(pq);
+        const { p: pBytes, q: qBytes } = FactorizatorLeemon.factorize(pqBuffer);
+        return {
+            p: Buffer.from(pBytes),
+            q: Buffer.from(qBytes),
+        };
+    }
+
+    // // TODO Broken. Sometimes this gets stuck for some reason.
+    // static factorize(pq) {
+    //     console.log("I am going to f", pq)
+    //     if (pq.remainder(2).equals(BigInt.zero)) {
+    //         return { p: BigInt(2), q: pq.divide(BigInt(2)) }
+    //     }
+    //     let y = BigInt.randBetween(1, pq.minus(1))
+    //     const c = BigInt.randBetween(1, pq.minus(1))
+    //     const m = BigInt.randBetween(1, pq.minus(1))
+    //     let g = BigInt.one
+    //     let r = BigInt.one
+    //     let q = BigInt.one
+    //     let x = BigInt.zero
+    //     let ys = BigInt.zero
+    //
+    //     let k
+    //     while (g.eq(BigInt.one)) {
+    //         x = y
+    //         for (let i = 0; BigInt(i).lesser(r); i++) {
+    //             y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq)
+    //         }
+    //         k = BigInt.zero
+    //         while (k.lesser(r) && g.eq(BigInt.one)) {
+    //             ys = y
+    //             let condition = BigInt.min(m, r.minus(k))
+    //             for (let i = 0; BigInt(i).lesser(condition); i++) {
+    //                 y = (modExp(y, BigInt(2), pq)).add(c).remainder(pq)
+    //                 q = q.multiply(x.minus(y).abs()).remainder(pq)
+    //             }
+    //
+    //             g = Factorizator.gcd(q, pq)
+    //             k = k.add(m)
+    //         }
+    //         r = r.multiply(2)
+    //     }
+    //
+    //     if (g.eq(pq)) {
+    //         while (true) {
+    //             ys = (modExp(y, BigInt(2), pq)).add(c).remainder(pq)
+    //             g = Factorizator.gcd(x.minus(ys).abs(), pq)
+    //             if (g.greater(1)) {
+    //                 break
+    //             }
+    //         }
+    //     }
+    //     const p = g
+    //     q = pq.divide(g)
+    //
+    //     return p < q ? { p: p, q: q } : { p: q, q: p }
+    // }
+}
+
+module.exports = Factorizator

+ 0 - 93
src/lib/gramjs/crypto/FactorizatorJSBI.js

@@ -1,93 +0,0 @@
-const {getRandomInt} = require('../Helpers')
-const BigInt = require('big-integer')
-
-class Factorizator {
-    /**
-     * Finds the small multiplier by using Lopatin's method
-     * @param what {BigInteger}
-     * @return {BigInteger}
-     */
-    static findSmallMultiplierLopatin(what) {
-        let g = BigInt(0)
-        for (let i = BigInt(0); i.lesser(BigInt(3)); i = i.add(BigInt(1))) {
-            const q = BigInt(30) || BigInt((getRandomInt(0, 127) & 15) + 17)
-            let x = BigInt(40) || BigInt(getRandomInt(0, 1000000000) + 1)
-
-            let y = x
-            const lim = BigInt(1).shiftLeft(i.add(BigInt(18)))
-            for (let j = BigInt(1); j.lesser(lim); j = j.add(BigInt(1))) {
-                let a = x
-                let b = x
-
-                let c = q
-                while (b.neq(BigInt(0))) {
-                    if ((b.and(BigInt(1))).neq(BigInt(0))) {
-                        c = c.add(a)
-                        if (c.greaterOrEquals(what)) {
-                            c = c.subtract(what)
-                        }
-                    }
-                    a = a.add(a)
-                    if (a.greaterOrEquals(what)) {
-                        a = a.subtract(what)
-                    }
-                    b = b.shiftRight(BigInt(1))
-                }
-
-                x = c
-                const z = BigInt(x.lesser(y) ? y.subtract(x) : x.subtract(y))
-                g = this.gcd(z, what)
-
-                if (g.neq(BigInt(1))) {
-                    break
-                }
-
-                if ((j.and(j.subtract(BigInt(1)))).neq(BigInt(0))) {
-                    y = x
-                }
-            }
-
-            if (g.greater(BigInt(1))) {
-                break
-            }
-        }
-        const p = what.divide(g)
-
-        return p.lesser(g) ? p : g
-    }
-
-    /**
-     * Calculates the greatest common divisor
-     * @param a {BigInteger}
-     * @param b {BigInteger}
-     * @returns {BigInteger}
-     */
-    static gcd(a, b) {
-        while (a.neq(BigInt(0)) && (b.neq(BigInt(0)))) {
-            while (b.and(BigInt(1)).neq(BigInt(0))) {
-                b = b.shiftRight(BigInt(1))
-            }
-            while (a.and(BigInt(1)).eq(BigInt(0))) {
-                a = a.shiftRight(BigInt(1))
-            }
-            if (a.greater(b)) {
-                a = a.subtract(b)
-            } else {
-                b = b.subtract(a)
-            }
-        }
-        return b.equals(BigInt(0)) ? a : b
-    }
-
-    /**
-     * Factorizes the given number and returns both the divisor and the number divided by the divisor
-     * @param pq {BigInteger}
-     * @returns {{p: BigInteger, q: BigInteger}}
-     */
-    static factorize(pq) {
-        const divisor = this.findSmallMultiplierLopatin(pq)
-        return {p: divisor, q: pq.divide(divisor)}
-    }
-}
-
-module.exports = Factorizator

+ 6 - 17
src/lib/gramjs/crypto/RSA.js

@@ -1,22 +1,9 @@
 const NodeRSA = require('node-rsa')
 const { TLObject } = require('../tl/tlobject')
-const { readBigIntFromBuffer, sha1, readBufferFromBigInt, generateRandomBytes } = require('../Helpers')
-const modExp = require('./modPowLeemon')
+const { readBigIntFromBuffer, readBufferFromBigInt, getByteArray, sha1, generateRandomBytes, modExp } = require('../Helpers')
 const _serverKeys = {}
 const BigInt = require('big-integer')
 
-/**
- * Gets the arbitrary-length byte array corresponding to the given integer
- * @param integer {number,BigInteger}
- * @param signed {boolean}
- * @returns {Buffer}
- */
-function getByteArray(integer, signed = false) {
-    console.log("the integer is ", integer)
-    integer = BigInt(integer)
-    const byteLength = Math.floor((integer.bitLength() + 8 - 1) / 8)
-    return readBufferFromBigInt(integer, byteLength, false, signed)
-}
 
 function _computeFingerprint(key) {
     const buf = readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
@@ -39,10 +26,12 @@ function encrypt(fingerprint, data) {
     if (!key) {
         return undefined
     }
+    const buf = readBigIntFromBuffer(key.keyPair.n.toBuffer(), false)
     const rand = generateRandomBytes(235 - data.length)
-    const toEncrypt = Buffer.concat([ sha1(data), data, rand ])
-    const encrypted = modExp(toEncrypt, getByteArray(BigInt(key.keyPair.e)), key.keyPair.n.toBuffer())
-    const block = Buffer.from(encrypted, undefined, 256)
+    const toEncrypt = Buffer.concat([sha1(data), data, rand])
+    const payload = readBigIntFromBuffer(toEncrypt, false)
+    const encrypted = modExp(payload, BigInt(key.keyPair.e), buf)
+    const block = readBufferFromBigInt(encrypted, 256, false)
     return block
 }
 

+ 14 - 24
src/lib/gramjs/network/Authenticator.js

@@ -1,8 +1,8 @@
+const BigInt = require('big-integer');
 const AES = require('../crypto/AES')
 const AuthKey = require('../crypto/AuthKey')
-const Factorizator = require('../crypto/FactorizatorLeemon')
+const Factorizator = require('../crypto/Factorizator')
 const RSA = require('../crypto/RSA')
-const modExp = require('../crypto/modPowLeemon')
 const Helpers = require('../Helpers')
 const { ServerDHParamsFail } = require('../tl/types')
 const { ServerDHParamsOk } = require('../tl/types')
@@ -18,7 +18,6 @@ const { SetClientDHParamsRequest } = require('../tl/functions')
 const { ServerDHInnerData } = require('../tl/types')
 const { ResPQ } = require('../tl/types')
 const { ReqPqMultiRequest } = require('../tl/functions')
-const BigInt = require('big-integer')
 
 /**
  * Executes the authentication process with the Telegram servers.
@@ -45,16 +44,17 @@ async function doAuthentication(sender, log) {
     log.debug('Finished authKey generation step 1')
     log.debug('Starting authKey generation step 2')
     // Step 2 sending: DH Exchange
-    let { p, q } = Factorizator.factorize(resPQ.pq)
+    let { p, q } = Factorizator.factorize(pq)
 
-    p = Buffer.from(p)
-    q = Buffer.from(q)
+    // TODO Bring back after `Factorizator` fix.
+    // p = Helpers.getByteArray(p)
+    // q = Helpers.getByteArray(q)
 
     bytes = Helpers.generateRandomBytes(32)
     const newNonce = Helpers.readBigIntFromBuffer(bytes, true, true)
 
     const pqInnerData = new PQInnerData({
-        pq: getByteArray(pq), // unsigned
+        pq: Helpers.getByteArray(pq), // unsigned
         p: p,
         q: q,
         nonce: resPQ.nonce,
@@ -130,18 +130,19 @@ async function doAuthentication(sender, log) {
     if (serverDhInner.serverNonce.neq(resPQ.serverNonce)) {
         throw new SecurityError('Step 3 Invalid server nonce in encrypted answer')
     }
+    const dhPrime = Helpers.readBigIntFromBuffer(serverDhInner.dhPrime, false, false)
+    const ga = Helpers.readBigIntFromBuffer(serverDhInner.gA, false, false)
     const timeOffset = serverDhInner.serverTime - Math.floor(new Date().getTime() / 1000)
-
-    const bBytes = Helpers.generateRandomBytes(256)
-    const gb = modExp(getByteArray(serverDhInner.g), bBytes, serverDhInner.dhPrime)
-    const gab = modExp(serverDhInner.gA, bBytes, serverDhInner.dhPrime)
+    const b = Helpers.readBigIntFromBuffer(Helpers.generateRandomBytes(256), false, false)
+    const gb = Helpers.modExp(BigInt(serverDhInner.g), b, dhPrime)
+    const gab = Helpers.modExp(ga, b, dhPrime)
 
     // Prepare client DH Inner Data
     const clientDhInner = new ClientDHInnerData({
         nonce: resPQ.nonce,
         serverNonce: resPQ.serverNonce,
         retryId: 0, // TODO Actual retry ID
-        gB: Buffer.from(gb),
+        gB: Helpers.getByteArray(gb, false),
     }).getBytes()
 
     const clientDdhInnerHashed = Buffer.concat([ Helpers.sha1(clientDhInner), clientDhInner ])
@@ -165,7 +166,7 @@ async function doAuthentication(sender, log) {
     if (dhGen.serverNonce.neq(resPQ.serverNonce)) {
         throw new SecurityError(`Step 3 invalid ${name} server nonce from server`)
     }
-    const authKey = new AuthKey(Buffer.from(gab))
+    const authKey = new AuthKey(Helpers.getByteArray(gab))
     const nonceNumber = 1 + nonceTypes.indexOf(dhGen.constructor)
 
     const newNonceHash = authKey.calcNewNonceHash(newNonce, nonceNumber)
@@ -183,16 +184,5 @@ async function doAuthentication(sender, log) {
     return { authKey, timeOffset }
 }
 
-/**
- * Gets the arbitrary-length byte array corresponding to the given integer
- * @param integer {number,BigInteger}
- * @param signed {boolean}
- * @returns {Buffer}
- */
-function getByteArray(integer, signed = false) {
-    const bits = integer.toString(2).length
-    const byteLength = Math.floor((bits + 8 - 1) / 8)
-    return Helpers.readBufferFromBigInt(BigInt(integer), byteLength, false, signed)
-}
 
 module.exports = doAuthentication

+ 0 - 13
src/lib/gramjs/sessions/JSONSession.js

@@ -191,19 +191,6 @@ class Session {
         }
     }
 
-    getNewMsgId() {
-        const msTime = new Date().getTime()
-        let newMessageId =
-            (BigInt(BigInt(Math.floor(msTime / 1000)) + this.timeOffset) << BigInt(32)) |
-            (BigInt(msTime % 1000) << BigInt(22)) |
-            (BigInt(getRandomInt(0, 524288)) << BigInt(2)) // 2^19
-
-        if (this.lastMessageId >= newMessageId) {
-            newMessageId = this.lastMessageId + BigInt(4)
-        }
-        this.lastMessageId = newMessageId
-        return newMessageId
-    }
 
     processEntities(tlo) {
         const entitiesSet = this._entitiesToRows(tlo)