1
0
mirror of https://github.com/danog/MadelineProto.git synced 2024-11-30 09:58:59 +01:00

Secret chat refactoring

This commit is contained in:
Daniil Gentili 2023-09-10 22:22:11 +02:00
parent ba3216b9a3
commit 7f2931bf7b
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
18 changed files with 727 additions and 697 deletions

View File

@ -267,7 +267,7 @@ final class Connection
/**
* Connects to a telegram DC using the specified protocol, proxy and connection parameters.
*/
public function connect(): self
private function connect(): self
{
if ($this->stream) {
return $this;
@ -335,7 +335,7 @@ final class Connection
* @param string $method Method name
* @param array $arguments Arguments
*/
private function methodAbstractions(string &$method, array &$arguments): ?DeferredFuture
private function methodAbstractions(string &$method, array &$arguments): void
{
if ($method === 'messages.importChatInvite' && isset($arguments['hash']) && \is_string($arguments['hash']) && $r = Tools::parseLink($arguments['hash'])) {
[$invite, $content] = $r;
@ -425,8 +425,7 @@ final class Connection
$arguments['message']['media']['size'] = $arguments['file']['size'];
}
}
$arguments['queuePromise'] = new DeferredFuture;
return $arguments['queuePromise'];
return;
} elseif (\in_array($method, ['messages.addChatUser', 'messages.deleteChatUser', 'messages.editChatAdmin', 'messages.editChatPhoto', 'messages.editChatTitle', 'messages.getFullChat', 'messages.exportChatInvite', 'messages.editChatAdmin', 'messages.migrateChat'], true) && isset($arguments['chat_id']) && (!\is_numeric($arguments['chat_id']) || $arguments['chat_id'] < 0)) {
$res = $this->API->getInfo($arguments['chat_id']);
if ($res['type'] !== 'chat') {
@ -463,10 +462,6 @@ final class Connection
}
}
}
if ($method === 'messages.sendEncrypted' || $method === 'messages.sendEncryptedService') {
$arguments['queuePromise'] = new DeferredFuture;
return $arguments['queuePromise'];
}
if (isset($arguments['reply_to_msg_id'])) {
if (isset($arguments['reply_to'])) {
throw new Exception("You can't provide a reply_to together with reply_to_msg_id and top_msg_id!");
@ -477,7 +472,6 @@ final class Connection
'top_msg_id' => $arguments['top_msg_id'] ?? null
];
}
return null;
}
/**
* Send an MTProto message.
@ -499,7 +493,11 @@ final class Connection
}
if ($message->isMethod()) {
$method = $message->getConstructor();
$queuePromise = $this->methodAbstractions($method, $body);
$this->methodAbstractions($method, $body);
if (\in_array($method, ['messages.sendEncrypted', 'messages.sendEncryptedFile', 'messages.sendEncryptedService'], true)) {
$id = $this->API->getSecretChat($body['peer'])['chat_id'];
$body = $this->API->secret_chats[$id]->encryptSecretMessage($message);
}
$body = $this->API->getTL()->serializeMethod($method, $body);
} else {
$body['_'] = $message->getConstructor();
@ -512,9 +510,6 @@ final class Connection
unset($body);
}
$this->pendingOutgoing[$this->pendingOutgoingKey++] = $message;
if (isset($queuePromise)) {
$queuePromise->complete();
}
if ($flush && isset($this->writer)) {
$this->writer->resume();
}

View File

@ -564,7 +564,7 @@ final class DataCenterConnection implements JsonSerializable
*/
private function getAuthConnection(): Connection
{
return $this->connections[0]->connect();
return $this->connections[0];
}
/**
* Check if any connection is available.
@ -583,7 +583,7 @@ final class DataCenterConnection implements JsonSerializable
if (empty($this->availableConnections)) {
$this->connectionsPromise->await();
}
return $this->getConnection()->connect();
return $this->getConnection();
}
/**
* Get best socket in round robin.

View File

@ -217,14 +217,19 @@ final class WriteLoop extends Loop
}
} elseif ($message->hasQueue()) {
$queueId = $message->getQueueId();
$this->connection->call_queue[$queueId] ??= [];
$MTmessage['body'] = ($this->API->getTL()->serializeMethod('invokeAfterMsgs', ['msg_ids' => $this->connection->call_queue[$queueId], 'query' => $MTmessage['body']]));
$this->connection->call_queue[$queueId][$message_id] = $message_id;
if (\count($this->connection->call_queue[$queueId]) > $this->API->settings->getRpc()->getLimitCallQueue()) {
\reset($this->connection->call_queue[$queueId]);
$key = \key($this->connection->call_queue[$queueId]);
unset($this->connection->call_queue[$queueId][$key]);
if (isset($this->connection->callQueue[$queueId])) {
$this->logger->logger("Adding $message to queue with ID $queueId", Logger::ULTRA_VERBOSE);
$MTmessage['body'] = $this->API->getTL()->serializeMethod(
'invokeAfterMsg',
[
'msg_id' => $this->connection->callQueue[$queueId],
'query' => $MTmessage['body']
]
);
} else {
$this->logger->logger("$message is the first in the queue with ID $queueId", Logger::ULTRA_VERBOSE);
}
$this->connection->callQueue[$queueId] = $message_id;
}
// TODO
/*

View File

@ -18,11 +18,13 @@ declare(strict_types=1);
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Update;
namespace danog\MadelineProto\Loop\Secret;
use danog\Loop\Loop;
use danog\MadelineProto\Loop\InternalLoop;
use danog\MadelineProto\MTProto;
use danog\MadelineProto\SecretChats\SecretChat;
use danog\MadelineProto\SecretChats\SecretChatController;
use danog\MadelineProto\SecurityException;
/**
@ -41,43 +43,32 @@ final class SecretFeedLoop extends Loop
* Incoming secret updates array.
*/
private array $incomingUpdates = [];
/**
* Secret chat ID.
*/
private int $secretId;
/**
* Constructor.
*
* @param MTProto $API API instance
* @param integer $secretId Secret chat ID
*/
public function __construct(MTProto $API, int $secretId)
public function __construct(MTProto $API, private readonly SecretChatController $secretChat)
{
$this->init($API);
$this->secretId = $secretId;
}
/**
* Main loop.
*/
public function loop(): ?float
{
if (!$this->isLoggedIn()) {
return self::PAUSE;
}
$this->logger->logger("Resumed {$this}");
while ($this->incomingUpdates) {
$updates = $this->incomingUpdates;
$this->incomingUpdates = [];
foreach ($updates as $update) {
try {
if (!$this->API->handleEncryptedUpdate($update)) {
if (!$this->secretChat->handleEncryptedUpdate($update)) {
$this->logger->logger("Secret chat deleted, exiting $this...");
unset($this->API->secretFeeders[$this->secretId]);
return self::STOP;
}
} catch (SecurityException $e) {
$this->logger->logger("Secret chat deleted, exiting $this...");
unset($this->API->secretFeeders[$this->secretId]);
throw $e;
}
}
@ -94,6 +85,6 @@ final class SecretFeedLoop extends Loop
}
public function __toString(): string
{
return "secret chat feed loop {$this->secretId}";
return "secret chat feed loop {$this->secretChat->public->chatId}";
}
}

View File

@ -37,7 +37,7 @@ use danog\MadelineProto\EventHandler\Message;
use danog\MadelineProto\Ipc\Server;
use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
use danog\MadelineProto\Loop\Update\FeedLoop;
use danog\MadelineProto\Loop\Update\SecretFeedLoop;
use danog\MadelineProto\Loop\Secret\SecretFeedLoop;
use danog\MadelineProto\Loop\Update\SeqLoop;
use danog\MadelineProto\Loop\Update\UpdateLoop;
use danog\MadelineProto\MTProtoTools\AuthKeyHandler;
@ -97,9 +97,6 @@ final class MTProto implements TLCallback, LoggerGetter
use UpdateHandler;
use Files;
use \danog\MadelineProto\SecretChats\AuthKeyHandler;
use MessageHandler;
use ResponseHandler;
use SeqNoHandler;
use BotAPI;
use BotAPIFiles;
use TD;
@ -123,7 +120,7 @@ final class MTProto implements TLCallback, LoggerGetter
* @internal
* @var int
*/
const V = 172;
const V = 173;
/**
* Bad message error codes.
*
@ -303,12 +300,6 @@ final class MTProto implements TLCallback, LoggerGetter
* @var array<FeedLoop>
*/
public array $feeders = [];
/**
* Secret chat feeder loops.
*
* @var array<SecretFeedLoop>
*/
public array $secretFeeders = [];
/**
* Updater loops.
*
@ -881,7 +872,8 @@ final class MTProto implements TLCallback, LoggerGetter
$this->config = ['expires' => -1];
$this->dh_config = ['version' => 0];
$this->initialize($this->settings);
foreach ($this->secret_chats as $chat => $data) {
foreach ($this->secret_chats as $chat => &$data) {
$data['chat_id'] = $chat;
try {
if (isset($this->secret_chats[$chat]) && $this->secret_chats[$chat]['InputEncryptedChat'] !== null) {
$this->notifyLayer($chat);
@ -1237,15 +1229,6 @@ final class MTProto implements TLCallback, LoggerGetter
return;
}
$this->logger('Starting update system');
foreach ($this->secret_chats as $id => $chat) {
if (!isset($this->secretFeeders[$id])) {
$this->secretFeeders[$id] = new SecretFeedLoop($this, $id);
}
$this->secretFeeders[$id]->start();
if (isset($this->secretFeeders[$id])) {
$this->secretFeeders[$id]->resume();
}
}
$this->channels_state->get(FeedLoop::GENERIC);
$channelIds = [];
foreach ($this->channels_state->get() as $state) {

View File

@ -120,9 +120,6 @@ trait CallHandler
$aargs['datacenter'] = -$this->datacenter;
return $this->API->methodCallAsyncWrite($method, $args, $aargs);
}
if (\in_array($method, ['messages.setEncryptedTyping', 'messages.readEncryptedHistory', 'messages.sendEncrypted', 'messages.sendEncryptedFile', 'messages.sendEncryptedService', 'messages.receivedQueue'], true)) {
$aargs['queue'] = 'secret';
}
if (\is_array($args)) {
if (isset($args['multiple'])) {
$aargs['multiple'] = true;
@ -130,7 +127,7 @@ trait CallHandler
if (isset($args['message']) && \is_string($args['message']) && \mb_strlen($args['message'], 'UTF-8') > ($this->API->getConfig())['message_length_max'] && \mb_strlen($this->API->parseMode($args)['message'], 'UTF-8') > ($this->API->getConfig())['message_length_max']) {
$args = $this->API->splitToChunks($args);
$promises = [];
$aargs['queue'] = $method.' '.\time();
$aargs['queue'] = $method.' '.$this->API->getId($args['peer']);
$aargs['multiple'] = true;
}
if (isset($aargs['multiple'])) {

View File

@ -291,6 +291,15 @@ trait ResponseHandler
case 500:
case -500:
case -503:
if ($request->hasQueue() &&
(
$response['error_message'] === 'MSG_WAIT_FAILED'
|| $response['error_message'] === 'MSG_WAIT_TIMEOUT'
)
) {
EventLoop::delay(1.0, fn () => $this->methodRecall(message_id: $request->getMsgId()));
return null;
}
if ((($response['error_code'] === -503 || $response['error_message'] === '-503') && !\in_array($request->getConstructor(), ['messages.getBotCallbackAnswer', 'messages.getInlineBotResults'], true))
|| (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CONNECT_FAILED', 'RPC_CALL_FAIL', 'RPC_MCGET_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'], true))) {
EventLoop::delay(1.0, fn () => $this->methodRecall(message_id: $request->getMsgId()));

View File

@ -81,8 +81,9 @@ trait Session
/**
* Call queue.
*
* @var array<string, int>
*/
public array $call_queue = [];
public array $callQueue = [];
/**
* Ack queue.
*

View File

@ -21,9 +21,10 @@ declare(strict_types=1);
namespace danog\MadelineProto\SecretChats;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Update\SecretFeedLoop;
use danog\MadelineProto\Loop\Secret\SecretFeedLoop;
use danog\MadelineProto\Loop\Update\UpdateLoop;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\MTProtoTools\DialogId;
use danog\MadelineProto\PeerNotInDbException;
use danog\MadelineProto\RPCErrorException;
use danog\MadelineProto\SecurityException;
@ -49,64 +50,9 @@ trait AuthKeyHandler
/**
* Secret chats.
*
* @var array<int, SecretChatController>
*/
protected array $secret_chats = [];
/**
* Accept secret chat.
*
* @param array $params Secret chat ID
*/
public function acceptSecretChat(array $params): void
{
//$this->logger->logger($params['id'],$this->secretChatStatus($params['id']));
if ($this->secretChatStatus($params['id']) !== 0) {
//$this->logger->logger($this->secretChatStatus($params['id']));
$this->logger->logger("I've already accepted secret chat ".$params['id']);
return;
}
$dh_config = ($this->getDhConfig());
$this->logger->logger('Generating b...', Logger::VERBOSE);
$b = new BigInteger(Tools::random(256), 256);
$params['g_a'] = new BigInteger((string) $params['g_a'], 256);
Crypt::checkG($params['g_a'], $dh_config['p']);
$key = ['auth_key' => \str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, \chr(0), STR_PAD_LEFT)];
//$this->logger->logger($key);
$key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = \substr(\sha1($key['auth_key'], true), 16);
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = [
'key' => $key,
'admin' => false,
'user_id' => $params['admin_id'],
'InputEncryptedChat' => [
'_' => 'inputEncryptedChat',
'chat_id' => $params['id'],
'access_hash' => $params['access_hash'],
],
'in_seq_no_x' => 1,
'out_seq_no_x' => 0,
'in_seq_no' => 0,
'out_seq_no' => 0,
'layer' => 8,
'ttl' => 0,
'ttr' => 100,
'updated' => \time(),
'incoming' => [],
'outgoing' => [],
'created' => \time(),
'rekeying' => [0],
'key_x' => 'from server',
'mtproto' => 1,
];
$this->secretFeeders[$params['id']] = new SecretFeedLoop($this, $params['id']);
$this->secretFeeders[$params['id']]->start();
$this->secretFeeders[$params['id']]->resume();
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
Crypt::checkG($g_b, $dh_config['p']);
$this->methodCallAsyncRead('messages.acceptEncryption', ['peer' => $params['id'], 'g_b' => $g_b->toBytes(), 'key_fingerprint' => $key['fingerprint']]);
$this->notifyLayer($params['id']);
$this->logger->logger('Secret chat '.$params['id'].' accepted successfully!', Logger::NOTICE);
}
public array $secret_chats = [];
/**
* Request secret chat.
*
@ -132,6 +78,65 @@ trait AuthKeyHandler
$this->logger->logger('Secret chat '.$res['id'].' requested successfully!', Logger::NOTICE);
return $res['id'];
}
/**
* Accept secret chat.
*
* @param array $params Secret chat ID
*/
public function acceptSecretChat(array $params): void
{
if ($this->secretChatStatus($params['id']) !== 0) {
$this->logger->logger("I've already accepted secret chat ".$params['id']);
return;
}
$dh_config = $this->getDhConfig();
$this->logger->logger('Generating b...', Logger::VERBOSE);
$b = new BigInteger(Tools::random(256), 256);
$params['g_a'] = new BigInteger((string) $params['g_a'], 256);
Crypt::checkG($params['g_a'], $dh_config['p']);
$key = ['auth_key' => \str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, \chr(0), STR_PAD_LEFT)];
//$this->logger->logger($key);
$key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = \substr(\sha1($key['auth_key'], true), 16);
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = new SecretChatController(
$this,
new SecretChat(
DialogId::fromSecretChatId($params['id']),
false,
$params['admin_id'],
time(),
0
)
) [
'chat_id' => $params['id'],
'key' => $key,
'admin' => false,
'user_id' => $params['admin_id'],
'InputEncryptedChat' => [
'_' => 'inputEncryptedChat',
'chat_id' => $params['id'],
'access_hash' => $params['access_hash'],
],
'in_seq_no_x' => 1,
'out_seq_no_x' => 0,
'in_seq_no' => 0,
'out_seq_no' => 0,
'layer' => 8,
'ttl' => 0,
'ttr' => 100,
'updated' => \time(),
'incoming' => [],
'outgoing' => [],
'created' => \time(),
'mtproto' => 1,
];
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
Crypt::checkG($g_b, $dh_config['p']);
$this->methodCallAsyncRead('messages.acceptEncryption', ['peer' => $params['id'], 'g_b' => $g_b->toBytes(), 'key_fingerprint' => $key['fingerprint']]);
$this->notifyLayer($params['id']);
$this->logger->logger('Secret chat '.$params['id'].' accepted successfully!', Logger::NOTICE);
}
/**
* Complete secret chat.
*
@ -157,166 +162,34 @@ trait AuthKeyHandler
}
$key['visualization_orig'] = \substr(\sha1($key['auth_key'], true), 16);
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputEncryptedChat' => ['chat_id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputEncryptedChat'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => \time(), 'incoming' => [], 'outgoing' => [], 'created' => \time(), 'rekeying' => [0], 'key_x' => 'to server', 'mtproto' => 1];
$this->secretFeeders[$params['id']] = new SecretFeedLoop($this, $params['id']);
$this->secretFeeders[$params['id']]->start();
$this->secretFeeders[$params['id']]->resume();
$this->notifyLayer($params['id']);
$this->secret_chats[$params['id']] = $chat = new SecretChatController(
$this,
new SecretChat(
DialogId::fromSecretChatId($params['id']),
true,
$params['participant_id'],
time(),
0
),
1,
0
);
$chat->notifyLayer();
$this->logger->logger('Secret chat '.$params['id'].' completed successfully!', Logger::NOTICE);
}
private function notifyLayer($chat): void
{
$this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNotifyLayer', 'layer' => $this->TL->getSecretLayer()]]]);
}
/**
* Temporary rekeyed secret chats.
*
*/
protected array $temp_rekeyed_secret_chats = [];
/**
* Rekey secret chat.
*
* @param int $chat Secret chat to rekey
*/
public function rekey(int $chat): ?string
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 0) {
return null;
}
$this->logger->logger('Rekeying secret chat '.$chat.'...', Logger::VERBOSE);
$dh_config = ($this->getDhConfig());
$this->logger->logger('Generating a...', Logger::VERBOSE);
$a = new BigInteger(Tools::random(256), 256);
$this->logger->logger('Generating g_a...', Logger::VERBOSE);
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
Crypt::checkG($g_a, $dh_config['p']);
$e = Tools::random(8);
$this->temp_rekeyed_secret_chats[$e] = $a;
$this->secret_chats[$chat]['rekeying'] = [1, $e];
$this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]]);
$this->updaters[UpdateLoop::GENERIC]->resume();
return $e;
}
/**
* Accept rekeying.
*
* @param int $chat Chat
* @param array $params Parameters
*/
private function acceptRekey(int $chat, array $params): void
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 0) {
$my_exchange_id = new BigInteger($this->secret_chats[$chat]['rekeying'][1], -256);
$other_exchange_id = new BigInteger($params['exchange_id'], -256);
//$this->logger->logger($my, $params);
if ($my_exchange_id->compare($other_exchange_id) > 0) {
return;
}
if ($my_exchange_id->compare($other_exchange_id) === 0) {
$this->secret_chats[$chat]['rekeying'] = [0];
return;
}
}
$this->logger->logger('Accepting rekeying of secret chat '.$chat.'...', Logger::VERBOSE);
$dh_config = ($this->getDhConfig());
$this->logger->logger('Generating b...', Logger::VERBOSE);
$b = new BigInteger(Tools::random(256), 256);
$params['g_a'] = new BigInteger((string) $params['g_a'], 256);
Crypt::checkG($params['g_a'], $dh_config['p']);
$key = ['auth_key' => \str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, \chr(0), STR_PAD_LEFT)];
$key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
$this->temp_rekeyed_secret_chats[$params['exchange_id']] = $key;
$this->secret_chats[$chat]['rekeying'] = [2, $params['exchange_id']];
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
Crypt::checkG($g_b, $dh_config['p']);
$this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
$this->updaters[UpdateLoop::GENERIC]->resume();
}
/**
* Commit rekeying of secret chat.
*
* @param int $chat Chat
* @param array $params Parameters
*/
private function commitRekey(int $chat, array $params): void
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 1 || !isset($this->temp_rekeyed_secret_chats[$params['exchange_id']])) {
$this->secret_chats[$chat]['rekeying'] = [0];
return;
}
$this->logger->logger('Committing rekeying of secret chat '.$chat.'...', Logger::VERBOSE);
$dh_config = ($this->getDhConfig());
$params['g_b'] = new BigInteger((string) $params['g_b'], 256);
Crypt::checkG($params['g_b'], $dh_config['p']);
$key = ['auth_key' => \str_pad($params['g_b']->powMod($this->temp_rekeyed_secret_chats[$params['exchange_id']], $dh_config['p'])->toBytes(), 256, \chr(0), STR_PAD_LEFT)];
$key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
$this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
throw new SecurityException('Invalid key fingerprint!');
}
$this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionCommitKey', 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
$this->secret_chats[$chat]['rekeying'] = [0];
$this->secret_chats[$chat]['old_key'] = $this->secret_chats[$chat]['key'];
$this->secret_chats[$chat]['key'] = $key;
$this->secret_chats[$chat]['ttr'] = 100;
$this->secret_chats[$chat]['updated'] = \time();
$this->updaters[UpdateLoop::GENERIC]->resume();
}
/**
* Complete rekeying.
*
* @param int $chat Chat
* @param array $params Parameters
*/
private function completeRekey(int $chat, array $params): bool
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 2 || !isset($this->temp_rekeyed_secret_chats[$params['exchange_id']]['fingerprint'])) {
return false;
}
if ($this->temp_rekeyed_secret_chats[$params['exchange_id']]['fingerprint'] !== $params['key_fingerprint']) {
$this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
throw new SecurityException('Invalid key fingerprint!');
}
$this->logger->logger('Completing rekeying of secret chat '.$chat.'...', Logger::VERBOSE);
$this->secret_chats[$chat]['rekeying'] = [0];
$this->secret_chats[$chat]['old_key'] = $this->secret_chats[$chat]['key'];
$this->secret_chats[$chat]['key'] = $this->temp_rekeyed_secret_chats[$params['exchange_id']];
$this->secret_chats[$chat]['ttr'] = 100;
$this->secret_chats[$chat]['updated'] = \time();
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
$this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNoop']]]);
$this->logger->logger('Secret chat '.$chat.' rekeyed successfully!', Logger::VERBOSE);
return true;
}
/**
* Get secret chat status.
*
* @param int $chat Chat ID
* @return int One of \danog\MadelineProto\API::SECRET_EMPTY, \danog\MadelineProto\API::SECRET_REQUESTED, \danog\MadelineProto\API::SECRET_READY
*/
public function secretChatStatus(int $chat): int
{
if (isset($this->secret_chats[$chat])) {
return \danog\MadelineProto\API::SECRET_READY;
}
if (isset($this->temp_requested_secret_chats[$chat])) {
return \danog\MadelineProto\API::SECRET_REQUESTED;
}
return \danog\MadelineProto\API::SECRET_EMPTY;
}
/**
* Get secret chat.
*
* @param array|int $chat Secret chat ID
*/
public function getSecretChat(array|int $chat): array
public function getSecretChat(array|int $chat): SecretChat
{
return $this->secret_chats[\is_array($chat) ? $chat['chat_id'] : $chat];
if (is_array($chat)) {
return $this->getInfo($chat);
} elseif (DialogId::isSecretChat($chat)) {
$chat = DialogId::toSecretChatId($chat);
}
return $this->secret_chats[$chat];
}
/**
* Check whether secret chat exists.

View File

@ -1,201 +0,0 @@
<?php
declare(strict_types=1);
/**
* MessageHandler module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;
use Amp\DeferredFuture;
use Amp\Future;
use danog\MadelineProto\Lang;
use danog\MadelineProto\Logger;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\SecurityException;
use danog\MadelineProto\Tools;
/**
* Manages packing and unpacking of messages, and the list of sent and received messages.
*
* @internal
*/
trait MessageHandler
{
/**
* Secret queue.
*
* @var array<Future<null>>
*/
private array $secretQueue = [];
/**
* Encrypt secret chat message.
*
* @param integer $chat_id Chat ID
* @param array $message Message to encrypt
* @param DeferredFuture<null> $queuePromise Queue promise
* @internal
*/
public function encryptSecretMessage(int $chat_id, array $message, DeferredFuture $queuePromise): string|false
{
if (!isset($this->secret_chats[$chat_id])) {
$this->logger->logger(\sprintf(Lang::$current_lang['secret_chat_skipping'], $chat_id));
return false;
}
$message['random_id'] = Tools::random(8);
$this->secret_chats[$chat_id]['ttr']--;
if ($this->secret_chats[$chat_id]['layer'] > 8) {
if (($this->secret_chats[$chat_id]['ttr'] <= 0 || \time() - $this->secret_chats[$chat_id]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$chat_id]['rekeying'][0] === 0) {
$this->rekey($chat_id);
}
if (isset($this->secretQueue[$chat_id])) {
$promise = $this->secretQueue[$chat_id];
$this->secretQueue[$chat_id] = $queuePromise->getFuture();
$promise->await();
} else {
$this->secretQueue[$chat_id] = $queuePromise->getFuture();
}
$message = ['_' => 'decryptedMessageLayer', 'layer' => $this->secret_chats[$chat_id]['layer'], 'in_seq_no' => $this->generateSecretInSeqNo($chat_id), 'out_seq_no' => $this->generateSecretOutSeqNo($chat_id), 'message' => $message];
$this->secret_chats[$chat_id]['out_seq_no']++;
}
$this->secret_chats[$chat_id]['outgoing'][$this->secret_chats[$chat_id]['out_seq_no']] = $message;
$constructor = $this->secret_chats[$chat_id]['layer'] === 8 ? 'DecryptedMessage' : 'DecryptedMessageLayer';
$message = $this->TL->serializeObject(['type' => $constructor], $message, $constructor, $this->secret_chats[$chat_id]['layer']);
$message = Tools::packUnsignedInt(\strlen($message)).$message;
if ($this->secret_chats[$chat_id]['mtproto'] === 2) {
$padding = Tools::posmod(-\strlen($message), 16);
if ($padding < 12) {
$padding += 16;
}
$message .= Tools::random($padding);
$message_key = \substr(\hash('sha256', \substr($this->secret_chats[$chat_id]['key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 0 : 8), 32).$message, true), 8, 16);
[$aes_key, $aes_iv] = Crypt::kdf($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], $this->secret_chats[$chat_id]['admin']);
} else {
$message_key = \substr(\sha1($message, true), -16);
[$aes_key, $aes_iv] = Crypt::oldKdf($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], true);
$message .= Tools::random(Tools::posmod(-\strlen($message), 16));
}
$message = $this->secret_chats[$chat_id]['key']['fingerprint'].$message_key.Crypt::igeEncrypt($message, $aes_key, $aes_iv);
return $message;
}
/**
* Handle encrypted update.
*
* @internal
*/
public function handleEncryptedUpdate(array $message): bool
{
if (!isset($this->secret_chats[$message['message']['chat_id']])) {
$this->logger->logger(\sprintf(Lang::$current_lang['secret_chat_skipping'], $message['message']['chat_id']));
return false;
}
$message['message']['bytes'] = (string) $message['message']['bytes'];
$auth_key_id = \substr($message['message']['bytes'], 0, 8);
$old = false;
if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['key']['fingerprint']) {
if (isset($this->secret_chats[$message['message']['chat_id']]['old_key']['fingerprint'])) {
if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['old_key']['fingerprint']) {
$this->discardSecretChat($message['message']['chat_id']);
throw new SecurityException('Key fingerprint mismatch');
}
$old = true;
} else {
$this->discardSecretChat($message['message']['chat_id']);
throw new SecurityException('Key fingerprint mismatch');
}
}
$message_key = \substr($message['message']['bytes'], 8, 16);
$encrypted_data = \substr($message['message']['bytes'], 24);
if ($this->secret_chats[$message['message']['chat_id']]['mtproto'] === 2) {
$this->logger->logger('Trying MTProto v2 decryption for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
try {
$message_data = $this->tryMTProtoV2Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v2 decryption OK for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
} catch (SecurityException $e) {
$this->logger->logger('MTProto v2 decryption failed with message '.$e->getMessage().', trying MTProto v1 decryption for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
$message_data = $this->tryMTProtoV1Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v1 decryption OK for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
$this->secret_chats[$message['message']['chat_id']]['mtproto'] = 1;
}
} else {
$this->logger->logger('Trying MTProto v1 decryption for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
try {
$message_data = $this->tryMTProtoV1Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v1 decryption OK for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
} catch (SecurityException $e) {
$this->logger->logger('MTProto v1 decryption failed with message '.$e->getMessage().', trying MTProto v2 decryption for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
$message_data = $this->tryMTProtoV2Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v2 decryption OK for chat '.$message['message']['chat_id'].'...', Logger::NOTICE);
$this->secret_chats[$message['message']['chat_id']]['mtproto'] = 2;
}
}
$deserialized = $this->TL->deserialize($message_data, ['type' => '']);
$this->secret_chats[$message['message']['chat_id']]['ttr']--;
if (($this->secret_chats[$message['message']['chat_id']]['ttr'] <= 0 || \time() - $this->secret_chats[$message['message']['chat_id']]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$message['message']['chat_id']]['rekeying'][0] === 0) {
$this->rekey($message['message']['chat_id']);
}
unset($message['message']['bytes']);
$message['message']['decrypted_message'] = $deserialized;
$this->secret_chats[$message['message']['chat_id']]['incoming'][$this->secret_chats[$message['message']['chat_id']]['in_seq_no']] = $message['message'];
$this->handleDecryptedUpdate($message);
return true;
}
private function tryMTProtoV1Decrypt($message_key, $chat_id, $old, $encrypted_data): string
{
[$aes_key, $aes_iv] = Crypt::oldKdf($message_key, $this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], true);
$decrypted_data = Crypt::igeDecrypt($encrypted_data, $aes_key, $aes_iv);
$message_data_length = \unpack('V', \substr($decrypted_data, 0, 4))[1];
$message_data = \substr($decrypted_data, 4, $message_data_length);
if ($message_data_length > \strlen($decrypted_data)) {
throw new SecurityException('message_data_length is too big');
}
if ($message_key != \substr(\sha1(\substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) {
throw new SecurityException('Msg_key mismatch');
}
if (\strlen($decrypted_data) - 4 - $message_data_length > 15) {
throw new SecurityException('difference between message_data_length and the length of the remaining decrypted buffer is too big');
}
if (\strlen($decrypted_data) % 16 != 0) {
throw new SecurityException("Length of decrypted data is not divisible by 16");
}
return $message_data;
}
private function tryMTProtoV2Decrypt($message_key, $chat_id, $old, $encrypted_data): string
{
[$aes_key, $aes_iv] = Crypt::kdf($message_key, $this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], !$this->secret_chats[$chat_id]['admin']);
$decrypted_data = Crypt::igeDecrypt($encrypted_data, $aes_key, $aes_iv);
if ($message_key != \substr(\hash('sha256', \substr($this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 8 : 0), 32).$decrypted_data, true), 8, 16)) {
throw new SecurityException('Msg_key mismatch');
}
$message_data_length = \unpack('V', \substr($decrypted_data, 0, 4))[1];
$message_data = \substr($decrypted_data, 4, $message_data_length);
if ($message_data_length > \strlen($decrypted_data)) {
throw new SecurityException('message_data_length is too big');
}
if (\strlen($decrypted_data) - 4 - $message_data_length < 12) {
throw new SecurityException('padding is too small');
}
if (\strlen($decrypted_data) - 4 - $message_data_length > 1024) {
throw new SecurityException('padding is too big');
}
if (\strlen($decrypted_data) % 16 != 0) {
throw new SecurityException("Length of decrypted data is not divisible by 16");
}
return $message_data;
}
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* Secret chat module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;
/**
* @internal
*/
enum RekeyState {
case IDLE;
case REQUESTED;
case ACCEPTED;
}

View File

@ -1,102 +0,0 @@
<?php
declare(strict_types=1);
/**
* ResponseHandler module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;
use danog\MadelineProto\Logger;
use danog\MadelineProto\ResponseException;
/**
* Manages responses.
*
* @internal
*/
trait ResponseHandler
{
private function handleDecryptedUpdate(array $update): void
{
$chatId = $update['message']['chat_id'];
$decryptedMessage = $update['message']['decrypted_message'];
if ($decryptedMessage['_'] === 'decryptedMessage') {
$this->saveUpdate($update);
return;
}
if ($decryptedMessage['_'] === 'decryptedMessageService') {
$action = $decryptedMessage['action'];
switch ($action['_']) {
case 'decryptedMessageActionRequestKey':
$this->acceptRekey($chatId, $action);
return;
case 'decryptedMessageActionAcceptKey':
$this->commitRekey($chatId, $action);
return;
case 'decryptedMessageActionCommitKey':
$this->completeRekey($chatId, $action);
return;
case 'decryptedMessageActionNotifyLayer':
$this->secret_chats[$chatId]['layer'] = $action['layer'];
if ($action['layer'] >= 17 && \time() - $this->secret_chats[$chatId]['created'] > 15) {
$this->notifyLayer($chatId);
}
if ($action['layer'] >= 73) {
$this->secret_chats[$chatId]['mtproto'] = 2;
}
return;
case 'decryptedMessageActionSetMessageTTL':
$this->secret_chats[$chatId]['ttl'] = $action['ttl_seconds'];
$this->saveUpdate($update);
return;
case 'decryptedMessageActionNoop':
return;
case 'decryptedMessageActionResend':
$action['start_seq_no'] -= $this->secret_chats[$chatId]['out_seq_no_x'];
$action['end_seq_no'] -= $this->secret_chats[$chatId]['out_seq_no_x'];
$action['start_seq_no'] /= 2;
$action['end_seq_no'] /= 2;
$this->logger->logger('Resending messages for secret chat '.$chatId, Logger::WARNING);
foreach ($this->secret_chats[$chatId]['outgoing'] as $seq => $message) {
if ($seq >= $action['start_seq_no'] && $seq <= $action['end_seq_no']) {
$this->methodCallAsyncRead('messages.sendEncrypted', ['peer' => $chatId, 'message' => $message]);
}
}
return;
default:
$this->saveUpdate($update);
}
return;
}
if ($decryptedMessage['_'] === 'decryptedMessageLayer') {
if (($this->checkSecretOutSeqNo($chatId, $decryptedMessage['out_seq_no']))
&& ($this->checkSecretInSeqNo($chatId, $decryptedMessage['in_seq_no']))) {
$this->secret_chats[$chatId]['in_seq_no']++;
if ($decryptedMessage['layer'] >= 17 && $decryptedMessage['layer'] !== $this->secret_chats[$chatId]['layer']) {
$this->secret_chats[$chatId]['layer'] = $decryptedMessage['layer'];
if ($decryptedMessage['layer'] >= 17 && \time() - $this->secret_chats[$chatId]['created'] > 15) {
$this->notifyLayer($chatId);
}
}
$update['message']['decrypted_message'] = $decryptedMessage['message'];
$this->handleDecryptedUpdate($update);
}
return;
}
throw new ResponseException('Unrecognized decrypted message received: '.\var_export($update, true));
}
}

View File

@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
/**
* Secret chat module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;
use AssertionError;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Secret\SecretFeedLoop;
use danog\MadelineProto\MTProto;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\SecurityException;
use danog\MadelineProto\Tools;
use phpseclib3\Math\BigInteger;
/**
* Represents a secret chat.
*/
final class SecretChat {
public function __construct(
public readonly int $chatId,
public readonly bool $creator,
public readonly int $otherID,
/** Creation date */
public readonly int $created,
public int $ttl = 0
)
{
}
}

View File

@ -0,0 +1,521 @@
<?php
declare(strict_types=1);
/**
* Secret chat module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;
use Amp\Sync\LocalMutex;
use AssertionError;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Secret\SecretFeedLoop;
use danog\MadelineProto\Loop\Update\UpdateLoop;
use danog\MadelineProto\MTProto;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\MTProtoTools\DialogId;
use danog\MadelineProto\ResponseException;
use danog\MadelineProto\RPCErrorException;
use danog\MadelineProto\SecurityException;
use danog\MadelineProto\Tools;
use phpseclib3\Math\BigInteger;
use Revolt\EventLoop;
use SplQueue;
use Stringable;
/**
* Represents a secret chat.
* @internal
*
* @psalm-type TKey=array{auth_key: string, fingerprint: string, visualization_orig: string, visualization_46: string}
*/
final class SecretChatController implements Stringable {
/**
* @var array<int, array>
*/
private array $incoming = [];
/**
* @var array<int, array>
*/
private array $outgoing = [];
private int $in_seq_no = 0;
private int $out_seq_no = 0;
private int $layer = 8;
private int $updated;
/**
* Secret queue.
*
* @var SplQueue<list{message, array}>
*/
private SplQueue $secretQueue;
private RekeyState $rekeyState = RekeyState::IDLE;
private ?int $rekeyExchangeId = null;
private ?BigInteger $rekeyParam = null;
private ?array $rekeyKey;
/** @var TKey */
private array $key;
/** @var ?TKey */
private ?array $oldKey;
private int $ttr = 100;
private int $mtproto = 1;
private readonly int $id;
private SecretFeedLoop $feedLoop;
public function __construct(
private readonly MTProto $API,
public readonly SecretChat $public,
private int $in_seq_no_x,
private int $out_seq_no_x
)
{
$this->updated = $public->created;
$this->id = DialogId::toSecretChatId($public->chatId);
$this->feedLoop = new SecretFeedLoop($API, $this);
$this->feedLoop->start();
$this->secretQueue = new SplQueue;
}
/**
* Discard secret chat.
*/
public function discard(): void
{
$this->API->discardSecretChat($this->id);
}
public function notifyLayer(): void
{
$this->API->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $this->id, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNotifyLayer', 'layer' => $this->API->getTL()->getSecretLayer()]]]);
}
private LocalMutex $rekeyMutex;
/**
* Rekey secret chat.
*/
public function rekey(): void
{
if ($this->rekeyState !== RekeyState::IDLE) {
return;
}
$lock = $this->rekeyMutex->acquire();
try {
if ($this->rekeyState !== RekeyState::IDLE) {
return;
}
$dh_config = $this->API->getDhConfig();
$this->API->logger->logger('Rekeying secret chat '.$this.'...', Logger::VERBOSE);
$this->API->logger->logger('Generating a...', Logger::VERBOSE);
$a = new BigInteger(Tools::random(256), 256);
$this->API->logger->logger('Generating g_a...', Logger::VERBOSE);
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
Crypt::checkG($g_a, $dh_config['p']);
$this->rekeyState = RekeyState::REQUESTED;
$this->rekeyExchangeId = Tools::randomInt();
$this->rekeyParam = $a;
$this->API->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $this->id, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]]);
$this->API->updaters[UpdateLoop::GENERIC]->resume();
} finally {
EventLoop::queue($lock->release(...));
}
}
/**
* Accept rekeying.
*
* @param array $params Parameters
*/
private function acceptRekey(array $params): void
{
$lock = $this->rekeyMutex->acquire();
try {
if ($this->rekeyState !== RekeyState::IDLE) {
if ($this->rekeyExchangeId > $params['exchange_id']) {
return;
}
if ($this->rekeyExchangeId === $params['exchange_id']) {
$this->rekeyState = RekeyState::IDLE;
return;
}
}
$this->API->logger->logger('Accepting rekeying of '.$this.'...', Logger::VERBOSE);
$dh_config = $this->API->getDhConfig();
$this->API->logger->logger('Generating b...', Logger::VERBOSE);
$b = new BigInteger(Tools::random(256), 256);
$params['g_a'] = new BigInteger((string) $params['g_a'], 256);
Crypt::checkG($params['g_a'], $dh_config['p']);
$key = ['auth_key' => \str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, \chr(0), STR_PAD_LEFT)];
$key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = $this->key['visualization_orig'];
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
$this->rekeyState = RekeyState::ACCEPTED;
$this->rekeyExchangeId = $params['exchange_id'];
$this->rekeyKey = $key;
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
Crypt::checkG($g_b, $dh_config['p']);
$this->API->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $this->id, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
$this->API->updaters[UpdateLoop::GENERIC]->resume();
} finally {
EventLoop::queue($lock->release(...));
}
}
/**
* Commit rekeying of secret chat.
*
* @param array $params Parameters
*/
private function commitRekey(array $params): void
{
if ($this->rekeyState !== RekeyState::REQUESTED || $this->rekeyExchangeId !== $params['exchange_id']) {
$this->rekeyState = RekeyState::IDLE;
return;
}
$this->API->logger->logger('Committing rekeying of '.$this.'...', Logger::VERBOSE);
$dh_config = ($this->API->getDhConfig());
$params['g_b'] = new BigInteger((string) $params['g_b'], 256);
Crypt::checkG($params['g_b'], $dh_config['p']);
$key = ['auth_key' => \str_pad($params['g_b']->powMod($this->rekeyParam, $dh_config['p'])->toBytes(), 256, \chr(0), STR_PAD_LEFT)];
$key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = $this->key['visualization_orig'];
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
$this->API->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $this->id, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
throw new SecurityException('Invalid key fingerprint!');
}
$this->API->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $this->id, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionCommitKey', 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
$this->rekeyState = RekeyState::IDLE;
$this->oldKey = $this->key;
$this->key = $key;
$this->ttr = 100;
$this->updated = time();
$this->API->updaters[UpdateLoop::GENERIC]->resume();
}
/**
* Complete rekeying.
*
* @param array $params Parameters
*/
private function completeRekey(array $params): bool
{
if ($this->rekeyState !== RekeyState::ACCEPTED || $this->rekeyExchangeId !== $params['exchange_id']) {
return false;
}
if ($this->rekeyKey['fingerprint'] !== $params['key_fingerprint']) {
$this->API->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $this->id, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
throw new SecurityException('Invalid key fingerprint!');
}
$this->API->logger->logger('Completing rekeying of secret chat '.$this.'...', Logger::VERBOSE);
$this->rekeyState = RekeyState::IDLE;
$this->oldKey = $this->key;
$this->key = $this->rekeyKey;
$this->ttr = 100;
$this->updated = time();
$this->API->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $this->id, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNoop']]]);
$this->API->logger->logger('Secret chat '.$this.' rekeyed successfully!', Logger::VERBOSE);
return true;
}
/**
* Encrypt secret chat message.
* @internal
*/
public function encryptSecretMessage(MTProtoOutgoingMessage $msg): string
{
$body = $msg->getBody();
if (isset($body['data'])) {
return $body;
}
$this->ttr--;
if ($this->layer > 8) {
if (($this->ttr <= 0 || \time() - $this->updated > 7 * 24 * 60 * 60) && $this->rekeyState === RekeyState::IDLE) {
$this->rekey();
}
}
$body['data'] = $this->encryptSecretMessageInner($body['message']);
unset($body['message']);
$this->secretQueue->enqueue([$msg->getConstructor(), $body]);
return $body;
}
private function encryptSecretMessageInner(array $message): void {
$message['random_id'] = Tools::random(8);
if ($this->layer > 8) {
$message = ['_' => 'decryptedMessageLayer', 'layer' => $this->layer, 'in_seq_no' => $this->generateSecretInSeqNo(), 'out_seq_no' => $this->generateSecretOutSeqNo(), 'message' => $message];
$this->out_seq_no++;
}
$this->outgoing[$this->out_seq_no] = $message;
$constructor = $this->layer === 8 ? 'DecryptedMessage' : 'DecryptedMessageLayer';
$message = $this->API->getTL()->serializeObject(['type' => $constructor], $message, $constructor, $this->layer);
$message = Tools::packUnsignedInt(\strlen($message)).$message;
if ($this->mtproto === 2) {
$padding = Tools::posmod(-\strlen($message), 16);
if ($padding < 12) {
$padding += 16;
}
$message .= Tools::random($padding);
$message_key = \substr(\hash('sha256', \substr($this->key['auth_key'], 88 + ($this->public->creator ? 0 : 8), 32).$message, true), 8, 16);
[$aes_key, $aes_iv] = Crypt::kdf($message_key, $this->key['auth_key'], $this->public->creator);
} else {
$message_key = \substr(\sha1($message, true), -16);
[$aes_key, $aes_iv] = Crypt::oldKdf($message_key, $this->key['auth_key'], true);
$message .= Tools::random(Tools::posmod(-\strlen($message), 16));
}
$message = $this->key['fingerprint'].$message_key.Crypt::igeEncrypt($message, $aes_key, $aes_iv);
}
private function handleDecryptedUpdate(array $update): void
{
$decryptedMessage = $update['message']['decrypted_message'];
if ($decryptedMessage['_'] === 'decryptedMessage') {
$this->API->saveUpdate($update);
return;
}
if ($decryptedMessage['_'] === 'decryptedMessageService') {
$action = $decryptedMessage['action'];
switch ($action['_']) {
case 'decryptedMessageActionRequestKey':
$this->acceptRekey($action);
return;
case 'decryptedMessageActionAcceptKey':
$this->commitRekey($action);
return;
case 'decryptedMessageActionCommitKey':
$this->completeRekey($action);
return;
case 'decryptedMessageActionNotifyLayer':
$this->layer = $action['layer'];
if ($action['layer'] >= 17 && \time() - $this->public->created > 15) {
$this->notifyLayer();
}
if ($action['layer'] >= 73) {
$this->mtproto = 2;
}
return;
case 'decryptedMessageActionSetMessageTTL':
$this->public->ttl = $action['ttl_seconds'];
$this->API->saveUpdate($update);
return;
case 'decryptedMessageActionNoop':
return;
case 'decryptedMessageActionResend':
$action['start_seq_no'] -= $this->out_seq_no_x;
$action['end_seq_no'] -= $this->out_seq_no_x;
$action['start_seq_no'] /= 2;
$action['end_seq_no'] /= 2;
$this->API->logger->logger('Resending messages for '.$this, Logger::WARNING);
foreach ($this->outgoing as $seq => $message) {
if ($seq >= $action['start_seq_no'] && $seq <= $action['end_seq_no']) {
$this->API->methodCallAsyncRead('messages.sendEncrypted', ['peer' => $this->id, 'message' => $message]);
}
}
return;
default:
$this->API->saveUpdate($update);
}
return;
}
if ($decryptedMessage['_'] === 'decryptedMessageLayer') {
if (($this->checkSecretOutSeqNo($decryptedMessage['out_seq_no']))
&& ($this->checkSecretInSeqNo($decryptedMessage['in_seq_no']))) {
$this->in_seq_no++;
if ($decryptedMessage['layer'] >= 17 && $decryptedMessage['layer'] !== $this->layer) {
$this->layer = $decryptedMessage['layer'];
if ($decryptedMessage['layer'] >= 17 && \time() - $this->public->created > 15) {
$this->notifyLayer();
}
}
$update['message']['decrypted_message'] = $decryptedMessage['message'];
$this->handleDecryptedUpdate($update);
}
return;
}
throw new ResponseException('Unrecognized decrypted message received: '.\var_export($update, true));
}
/**
* Handle encrypted update.
*
* @internal
*/
public function handleEncryptedUpdate(array $message): bool
{
$message['message']['bytes'] = (string) $message['message']['bytes'];
$auth_key_id = \substr($message['message']['bytes'], 0, 8);
$old = false;
if ($auth_key_id !== $this->key['fingerprint']) {
if (isset($this->oldKey['fingerprint'])) {
if ($auth_key_id !== $this->oldKey['fingerprint']) {
$this->discard();
throw new SecurityException('Key fingerprint mismatch');
}
$old = true;
} else {
$this->discard();
throw new SecurityException('Key fingerprint mismatch');
}
}
$message_key = \substr($message['message']['bytes'], 8, 16);
$encrypted_data = \substr($message['message']['bytes'], 24);
if ($this->mtproto === 2) {
$this->API->logger->logger('Trying MTProto v2 decryption for '.$this.'...', Logger::NOTICE);
try {
$message_data = $this->tryMTProtoV2Decrypt($message_key, $old, $encrypted_data);
$this->API->logger->logger('MTProto v2 decryption OK for '.$this.'...', Logger::NOTICE);
} catch (SecurityException $e) {
$this->API->logger->logger('MTProto v2 decryption failed with message '.$e->getMessage().', trying MTProto v1 decryption for '.$this.'...', Logger::NOTICE);
$message_data = $this->tryMTProtoV1Decrypt($message_key, $old, $encrypted_data);
$this->API->logger->logger('MTProto v1 decryption OK for '.$this.'...', Logger::NOTICE);
$this->mtproto = 1;
}
} else {
$this->API->logger->logger('Trying MTProto v1 decryption for '.$this.'...', Logger::NOTICE);
try {
$message_data = $this->tryMTProtoV1Decrypt($message_key, $old, $encrypted_data);
$this->API->logger->logger('MTProto v1 decryption OK for '.$this.'...', Logger::NOTICE);
} catch (SecurityException $e) {
$this->API->logger->logger('MTProto v1 decryption failed with message '.$e->getMessage().', trying MTProto v2 decryption for '.$this.'...', Logger::NOTICE);
$message_data = $this->tryMTProtoV2Decrypt($message_key, $old, $encrypted_data);
$this->API->logger->logger('MTProto v2 decryption OK for '.$this.'...', Logger::NOTICE);
$this->mtproto = 2;
}
}
$deserialized = $this->API->getTL()->deserialize($message_data, ['type' => '']);
$this->ttr--;
if (($this->ttr <= 0 || \time() - $this->updated > 7 * 24 * 60 * 60) && $this->rekeyState === RekeyState::IDLE) {
$this->rekey($message['message']['chat_id']);
}
unset($message['message']['bytes']);
$message['message']['decrypted_message'] = $deserialized;
$this->incoming[$this->in_seq_no] = $message['message'];
$this->handleDecryptedUpdate($message);
return true;
}
private function tryMTProtoV1Decrypt(string $message_key, bool $old, string $encrypted_data): string
{
[$aes_key, $aes_iv] = Crypt::oldKdf($message_key, ($old ? $this->oldKey : $this->key)['auth_key'], true);
$decrypted_data = Crypt::igeDecrypt($encrypted_data, $aes_key, $aes_iv);
$message_data_length = \unpack('V', \substr($decrypted_data, 0, 4))[1];
$message_data = \substr($decrypted_data, 4, $message_data_length);
if ($message_data_length > \strlen($decrypted_data)) {
throw new SecurityException('message_data_length is too big');
}
if ($message_key != \substr(\sha1(\substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) {
throw new SecurityException('Msg_key mismatch');
}
if (\strlen($decrypted_data) - 4 - $message_data_length > 15) {
throw new SecurityException('difference between message_data_length and the length of the remaining decrypted buffer is too big');
}
if (\strlen($decrypted_data) % 16 != 0) {
throw new SecurityException("Length of decrypted data is not divisible by 16");
}
return $message_data;
}
private function tryMTProtoV2Decrypt(string $message_key, bool $old, string $encrypted_data): string
{
$key = ($old ? $this->oldKey : $this->key)['auth_key'];
[$aes_key, $aes_iv] = Crypt::kdf($message_key, $key, !$this->public->creator);
$decrypted_data = Crypt::igeDecrypt($encrypted_data, $aes_key, $aes_iv);
if ($message_key != \substr(\hash('sha256', \substr($key, 88 + ($this->public->creator ? 8 : 0), 32).$decrypted_data, true), 8, 16)) {
throw new SecurityException('Msg_key mismatch');
}
$message_data_length = \unpack('V', \substr($decrypted_data, 0, 4))[1];
$message_data = \substr($decrypted_data, 4, $message_data_length);
if ($message_data_length > \strlen($decrypted_data)) {
throw new SecurityException('message_data_length is too big');
}
if (\strlen($decrypted_data) - 4 - $message_data_length < 12) {
throw new SecurityException('padding is too small');
}
if (\strlen($decrypted_data) - 4 - $message_data_length > 1024) {
throw new SecurityException('padding is too big');
}
if (\strlen($decrypted_data) % 16 != 0) {
throw new SecurityException("Length of decrypted data is not divisible by 16");
}
return $message_data;
}
private function checkSecretInSeqNo(int $seqno): bool
{
$seqno = ($seqno - $this->out_seq_no_x) / 2;
$last = 0;
foreach ($this->incoming as $message) {
if (isset($message['decrypted_message']['in_seq_no'])) {
if (($message['decrypted_message']['in_seq_no'] - $this->out_seq_no_x) / 2 < $last) {
$this->API->logger->logger("Discarding $this, in_seq_no is not increasing", Logger::LEVEL_FATAL);
$this->discard();
throw new SecurityException('in_seq_no is not increasing');
}
$last = ($message['decrypted_message']['in_seq_no'] - $this->out_seq_no_x) / 2;
}
}
if ($seqno > $this->out_seq_no + 1) {
$this->API->logger->logger("Discarding $this, in_seq_no is too big", Logger::LEVEL_FATAL);
$this->discard();
throw new SecurityException('in_seq_no is too big');
}
return true;
}
private function checkSecretOutSeqNo(int $seqno): bool
{
$seqno = ($seqno - $this->in_seq_no_x) / 2;
$C = 0;
foreach ($this->incoming as $message) {
if (isset($message['decrypted_message']['out_seq_no']) && $C < $this->in_seq_no) {
$temp = ($message['decrypted_message']['out_seq_no'] - $this->in_seq_no_x) / 2;
if ($temp !== $C) {
$this->API->logger->logger("Discarding $this, out_seq_no hole: should be $C, is $temp", Logger::LEVEL_FATAL);
$this->discard();
throw new SecurityException("out_seq_no hole: should be $C, is $temp");
}
$C++;
}
}
//$this->API->logger->logger($C, $seqno);
if ($seqno < $C) {
// <= C
$this->API->logger->logger('WARNING: dropping repeated message with seqno '.$seqno);
return false;
}
if ($seqno > $C) {
// > C+1
$this->API->logger->logger("Discarding $this, out_seq_no gap detected: ($seqno > $C)", Logger::LEVEL_FATAL);
$this->discard();
throw new SecurityException('WARNING: out_seq_no gap detected ('.$seqno.' > '.$C.')!');
}
return true;
}
private function generateSecretInSeqNo(): int
{
return $this->layer > 8 ? $this->in_seq_no * 2 + $this->in_seq_no_x : -1;
}
private function generateSecretOutSeqNo(): int
{
return $this->layer > 8 ? $this->out_seq_no * 2 + $this->out_seq_no_x : -1;
}
public function __toString(): string
{
return "secret chat {$this->id}";
}
}

View File

@ -1,91 +0,0 @@
<?php
declare(strict_types=1);
/**
* SeqNoHandler module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2023 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;
use danog\MadelineProto\Logger;
use danog\MadelineProto\SecurityException;
/**
* Manages sequence numbers.
*
* @internal
*/
trait SeqNoHandler
{
private function checkSecretInSeqNo($chat_id, $seqno)
{
$seqno = ($seqno - $this->secret_chats[$chat_id]['out_seq_no_x']) / 2;
$last = 0;
foreach ($this->secret_chats[$chat_id]['incoming'] as $message) {
if (isset($message['decrypted_message']['in_seq_no'])) {
if (($message['decrypted_message']['in_seq_no'] - $this->secret_chats[$chat_id]['out_seq_no_x']) / 2 < $last) {
$this->logger->logger("Discarding secret chat $chat_id, in_seq_no is not increasing", Logger::LEVEL_FATAL);
$this->discardSecretChat($chat_id);
throw new SecurityException('in_seq_no is not increasing');
}
$last = ($message['decrypted_message']['in_seq_no'] - $this->secret_chats[$chat_id]['out_seq_no_x']) / 2;
}
}
if ($seqno > $this->secret_chats[$chat_id]['out_seq_no'] + 1) {
$this->logger->logger("Discarding secret chat $chat_id, in_seq_no is too big", Logger::LEVEL_FATAL);
$this->discardSecretChat($chat_id);
throw new SecurityException('in_seq_no is too big');
}
return true;
}
private function checkSecretOutSeqNo($chat_id, $seqno)
{
$seqno = ($seqno - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2;
$C = 0;
foreach ($this->secret_chats[$chat_id]['incoming'] as $message) {
if (isset($message['decrypted_message']['out_seq_no']) && $C < $this->secret_chats[$chat_id]['in_seq_no']) {
$temp = ($message['decrypted_message']['out_seq_no'] - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2;
if ($temp !== $C) {
$this->logger->logger("Discarding secret chat $chat_id, out_seq_no hole: should be $C, is $temp", Logger::LEVEL_FATAL);
$this->discardSecretChat($chat_id);
throw new SecurityException("out_seq_no hole: should be $C, is $temp");
}
$C++;
}
}
//$this->logger->logger($C, $seqno);
if ($seqno < $C) {
// <= C
$this->logger->logger('WARNING: dropping repeated message with seqno '.$seqno);
return false;
}
if ($seqno > $C) {
// > C+1
$this->logger->logger("Discarding secret chat $chat_id, out_seq_no gap detected: ($seqno > $C)", Logger::LEVEL_FATAL);
$this->discardSecretChat($chat_id);
throw new SecurityException('WARNING: out_seq_no gap detected ('.$seqno.' > '.$C.')!');
}
return true;
}
private function generateSecretInSeqNo($chat)
{
return $this->secret_chats[$chat]['layer'] > 8 ? $this->secret_chats[$chat]['in_seq_no'] * 2 + $this->secret_chats[$chat]['in_seq_no_x'] : -1;
}
private function generateSecretOutSeqNo($chat)
{
return $this->secret_chats[$chat]['layer'] > 8 ? $this->secret_chats[$chat]['out_seq_no'] * 2 + $this->secret_chats[$chat]['out_seq_no_x'] : -1;
}
}

View File

@ -37,11 +37,6 @@ final class RPC extends SettingsAbstract
*/
protected int $floodTimeout = 30;
/**
* Maximum number of message IDs to consider when using call queues.
*/
protected int $limitCallQueue = 100;
/**
* Encode payload with GZIP if bigger than.
*/
@ -109,26 +104,6 @@ final class RPC extends SettingsAbstract
return $this;
}
/**
* Get maximum number of messages to consider when using call queues.
*/
public function getLimitCallQueue(): int
{
return $this->limitCallQueue;
}
/**
* Set maximum number of messages to consider when using call queues.
*
* @param int $limitCallQueue Maximum number of messages to consider when using call queues
*/
public function setLimitCallQueue(int $limitCallQueue): self
{
$this->limitCallQueue = $limitCallQueue;
return $this;
}
/**
* Get encode payload with GZIP if bigger than.
*/

View File

@ -666,10 +666,6 @@ final class TL implements TLInterface
$serialized .= $this->serializeObject(['type' => 'bytes'], Tools::random(15 + 4 * Tools::randomInt(modulus: 3)), 'random_bytes');
continue;
}
if ($current_argument['name'] === 'data' && isset($tl['method']) && \in_array($tl['method'], ['messages.sendEncrypted', 'messages.sendEncryptedFile', 'messages.sendEncryptedService'], true) && isset($arguments['message'])) {
$serialized .= $this->serializeObject($current_argument, $this->API->encryptSecretMessage($arguments['peer']['chat_id'], $arguments['message'], $arguments['queuePromise']), 'data');
continue;
}
if ($current_argument['name'] === 'random_id') {
switch ($current_argument['type']) {
case 'long':

View File

@ -42,7 +42,7 @@ trait TLParams
$param['type'] = ($mtproto && $param['type'] === 'Message' ? 'MT' : '').$param['type'];
$param['type'] = $mtproto && $param['type'] === '%Message' ? '%MTMessage' : $param['type'];
if (\in_array($param['name'], ['key_fingerprint', 'server_salt', 'new_server_salt', 'ping_id', 'exchange_id'], true) && $param['type'] === 'long') {
if (\in_array($param['name'], ['key_fingerprint', 'server_salt', 'new_server_salt', 'ping_id'], true) && $param['type'] === 'long') {
$param['type'] = 'strlong';
} elseif (\in_array($param['name'], ['peer_tag', 'file_token', 'cdn_key', 'cdn_iv', 'encryption_key', 'encryption_iv'], true)) {
$param['type'] = 'string';