Socket
Socket
Sign inDemoInstall

@mtproto/core

Package Overview
Dependencies
7
Maintainers
1
Versions
43
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.8 to 1.0.0

src/__playground/index.html

14

package.json
{
"name": "@mtproto/core",
"version": "0.0.8",
"version": "1.0.0",
"description": "Telegram API (MTProto) client library for browser",

@@ -32,7 +32,9 @@ "keywords": [

"start": "webpack-dev-server --color",
"build": "webpack --mode production"
"build": "webpack --mode production",
"watch": "karma start --browsers Chrome",
"test": "karma start --browsers ChromeCI --single-run"
},
"license": "GPL-3.0",
"dependencies": {
"axios": "0.19.0",
"aes-js": "3.1.2",
"big-integer": "1.6.48",

@@ -48,2 +50,8 @@ "events": "3.1.0",

"html-webpack-plugin": "3.2.0",
"jasmine-core": "3.5.0",
"karma": "4.4.1",
"karma-chrome-launcher": "3.1.0",
"karma-jasmine": "3.1.1",
"karma-mocha-reporter": "2.2.5",
"karma-webpack": "4.0.2",
"webpack": "4.41.2",

@@ -50,0 +58,0 @@ "webpack-cli": "3.3.10",

# @mtproto/core
[![NPM](https://img.shields.io/npm/v/@mtproto/core.svg?style=flat-square)](https://www.npmjs.com/package/@mtproto/core)
[![Travis](https://img.shields.io/travis/com/alik0211/mtproto-core/master.svg?style=flat-square)](https://travis-ci.com/alik0211/mtproto-core)
> Telegram API (MTProto) client library for browser
* **Relevant.** 108 layer in the API scheme
* **Actual.** 108 layer in the API scheme
* **Fast.** Uses WebSocket to work with the network
* **Easy.** Cryptography is hidden. Just make requests to the API
* **Events.** Subscribe to updates via the EventEmitter API
* **Reliable.** Supports all data centers. Automatically handles `MIGRATE` errors
* **2FA.** Use the library's built-in function to calculate 2FA parameters
## Install
```
yarn add @mtproto/core -E
```
## Quick start
You need **api_id** and **api_hash**. If you do not have them yet, then get them according to the official instructions: [creating your Telegram application](https://core.telegram.org/api/obtaining_api_id).
```js
const MTProto = require('@mtproto/core');
// 1. Create an instance
const mtproto = new MTProto({
api_id: YOU_API_ID,
api_hash: YOU_API_HASH,
// Use test server
test: true,
});
// 2. Log in using phone number
// https://core.telegram.org/api/auth#test-phone-numbers
const phone = '+9996621111';
const code = '22222';
mtproto
.call('auth.sendCode', {
phone_number: phone,
settings: {
_: 'codeSettings',
},
})
.then(result => {
mtproto
.call('auth.signIn', {
phone_code: code,
phone_number: phone,
phone_code_hash: result.phone_code_hash,
})
.then(result => {
console.log(`auth.signIn[result]:`, result);
})
.catch(error => {
if (error.error_message === 'SESSION_PASSWORD_NEEDED') {
// Need use 2FA
}
});
});
```
## 2FA (Two-factor authentication)
```js
const { getSRPParams } = require('@mtproto/core/utils');
const password = 'YOU_PASSWORD';
mtproto
.call('account.getPassword')
.then(async result => {
const { srp_id, current_algo, srp_B } = result;
const { salt1, salt2, g, p } = current_algo;
const { A, M1 } = await getSRPParams({
g,
p,
salt1,
salt2,
gB: srp_B,
password,
});
return mtproto.call('auth.checkPassword', {
password: {
_: 'inputCheckPasswordSRP',
srp_id,
A,
M1,
},
});
})
.then(result => {
console.log(`auth.checkPassword[result]:`, result);
});
```
const bigInt = require('big-integer');
const debounce = require('lodash.debounce');
const EventEmitter = require('events');
const http = require('./transport');
const { SecureRandom } = require('./vendors/jsbn');
const { TLSerialization, TLDeserialization } = require('./tl');
const TLSerializer = require('./tl/serializer');
const TLDeserializer = require('./tl/deserializer');
const {
getSRPParams,
arrayBufferToBase64,
bigStringInt,
bytesToHex,
bytesFromHex,
bytesCmp,
bytesXor,
getRandomBytes,
concatBytes,
bytesToBigInt,
bigIntToBytes,
bytesToArrayBuffer,
convertToArrayBuffer,
convertToUint8Array,
bytesFromArrayBuffer,
bufferConcat,
longToBytes,
longFromInts,
sha1BytesSync,
sha256HashSync,
rsaEncrypt,
aesEncryptSync,
aesDecryptSync,
nextRandomInt,
getRandomInt,
pqPrimeFactorization,
bytesModPow,
getNonce,
getAesKeyIv,
tsNow,
convertToByteArray,
xorBytes,
gzipUncompress,
} = require('./utils');
const RsaKeysManager = require('./utils/rsa');
const { AES, SHA1, SHA256 } = require('./utils/crypto');
const { getRsaKeyByFingerprints } = require('./utils/rsa');
const secureRandom = new SecureRandom();
const defaultDC = 2;
function Deferred() {
this.resolve = null;
this.reject = null;
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
Object.freeze(this);
}
class Storage {
constructor(prefix) {
this._prefix = prefix;
}
class API extends EventEmitter {
constructor({ api_id, api_hash, test, https }) {
super();
setPrefix(prefix) {
this._prefix = prefix;
}
this.api_id = api_id;
this.api_hash = api_hash;
this.test = test;
this.https = https;
// Set with prefix
pSet(name, value) {
const key = `${this._prefix}${name}`;
this[key] = value;
this.localTime = tsNow();
this.lastMessageId = [0, 0];
this.timeOffset = 0;
this._seqNo = 0;
this.sessionId = null;
this.prevSessionId = null;
this.longPollRunning = false;
localStorage[key] = JSON.stringify(value);
}
// TODO: Use Map()
this.sentMessages = {};
this.pendingAcks = [];
pGetBytes(name) {
return new Uint8Array(this.pGet(name));
}
this.sendAcks = debounce(() => {
if (!this.pendingAcks.length) {
return;
}
// Get with prefix
pGet(name) {
const key = `${this._prefix}${name}`;
// console.log(`JSON.stringify(pendingAcks):`, JSON.stringify(pendingAcks));
if (key in this) {
return this[key];
}
var waitSerializer = new TLSerialization({ mtproto: true });
waitSerializer.storeMethod('http_wait', {
max_delay: 500,
wait_after: 150,
max_wait: 1000,
});
const waitMessage = {
msg_id: this.generateMessageId(),
seq_no: this.generateSeqNo(),
body: waitSerializer.getBytes(),
};
if (key in localStorage) {
this[key] = JSON.parse(localStorage[key]);
const serializer = new TLSerialization({ mtproto: true });
serializer.storeObject(
{ _: 'msgs_ack', msg_ids: this.pendingAcks },
'Object'
);
return this[key];
}
const message = {
msg_id: this.generateMessageId(),
seq_no: this.generateSeqNo(true),
body: serializer.getBytes(),
};
return null;
}
this.pendingAcks = [];
set(key, value) {
this[key] = value;
this.sendEncryptedRequest([waitMessage, message]);
}, 500);
this.updateSession();
this.setDc();
localStorage[key] = JSON.stringify(value);
}
init() {
const serverSalt = this.getServerSalt();
const authKey = this.getAuthKey();
get(key) {
if (key in this) {
return this[key];
}
if (serverSalt && authKey) {
this.setServerSalt(serverSalt);
this.setAuthKey(authKey);
if (key in localStorage) {
this[key] = JSON.parse(localStorage[key]);
this.runLongPoll();
return Promise.resolve();
return this[key];
}
const nonce = getNonce();
const request = new TLSerialization({ mtproto: true });
request.storeMethod('req_pq_multi', { nonce });
return null;
}
}
return this.sendPlainRequest(request.getBuffer()).then(deserializer => {
const responsePQ = deserializer.fetchObject('ResPQ');
console.log('2. response', responsePQ);
class MTProto {
constructor({ api_id, api_hash, test = false }) {
this.api_id = api_id;
this.api_hash = api_hash;
this.test = test;
if (responsePQ._ != 'resPQ') {
throw new Error('[MT] resPQ response invalid: ' + responsePQ._);
}
this.messagesWaitResponse = new Map();
this.messagesWaitAuth = [];
this.pendingAcks = [];
if (!bytesCmp(nonce, responsePQ.nonce)) {
throw new Error('[MT] resPQ nonce mismatch');
}
this.storage = new Storage();
this.storage.set('timeOffset', 0);
const serverNonce = responsePQ.server_nonce;
const pq = responsePQ.pq;
const publicKey = RsaKeysManager.select(
responsePQ.server_public_key_fingerprints
);
this.updateSession();
this.setDc();
// console.log(
// 'Got ResPQ',
// bytesToHex(responsePQ.server_nonce),
// bytesToHex(responsePQ.pq),
// responsePQ.server_public_key_fingerprints
// );
this.handleWSError = this.handleWSError.bind(this);
this.handleWSOpen = this.handleWSOpen.bind(this);
this.handleWSClose = this.handleWSClose.bind(this);
this.handleWSMessage = this.handleWSMessage.bind(this);
if (!publicKey) {
throw new Error('[MT] No public key found');
}
this.connect();
}
async handleWSError(event) {}
async handleWSOpen(event) {
const initialMessage = await this.generateObfuscationKeys();
this.socket.send(initialMessage);
const [p, q] = pqPrimeFactorization(pq);
const authKey = this.storage.pGet('authKey');
const serverSalt = this.storage.pGet('serverSalt');
const newNonce = new Array(32);
secureRandom.nextBytes(newNonce);
if (authKey && serverSalt) {
this.handleMessage = this.handleEncryptedMessage;
} else {
this.nonce = getRandomBytes(16);
this.handleMessage = this.handlePQResponse;
this.sendPlainMessage('req_pq_multi', { nonce: this.nonce });
}
}
async handleWSClose(event) {
this.recconect();
}
async handleWSMessage(event) {
const fileReader = new FileReader();
fileReader.onload = async event => {
const obfuscatedBytes = new Uint8Array(event.target.result);
const buffer = await this.deobfuscate(obfuscatedBytes);
const data = new TLSerialization({ mtproto: true });
data.storeObject(
{
_: 'p_q_inner_data',
pq: pq,
p: p,
q: q,
nonce: nonce,
server_nonce: serverNonce,
new_nonce: newNonce,
},
'P_Q_inner_data',
'DECRYPTED_DATA'
);
this.handleMessage(buffer);
};
fileReader.readAsArrayBuffer(event.data);
}
const dataWithHash = sha1BytesSync(data.getBuffer()).concat(
data.getBytes()
);
async handlePQResponse(buffer) {
const deserializer = new TLDeserializer(buffer, { mtproto: true });
const auth_key_id = deserializer.long('auth_key_id');
const msg_id = deserializer.long('msg_id');
const msg_len = deserializer.int('msg_len');
const request = new TLSerialization({ mtproto: true });
request.storeMethod('req_DH_params', {
nonce: nonce,
server_nonce: serverNonce,
p: p,
q: q,
public_key_fingerprint: publicKey.fingerprint,
encrypted_data: rsaEncrypt(publicKey, dataWithHash),
});
const responsePQ = deserializer.predicate('ResPQ');
const {
pq,
nonce,
server_nonce,
server_public_key_fingerprints,
} = responsePQ;
return this.sendPlainRequest(request.getBuffer()).then(deserializer => {
const responseDH = deserializer.fetchObject(
'Server_DH_Params',
'RESPONSE'
);
console.log('3. responseDH', responseDH);
const publicKey = await getRsaKeyByFingerprints(
server_public_key_fingerprints
);
if (
responseDH._ != 'server_DH_params_fail' &&
responseDH._ != 'server_DH_params_ok'
) {
throw new Error(
'[MT] Server_DH_Params response invalid: ' + responseDH._
);
}
const [p, q] = pqPrimeFactorization(pq);
if (!bytesCmp(nonce, responseDH.nonce)) {
throw new Error('[MT] Server_DH_Params nonce mismatch');
}
this.newNonce = getRandomBytes(32);
this.serverNonce = server_nonce;
if (!bytesCmp(serverNonce, responseDH.server_nonce)) {
throw new Error('[MT] Server_DH_Params server_nonce mismatch');
}
const serializer = new TLSerializer({ mtproto: true });
serializer.predicate(
{
_: 'p_q_inner_data',
pq: pq,
p: p,
q: q,
nonce: this.nonce,
server_nonce: this.serverNonce,
new_nonce: this.newNonce,
},
'P_Q_inner_data'
);
if (responseDH._ == 'server_DH_params_fail') {
var newNonceHash = sha1BytesSync(newNonce).slice(-16);
if (!bytesCmp(newNonceHash, responseDH.new_nonce_hash)) {
throw new Error(
'[MT] server_DH_params_fail new_nonce_hash mismatch'
);
}
throw new Error('[MT] server_DH_params_fail');
}
const data = serializer.getBytes();
const dataHash = await SHA1(data);
this.localTime = tsNow();
const innerData = getRandomBytes(255);
innerData.set(dataHash);
innerData.set(data, dataHash.length);
const tmpAesKey = sha1BytesSync(newNonce.concat(serverNonce)).concat(
sha1BytesSync(serverNonce.concat(newNonce)).slice(0, 12)
);
const tmpAesIv = sha1BytesSync(serverNonce.concat(newNonce))
.slice(12)
.concat(
sha1BytesSync([].concat(newNonce, newNonce)),
newNonce.slice(0, 4)
);
const encryptedData = rsaEncrypt(publicKey, innerData);
const answerWithHash = aesDecryptSync(
responseDH.encrypted_answer,
tmpAesKey,
tmpAesIv
);
this.sendPlainMessage('req_DH_params', {
nonce: this.nonce,
server_nonce: this.serverNonce,
p: p,
q: q,
public_key_fingerprint: publicKey.fingerprint,
encrypted_data: encryptedData,
});
var hash = answerWithHash.slice(0, 20);
var answerWithPadding = answerWithHash.slice(20);
var buffer = bytesToArrayBuffer(answerWithPadding);
var deserializer = new TLDeserialization(buffer, { mtproto: true });
const responseDHInner = deserializer.fetchObject(
'Server_DH_inner_data'
);
console.log('4. responseDHInner', responseDHInner);
if (responseDHInner._ != 'server_DH_inner_data') {
throw new Error(
'[MT] server_DH_inner_data response invalid: ' + constructor
);
}
if (!bytesCmp(nonce, responseDHInner.nonce)) {
throw new Error('[MT] server_DH_inner_data nonce mismatch');
}
if (!bytesCmp(serverNonce, responseDHInner.server_nonce)) {
throw new Error('[MT] server_DH_inner_data serverNonce mismatch');
}
console.log('5. Done decrypting answer');
const g = responseDHInner.g;
const dhPrime = responseDHInner.dh_prime;
const gA = responseDHInner.g_a;
const retry = 0;
console.log('6. verifyDhParams start');
this.verifyDhParams(g, dhPrime, gA);
console.log('7. verifyDhParams finish');
var offset = deserializer.getOffset();
if (
!bytesCmp(hash, sha1BytesSync(answerWithPadding.slice(0, offset)))
) {
throw new Error('[MT] server_DH_inner_data SHA1-hash mismatch');
}
this.applyServerTime(responseDHInner.server_time);
return this.sendSetClientDhParams({
nonce,
serverNonce,
newNonce,
tmpAesKey,
tmpAesIv,
g,
dhPrime,
gA,
retry,
}).then(() => {
this.runLongPoll();
return Promise.resolve();
});
});
});
this.handleMessage = this.handleDHParams;
}
verifyDhParams(g, dhPrime, gA) {
console.log('Verifying DH params');
const dhPrimeHex = bytesToHex(dhPrime);
async handleDHParams(buffer) {
const deserializer = new TLDeserializer(buffer, { mtproto: true });
const auth_key_id = deserializer.long('auth_key_id');
const msg_id = deserializer.long('msg_id');
const msg_len = deserializer.int('msg_len');
if (
g != 3 ||
dhPrimeHex !==
'c71caeb9c6b1c9048e6c522f70f13f73980d40238e3e21c14934d037563d930f48198a0aa7c14058229493d22530f4dbfa336f6e0ac925139543aed44cce7c3720fd51f69458705ac68cd4fe6b6b13abdc9746512969328454f18faf8c595f642477fe96bb2a941d5bcd1d4ac8cc49880708fa9b378e3c4f3a9060bee67cf9a4a4a695811051907e162753b56b0f6b410dba74d8a84b2a14b3144e0ef1284754fd17ed950d5965b4b9dd46582db1178d169c6bc465b0d6ff9ca3928fef5b9ae4e418fc15e83ebea0f87fa9ff5eed70050ded2849f47bf959d956850ce929851f0d8115f635b105ee2e4e15d04b2454bf6f4fadf034b10403119cd8e3b92fcc5b'
) {
// The verified value is from https://core.telegram.org/mtproto/security_guidelines
throw new Error('[MT] DH params are not verified: unknown dhPrime');
}
const serverDH = deserializer.predicate('Server_DH_Params');
console.log('dhPrime cmp OK');
this.tmpAesKey = concatBytes(
await SHA1(concatBytes(this.newNonce, this.serverNonce)),
(await SHA1(concatBytes(this.serverNonce, this.newNonce))).slice(0, 12)
);
this.tmpAesIV = concatBytes(
(await SHA1(concatBytes(this.serverNonce, this.newNonce))).slice(12, 20),
await SHA1(concatBytes(this.newNonce, this.newNonce)),
this.newNonce.slice(0, 4)
);
const gABigInt = bigInt(bytesToHex(gA), 16);
const dhPrimeBigInt = bigInt(dhPrimeHex, 16);
const decryptedData = new AES.IGE(this.tmpAesKey, this.tmpAesIV).decrypt(
serverDH.encrypted_answer
);
const innerDataHash = decryptedData.slice(0, 20);
const innerDeserializer = new TLDeserializer(
decryptedData.slice(20).buffer,
{
mtproto: true,
isPlain: true,
}
);
if (gABigInt.compareTo(bigInt.one) <= 0) {
throw new Error('[MT] DH params are not verified: gA <= 1');
}
const serverDHInnerData = innerDeserializer.predicate(
'Server_DH_inner_data'
);
if (gABigInt.compareTo(dhPrimeBigInt.subtract(bigInt.one)) >= 0) {
throw new Error('[MT] DH params are not verified: gA >= dhPrime - 1');
}
this.storage.set(
'timeOffset',
Math.floor(Date.now() / 1000) - serverDHInnerData.server_time
);
console.log('1 < gA < dhPrime-1 OK');
this.dhPrime = bytesToBigInt(serverDHInnerData.dh_prime);
this.g = bigInt(serverDHInnerData.g);
this.gA = bytesToBigInt(serverDHInnerData.g_a);
const twoPow = bigInt(2).pow(2048 - 64);
if (gABigInt.compareTo(twoPow) < 0) {
throw new Error('[MT] DH params are not verified: gA < 2^{2048-64}');
}
if (gABigInt.compareTo(dhPrimeBigInt.subtract(twoPow)) >= 0) {
throw new Error(
'[MT] DH params are not verified: gA > dhPrime - 2^{2048-64}'
);
}
console.log('2^{2048-64} < gA < dhPrime-2^{2048-64} OK');
return true;
this.generateDH();
}
sendSetClientDhParams(auth) {
var gBytes = bytesFromHex(auth.g.toString(16));
async generateDH(retryId = 0) {
const b = bytesToBigInt(getRandomBytes(256));
const authKey = convertToByteArray(
bigIntToBytes(this.gA.modPow(b, this.dhPrime))
);
const serverSalt = convertToByteArray(
xorBytes(this.newNonce.slice(0, 8), this.serverNonce.slice(0, 8))
);
auth.b = new Array(256);
secureRandom.nextBytes(auth.b);
this.storage.pSet('authKey', authKey);
this.storage.pSet('serverSalt', serverSalt);
const gB = bytesModPow(gBytes, auth.b, auth.dhPrime);
var data = new TLSerialization({ mtproto: true });
data.storeObject(
const innerSerializer = new TLSerializer({ mtproto: true });
innerSerializer.predicate(
{
_: 'client_DH_inner_data',
nonce: auth.nonce,
server_nonce: auth.serverNonce,
retry_id: [0, auth.retry++],
g_b: gB,
nonce: this.nonce,
server_nonce: this.serverNonce,
retry_id: retryId,
g_b: bigIntToBytes(this.g.modPow(b, this.dhPrime)),
},
'Client_DH_Inner_Data'
);
const innerData = innerSerializer.getBytes();
var dataWithHash = sha1BytesSync(data.getBuffer()).concat(data.getBytes());
var encryptedData = aesEncryptSync(
dataWithHash,
auth.tmpAesKey,
auth.tmpAesIv
const encryptedData = new AES.IGE(this.tmpAesKey, this.tmpAesIV).encrypt(
concatBytes(await SHA1(innerData), innerData, 16)
);
var request = new TLSerialization({ mtproto: true });
request.storeMethod('set_client_DH_params', {
nonce: auth.nonce,
server_nonce: auth.serverNonce,
this.sendPlainMessage('set_client_DH_params', {
nonce: this.nonce,
server_nonce: this.serverNonce,
encrypted_data: encryptedData,
});
console.log('Send set_client_DH_params');
return this.sendPlainRequest(request.getBuffer()).then(deserializer => {
var response = deserializer.fetchObject('Set_client_DH_params_answer');
if (
response._ != 'dh_gen_ok' &&
response._ != 'dh_gen_retry' &&
response._ != 'dh_gen_fail'
) {
throw new Error(
'[MT] Set_client_DH_params_answer response invalid: ' + response._
);
}
if (!bytesCmp(auth.nonce, response.nonce)) {
throw new Error('[MT] Set_client_DH_params_answer nonce mismatch');
}
if (!bytesCmp(auth.serverNonce, response.server_nonce)) {
throw new Error(
'[MT] Set_client_DH_params_answer server_nonce mismatch'
);
}
const authKey = bytesModPow(auth.gA, auth.b, auth.dhPrime);
const authKeyHash = sha1BytesSync(authKey);
const authKeyAux = authKeyHash.slice(0, 8);
const authKeyId = authKeyHash.slice(-8);
console.log('Got Set_client_DH_params_answer', response._, response);
switch (response._) {
case 'dh_gen_ok':
var newNonceHash1 = sha1BytesSync(
auth.newNonce.concat([1], authKeyAux)
).slice(-16);
if (!bytesCmp(newNonceHash1, response.new_nonce_hash1)) {
throw new Error(
'[MT] Set_client_DH_params_answer new_nonce_hash1 mismatch'
);
}
var serverSalt = bytesXor(
auth.newNonce.slice(0, 8),
auth.serverNonce.slice(0, 8)
);
console.log('Auth successfull!', authKeyId, authKey, serverSalt);
auth.authKeyId = authKeyId;
this.setAuthKey(authKey);
this.setServerSalt(serverSalt);
return auth;
case 'dh_gen_retry':
var newNonceHash2 = sha1BytesSync(
auth.newNonce.concat([2], authKeyAux)
).slice(-16);
if (!bytesCmp(newNonceHash2, response.new_nonce_hash2)) {
throw new Error(
'[MT] Set_client_DH_params_answer new_nonce_hash2 mismatch'
);
}
return this.sendSetClientDhParams(auth);
case 'dh_gen_fail':
var newNonceHash3 = sha1BytesSync(
auth.newNonce.concat([3], authKeyAux)
).slice(-16);
if (!bytesCmp(newNonceHash3, response.new_nonce_hash3)) {
throw new Error(
'[MT] Set_client_DH_params_answer new_nonce_hash3 mismatch'
);
}
throw new Error('[MT] Set_client_DH_params_answer fail');
}
});
this.handleMessage = this.handleDHAnswer;
}
sendEncryptedRequest(messages) {
// console.log(`sendEncryptedRequest[messages]:`, messages);
async handleDHAnswer(buffer) {
const deserializer = new TLDeserializer(buffer, { mtproto: true });
const auth_key_id = deserializer.long('auth_key_id');
const msg_id = deserializer.long('msg_id');
const msg_len = deserializer.int('msg_len');
let resultMessage = messages;
const serverDHAnswer = deserializer.predicate(
'Set_client_DH_params_answer'
);
if (Array.isArray(messages)) {
const messagesByteLen = messages.reduce(
(acc, message) =>
acc + (message.body.byteLength || message.body.length) + 32,
0
);
//create container;
var container = new TLSerialization({
mtproto: true,
startMaxLength: messagesByteLen + 64,
});
container.storeInt(0x73f1f8dc, 'CONTAINER[id]');
container.storeInt(messages.length, 'CONTAINER[count]');
var onloads = [];
var innerMessages = [];
for (var i = 0; i < messages.length; i++) {
container.storeLong(messages[i].msg_id, 'CONTAINER[' + i + '][msg_id]');
innerMessages.push(messages[i].msg_id);
/* sentMessages[messages[i].msg_id] = messages[i];
* sentMessages[messages[i].msg_id].inContainer = true; */
container.storeInt(messages[i].seq_no, 'CONTAINER[' + i + '][seq_no]');
container.storeInt(
messages[i].body.length,
'CONTAINER[' + i + '][bytes]'
);
container.storeRawBytes(messages[i].body, 'CONTAINER[' + i + '][body]');
/* if (messages[i].noResponse) {
* //noResponseMsgs.push(messages[i].msg_id);
* } */
}
const containerSentMessage = {
msg_id: this.generateMessageId(),
seq_no: this.generateSeqNo(true),
container: true,
inner: innerMessages,
};
resultMessage = {
...{ body: container.getBytes(true) },
...containerSentMessage,
};
this.sentMessages[resultMessage.msg_id] = containerSentMessage;
if (serverDHAnswer._ === 'dh_gen_ok') {
this.handleMessage = this.handleEncryptedMessage;
this.handleAuth();
} else {
console.error(`Invalid Set_client_DH_params_answer:`, serverDHAnswer);
}
return this._sendEncryptedRequest(resultMessage).then(responsePackage => {
// console.log(`responsePackage:`, responsePackage);
const { response, messageId } = responsePackage;
this.processMessage(response, messageId);
this.sendAcks();
return responsePackage;
});
}
_sendEncryptedRequest(message) {
const authKey = this.getAuthKey();
const authKeyUint8 = convertToUint8Array(authKey);
const authKeyId = sha1BytesSync(authKey).slice(-8);
const serverSalt = this.getServerSalt();
var data = new TLSerialization({
startMaxLength: message.body.length + 2048,
async handleAuth() {
this.messagesWaitAuth.forEach(message => {
const { method, params, resolve, reject } = message;
this.call(method, params)
.then(resolve)
.catch(reject);
});
message.deferred = message.deferred || new Deferred();
this.sentMessages[message.msg_id] = message;
this.messagesWaitAuth = [];
}
data.storeIntBytes(serverSalt, 64, 'salt');
data.storeIntBytes(this.sessionId, 64, 'session_id');
async handleEncryptedMessage(buffer) {
const authKey = this.storage.pGetBytes('authKey');
data.storeLong(message.msg_id, 'message_id');
data.storeInt(message.seq_no, 'seq_no');
const deserializer = new TLDeserializer(buffer);
const authKeyId = deserializer.long();
const messageKey = deserializer.int128();
data.storeInt(message.body.length, 'message_data_length');
data.storeRawBytes(message.body, 'message_data');
const encryptedData = deserializer.byteView.slice(deserializer.offset);
var dataBuffer = data.getBuffer();
const plaintextData = (
await this.getAESInstance(authKey, messageKey, true)
).decrypt(encryptedData);
var paddingLength = 16 - (data.offset % 16) + 16 * (1 + nextRandomInt(5));
var padding = new Array(paddingLength);
secureRandom.nextBytes(padding);
const plainDeserializer = new TLDeserializer(plaintextData.buffer, {
isPlain: true,
});
var dataWithPadding = bufferConcat(dataBuffer, padding);
const salt = plainDeserializer.long();
const sessionId = plainDeserializer.long();
const messageId = plainDeserializer.long();
const seqNo = plainDeserializer.uint32();
const length = plainDeserializer.uint32();
const encryptedResult = this.getEncryptedMessage(
dataWithPadding,
authKeyUint8
);
const result = plainDeserializer.predicate();
//console.log('encryptedResult.msgKey', encryptedResult.msgKey, dHexDump(encryptedResult.msgKey));
this.handleDecryptedMessage(result, { messageId, seqNo });
}
var request = new TLSerialization({
startMaxLength: encryptedResult.bytes.byteLength + 256,
});
request.storeIntBytes(authKeyId, 64, 'auth_key_id');
request.storeIntBytes(encryptedResult.msgKey, 128, 'msg_key');
request.storeRawBytes(encryptedResult.bytes, 'encrypted_data');
async handleDecryptedMessage(message, params = {}) {
// console.group(`handleDecryptedMessage ${message._}`);
// console.log(`message:`, message);
// console.log(`params:`, params);
// console.groupEnd(`handleDecryptedMessage ${message._}`);
var requestData = request.getArray();
const { messageId } = params;
let waitMessage = null;
return http
.post(this.url, requestData, {
responseType: 'arraybuffer',
transformRequest: null,
})
.then(result => {
if (!result.data || !result.data.byteLength) {
throw new Error('No data');
}
switch (message._) {
case 'msg_container':
message.messages.forEach(message => {
this.handleDecryptedMessage(message.body, {
messageId: message.msg_id,
});
});
return;
const self = this;
case 'bad_server_salt':
waitMessage = this.messagesWaitResponse.get(message.bad_msg_id);
const responseBuffer = result.data;
var responseDeserializer = new TLDeserialization(responseBuffer);
const serverAuthKeyId = responseDeserializer.fetchIntBytes(
64,
false,
'auth_key_id'
);
if (!bytesCmp(serverAuthKeyId, authKeyId)) {
if (!waitMessage) {
throw new Error(
'[MT] Invalid server auth_key_id: ' + bytesToHex(serverAuthKeyId)
`bad_server_salt. Not found message width id ${message.bad_msg_id}`
);
}
var msgKey = responseDeserializer.fetchIntBytes(128, true, 'msg_key');
var encryptedData = responseDeserializer.fetchRawBytes(
responseBuffer.byteLength - responseDeserializer.getOffset(),
true,
'encrypted_data'
);
const dataWithPadding = this.getDecryptedMessage(
authKeyUint8,
msgKey,
encryptedData
);
const calcMsgKey = this.getMsgKey(authKeyUint8, dataWithPadding, false);
this.storage.pSet('serverSalt', longToBytes(message.new_server_salt));
this.call(waitMessage.method, waitMessage.params)
.then(waitMessage.resolve)
.catch(waitMessage.reject);
this.messagesWaitResponse.delete(message.bad_msg_id);
this.ackMessage(messageId);
return;
//console.log(msgKey, calcMsgKey, dHexDump(msgKey), dHexDump(calcMsgKey));
case 'bad_msg_notification':
return;
if (!bytesCmp(msgKey, calcMsgKey)) {
console.warn(
'[MT] msg_keys',
msgKey,
bytesFromArrayBuffer(calcMsgKey)
);
throw new Error('[MT] server msgKey mismatch');
}
case 'new_session_created':
this.ackMessage(messageId);
var dataDeserializer = new TLDeserialization(dataWithPadding, {
mtproto: true,
});
// this.messagesWaitResponse.delete(message.first_msg_id);
this.storage.pSet('serverSalt', longToBytes(message.server_salt));
var salt = dataDeserializer.fetchIntBytes(64, false, 'salt');
var serverSessionId = dataDeserializer.fetchIntBytes(
64,
false,
'session_id'
);
var messageId = dataDeserializer.fetchLong('message_id');
return;
if (
!bytesCmp(serverSessionId, this.sessionId) &&
(!this.prevSessionId || !bytesCmp(this.sessionId, this.prevSessionId))
) {
console.warn(
'Sessions',
serverSessionId,
this.sessionId,
this.prevSessionId
);
throw new Error(
'[MT] Invalid server session_id: ' + bytesToHex(serverSessionId)
);
}
case 'msgs_ack':
// console.log(`msgs_ack:`, message.msg_ids);
// console.log(`this.messagesWaitResponse:`, this.messagesWaitResponse);
// message.msg_ids.forEach(msgId => {
// this.pendingAcks.forEach((pendingAckMsgId, index) => {
// if (msgId === pendingAckMsgId) {
// this.pendingAcks.splice(index, 1);
// }
// });
// });
return;
var seqNo = dataDeserializer.fetchInt('seq_no');
case 'rpc_result':
this.ackMessage(messageId);
var totalLength = dataWithPadding.byteLength;
if (message.result._ === 'gzip_packed') {
const uncompressed = bytesToArrayBuffer(
gzipUncompress(message.result.packed_data)
);
var messageBodyLength = dataDeserializer.fetchInt(
'message_data[length]'
);
const deserializer = new TLDeserializer(uncompressed, {
isPlain: true,
});
if (
messageBodyLength % 4 ||
messageBodyLength > totalLength - dataDeserializer.getOffset()
) {
throw new Error('[MT] Invalid body length: ' + messageBodyLength);
message.result = deserializer.predicate();
}
var messageBody = dataDeserializer.fetchRawBytes(
messageBodyLength,
true,
'message_data'
);
waitMessage = this.messagesWaitResponse.get(message.req_msg_id);
var paddingLength = totalLength - dataDeserializer.getOffset();
if (paddingLength < 12 || paddingLength > 1024) {
throw new Error('[MT] Invalid padding length: ' + paddingLength);
}
waitMessage.resolve(message.result);
var buffer = bytesToArrayBuffer(messageBody);
var deserializerOptions = {
mtproto: true,
override: {
mt_message: function(result, field) {
result.msg_id = this.fetchLong(field + '[msg_id]');
result.seqno = this.fetchInt(field + '[seqno]');
result.bytes = this.fetchInt(field + '[bytes]');
this.messagesWaitResponse.delete(message.req_msg_id);
return;
var offset = this.getOffset();
default:
return;
}
}
try {
result.body = this.fetchObject('Object', field + '[body]');
} catch (e) {
console.error('parse error', e.message, e.stack);
result.body = { _: 'parse_error', error: e };
}
if (this.offset != offset + result.bytes) {
this.offset = offset + result.bytes;
}
},
mt_rpc_result: function(result, field) {
result.req_msg_id = this.fetchLong(field + '[req_msg_id]');
// TODO: Use debounce
sendAcks() {
if (!this.pendingAcks.length) {
return;
}
var sentMessage = self.sentMessages[result.req_msg_id];
var type = (sentMessage && sentMessage.resultType) || 'Object';
const serializer = new TLSerializer({ mtproto: true });
serializer.predicate(
{
_: 'msgs_ack',
msg_ids: this.pendingAcks,
},
'MsgsAck'
);
if (result.req_msg_id && !sentMessage) {
return;
}
result.result = this.fetchObject(type, field + '[result]');
},
},
};
var finalDeserializer = new TLDeserialization(
buffer,
deserializerOptions
);
var response = finalDeserializer.fetchObject('', 'INPUT');
this.pendingAcks = [];
return {
response,
messageId,
sessionId: this.sessionId,
seqNo,
messageDeferred: message.deferred.promise,
};
});
this.sendEncryptedMessage(serializer, { isContentRelated: false });
}
sendPlainRequest(requestBuffer) {
const requestLength = requestBuffer.byteLength;
const requestArray = new Int32Array(requestBuffer);
ackMessage(messageId) {
this.pendingAcks.push(messageId);
const header = new TLSerialization();
header.storeLongP(0, 0, 'auth_key_id');
header.storeLong(this.generateMessageId(), 'msg_id');
header.storeInt(requestLength, 'request_length');
this.sendAcks();
}
const headerBuffer = header.getBuffer();
const headerArray = new Int32Array(headerBuffer);
const headerLength = headerBuffer.byteLength;
call(method, params = {}) {
if (!this.storage.pGet('authKey')) {
return new Promise((resolve, reject) => {
this.messagesWaitAuth.push({ method, params, resolve, reject });
});
}
const resultBuffer = new ArrayBuffer(headerLength + requestLength);
const resultArray = new Int32Array(resultBuffer);
const serializer = new TLSerializer();
resultArray.set(headerArray);
resultArray.set(requestArray, headerArray.length);
serializer.method('invokeWithLayer', {
layer: 108,
});
const requestData = resultArray;
serializer.method('initConnection', {
flags: 0, // because the proxy is not set
api_id: this.api_id,
device_model: navigator.userAgent || 'Unknown UserAgent',
system_version: navigator.platform || 'Unknown Platform',
app_version: '1.0.0',
system_lang_code: navigator.language || 'en',
lang_code: navigator.language || 'en',
});
return http
.post(this.url, requestData, {
responseType: 'arraybuffer',
transformRequest: null,
})
.then(function(result) {
if (!result.data || !result.data.byteLength) {
throw new Error('no data');
}
serializer.method(method, {
api_hash: this.api_hash, // TODO: not found in scheme
api_id: this.api_id, // TODO: not found in scheme
...params,
});
var deserializer = new TLDeserialization(result.data, {
mtproto: true,
});
var auth_key_id = deserializer.fetchLong('auth_key_id');
var msg_id = deserializer.fetchLong('msg_id');
var msg_len = deserializer.fetchInt('msg_len');
return new Promise(async (resolve, reject) => {
const messageId = await this.sendEncryptedMessage(serializer);
const messageIdAsKey = longFromInts(messageId[0], messageId[1]);
return deserializer;
this.messagesWaitResponse.set(messageIdAsKey, {
method,
params,
resolve,
reject,
});
});
}
getEncryptedMessage(dataWithPadding, authKeyUint8) {
const msgKey = this.getMsgKey(authKeyUint8, dataWithPadding, true);
const keyIv = getAesKeyIv(authKeyUint8, msgKey, true);
// console.log('after msg key iv')
//convertToArrayBuffer(aesEncryptSync(dataWithPadding, msgKey, keyIv)) ?
const encryptedBytes = convertToArrayBuffer(
aesEncryptSync(dataWithPadding, keyIv[0], keyIv[1])
);
return {
bytes: encryptedBytes,
msgKey: msgKey,
};
}
// https://core.telegram.org/mtproto/description#schematic-presentation-of-messages
// Encrypted Message:
// 1. auth_key_id (int64)
// 2. msg_key (int128)
// 3. encrypted_data
// encrypted_data:
// 4. salt (int64)
// 5. session_id (int64)
// 6. message_id (int64)
// 7. seq_no (int32)
// 8. message_data_length (int32)
// 9. message_data
// 10. padding 12..1024
async sendEncryptedMessage(messageSerializer, options = {}) {
const { isContentRelated = true } = options;
getDecryptedMessage(authKeyUint8, msgKey, encryptedData) {
const keyIv = getAesKeyIv(authKeyUint8, msgKey, false);
return convertToArrayBuffer(
aesDecryptSync(encryptedData, keyIv[0], keyIv[1])
);
}
const messageData = messageSerializer.getBytes();
const authKey = this.storage.pGetBytes('authKey');
const serverSalt = this.storage.pGetBytes('serverSalt');
const messageId = this.getMessageId();
const seqNo = this.getSeqNo(isContentRelated);
const minPadding = 12;
const unpadded = (32 + messageData.length + minPadding) % 16;
const padding = minPadding + (unpadded ? 16 - unpadded : 0);
processMessage(message, messageId) {
// console.log('processMessage', message, messageId);
let sentMessage;
const plainDataSerializer = new TLSerializer();
plainDataSerializer.bytesRaw(serverSalt);
plainDataSerializer.bytesRaw(this.sessionId);
plainDataSerializer.long(messageId);
plainDataSerializer.int(seqNo);
plainDataSerializer.uint32(messageData.length);
plainDataSerializer.bytesRaw(messageData);
plainDataSerializer.bytesRaw(getRandomBytes(padding));
switch (message._) {
case 'msg_container':
var len = message.messages.length;
for (var i = 0; i < len; i++) {
this.processMessage(message.messages[i], message.messages[i].msg_id);
}
break;
const plainData = plainDataSerializer.getBytes();
case 'bad_server_salt':
console.log('Bad server salt');
sentMessage = this.sentMessages[message.bad_msg_id];
if (!sentMessage || sentMessage.seq_no != message.bad_msg_seqno) {
console.log(message.bad_msg_id, message.bad_msg_seqno);
throw new Error('[MT] Bad server salt for invalid message');
}
const messageKeyLarge = await SHA256(
concatBytes(authKey.slice(88, 120), plainData)
);
const messageKey = messageKeyLarge.slice(8, 24);
const encryptedData = (
await this.getAESInstance(authKey, messageKey, false)
).encrypt(plainData);
this.setServerSalt(longToBytes(message.new_server_salt));
this.sendEncryptedRequest(this.sentMessages[message.bad_msg_id]);
this.ackMessage(messageId);
break;
const authKeyId = (await SHA1(authKey)).slice(12, 20);
const serializer = new TLSerializer();
serializer.setAbridgedHeader(
authKeyId.length + messageKey.length + encryptedData.length
);
serializer.bytesRaw(authKeyId);
serializer.bytesRaw(messageKey);
serializer.bytesRaw(encryptedData);
case 'bad_msg_notification':
console.log('Bad msg notification', message);
sentMessage = this.sentMessages[message.bad_msg_id];
if (!sentMessage || sentMessage.seq_no != message.bad_msg_seqno) {
console.log(message.bad_msg_id, message.bad_msg_seqno);
throw new Error('[MT] Bad msg notification for invalid message');
}
this.sendMessage(serializer.getBytes());
if (message.error_code == 16 || message.error_code == 17) {
if (
this.applyServerTime(
bigStringInt(messageId)
.shiftRight(32)
.toString(10)
)
) {
console.log('Update session');
this.updateSession();
}
this.sendEncryptedRequest(this.sentMessages[message.bad_msg_id]);
this.ackMessage(messageId);
}
break;
return messageId;
}
case 'message':
this.ackMessage(messageId);
this.processMessage(message.body, message.msg_id);
break;
sendPlainMessage(method, params) {
const serializer = new TLSerializer({ mtproto: true });
serializer.method(method, params);
case 'new_session_created':
this.ackMessage(messageId);
const requestBuffer = serializer.getBuffer();
const requestLength = requestBuffer.byteLength;
const requestBytes = new Uint8Array(requestBuffer);
this.processMessageAck(message.first_msg_id);
this.setServerSalt(longToBytes(message.server_salt));
const header = new TLSerializer();
header.bytesRaw(new Uint8Array([(requestLength + 20) / 4]));
header.long([0, 0]); // auth_key_id (8)
header.long(this.getMessageId()); // msg_id (8)
header.uint32(requestLength); // request_length (4)
break;
const headerBuffer = header.getBuffer();
const headerArray = new Uint8Array(headerBuffer);
const headerLength = headerBuffer.byteLength;
case 'msgs_ack':
for (var i = 0; i < message.msg_ids.length; i++) {
this.processMessageAck(message.msg_ids[i]);
}
break;
const resultBuffer = new ArrayBuffer(headerLength + requestLength);
const resultBytes = new Uint8Array(resultBuffer);
case 'msg_detailed_info':
//console.log('msg_detailed_info', message);
break;
/* if (!this.sentMessages[message.msg_id]) {
* this.ackMessage(message.answer_msg_id)
* break
* } */
case 'msg_new_detailed_info':
//console.log('msg_detailed_info', message);
break;
/* if (this.pendingAcks.indexOf(message.answer_msg_id)) {
* break
* }
* this.reqResendMessage(message.answer_msg_id)
* break */
resultBytes.set(headerArray);
resultBytes.set(requestBytes, headerArray.length);
case 'msgs_state_info':
this.ackMessage(message.answer_msg_id);
console.log('msgs_state_info', message);
/* if (this.lastResendReq && this.lastResendReq.req_msg_id == message.req_msg_id && this.pendingResends.length) {
* var i, badMsgId, pos
* for (i = 0; i < this.lastResendReq.resend_msg_ids.length; i++) {
* badMsgId = this.lastResendReq.resend_msg_ids[i]
* pos = this.pendingResends.indexOf(badMsgId)
* if (pos != -1) {
* this.pendingResends.splice(pos, 1)
* }
* }
* } */
break;
case 'rpc_result':
const sentMessageId = message.req_msg_id;
this.ackMessage(messageId);
this.processMessageAck(sentMessageId);
if (this.sentMessages[sentMessageId]) {
const { deferred } = this.sentMessages[sentMessageId];
deferred.resolve(message);
delete this.sentMessages[sentMessageId];
}
break;
default:
console.log('default', message);
this.ackMessage(messageId);
this.emit(message._, message);
// console.log('processMessage', message, messageId);
break;
}
this.sendMessage(resultBytes);
}
ackMessage(messageId) {
// console.log('ackMessage[messageId]:', messageId);
this.pendingAcks.push(messageId);
async sendMessage(bytes) {
this.socket.send(await this.obfuscate(bytes));
}
processMessageAck(messageId) {
const sentMessage = this.sentMessages[messageId];
if (sentMessage && !sentMessage.acked) {
delete sentMessage.body;
sentMessage.acked = true;
async generateObfuscationKeys() {
const protocolId = 0xefefefef;
const init1bytes = getRandomBytes(64);
const init1buffer = init1bytes.buffer;
const init1data = new DataView(init1buffer);
init1data.setUint32(56, protocolId, true);
return true;
const init2buffer = new ArrayBuffer(init1buffer.byteLength);
const init2bytes = new Uint8Array(init2buffer);
for (let i = 0; i < init2buffer.byteLength; i++) {
init2bytes[init2buffer.byteLength - i - 1] = init1bytes[i];
}
return false;
}
let encryptKey = new Uint8Array(init1buffer, 8, 32);
const encryptIV = new Uint8Array(16);
encryptIV.set(new Uint8Array(init1buffer, 40, 16));
applyServerTime(serverTime) {
const newTimeOffset = serverTime - Math.floor(this.localTime / 1000);
const changed = Math.abs(this.timeOffset - newTimeOffset) > 10;
let decryptKey = new Uint8Array(init2buffer, 8, 32);
const decryptIV = new Uint8Array(16);
decryptIV.set(new Uint8Array(init2buffer, 40, 16));
this.lastMessageId = [0, 0];
this.timeOffset = newTimeOffset;
this.encryptAES = new AES.CTR(encryptKey, encryptIV);
this.decryptAES = new AES.CTR(decryptKey, decryptIV);
console.log(
'Apply server time',
serverTime,
this.localTime,
newTimeOffset,
changed
);
const init3buffer = await this.obfuscate(init1bytes);
init1bytes.set(new Uint8Array(init3buffer, 56, 8), 56);
return changed;
return init1bytes;
}
updateSession() {
this.prevSessionId = this.sessionId;
this.sessionId = new Array(8);
secureRandom.nextBytes(this.sessionId);
this._seqNo = 0;
async obfuscate(bytes) {
return this.encryptAES.encrypt(bytes).buffer;
}
getMsgKey(authKeyUint8, dataWithPadding, isOut) {
var authKey = authKeyUint8;
var x = isOut ? 0 : 8;
var msgKeyLargePlain = bufferConcat(
authKey.subarray(88 + x, 88 + x + 32),
dataWithPadding
);
const msgKeyLarge = sha256HashSync(msgKeyLargePlain);
return new Uint8Array(msgKeyLarge).subarray(8, 24);
async deobfuscate(bytes) {
return this.decryptAES.decrypt(bytes).buffer;
}
generateSeqNo(notContentRelated) {
var seqNo = this._seqNo * 2;
getMessageId() {
const timeOffset = this.storage.get('timeOffset');
if (!notContentRelated) {
seqNo += 1;
this._seqNo += 1;
}
return seqNo;
}
generateMessageId() {
const timeTicks = tsNow();
const timeSec = Math.floor(timeTicks / 1000) + this.timeOffset;
const timeTicks = Date.now();
const timeSec = Math.floor(timeTicks / 1000) + timeOffset;
const timeMSec = timeTicks % 1000;
const random = nextRandomInt(0xffff);
const random = getRandomInt(0xffff);

@@ -997,58 +637,54 @@ const { lastMessageId } = this;

return longFromInts(messageId[0], messageId[1]);
return messageId;
}
getServerSalt() {
const key = `dc${this.dcId}ServerSalt`;
getSeqNo(isContentRelated = true) {
let seqNo = this.seqNo * 2;
const fromThis = this[key];
if (fromThis) {
return fromThis;
if (isContentRelated) {
seqNo += 1;
this.seqNo += 1;
}
const fromStorage = localStorage.getItem(key);
if (fromStorage) {
return JSON.parse(fromStorage);
}
return null;
return seqNo;
}
setServerSalt(serverSalt) {
const key = `dc${this.dcId}ServerSalt`;
this[key] = serverSalt;
localStorage.setItem(key, JSON.stringify(serverSalt));
updateSession() {
this.seqNo = 0;
this.sessionId = getRandomBytes(8);
this.lastMessageId = [0, 0];
}
getAuthKey() {
const key = `dc${this.dcId}AuthKey`;
const fromThis = this[key];
if (fromThis) {
return fromThis;
}
const fromStorage = localStorage.getItem(key);
if (fromStorage) {
return JSON.parse(fromStorage);
}
return null;
async getAESInstance(authKey, messageKey, isServer) {
const x = isServer ? 8 : 0;
const sha256a = await SHA256(
concatBytes(messageKey, authKey.slice(x, 36 + x))
);
const sha256b = await SHA256(
concatBytes(authKey.slice(40 + x, 76 + x), messageKey)
);
const aesKey = concatBytes(
sha256a.slice(0, 8),
sha256b.slice(8, 24),
sha256a.slice(24, 32)
);
const aesIV = concatBytes(
sha256b.slice(0, 8),
sha256a.slice(8, 24),
sha256b.slice(24, 32)
);
return new AES.IGE(aesKey, aesIV);
}
setAuthKey(authKey) {
const key = `dc${this.dcId}AuthKey`;
this[key] = authKey;
localStorage.setItem(key, JSON.stringify(authKey));
changeDc(dcId) {
// TODO: Add import/export auth
this.setDc(dcId);
this.recconect();
}
setDc(dcId) {
// TODO: Use this.storage for save dcId
const fromStorage = localStorage.getItem('dcId', dcId);
this.dcId = dcId || fromStorage || 2;
this.dcId = dcId || fromStorage || defaultDC;
this.storage.setPrefix(this.dcId);
localStorage.setItem('dcId', this.dcId);

@@ -1064,176 +700,30 @@

const ipMap = this.test
? {
1: '149.154.175.10',
2: '149.154.167.40',
3: '149.154.175.117',
}
: {
1: '149.154.175.50',
2: '149.154.167.51',
3: '149.154.175.100',
4: '149.154.167.91',
5: '149.154.171.5',
};
const urlPath = this.test ? '/apiws_test' : '/apiws';
const urlPath = this.test ? '/apiw_test1' : '/apiw1';
if (this.https) {
this.url = `https://${
subdomainsMap[this.dcId]
}.web.telegram.org${urlPath}`;
} else {
this.url = `http://${ipMap[this.dcId]}${urlPath}`;
}
this.url = `wss://${subdomainsMap[this.dcId]}.web.telegram.org${urlPath}`;
}
runLongPoll() {
if (this.longPollRunning) {
return;
}
this.longPollRunning = true;
connect() {
this.socket = new WebSocket(this.url, 'binary');
const longPollInner = () => {
const serializer = new TLSerialization({ mtproto: true });
serializer.storeMethod('http_wait', {
max_delay: 500,
wait_after: 150,
max_wait: 15000,
});
const message = {
msg_id: this.generateMessageId(),
seq_no: this.generateSeqNo(),
body: serializer.getBytes(),
};
this.sendEncryptedRequest(message).finally(longPollInner);
};
longPollInner();
this.socket.addEventListener('error', this.handleWSError);
this.socket.addEventListener('open', this.handleWSOpen);
this.socket.addEventListener('close', this.handleWSClose);
this.socket.addEventListener('message', this.handleWSMessage);
}
getApiCallMessage(method, params = {}) {
const serializer = new TLSerialization();
recconect() {
this.socket.removeEventListener('error', this.handleWSError);
this.socket.removeEventListener('open', this.handleWSOpen);
this.socket.removeEventListener('close', this.handleWSClose);
this.socket.removeEventListener('message', this.handleWSMessage);
serializer.storeInt(0xda9b0d0d, 'invokeWithLayer');
serializer.storeInt(108, 'layer');
serializer.storeInt(0x785188b8, 'initConnection');
serializer.storeInt(0, 'flags'); // because the proxy is not set
serializer.storeInt(this.api_id, 'api_id');
serializer.storeString(
navigator.userAgent || 'Unknown UserAgent',
'device_model'
);
serializer.storeString(
navigator.platform || 'Unknown Platform',
'system_version'
);
serializer.storeString('1.0.0', 'app_version');
serializer.storeString(navigator.language || 'en', 'system_lang_code');
serializer.storeString('', 'lang_pack');
serializer.storeString(navigator.language || 'en', 'lang_code');
if (this.socket.readyState === 1) {
this.socket.close();
}
serializer.storeMethod(method, {
api_hash: this.api_hash,
api_id: this.api_id,
...params,
});
let toAck = []; //msgs_ack
const message = {
msg_id: this.generateMessageId(),
seq_no: this.generateSeqNo(),
body: serializer.getBytes(true),
isAPI: true,
method,
};
const messageByteLength =
(message.body.byteLength || message.body.length) + 32;
return message;
this.connect();
}
innerCall(method, params) {
return this.init().then(() => {
const message = this.getApiCallMessage(method, params);
this.sendAcks();
return new Promise((resolve, reject) => {
this.sendEncryptedRequest(message)
.then(response => {
const { messageDeferred } = response;
messageDeferred.then(message => {
if (message.result._ === 'rpc_error') {
reject(message.result);
} else {
resolve(message.result);
}
});
})
.catch(reject);
});
});
}
call(method, params) {
return this.innerCall(method, params).catch(error => {
console.log(`error:`, error);
const { error_message } = error;
if (error_message.includes('_MIGRATE_')) {
const [_type, dcId] = error_message.split('_MIGRATE_');
this.setDc(dcId);
return this.call(method, params);
}
throw error;
});
}
checkPassword(password) {
return this.call('account.getPassword').then(async result => {
const { srp_id, current_algo, secure_random, srp_B } = result;
const { salt1, salt2, g, p } = current_algo;
const { A, M1 } = await getSRPParams({
g,
p,
salt1,
salt2,
gB: srp_B,
password,
});
return this.call('auth.checkPassword', {
password: {
_: 'inputCheckPasswordSRP',
srp_id,
A,
M1,
},
});
});
}
getFileInBase64({ location, offset = 0, limit = 1024 * 1024 }) {
return this.call('upload.getFile', {
flags: 0,
offset,
limit,
location,
}).then(response => {
return arrayBufferToBase64(response.bytes);
});
}
}
class MTProto {
constructor({ api_id, api_hash, test = false, https = false }) {
this.api = new API({ api_id, api_hash, test, https });
}
}
module.exports = MTProto;

@@ -23,5 +23,6 @@ const { Zlib } = require('zlibjs/bin/gunzip.min.js');

const { BigInteger, SecureRandom } = require('../vendors/jsbn');
const { PBKDF2, SHA256 } = require('./crypto');
function bigIntToBytes(bigInt, len) {
return hexToBytes(bigInt.toString(16), len);
function bigIntToBytes(bigInt, length) {
return hexToBytes(bigInt.toString(16), length);
}

@@ -52,4 +53,4 @@

function randomBytes(len) {
const bytes = new Uint8Array(len);
function getRandomBytes(length) {
const bytes = new Uint8Array(length);
crypto.getRandomValues(bytes);

@@ -81,3 +82,3 @@ return bytes;

if (typeof bytes === 'number') {
merged.set(randomBytes(totalLength - offset), offset);
merged.set(getRandomBytes(totalLength - offset), offset);
} else {

@@ -94,27 +95,2 @@ merged.set(

async function SHA256(data) {
return new Uint8Array(await crypto.subtle.digest('SHA-256', data));
}
async function PBKDF2(hash, password, salt, iterations) {
return new Uint8Array(
await crypto.subtle.deriveBits(
{
name: 'PBKDF2',
hash,
salt,
iterations,
},
await crypto.subtle.importKey(
'raw',
password,
{ name: 'PBKDF2' },
false,
['deriveBits']
),
512
)
);
}
async function getSRPParams({ g, p, salt1, salt2, gB, password }) {

@@ -140,3 +116,3 @@ const H = SHA256;

const pBigInt = bytesToBigInt(p);
const aBigInt = bytesToBigInt(randomBytes(256));
const aBigInt = bytesToBigInt(getRandomBytes(256));
const gABigInt = gBigInt.modPow(aBigInt, pBigInt);

@@ -499,14 +475,12 @@ const gABytes = bigIntToBytes(gABigInt);

function longFromInts(high, low) {
return bigint(high)
return bigInt(high)
.shiftLeft(32)
.add(bigint(low))
.add(bigInt(low))
.toString(10);
}
function intToUint(val) {
val = parseInt(val);
if (val < 0) {
val = val + 4294967296;
}
return val;
function intToUint(value) {
value = +value;
return value < 0 ? value + 4294967296 : value;
}

@@ -546,13 +520,8 @@

function rsaEncrypt(publicKey, bytes) {
bytes = addPadding(bytes, 255);
const encryptedBigInt = bytesToBigInt(bytes).modPow(
bigInt(publicKey.exponent, 16),
bigInt(publicKey.modulus, 16)
);
// console.log('RSA encrypt start')
var N = new BigInteger(publicKey.modulus, 16);
var E = new BigInteger(publicKey.exponent, 16);
var X = new BigInteger(bytes);
var encryptedBigInt = X.modPowInt(E, N),
encryptedBytes = bytesFromBigInt(encryptedBigInt, 256);
// console.log('RSA encrypt finish')
return encryptedBytes;
return bigIntToBytes(encryptedBigInt, 256);
}

@@ -631,3 +600,3 @@

function nextRandomInt(maxValue) {
function getRandomInt(maxValue) {
return Math.floor(Math.random() * maxValue);

@@ -676,4 +645,4 @@ }

for (var i = 0; i < 3; i++) {
var q = (nextRandomInt(128) & 15) + 17;
var x = bigint(nextRandomInt(1000000000) + 1);
var q = (getRandomInt(128) & 15) + 17;
var x = bigint(getRandomInt(1000000000) + 1);
var y = x.clone();

@@ -753,4 +722,4 @@ var lim = 1 << (i + 18);

for (var i = 0; i < 3; i++) {
var q = goog.math.Long.fromInt((nextRandomInt(128) & 15) + 17);
var x = goog.math.Long.fromInt(nextRandomInt(1000000000) + 1);
var q = goog.math.Long.fromInt((getRandomInt(128) & 15) + 17);
var x = goog.math.Long.fromInt(getRandomInt(1000000000) + 1);
var y = x;

@@ -826,4 +795,4 @@ var lim = 1 << (i + 18);

for (i = 0; i < 3; i++) {
q = (nextRandomInt(128) & 15) + 17;
copyInt_(x, nextRandomInt(1000000000) + 1);
q = (getRandomInt(128) & 15) + 17;
copyInt_(x, getRandomInt(1000000000) + 1);
copy_(y, x);

@@ -909,3 +878,3 @@ lim = 1 << (i + 18);

for (var i = 0; i < 16; i++) {
nonce.push(nextRandomInt(0xff));
nonce.push(getRandomInt(0xff));
}

@@ -952,7 +921,5 @@ return nonce;

bytesToBigInt,
randomBytes,
getRandomBytes,
xorBytes,
concatBytes,
SHA256,
PBKDF2,
getSRPParams,

@@ -996,3 +963,3 @@ bigint,

gzipUncompress,
nextRandomInt,
getRandomInt,
pqPrimeFactorization,

@@ -999,0 +966,0 @@ pqPrimeBigInteger,

@@ -1,87 +0,80 @@

const { TLSerialization } = require('../tl');
const {
bigStringInt,
bytesToHex,
bytesFromHex,
sha1BytesSync,
} = require('../utils');
const bigInt = require('big-integer');
const TLSerializer = require('../tl/serializer');
const { bytesToHex, bytesFromHex } = require('../utils');
const { SHA1 } = require('../utils/crypto');
const RsaKeysManager = (function() {
var publisKeysHex = [
{
modulus:
'c150023e2f70db7985ded064759cfecf0af328e69a41daf4d6f01b538135a6f91f8f8b2a0ec9ba9720ce352efcf6c5680ffc424bd634864902de0b4bd6d49f4e580230e3ae97d95c8b19442b3c0a10d8f5633fecedd6926a7f6dab0ddb7d457f9ea81b8465fcd6fffeed114011df91c059caedaf97625f6c96ecc74725556934ef781d866b34f011fce4d835a090196e9a5f0e4449af7eb697ddb9076494ca5f81104a305b6dd27665722c46b60e5df680fb16b210607ef217652e60236c255f6a28315f4083a96791d7214bf64c1df4fd0db1944fb26a2a57031b32eee64ad15a8ba68885cde74a5bfc920f6abf59ba5c75506373e7130f9042da922179251f',
exponent: '010001',
},
{
modulus:
'aeec36c8ffc109cb099624685b97815415657bd76d8c9c3e398103d7ad16c9bba6f525ed0412d7ae2c2de2b44e77d72cbf4b7438709a4e646a05c43427c7f184debf72947519680e651500890c6832796dd11f772c25ff8f576755afe055b0a3752c696eb7d8da0d8be1faf38c9bdd97ce0a77d3916230c4032167100edd0f9e7a3a9b602d04367b689536af0d64b613ccba7962939d3b57682beb6dae5b608130b2e52aca78ba023cf6ce806b1dc49c72cf928a7199d22e3d7ac84e47bc9427d0236945d10dbd15177bab413fbf0edfda09f014c7a7da088dde9759702ca760af2b8e4e97cc055c617bd74c3d97008635b98dc4d621b4891da9fb0473047927',
exponent: '010001',
},
{
modulus:
'bdf2c77d81f6afd47bd30f29ac76e55adfe70e487e5e48297e5a9055c9c07d2b93b4ed3994d3eca5098bf18d978d54f8b7c713eb10247607e69af9ef44f38e28f8b439f257a11572945cc0406fe3f37bb92b79112db69eedf2dc71584a661638ea5becb9e23585074b80d57d9f5710dd30d2da940e0ada2f1b878397dc1a72b5ce2531b6f7dd158e09c828d03450ca0ff8a174deacebcaa22dde84ef66ad370f259d18af806638012da0ca4a70baa83d9c158f3552bc9158e69bf332a45809e1c36905a5caa12348dd57941a482131be7b2355a5f4635374f3bd3ddf5ff925bf4809ee27c1e67d9120c5fe08a9de458b1b4a3c5d0a428437f2beca81f4e2d5ff',
exponent: '010001',
},
{
modulus:
'b3f762b739be98f343eb1921cf0148cfa27ff7af02b6471213fed9daa0098976e667750324f1abcea4c31e43b7d11f1579133f2b3d9fe27474e462058884e5e1b123be9cbbc6a443b2925c08520e7325e6f1a6d50e117eb61ea49d2534c8bb4d2ae4153fabe832b9edf4c5755fdd8b19940b81d1d96cf433d19e6a22968a85dc80f0312f596bd2530c1cfb28b5fe019ac9bc25cd9c2a5d8a0f3a1c0c79bcca524d315b5e21b5c26b46babe3d75d06d1cd33329ec782a0f22891ed1db42a1d6c0dea431428bc4d7aabdcf3e0eb6fda4e23eb7733e7727e9a1915580796c55188d2596d2665ad1182ba7abf15aaa5a8b779ea996317a20ae044b820bff35b6e8a1',
exponent: '010001',
},
{
modulus:
'be6a71558ee577ff03023cfa17aab4e6c86383cff8a7ad38edb9fafe6f323f2d5106cbc8cafb83b869cffd1ccf121cd743d509e589e68765c96601e813dc5b9dfc4be415c7a6526132d0035ca33d6d6075d4f535122a1cdfe017041f1088d1419f65c8e5490ee613e16dbf662698c0f54870f0475fa893fc41eb55b08ff1ac211bc045ded31be27d12c96d8d3cfc6a7ae8aa50bf2ee0f30ed507cc2581e3dec56de94f5dc0a7abee0be990b893f2887bd2c6310a1e0a9e3e38bd34fded2541508dc102a9c9b4c95effd9dd2dfe96c29be647d6c69d66ca500843cfaed6e440196f1dbe0e2e22163c61ca48c79116fa77216726749a976a1c4b0944b5121e8c01',
exponent: '010001',
},
];
const publisKeys = [
{
modulus:
'c150023e2f70db7985ded064759cfecf0af328e69a41daf4d6f01b538135a6f91f8f8b2a0ec9ba9720ce352efcf6c5680ffc424bd634864902de0b4bd6d49f4e580230e3ae97d95c8b19442b3c0a10d8f5633fecedd6926a7f6dab0ddb7d457f9ea81b8465fcd6fffeed114011df91c059caedaf97625f6c96ecc74725556934ef781d866b34f011fce4d835a090196e9a5f0e4449af7eb697ddb9076494ca5f81104a305b6dd27665722c46b60e5df680fb16b210607ef217652e60236c255f6a28315f4083a96791d7214bf64c1df4fd0db1944fb26a2a57031b32eee64ad15a8ba68885cde74a5bfc920f6abf59ba5c75506373e7130f9042da922179251f',
exponent: '010001',
},
{
modulus:
'aeec36c8ffc109cb099624685b97815415657bd76d8c9c3e398103d7ad16c9bba6f525ed0412d7ae2c2de2b44e77d72cbf4b7438709a4e646a05c43427c7f184debf72947519680e651500890c6832796dd11f772c25ff8f576755afe055b0a3752c696eb7d8da0d8be1faf38c9bdd97ce0a77d3916230c4032167100edd0f9e7a3a9b602d04367b689536af0d64b613ccba7962939d3b57682beb6dae5b608130b2e52aca78ba023cf6ce806b1dc49c72cf928a7199d22e3d7ac84e47bc9427d0236945d10dbd15177bab413fbf0edfda09f014c7a7da088dde9759702ca760af2b8e4e97cc055c617bd74c3d97008635b98dc4d621b4891da9fb0473047927',
exponent: '010001',
},
{
modulus:
'bdf2c77d81f6afd47bd30f29ac76e55adfe70e487e5e48297e5a9055c9c07d2b93b4ed3994d3eca5098bf18d978d54f8b7c713eb10247607e69af9ef44f38e28f8b439f257a11572945cc0406fe3f37bb92b79112db69eedf2dc71584a661638ea5becb9e23585074b80d57d9f5710dd30d2da940e0ada2f1b878397dc1a72b5ce2531b6f7dd158e09c828d03450ca0ff8a174deacebcaa22dde84ef66ad370f259d18af806638012da0ca4a70baa83d9c158f3552bc9158e69bf332a45809e1c36905a5caa12348dd57941a482131be7b2355a5f4635374f3bd3ddf5ff925bf4809ee27c1e67d9120c5fe08a9de458b1b4a3c5d0a428437f2beca81f4e2d5ff',
exponent: '010001',
},
{
modulus:
'b3f762b739be98f343eb1921cf0148cfa27ff7af02b6471213fed9daa0098976e667750324f1abcea4c31e43b7d11f1579133f2b3d9fe27474e462058884e5e1b123be9cbbc6a443b2925c08520e7325e6f1a6d50e117eb61ea49d2534c8bb4d2ae4153fabe832b9edf4c5755fdd8b19940b81d1d96cf433d19e6a22968a85dc80f0312f596bd2530c1cfb28b5fe019ac9bc25cd9c2a5d8a0f3a1c0c79bcca524d315b5e21b5c26b46babe3d75d06d1cd33329ec782a0f22891ed1db42a1d6c0dea431428bc4d7aabdcf3e0eb6fda4e23eb7733e7727e9a1915580796c55188d2596d2665ad1182ba7abf15aaa5a8b779ea996317a20ae044b820bff35b6e8a1',
exponent: '010001',
},
{
modulus:
'be6a71558ee577ff03023cfa17aab4e6c86383cff8a7ad38edb9fafe6f323f2d5106cbc8cafb83b869cffd1ccf121cd743d509e589e68765c96601e813dc5b9dfc4be415c7a6526132d0035ca33d6d6075d4f535122a1cdfe017041f1088d1419f65c8e5490ee613e16dbf662698c0f54870f0475fa893fc41eb55b08ff1ac211bc045ded31be27d12c96d8d3cfc6a7ae8aa50bf2ee0f30ed507cc2581e3dec56de94f5dc0a7abee0be990b893f2887bd2c6310a1e0a9e3e38bd34fded2541508dc102a9c9b4c95effd9dd2dfe96c29be647d6c69d66ca500843cfaed6e440196f1dbe0e2e22163c61ca48c79116fa77216726749a976a1c4b0944b5121e8c01',
exponent: '010001',
},
];
var publicKeysParsed = {};
var prepared = false;
async function getPublisKeysByHex() {
const publisKeysByHex = {};
function prepareRsaKeys() {
if (prepared) {
return;
}
for (const publisKey of publisKeys) {
const RSAPublicKey = new TLSerializer();
RSAPublicKey.bytes(bytesFromHex(publisKey.modulus), 'n');
RSAPublicKey.bytes(bytesFromHex(publisKey.exponent), 'e');
for (var i = 0; i < publisKeysHex.length; i++) {
var keyParsed = publisKeysHex[i];
const buffer = RSAPublicKey.getBuffer();
var RSAPublicKey = new TLSerialization();
RSAPublicKey.storeBytes(bytesFromHex(keyParsed.modulus), 'n');
RSAPublicKey.storeBytes(bytesFromHex(keyParsed.exponent), 'e');
const fingerprintBytes = (await SHA1(buffer)).slice(-8);
fingerprintBytes.reverse();
var buffer = RSAPublicKey.getBuffer();
publisKeysByHex[bytesToHex(fingerprintBytes)] = {
modulus: publisKey.modulus,
exponent: publisKey.exponent,
};
}
var fingerprintBytes = sha1BytesSync(buffer).slice(-8);
fingerprintBytes.reverse();
return publisKeysByHex;
}
publicKeysParsed[bytesToHex(fingerprintBytes)] = {
modulus: keyParsed.modulus,
exponent: keyParsed.exponent,
};
async function getRsaKeyByFingerprints(fingerprints) {
let resultKey = null;
const publisKeysByHex = await getPublisKeysByHex();
fingerprints.forEach(fingerprint => {
if (!!resultKey) {
return;
}
prepared = true;
}
const fingerprintHex = bigInt(fingerprint).toString(16);
function selectRsaKeyByFingerPrint(fingerprints) {
prepareRsaKeys();
const foundKey = publisKeysByHex[fingerprintHex];
var fingerprintHex, foundKey, i;
for (i = 0; i < fingerprints.length; i++) {
fingerprintHex = bigStringInt(fingerprints[i]).toString(16);
if ((foundKey = publicKeysParsed[fingerprintHex])) {
return { ...{ fingerprint: fingerprints[i] }, ...foundKey };
}
if (foundKey) {
resultKey = {
fingerprint,
...foundKey,
};
}
});
return false;
}
return resultKey;
}
return {
prepare: prepareRsaKeys,
select: selectRsaKeyByFingerPrint,
};
})();
module.exports = RsaKeysManager;
module.exports = { getRsaKeyByFingerprints };
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc