From 173ae746a296a1815e79614e6179255f00379c5d Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Tue, 19 Nov 2019 13:10:51 +0300
Subject: [PATCH] First version of working through temp keys.

---
 Telegram/SourceFiles/mtproto/connection.cpp   | 258 +++---
 Telegram/SourceFiles/mtproto/connection.h     |  12 +-
 Telegram/SourceFiles/mtproto/dcenter.cpp      |  54 +-
 Telegram/SourceFiles/mtproto/dcenter.h        |  14 +-
 ..._checker.cpp => mtproto_dc_key_binder.cpp} |  85 +-
 ..._key_checker.h => mtproto_dc_key_binder.h} |  26 +-
 .../details/mtproto_dc_key_creator.cpp        | 774 +++++++++---------
 .../mtproto/details/mtproto_dc_key_creator.h  |  75 +-
 Telegram/SourceFiles/mtproto/mtp_instance.cpp | 137 ++--
 Telegram/SourceFiles/mtproto/mtp_instance.h   |   5 +-
 .../SourceFiles/mtproto/mtproto_auth_key.cpp  |  10 +
 .../SourceFiles/mtproto/mtproto_auth_key.h    |   4 +
 Telegram/SourceFiles/mtproto/session.cpp      |  75 +-
 Telegram/SourceFiles/mtproto/session.h        |  18 +-
 Telegram/gyp/lib_mtproto.gyp                  |   4 +-
 15 files changed, 883 insertions(+), 668 deletions(-)
 rename Telegram/SourceFiles/mtproto/details/{mtproto_dc_key_checker.cpp => mtproto_dc_key_binder.cpp} (67%)
 rename Telegram/SourceFiles/mtproto/details/{mtproto_dc_key_checker.h => mtproto_dc_key_binder.h} (58%)

diff --git a/Telegram/SourceFiles/mtproto/connection.cpp b/Telegram/SourceFiles/mtproto/connection.cpp
index 1ab49e795..64a766075 100644
--- a/Telegram/SourceFiles/mtproto/connection.cpp
+++ b/Telegram/SourceFiles/mtproto/connection.cpp
@@ -7,8 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "mtproto/connection.h"
 
+#include "mtproto/details/mtproto_dc_key_binder.h"
 #include "mtproto/details/mtproto_dc_key_creator.h"
-#include "mtproto/details/mtproto_dc_key_checker.h"
 #include "mtproto/details/mtproto_dump_to_text.h"
 #include "mtproto/session.h"
 #include "mtproto/mtproto_rsa_public_key.h"
@@ -23,10 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "base/qthelp_url.h"
 #include "base/unixtime.h"
 
-#ifdef small
-#undef small
-#endif // small
-
 namespace MTP {
 namespace internal {
 namespace {
@@ -42,6 +38,7 @@ constexpr auto kPingDelayDisconnect = 60;
 constexpr auto kPingSendAfter = 30 * crl::time(1000);
 constexpr auto kPingSendAfterForce = 45 * crl::time(1000);
 constexpr auto kCheckKeyExpiresIn = TimeId(3600);
+constexpr auto kTemporaryExpiresIn = TimeId(10);
 constexpr auto kTestModeDcIdShift = 10000;
 
 // If we can't connect for this time we will ask _instance to update config.
@@ -248,6 +245,7 @@ ConnectionPrivate::ConnectionPrivate(
 
 ConnectionPrivate::~ConnectionPrivate() {
 	clearKeyCreatorOnFail();
+	cancelKeyBinder();
 
 	Expects(_finished);
 	Expects(!_connection);
@@ -548,12 +546,20 @@ void ConnectionPrivate::tryToSend() {
 		return;
 	}
 
-	auto needsLayer = !_sessionData->connectionInited();
-	auto state = getState();
-	auto sendOnlyFirstPing = (state != ConnectedState);
+	const auto needsLayer = !_sessionData->connectionInited();
+	const auto state = getState();
+	const auto sendOnlyFirstPing = (state != ConnectedState);
+	const auto sendAll = !sendOnlyFirstPing && !_keyBinder;
+	const auto isMainSession = (GetDcIdShift(_shiftedDcId) == 0);
 	if (sendOnlyFirstPing && !_pingIdToSend) {
 		DEBUG_LOG(("MTP Info: dc %1 not sending, waiting for Connected state, state: %2").arg(_shiftedDcId).arg(state));
 		return; // just do nothing, if is not connected yet
+	} else if (isMainSession
+		&& !sendOnlyFirstPing
+		&& !_pingIdToSend
+		&& !_pingId
+		&& _pingSendAt <= crl::now()) {
+		_pingIdToSend = openssl::RandomValue<mtpPingId>();
 	}
 
 	auto pingRequest = SecureRequest();
@@ -561,34 +567,31 @@ void ConnectionPrivate::tryToSend() {
 	auto resendRequest = SecureRequest();
 	auto stateRequest = SecureRequest();
 	auto httpWaitRequest = SecureRequest();
-	auto checkDcKeyRequest = SecureRequest();
-	if (_shiftedDcId == BareDcId(_shiftedDcId)) { // main session
-		if (!sendOnlyFirstPing && !_pingIdToSend && !_pingId && _pingSendAt <= crl::now()) {
-			_pingIdToSend = rand_value<mtpPingId>();
-		}
-	}
+	auto bindDcKeyRequest = SecureRequest();
 	if (_pingIdToSend) {
-		if (sendOnlyFirstPing || _shiftedDcId != BareDcId(_shiftedDcId)) {
+		if (sendOnlyFirstPing || !isMainSession) {
+			DEBUG_LOG(("MTP Info: sending ping, ping_id: %1"
+				).arg(_pingIdToSend));
 			pingRequest = SecureRequest::Serialize(MTPPing(
 				MTP_long(_pingIdToSend)
 			));
-			DEBUG_LOG(("MTP Info: sending ping, ping_id: %1"
-				).arg(_pingIdToSend));
 		} else {
+			DEBUG_LOG(("MTP Info: sending ping_delay_disconnect, "
+				"ping_id: %1").arg(_pingIdToSend));
 			pingRequest = SecureRequest::Serialize(MTPPing_delay_disconnect(
 				MTP_long(_pingIdToSend),
 				MTP_int(kPingDelayDisconnect)));
-			DEBUG_LOG(("MTP Info: sending ping_delay_disconnect, "
-				"ping_id: %1").arg(_pingIdToSend));
-		}
-
-		_pingSendAt = pingRequest->msDate + kPingSendAfter;
-		if (_shiftedDcId == BareDcId(_shiftedDcId) && !sendOnlyFirstPing) { // main session
 			_pingSender.callOnce(kPingSendAfterForce);
 		}
+		_pingSendAt = pingRequest->msDate + kPingSendAfter;
 		_pingId = base::take(_pingIdToSend);
+	} else if (!sendAll) {
+		DEBUG_LOG(("MTP Info: dc %1 sending only service or bind."
+			).arg(_shiftedDcId));
 	} else {
-		DEBUG_LOG(("MTP Info: dc %1 trying to send after ping, state: %2").arg(_shiftedDcId).arg(state));
+		DEBUG_LOG(("MTP Info: dc %1 trying to send after ping, state: %2"
+			).arg(_shiftedDcId
+			).arg(state));
 	}
 
 	if (!sendOnlyFirstPing) {
@@ -625,23 +628,34 @@ void ConnectionPrivate::tryToSend() {
 			httpWaitRequest = SecureRequest::Serialize(MTPHttpWait(
 				MTP_http_wait(MTP_int(100), MTP_int(30), MTP_int(25000))));
 		}
-		if (!_keyChecker) {
-			if (const auto &keyForCheck = _sessionData->getKeyForCheck()) {
-				_keyChecker = std::make_unique<details::DcKeyChecker>(
-					_instance,
-					_shiftedDcId,
-					keyForCheck);
-				checkDcKeyRequest = _keyChecker->prepareRequest(
-					_key,
-					_sessionData->getSessionId());
+		if (_keyBinder && !_keyBinder->requested()) {
+			bindDcKeyRequest = _keyBinder->prepareRequest(
+				_temporaryKey,
+				_sessionData->getSessionId());
 
-				// This is a special request with msgId used inside the message
-				// body, so it is prepared already with a msgId and we place
-				// seqNo for it manually here.
-				checkDcKeyRequest.setSeqNo(
-					_sessionData->nextRequestSeqNumber(
-						checkDcKeyRequest.needAck()));
-			}
+			// This is a special request with msgId used inside the message
+			// body, so it is prepared already with a msgId and we place
+			// seqNo for it manually here.
+			bindDcKeyRequest.setSeqNo(
+				_sessionData->nextRequestSeqNumber(
+					bindDcKeyRequest.needAck()));
+		//} else if (!_keyChecker) {
+		//	if (const auto &keyForCheck = _sessionData->getKeyForCheck()) {
+		//		_keyChecker = std::make_unique<details::DcKeyChecker>(
+		//			_instance,
+		//			_shiftedDcId,
+		//			keyForCheck);
+		//		bindDcKeyRequest = _keyChecker->prepareRequest(
+		//			_temporaryKey,
+		//			_sessionData->getSessionId());
+
+		//		// This is a special request with msgId used inside the message
+		//		// body, so it is prepared already with a msgId and we place
+		//		// seqNo for it manually here.
+		//		bindDcKeyRequest.setSeqNo(
+		//			_sessionData->nextRequestSeqNumber(
+		//				bindDcKeyRequest.needAck()));
+		//	}
 		}
 	}
 
@@ -696,11 +710,14 @@ void ConnectionPrivate::tryToSend() {
 		QWriteLocker locker1(_sessionData->toSendMutex());
 
 		auto toSendDummy = PreRequestMap();
-		auto &toSend = sendOnlyFirstPing
-			? toSendDummy
-			: _sessionData->toSendMap();
-		if (sendOnlyFirstPing) {
+		auto &toSend = sendAll
+			? _sessionData->toSendMap()
+			: toSendDummy;
+		if (!sendAll) {
 			locker1.unlock();
+		} else {
+			int time = crl::now();
+			int now = crl::now();
 		}
 
 		uint32 toSendCount = toSend.size();
@@ -709,7 +726,7 @@ void ConnectionPrivate::tryToSend() {
 		if (resendRequest) ++toSendCount;
 		if (stateRequest) ++toSendCount;
 		if (httpWaitRequest) ++toSendCount;
-		if (checkDcKeyRequest) ++toSendCount;
+		if (bindDcKeyRequest) ++toSendCount;
 
 		if (!toSendCount) {
 			return; // nothing to send
@@ -725,12 +742,12 @@ void ConnectionPrivate::tryToSend() {
 			? stateRequest
 			: httpWaitRequest
 			? httpWaitRequest
-			: checkDcKeyRequest
-			? checkDcKeyRequest
+			: bindDcKeyRequest
+			? bindDcKeyRequest
 			: toSend.cbegin().value();
 		if (toSendCount == 1 && first->msDate > 0) { // if can send without container
 			toSendRequest = first;
-			if (!sendOnlyFirstPing) {
+			if (sendAll) {
 				toSend.clear();
 				locker1.unlock();
 			}
@@ -753,9 +770,7 @@ void ConnectionPrivate::tryToSend() {
 					auto &haveSent = _sessionData->haveSentMap();
 					haveSent.insert(msgId, toSendRequest);
 
-					if (needsLayer && !toSendRequest->needsLayer) {
-						needsLayer = false;
-					}
+					const auto wrapLayer = needsLayer && toSendRequest->needsLayer;
 					if (toSendRequest->after) {
 						const auto toSendSize = tl::count_length(toSendRequest) >> 2;
 						auto wrappedRequest = SecureRequest::Prepare(
@@ -766,7 +781,7 @@ void ConnectionPrivate::tryToSend() {
 						wrapInvokeAfter(wrappedRequest, toSendRequest, haveSent);
 						toSendRequest = std::move(wrappedRequest);
 					}
-					if (needsLayer) {
+					if (wrapLayer) {
 						const auto noWrapSize = (tl::count_length(toSendRequest) >> 2);
 						const auto toSendSize = noWrapSize + initSizeInInts;
 						auto wrappedRequest = SecureRequest::Prepare(toSendSize);
@@ -793,7 +808,7 @@ void ConnectionPrivate::tryToSend() {
 			if (resendRequest) containerSize += resendRequest.messageSize();
 			if (stateRequest) containerSize += stateRequest.messageSize();
 			if (httpWaitRequest) containerSize += httpWaitRequest.messageSize();
-			if (checkDcKeyRequest) containerSize += checkDcKeyRequest.messageSize();
+			if (bindDcKeyRequest) containerSize += bindDcKeyRequest.messageSize();
 			for (auto i = toSend.begin(), e = toSend.end(); i != e; ++i) {
 				containerSize += i.value().messageSize();
 				if (needsLayer && i.value()->needsLayer) {
@@ -836,7 +851,7 @@ void ConnectionPrivate::tryToSend() {
 			if (pingRequest) {
 				_pingMsgId = placeToContainer(toSendRequest, bigMsgId, haveSentArr, pingRequest);
 				needAnyResponse = true;
-			} else if (resendRequest || stateRequest || checkDcKeyRequest) {
+			} else if (resendRequest || stateRequest || bindDcKeyRequest) {
 				needAnyResponse = true;
 			}
 			for (auto i = toSend.begin(), e = toSend.end(); i != e; ++i) {
@@ -890,7 +905,7 @@ void ConnectionPrivate::tryToSend() {
 			if (resendRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, resendRequest);
 			if (ackRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, ackRequest);
 			if (httpWaitRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, httpWaitRequest);
-			if (checkDcKeyRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, checkDcKeyRequest);
+			if (bindDcKeyRequest) placeToContainer(toSendRequest, bigMsgId, haveSentArr, bindDcKeyRequest);
 
 			mtpMsgId contMsgId = prepareToSend(toSendRequest, bigMsgId);
 			*(mtpMsgId*)(haveSentIdsWrap->data() + 4) = contMsgId;
@@ -930,7 +945,7 @@ void ConnectionPrivate::connectToServer(bool afterConfig) {
 		_sessionData->connectionOptions());
 
 	// #TODO race.
-	const auto hasKey = (_sessionData->getKey() != nullptr);
+	const auto hasKey = (_sessionData->getTemporaryKey() != nullptr);
 
 	const auto bareDc = BareDcId(_shiftedDcId);
 	_dcType = _instance->dcOptions()->dcType(_shiftedDcId);
@@ -1236,9 +1251,9 @@ void ConnectionPrivate::handleReceived() {
 		auto msgKey = *(MTPint128*)(ints + 2);
 
 #ifdef TDESKTOP_MTPROTO_OLD
-		aesIgeDecrypt_oldmtp(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _key, msgKey);
+		aesIgeDecrypt_oldmtp(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _temporaryKey, msgKey);
 #else // TDESKTOP_MTPROTO_OLD
-		aesIgeDecrypt(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _key, msgKey);
+		aesIgeDecrypt(encryptedInts, decryptedBuffer.data(), encryptedBytesCount, _temporaryKey, msgKey);
 #endif // TDESKTOP_MTPROTO_OLD
 
 		auto decryptedInts = reinterpret_cast<const mtpPrime*>(decryptedBuffer.constData());
@@ -1285,7 +1300,7 @@ void ConnectionPrivate::handleReceived() {
 
 		SHA256_CTX msgKeyLargeContext;
 		SHA256_Init(&msgKeyLargeContext);
-		SHA256_Update(&msgKeyLargeContext, _key->partForMsgKey(false), 32);
+		SHA256_Update(&msgKeyLargeContext, _temporaryKey->partForMsgKey(false), 32);
 		SHA256_Update(&msgKeyLargeContext, decryptedInts, encryptedBytesCount);
 		SHA256_Final(sha256Buffer.data(), &msgKeyLargeContext);
 
@@ -1386,7 +1401,9 @@ void ConnectionPrivate::handleReceived() {
 
 		if (res != HandleResult::Success && res != HandleResult::Ignored) {
 			_needSessionReset = (res == HandleResult::ResetSession);
-
+			if (res == HandleResult::DestroyTemporaryKey) {
+				destroyTemporaryKey();
+			}
 			return restart();
 		}
 		_retryTimeout = 1; // reset restart() timer
@@ -1884,15 +1901,37 @@ ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPr
 			response.resize(end - from);
 			memcpy(response.data(), from, (end - from) * sizeof(mtpPrime));
 		}
-		if (typeId != mtpc_rpc_error) {
+		if (typeId == mtpc_rpc_error) {
+			if (DcKeyBinder::IsDestroyedTemporaryKeyError(response)) {
+				return HandleResult::DestroyTemporaryKey;
+			}
+		} else {
 			// An error could be some RPC_CALL_FAIL or other error inside
 			// the initConnection, so we're not sure yet that it was inited.
 			// Wait till a good response is received.
 			_sessionData->notifyConnectionInited(*_connectionOptions);
 		}
 
-		if (_keyChecker && _keyChecker->handleResponse(reqMsgId, response)) {
-			return HandleResult::Success;
+		if (_keyBinder) {
+			const auto result = _keyBinder->handleResponse(
+				reqMsgId,
+				response);
+			if (result == DcKeyBindState::Success) {
+				_sessionData->releaseKeyCreationOnDone(
+					_temporaryKey,
+					base::take(_keyBinder)->persistentKey());
+				_sessionData->queueNeedToResumeAndSend();
+				return HandleResult::Success;
+			} else if (result == DcKeyBindState::Failed
+				|| result == DcKeyBindState::DefinitelyDestroyed) {
+				// #TODO maybe destroy persistent key
+				// crl::on_main(
+				//   _keyBinder->persistentKey()->setLastCheckTime(crl::now());
+				//   instance->keyDestroyedOnServer(BareDcId(shiftedDcId), base::take(_keyBinder)->persistentKey()->keyId());
+				// )
+				_sessionData->queueNeedToResumeAndSend();
+				return HandleResult::Success;
+			}
 		}
 		auto requestId = wasSent(reqMsgId.v);
 		if (requestId && requestId != mtpRequestId(0xFFFFFFFF)) {
@@ -2341,17 +2380,17 @@ void ConnectionPrivate::checkAuthKey() {
 }
 
 void ConnectionPrivate::updateAuthKey() {
-	if (_keyCreator) {
+	if (_keyCreator || _keyBinder) {
 		return;
 	}
 
 	DEBUG_LOG(("AuthKey Info: Connection updating key from Session, dc %1").arg(_shiftedDcId));
-	applyAuthKey(_sessionData->getKey());
+	applyAuthKey(_sessionData->getTemporaryKey());
 }
 
-void ConnectionPrivate::applyAuthKey(AuthKeyPtr &&key) {
-	_key = std::move(key);
-	const auto newKeyId = _key ? _key->keyId() : 0;
+void ConnectionPrivate::applyAuthKey(AuthKeyPtr &&temporaryKey) {
+	_temporaryKey = std::move(temporaryKey);
+	const auto newKeyId = _temporaryKey ? _temporaryKey->keyId() : 0;
 	if (newKeyId) {
 		if (_keyId == newKeyId) {
 			return;
@@ -2389,19 +2428,39 @@ void ConnectionPrivate::applyAuthKey(AuthKeyPtr &&key) {
 }
 
 void ConnectionPrivate::createDcKey() {
+	Expects(_keyCreator == nullptr);
+	Expects(_keyBinder == nullptr);
+
 	using Result = DcKeyCreator::Result;
 	using Error = DcKeyCreator::Error;
 	auto delegate = DcKeyCreator::Delegate();
 	delegate.done = [=](base::expected<Result, Error> result) {
 		if (result) {
-			DEBUG_LOG(("AuthKey Info: auth key gen succeed, id: %1, server salt: %2").arg(result->key->keyId()).arg(result->serverSalt));
+			DEBUG_LOG(("AuthKey Info: auth key gen succeed, "
+				"ids: (%1, %2) server salts: (%3, %4)"
+				).arg(result->temporaryKey
+					? result->temporaryKey->keyId()
+					: 0
+				).arg(result->persistentKey
+					? result->persistentKey->keyId()
+					: 0
+				).arg(result->temporaryServerSalt
+				).arg(result->persistentServerSalt));
 
-			_sessionData->setSalt(result->serverSalt);
+			_sessionData->setSalt(result->temporaryServerSalt);
 			_sessionData->clearForNewKey(_instance);
 
+			auto key = result->persistentKey
+				? std::move(result->persistentKey)
+				: _sessionData->getPersistentKey();
+			if (!key) {
+				restart();
+			}
 			_keyCreator = nullptr;
-			_sessionData->releaseKeyCreationOnDone(result->key);
-			applyAuthKey(std::move(result->key));
+			_keyBinder = std::make_unique<DcKeyBinder>(std::move(key));
+			result->temporaryKey->setExpiresAt(
+				base::unixtime::now() + kTemporaryExpiresIn);
+			applyAuthKey(std::move(result->temporaryKey));
 			return;
 		}
 		clearKeyCreatorOnFail();
@@ -2417,16 +2476,23 @@ void ConnectionPrivate::createDcKey() {
 			restart();
 		}
 	};
-	const auto expireIn = (GetDcIdShift(_shiftedDcId) == kCheckKeyDcShift)
-		? kCheckKeyExpiresIn
-		: TimeId(0);
+	delegate.sentSome = [=](uint64 size) {
+		onSentSome(size);
+	};
+	delegate.receivedSome = [=] {
+		onReceivedSome();
+	};
+//	const auto check = (GetDcIdShift(_shiftedDcId) == kCheckKeyDcShift); // #TODO remove kCheckKeyDcShift
+	auto request = DcKeyCreator::Request();
+	request.persistentNeeded = !_sessionData->getPersistentKey();
+	request.temporaryExpiresIn = kTemporaryExpiresIn;
 	_keyCreator = std::make_unique<DcKeyCreator>(
 		BareDcId(_shiftedDcId),
 		getProtocolDcId(),
 		_connection.get(),
 		_instance->dcOptions(),
 		std::move(delegate),
-		expireIn);
+		request);
 }
 
 void ConnectionPrivate::authKeyChecked() {
@@ -2471,28 +2537,26 @@ void ConnectionPrivate::handleError(int errorCode) {
 	_waitForConnectedTimer.cancel();
 
 	if (errorCode == -404) {
-		if (_dcType == DcType::Cdn && !_instance->isKeysDestroyer()) {
-			LOG(("MTP Info: -404 error received in CDN dc %1, assuming it was destroyed, recreating.").arg(_shiftedDcId));
-			destroyCdnKey();
-			return restart();
-		} else {
-			LOG(("MTP Info: -404 error received, informing instance."));
+		if (_instance->isKeysDestroyer()) {
+			LOG(("MTP Info: -404 error received in destroyer %1, assuming key was destroyed.").arg(_shiftedDcId));
 			_instance->checkIfKeyWasDestroyed(_shiftedDcId);
-			if (_instance->isKeysDestroyer()) {
-				return;
-			}
+			return;
+		} else if (_temporaryKey) {
+			LOG(("MTP Info: -404 error received in %1 with temporary key, assuming it was destroyed.").arg(_shiftedDcId));
+			destroyTemporaryKey();
 		}
 	}
 	MTP_LOG(_shiftedDcId, ("Restarting after error in connection, error code: %1...").arg(errorCode));
 	return restart();
 }
 
-void ConnectionPrivate::destroyCdnKey() {
-	if (_key) {
-		_sessionData->destroyCdnKey(_keyId);
+void ConnectionPrivate::destroyTemporaryKey() {
+	cancelKeyBinder();
+	if (_temporaryKey) {
+		_sessionData->destroyTemporaryKey(_temporaryKey->keyId());
 	}
-	_key = nullptr;
-	_keyId = 0;
+	_needSessionReset = true;
+	applyAuthKey(nullptr);
 }
 
 bool ConnectionPrivate::sendSecureRequest(
@@ -2542,7 +2606,7 @@ bool ConnectionPrivate::sendSecureRequest(
 		request->constData(),
 		&packet[prefix],
 		fullSize * sizeof(mtpPrime),
-		_key,
+		_temporaryKey,
 		msgKey);
 #else // TDESKTOP_MTPROTO_OLD
 	uchar encryptedSHA256[32];
@@ -2550,7 +2614,7 @@ bool ConnectionPrivate::sendSecureRequest(
 
 	SHA256_CTX msgKeyLargeContext;
 	SHA256_Init(&msgKeyLargeContext);
-	SHA256_Update(&msgKeyLargeContext, _key->partForMsgKey(true), 32);
+	SHA256_Update(&msgKeyLargeContext, _temporaryKey->partForMsgKey(true), 32);
 	SHA256_Update(&msgKeyLargeContext, request->constData(), fullSize * sizeof(mtpPrime));
 	SHA256_Final(encryptedSHA256, &msgKeyLargeContext);
 
@@ -2562,7 +2626,7 @@ bool ConnectionPrivate::sendSecureRequest(
 		request->constData(),
 		&packet[prefix],
 		fullSize * sizeof(mtpPrime),
-		_key,
+		_temporaryKey,
 		msgKey);
 #endif // TDESKTOP_MTPROTO_OLD
 
@@ -2613,6 +2677,14 @@ void ConnectionPrivate::clearKeyCreatorOnFail() {
 	_sessionData->releaseKeyCreationOnFail();
 }
 
+void ConnectionPrivate::cancelKeyBinder() {
+	if (!_keyBinder) {
+		return;
+	}
+	_keyBinder = nullptr;
+	_sessionData->releaseKeyCreationOnFail();
+}
+
 void ConnectionPrivate::stop() {
 }
 
diff --git a/Telegram/SourceFiles/mtproto/connection.h b/Telegram/SourceFiles/mtproto/connection.h
index 78c2217a8..ed6950adf 100644
--- a/Telegram/SourceFiles/mtproto/connection.h
+++ b/Telegram/SourceFiles/mtproto/connection.h
@@ -17,7 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace MTP {
 namespace details {
 class DcKeyCreator;
-class DcKeyChecker;
+class DcKeyBinder;
 } // namespace details
 
 // How much time to wait for some more requests, when sending msg acks.
@@ -103,6 +103,7 @@ private:
 		Ignored,
 		RestartConnection,
 		ResetSession,
+		DestroyTemporaryKey,
 		ParseError,
 	};
 
@@ -180,9 +181,10 @@ private:
 	void resetSession();
 	void checkAuthKey();
 	void authKeyChecked();
-	void destroyCdnKey();
+	void destroyTemporaryKey();
 	void clearKeyCreatorOnFail();
-	void applyAuthKey(AuthKeyPtr &&key);
+	void cancelKeyBinder();
+	void applyAuthKey(AuthKeyPtr &&temporaryKey);
 
 	const not_null<Instance*> _instance;
 	DcType _dcType = DcType::Regular;
@@ -224,13 +226,13 @@ private:
 	bool _restarted = false;
 	bool _finished = false;
 
-	AuthKeyPtr _key;
+	AuthKeyPtr _temporaryKey;
 	uint64 _keyId = 0;
 	std::shared_ptr<SessionData> _sessionData;
 	std::unique_ptr<ConnectionOptions> _connectionOptions;
 
 	std::unique_ptr<details::DcKeyCreator> _keyCreator;
-	std::unique_ptr<details::DcKeyChecker> _keyChecker;
+	std::unique_ptr<details::DcKeyBinder> _keyBinder;
 
 };
 
diff --git a/Telegram/SourceFiles/mtproto/dcenter.cpp b/Telegram/SourceFiles/mtproto/dcenter.cpp
index fa2f10d8a..22e212698 100644
--- a/Telegram/SourceFiles/mtproto/dcenter.cpp
+++ b/Telegram/SourceFiles/mtproto/dcenter.cpp
@@ -25,34 +25,40 @@ constexpr auto kSpecialRequestTimeoutMs = 6000; // 4 seconds timeout for it to w
 
 Dcenter::Dcenter(DcId dcId, AuthKeyPtr &&key)
 : _id(dcId)
-, _key(std::move(key)) {
+, _persistentKey(std::move(key)) {
 }
 
 DcId Dcenter::id() const {
 	return _id;
 }
 
-AuthKeyPtr Dcenter::getKey() const {
+AuthKeyPtr Dcenter::getTemporaryKey() const {
 	QReadLocker lock(&_mutex);
-	return _key;
+	return _temporaryKey;
 }
 
-bool Dcenter::destroyCdnKey(uint64 keyId) {
-	return destroyKey(keyId);
+AuthKeyPtr Dcenter::getPersistentKey() const {
+	QReadLocker lock(&_mutex);
+	return _persistentKey;
+}
+
+bool Dcenter::destroyTemporaryKey(uint64 keyId) {
+	QWriteLocker lock(&_mutex);
+	if (!_temporaryKey || _temporaryKey->keyId() != keyId) {
+		return false;
+	}
+	_temporaryKey = nullptr;
+	_connectionInited = false;
+	return true;
 }
 
 bool Dcenter::destroyConfirmedForgottenKey(uint64 keyId) {
-	return destroyKey(keyId);
-}
-
-bool Dcenter::destroyKey(uint64 keyId) {
-	Expects(!_creatingKey || !_key);
-
 	QWriteLocker lock(&_mutex);
-	if (_key->keyId() != keyId) {
+	if (!_persistentKey || _persistentKey->keyId() != keyId) {
 		return false;
 	}
-	_key = nullptr;
+	_temporaryKey = nullptr;
+	_persistentKey = nullptr;
 	_connectionInited = false;
 	return true;
 }
@@ -69,7 +75,7 @@ void Dcenter::setConnectionInited(bool connectionInited) {
 
 bool Dcenter::acquireKeyCreation() {
 	QReadLocker lock(&_mutex);
-	if (_key != nullptr) {
+	if (_temporaryKey != nullptr) {
 		return false;
 	}
 	auto expected = false;
@@ -78,21 +84,27 @@ bool Dcenter::acquireKeyCreation() {
 
 void Dcenter::releaseKeyCreationOnFail() {
 	Expects(_creatingKey);
-	Expects(_key == nullptr);
+	Expects(_temporaryKey == nullptr);
 
 	_creatingKey = false;
 }
 
-void Dcenter::releaseKeyCreationOnDone(const AuthKeyPtr &key) {
+void Dcenter::releaseKeyCreationOnDone(
+		const AuthKeyPtr &temporaryKey,
+		const AuthKeyPtr &persistentKey) {
 	Expects(_creatingKey);
-	Expects(_key == nullptr);
+	Expects(_temporaryKey == nullptr);
 
 	QWriteLocker lock(&_mutex);
-	DEBUG_LOG(("AuthKey Info: Dcenter::releaseKeyCreationOnDone(%1), "
-		"emitting authKeyChanged, dc %2"
-		).arg(key ? key->keyId() : 0
+	DEBUG_LOG(("AuthKey Info: Dcenter::releaseKeyCreationOnDone(%1, %2), "
+		"emitting authKeyChanged, dc %3"
+		).arg(temporaryKey ? temporaryKey->keyId() : 0
+		).arg(persistentKey ? persistentKey->keyId() : 0
 		).arg(_id));
-	_key = key;
+	_temporaryKey = temporaryKey;
+	if (persistentKey) {
+		_persistentKey = persistentKey;
+	}
 	_connectionInited = false;
 	_creatingKey = false;
 }
diff --git a/Telegram/SourceFiles/mtproto/dcenter.h b/Telegram/SourceFiles/mtproto/dcenter.h
index f935c8619..96326c425 100644
--- a/Telegram/SourceFiles/mtproto/dcenter.h
+++ b/Telegram/SourceFiles/mtproto/dcenter.h
@@ -23,10 +23,13 @@ public:
 	// Thread-safe.
 	[[nodiscard]] DcId id() const;
 
-	[[nodiscard]] AuthKeyPtr getKey() const;
-	bool destroyCdnKey(uint64 keyId);
+	[[nodiscard]] AuthKeyPtr getTemporaryKey() const;
+	[[nodiscard]] AuthKeyPtr getPersistentKey() const;
+	bool destroyTemporaryKey(uint64 keyId);
 	bool destroyConfirmedForgottenKey(uint64 keyId);
-	void releaseKeyCreationOnDone(const AuthKeyPtr &key);
+	void releaseKeyCreationOnDone(
+		const AuthKeyPtr &temporaryKey,
+		const AuthKeyPtr &persistentKey);
 
 	[[nodiscard]] bool connectionInited() const;
 	void setConnectionInited(bool connectionInited = true);
@@ -35,12 +38,11 @@ public:
 	void releaseKeyCreationOnFail();
 
 private:
-	bool destroyKey(uint64 keyId);
-
 	const DcId _id = 0;
 	mutable QReadWriteLock _mutex;
 
-	AuthKeyPtr _key;
+	AuthKeyPtr _temporaryKey;
+	AuthKeyPtr _persistentKey;
 	bool _connectionInited = false;
 	std::atomic<bool> _creatingKey = false;
 
diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_checker.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.cpp
similarity index 67%
rename from Telegram/SourceFiles/mtproto/details/mtproto_dc_key_checker.cpp
rename to Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.cpp
index 81dded5d4..bd2f3686b 100644
--- a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_checker.cpp
+++ b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.cpp
@@ -5,7 +5,7 @@ the official desktop application for the Telegram messaging service.
 For license and copyright information please follow this link:
 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
-#include "mtproto/details/mtproto_dc_key_checker.h"
+#include "mtproto/details/mtproto_dc_key_binder.h"
 
 #include "mtproto/mtp_instance.h"
 #include "base/unixtime.h"
@@ -17,8 +17,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 namespace MTP::details {
 namespace {
 
-constexpr auto kBindKeyExpireTimeout = TimeId(3600);
-
 [[nodiscard]] QByteArray EncryptBindAuthKeyInner(
 		const AuthKeyPtr &persistentKey,
 		mtpMsgId realMsgId,
@@ -74,26 +72,28 @@ constexpr auto kBindKeyExpireTimeout = TimeId(3600);
 
 } // namespace
 
-DcKeyChecker::DcKeyChecker(
-	not_null<Instance*> instance,
-	ShiftedDcId shiftedDcId,
-	const AuthKeyPtr &persistentKey)
-: _instance(instance)
-, _shiftedDcId(shiftedDcId)
-, _persistentKey(persistentKey) {
+DcKeyBinder::DcKeyBinder(AuthKeyPtr &&persistentKey)
+: _persistentKey(std::move(persistentKey)) {
+	Expects(_persistentKey != nullptr);
 }
 
-SecureRequest DcKeyChecker::prepareRequest(
+bool DcKeyBinder::requested() const {
+	return _requestMsgId != 0;
+}
+
+SecureRequest DcKeyBinder::prepareRequest(
 		const AuthKeyPtr &temporaryKey,
 		uint64 sessionId) {
 	Expects(_requestMsgId == 0);
+	Expects(temporaryKey != nullptr);
+	Expects(temporaryKey->expiresAt() != 0);
 
 	const auto nonce = openssl::RandomValue<uint64>();
 	_requestMsgId = base::unixtime::mtproto_msg_id();
 	auto result = SecureRequest::Serialize(MTPauth_BindTempAuthKey(
 		MTP_long(_persistentKey->keyId()),
 		MTP_long(nonce),
-		MTP_int(kBindKeyExpireTimeout),
+		MTP_int(temporaryKey->expiresAt()),
 		MTP_bytes(EncryptBindAuthKeyInner(
 			_persistentKey,
 			_requestMsgId,
@@ -102,49 +102,56 @@ SecureRequest DcKeyChecker::prepareRequest(
 				MTP_long(temporaryKey->keyId()),
 				MTP_long(_persistentKey->keyId()),
 				MTP_long(sessionId),
-				MTP_int(kBindKeyExpireTimeout))))));
+				MTP_int(temporaryKey->expiresAt()))))));
 	result.setMsgId(_requestMsgId);
 	return result;
 }
 
-bool DcKeyChecker::handleResponse(
+DcKeyBindState DcKeyBinder::handleResponse(
 		MTPlong requestMsgId,
 		const mtpBuffer &response) {
 	Expects(!response.isEmpty());
 
 	if (!_requestMsgId || requestMsgId.v != _requestMsgId) {
-		return false;
+		return DcKeyBindState::Unknown;
 	}
+	_requestMsgId = 0;
 
-	const auto destroyed = [&] {
-		if (response[0] != mtpc_rpc_error) {
-			return false;
-		}
-		auto error = MTPRpcError();
-		auto from = response.begin();
-		const auto end = from + response.size();
-		if (!error.read(from, end)) {
-			return false;
-		}
-		return error.match([&](const MTPDrpc_error &data) {
+	auto from = response.begin();
+	const auto end = from + response.size();
+	auto error = MTPRpcError();
+	auto result = MTPBool();
+	if (response[0] == mtpc_boolTrue) {
+		return DcKeyBindState::Success;
+	} else if (response[0] == mtpc_rpc_error && error.read(from, end)) {
+		const auto destroyed = error.match([&](const MTPDrpc_error &data) {
 			return (data.verror_code().v == 400)
 				&& (data.verror_message().v == "ENCRYPTED_MESSAGE_INVALID");
 		});
-	}();
+		return destroyed
+			? DcKeyBindState::DefinitelyDestroyed
+			: DcKeyBindState::Failed;
+	} else {
+		return DcKeyBindState::Failed;
+	}
+}
 
-	const auto instance = _instance;
-	const auto shiftedDcId = _shiftedDcId;
-	const auto keyId = _persistentKey->keyId();
-	_persistentKey->setLastCheckTime(crl::now());
-	crl::on_main(instance, [=] {
-		instance->killSession(shiftedDcId);
-		if (destroyed) {
-			instance->keyDestroyedOnServer(BareDcId(shiftedDcId), keyId);
-		}
+AuthKeyPtr DcKeyBinder::persistentKey() const {
+	return _persistentKey;
+}
+
+bool DcKeyBinder::IsDestroyedTemporaryKeyError(
+		const mtpBuffer &buffer) {
+	auto from = buffer.data();
+	const auto end = from + buffer.size();
+	auto error = MTPRpcError();
+	if (!error.read(from, from + buffer.size())) {
+		return false;
+	}
+	return error.match([&](const MTPDrpc_error &data) {
+		return (data.verror_code().v == 401)
+			&& (data.verror_message().v == "AUTH_KEY_PERM_EMPTY");
 	});
-
-	_requestMsgId = 0;
-	return true;
 }
 
 } // namespace MTP::details
diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_checker.h b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.h
similarity index 58%
rename from Telegram/SourceFiles/mtproto/details/mtproto_dc_key_checker.h
rename to Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.h
index 85d4a55da..2ea16f33e 100644
--- a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_checker.h
+++ b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_binder.h
@@ -16,27 +16,31 @@ class Instance;
 
 namespace MTP::details {
 
-enum class DcKeyState {
-	MaybeExisting,
+enum class DcKeyBindState {
+	Unknown,
+	Success,
+	Failed,
 	DefinitelyDestroyed,
 };
 
-class DcKeyChecker final {
+class DcKeyBinder final {
 public:
-	DcKeyChecker(
-		not_null<Instance*> instance,
-		ShiftedDcId shiftedDcId,
-		const AuthKeyPtr &persistentKey);
+	DcKeyBinder(AuthKeyPtr &&persistentKey);
 
+	[[nodiscard]] bool requested() const;
 	[[nodiscard]] SecureRequest prepareRequest(
 		const AuthKeyPtr &temporaryKey,
 		uint64 sessionId);
-	bool handleResponse(MTPlong requestMsgId, const mtpBuffer &response);
+	[[nodiscard]] DcKeyBindState handleResponse(
+		MTPlong requestMsgId,
+		const mtpBuffer &response);
+	[[nodiscard]] AuthKeyPtr persistentKey() const;
+
+	[[nodiscard]] static bool IsDestroyedTemporaryKeyError(
+		const mtpBuffer &buffer);
 
 private:
-	const not_null<Instance*> _instance;
-	const ShiftedDcId _shiftedDcId = 0;
-	const AuthKeyPtr _persistentKey;
+	AuthKeyPtr _persistentKey;
 	mtpMsgId _requestMsgId = 0;
 
 };
diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
index 7b1ca17f0..71f12b2ca 100644
--- a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
+++ b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.cpp
@@ -153,383 +153,50 @@ MTPint128 NonceDigest(bytes::const_span data) {
 
 } // namespace
 
+DcKeyCreator::Attempt::~Attempt() {
+	const auto clearBytes = [](bytes::span bytes) {
+		OPENSSL_cleanse(bytes.data(), bytes.size());
+	};
+	OPENSSL_cleanse(&data, sizeof(data));
+	clearBytes(dhPrime);
+	clearBytes(g_a);
+	clearBytes(authKey);
+}
+
 DcKeyCreator::DcKeyCreator(
 	DcId dcId,
 	int16 protocolDcId,
 	not_null<AbstractConnection*> connection,
 	not_null<DcOptions*> dcOptions,
 	Delegate delegate,
-	TimeId expireIn)
+	Request request)
 : _connection(connection)
 , _dcOptions(dcOptions)
 , _dcId(dcId)
 , _protocolDcId(protocolDcId)
-, _expireIn(expireIn)
+, _request(request)
 , _delegate(std::move(delegate)) {
-	Expects(_expireIn >= 0);
+	Expects(_request.temporaryExpiresIn > 0);
 	Expects(_delegate.done != nullptr);
 
-	_data.nonce = openssl::RandomValue<MTPint128>();
-	pqSend();
+	QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
+		answered();
+	});
+
+	pqSend(&_temporary, _request.temporaryExpiresIn);
+	if (_request.persistentNeeded) {
+		pqSend(&_persistent, 0);
+	}
 }
 
 DcKeyCreator::~DcKeyCreator() {
 	if (_delegate.done) {
 		stopReceiving();
 	}
-	const auto clearBytes = [](bytes::span bytes) {
-		OPENSSL_cleanse(bytes.data(), bytes.size());
-	};
-	OPENSSL_cleanse(&_data, sizeof(_data));
-	clearBytes(_dhPrime);
-	clearBytes(_g_a);
-	clearBytes(_authKey);
 }
 
-void DcKeyCreator::pqSend() {
-	QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
-		pqAnswered();
-	});
-
-	DEBUG_LOG(("AuthKey Info: sending Req_pq..."));
-	sendNotSecureRequest(MTPReq_pq_multi(_data.nonce));
-}
-
-void DcKeyCreator::pqAnswered() {
-	stopReceiving();
-	DEBUG_LOG(("AuthKey Info: receiving Req_pq answer..."));
-
-	MTPReq_pq::ResponseType res_pq;
-	if (!readNotSecureResponse(res_pq)) {
-		return failed();
-	}
-
-	auto &res_pq_data = res_pq.c_resPQ();
-	if (res_pq_data.vnonce() != _data.nonce) {
-		LOG(("AuthKey Error: received nonce <> sent nonce (in res_pq)!"));
-		DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&res_pq_data.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
-		return failed();
-	}
-
-	const auto rsaKey = _dcOptions->getDcRSAKey(
-		_dcId,
-		res_pq.c_resPQ().vserver_public_key_fingerprints().v);
-	if (!rsaKey.valid()) {
-		return failed(Error::UnknownPublicKey);
-	}
-
-	_data.server_nonce = res_pq_data.vserver_nonce();
-	_data.new_nonce = openssl::RandomValue<MTPint256>();
-
-	const auto &pq = res_pq_data.vpq().v;
-	const auto parsed = ParsePQ(res_pq_data.vpq().v);
-	if (parsed.p.isEmpty() || parsed.q.isEmpty()) {
-		LOG(("AuthKey Error: could not factor pq!"));
-		DEBUG_LOG(("AuthKey Error: problematic pq: %1").arg(Logs::mb(pq.constData(), pq.length()).str()));
-		return failed();
-	}
-
-	const auto dhEncString = [&] {
-		return (_expireIn == 0)
-			? EncryptPQInnerRSA(
-				MTP_p_q_inner_data_dc(
-					res_pq_data.vpq(),
-					MTP_bytes(parsed.p),
-					MTP_bytes(parsed.q),
-					_data.nonce,
-					_data.server_nonce,
-					_data.new_nonce,
-					MTP_int(_protocolDcId)),
-				rsaKey)
-			: EncryptPQInnerRSA(
-				MTP_p_q_inner_data_temp_dc(
-					res_pq_data.vpq(),
-					MTP_bytes(parsed.p),
-					MTP_bytes(parsed.q),
-					_data.nonce,
-					_data.server_nonce,
-					_data.new_nonce,
-					MTP_int(_protocolDcId),
-					MTP_int(_expireIn)),
-				rsaKey);
-	}();
-	if (dhEncString.empty()) {
-		return failed();
-	}
-
-	QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
-		dhParamsAnswered();
-	});
-
-	DEBUG_LOG(("AuthKey Info: sending Req_DH_params..."));
-
-	sendNotSecureRequest(MTPReq_DH_params(
-		_data.nonce,
-		_data.server_nonce,
-		MTP_bytes(parsed.p),
-		MTP_bytes(parsed.q),
-		MTP_long(rsaKey.fingerprint()),
-		MTP_bytes(dhEncString)));
-}
-
-void DcKeyCreator::dhParamsAnswered() {
-	stopReceiving();
-	DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer..."));
-
-	MTPReq_DH_params::ResponseType res_DH_params;
-	if (!readNotSecureResponse(res_DH_params)) {
-		return failed();
-	}
-
-	switch (res_DH_params.type()) {
-	case mtpc_server_DH_params_ok: {
-		const auto &encDH(res_DH_params.c_server_DH_params_ok());
-		if (encDH.vnonce() != _data.nonce) {
-			LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_ok)!"));
-			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
-			return failed();
-		}
-		if (encDH.vserver_nonce() != _data.server_nonce) {
-			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_ok)!"));
-			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&encDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
-			return failed();
-		}
-
-		auto &encDHStr = encDH.vencrypted_answer().v;
-		uint32 encDHLen = encDHStr.length(), encDHBufLen = encDHLen >> 2;
-		if ((encDHLen & 0x03) || encDHBufLen < 6) {
-			LOG(("AuthKey Error: bad encrypted data length %1 (in server_DH_params_ok)!").arg(encDHLen));
-			DEBUG_LOG(("AuthKey Error: received encrypted data %1").arg(Logs::mb(encDHStr.constData(), encDHLen).str()));
-			return failed();
-		}
-
-		const auto nlen = sizeof(_data.new_nonce);
-		const auto slen = sizeof(_data.server_nonce);
-		auto tmp_aes_buffer = bytes::array<1024>();
-		const auto tmp_aes = bytes::make_span(tmp_aes_buffer);
-		bytes::copy(tmp_aes, bytes::object_as_span(&_data.new_nonce));
-		bytes::copy(tmp_aes.subspan(nlen), bytes::object_as_span(&_data.server_nonce));
-		bytes::copy(tmp_aes.subspan(nlen + slen), bytes::object_as_span(&_data.new_nonce));
-		bytes::copy(tmp_aes.subspan(nlen + slen + nlen), bytes::object_as_span(&_data.new_nonce));
-		const auto sha1ns = openssl::Sha1(tmp_aes.subspan(0, nlen + slen));
-		const auto sha1sn = openssl::Sha1(tmp_aes.subspan(nlen, nlen + slen));
-		const auto sha1nn = openssl::Sha1(tmp_aes.subspan(nlen + slen, nlen + nlen));
-
-		mtpBuffer decBuffer;
-		decBuffer.resize(encDHBufLen);
-
-		const auto aesKey = bytes::make_span(_data.aesKey);
-		const auto aesIV = bytes::make_span(_data.aesIV);
-		bytes::copy(aesKey, bytes::make_span(sha1ns).subspan(0, 20));
-		bytes::copy(aesKey.subspan(20), bytes::make_span(sha1sn).subspan(0, 12));
-		bytes::copy(aesIV, bytes::make_span(sha1sn).subspan(12, 8));
-		bytes::copy(aesIV.subspan(8), bytes::make_span(sha1nn).subspan(0, 20));
-		bytes::copy(aesIV.subspan(28), bytes::object_as_span(&_data.new_nonce).subspan(0, 4));
-
-		aesIgeDecryptRaw(encDHStr.constData(), &decBuffer[0], encDHLen, aesKey.data(), aesIV.data());
-
-		const mtpPrime *from(&decBuffer[5]), *to(from), *end(from + (encDHBufLen - 5));
-		MTPServer_DH_inner_data dh_inner;
-		if (!dh_inner.read(to, end)) {
-			LOG(("AuthKey Error: could not decrypt server_DH_inner_data!"));
-			return failed();
-		}
-		const auto &dh_inner_data(dh_inner.c_server_DH_inner_data());
-		if (dh_inner_data.vnonce() != _data.nonce) {
-			LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_inner_data)!"));
-			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&dh_inner_data.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
-			return failed();
-		}
-		if (dh_inner_data.vserver_nonce() != _data.server_nonce) {
-			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_inner_data)!"));
-			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&dh_inner_data.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
-			return failed();
-		}
-		const auto sha1Buffer = openssl::Sha1(
-			bytes::make_span(decBuffer).subspan(
-				5 * sizeof(mtpPrime),
-				(to - from) * sizeof(mtpPrime)));
-		const auto sha1Dec = bytes::make_span(decBuffer).subspan(
-			0,
-			openssl::kSha1Size);
-		if (bytes::compare(sha1Dec, sha1Buffer)) {
-			LOG(("AuthKey Error: sha1 hash of encrypted part did not match!"));
-			DEBUG_LOG(("AuthKey Error: sha1 did not match, server_nonce: %1, new_nonce %2, encrypted data %3").arg(Logs::mb(&_data.server_nonce, 16).str()).arg(Logs::mb(&_data.new_nonce, 16).str()).arg(Logs::mb(encDHStr.constData(), encDHLen).str()));
-			return failed();
-		}
-		base::unixtime::update(dh_inner_data.vserver_time().v);
-
-		// check that dhPrime and (dhPrime - 1) / 2 are really prime
-		if (!IsPrimeAndGood(bytes::make_span(dh_inner_data.vdh_prime().v), dh_inner_data.vg().v)) {
-			LOG(("AuthKey Error: bad dh_prime primality!"));
-			return failed();
-		}
-
-		_dhPrime = bytes::make_vector(
-			dh_inner_data.vdh_prime().v);
-		_data.g = dh_inner_data.vg().v;
-		_g_a = bytes::make_vector(dh_inner_data.vg_a().v);
-		_data.retry_id = MTP_long(0);
-		_data.retries = 0;
-	} return dhClientParamsSend();
-
-	case mtpc_server_DH_params_fail: {
-		const auto &encDH(res_DH_params.c_server_DH_params_fail());
-		if (encDH.vnonce() != _data.nonce) {
-			LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_params_fail)!"));
-			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&encDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
-			return failed();
-		}
-		if (encDH.vserver_nonce() != _data.server_nonce) {
-			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_fail)!"));
-			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&encDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
-			return failed();
-		}
-		if (encDH.vnew_nonce_hash() != NonceDigest(bytes::object_as_span(&_data.new_nonce))) {
-			LOG(("AuthKey Error: received new_nonce_hash did not match!"));
-			DEBUG_LOG(("AuthKey Error: received new_nonce_hash: %1, new_nonce: %2").arg(Logs::mb(&encDH.vnew_nonce_hash(), 16).str()).arg(Logs::mb(&_data.new_nonce, 32).str()));
-			return failed();
-		}
-		LOG(("AuthKey Error: server_DH_params_fail received!"));
-	} return failed();
-
-	}
-	LOG(("AuthKey Error: unknown server_DH_params received, typeId = %1").arg(res_DH_params.type()));
-	return failed();
-}
-
-void DcKeyCreator::dhClientParamsSend() {
-	if (++_data.retries > 5) {
-		LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(_data.retries - 1));
-		return failed();
-	}
-
-	// gen rand 'b'
-	auto randomSeed = bytes::vector(ModExpFirst::kRandomPowerSize);
-	bytes::set_random(randomSeed);
-	auto g_b_data = CreateModExp(_data.g, _dhPrime, randomSeed);
-	if (g_b_data.modexp.empty()) {
-		LOG(("AuthKey Error: could not generate good g_b."));
-		return failed();
-	}
-
-	auto computedAuthKey = CreateAuthKey(_g_a, g_b_data.randomPower, _dhPrime);
-	if (computedAuthKey.empty()) {
-		LOG(("AuthKey Error: could not generate auth_key."));
-		return failed();
-	}
-	AuthKey::FillData(_authKey, computedAuthKey);
-
-	auto auth_key_sha = openssl::Sha1(_authKey);
-	memcpy(&_data.auth_key_aux_hash, auth_key_sha.data(), 8);
-	memcpy(&_data.auth_key_hash, auth_key_sha.data() + 12, 8);
-
-	const auto client_dh_inner = MTP_client_DH_inner_data(
-		_data.nonce,
-		_data.server_nonce,
-		_data.retry_id,
-		MTP_bytes(g_b_data.modexp));
-
-	auto sdhEncString = EncryptClientDHInner(
-		client_dh_inner,
-		_data.aesKey.data(),
-		_data.aesIV.data());
-
-	QObject::connect(_connection, &AbstractConnection::receivedData, [=] {
-		dhClientParamsAnswered();
-	});
-
-	DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params..."));
-	sendNotSecureRequest(MTPSet_client_DH_params(
-		_data.nonce,
-		_data.server_nonce,
-		MTP_string(std::move(sdhEncString))));
-}
-
-void DcKeyCreator::dhClientParamsAnswered() {
-	stopReceiving();
-	DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer..."));
-
-	MTPSet_client_DH_params::ResponseType res_client_DH_params;
-	if (!readNotSecureResponse(res_client_DH_params)) {
-		return failed();
-	}
-
-	switch (res_client_DH_params.type()) {
-	case mtpc_dh_gen_ok: {
-		const auto &resDH(res_client_DH_params.c_dh_gen_ok());
-		if (resDH.vnonce() != _data.nonce) {
-			LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_ok)!"));
-			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
-			return failed();
-		}
-		if (resDH.vserver_nonce() != _data.server_nonce) {
-			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_ok)!"));
-			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
-			return failed();
-		}
-		_data.new_nonce_buf[32] = bytes::type(1);
-		if (resDH.vnew_nonce_hash1() != NonceDigest(_data.new_nonce_buf)) {
-			LOG(("AuthKey Error: received new_nonce_hash1 did not match!"));
-			DEBUG_LOG(("AuthKey Error: received new_nonce_hash1: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash1(), 16).str()).arg(Logs::mb(_data.new_nonce_buf.data(), 41).str()));
-			return failed();
-		}
-
-		uint64 salt1 = _data.new_nonce.l.l, salt2 = _data.server_nonce.l;
-		done(salt1 ^ salt2);
-	} return;
-
-	case mtpc_dh_gen_retry: {
-		const auto &resDH(res_client_DH_params.c_dh_gen_retry());
-		if (resDH.vnonce() != _data.nonce) {
-			LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_retry)!"));
-			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
-			return failed();
-		}
-		if (resDH.vserver_nonce() != _data.server_nonce) {
-			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_retry)!"));
-			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
-			return failed();
-		}
-		_data.new_nonce_buf[32] = bytes::type(2);
-		uchar sha1Buffer[20];
-		if (resDH.vnew_nonce_hash2() != NonceDigest(_data.new_nonce_buf)) {
-			LOG(("AuthKey Error: received new_nonce_hash2 did not match!"));
-			DEBUG_LOG(("AuthKey Error: received new_nonce_hash2: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash2(), 16).str()).arg(Logs::mb(_data.new_nonce_buf.data(), 41).str()));
-			return failed();
-		}
-		_data.retry_id = _data.auth_key_aux_hash;
-	} return dhClientParamsSend();
-
-	case mtpc_dh_gen_fail: {
-		const auto &resDH(res_client_DH_params.c_dh_gen_fail());
-		if (resDH.vnonce() != _data.nonce) {
-			LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_fail)!"));
-			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&resDH.vnonce(), 16).str()).arg(Logs::mb(&_data.nonce, 16).str()));
-			return failed();
-		}
-		if (resDH.vserver_nonce() != _data.server_nonce) {
-			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_fail)!"));
-			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&resDH.vserver_nonce(), 16).str()).arg(Logs::mb(&_data.server_nonce, 16).str()));
-			return failed();
-		}
-		_data.new_nonce_buf[32] = bytes::type(3);
-		uchar sha1Buffer[20];
-		if (resDH.vnew_nonce_hash3() != NonceDigest(_data.new_nonce_buf)) {
-			LOG(("AuthKey Error: received new_nonce_hash3 did not match!"));
-			DEBUG_LOG(("AuthKey Error: received new_nonce_hash3: %1, new_nonce_buf: %2").arg(Logs::mb(&resDH.vnew_nonce_hash3(), 16).str()).arg(Logs::mb(_data.new_nonce_buf.data(), 41).str()));
-			return failed();
-		}
-		LOG(("AuthKey Error: dh_gen_fail received!"));
-	} return failed();
-	}
-
-	LOG(("AuthKey Error: unknown set_client_DH_params_answer received, typeId = %1").arg(res_client_DH_params.type()));
-	return failed();
-}
-
-template <typename Request>
-void DcKeyCreator::sendNotSecureRequest(const Request &request) {
+template <typename RequestType>
+void DcKeyCreator::sendNotSecureRequest(const RequestType &request) {
 	auto packet = _connection->prepareNotSecurePacket(
 		request,
 		base::unixtime::mtproto_msg_id());
@@ -547,8 +214,18 @@ void DcKeyCreator::sendNotSecureRequest(const Request &request) {
 	}
 }
 
-template <typename Response>
-bool DcKeyCreator::readNotSecureResponse(Response &response) {
+template <typename RequestType, typename Response>
+std::optional<Response> DcKeyCreator::readNotSecureResponse(
+		gsl::span<const mtpPrime> answer) {
+	auto from = answer.data();
+	auto result = Response();
+	if (result.read(from, from + answer.size())) {
+		return result;
+	}
+	return std::nullopt;
+}
+
+void DcKeyCreator::answered() {
 	if (_delegate.receivedSome) {
 		_delegate.receivedSome();
 	}
@@ -556,7 +233,7 @@ bool DcKeyCreator::readNotSecureResponse(Response &response) {
 	if (_connection->received().empty()) {
 		LOG(("AuthKey Error: "
 			"trying to read response from empty received list"));
-		return false;
+		return failed();
 	}
 
 	const auto buffer = std::move(_connection->received().front());
@@ -564,10 +241,358 @@ bool DcKeyCreator::readNotSecureResponse(Response &response) {
 
 	const auto answer = _connection->parseNotSecureResponse(buffer);
 	if (answer.empty()) {
-		return false;
+		return failed();
 	}
-	auto from = answer.data();
-	return response.read(from, from + answer.size());
+
+	handleAnswer(answer);
+}
+
+DcKeyCreator::Attempt *DcKeyCreator::attemptByNonce(const MTPint128 &nonce) {
+	if (_temporary.data.nonce == nonce) {
+		DEBUG_LOG(("AuthKey Info: receiving answer for temporary..."));
+		return &_temporary;
+	} else if (_persistent.data.nonce == nonce) {
+		DEBUG_LOG(("AuthKey Info: receiving answer for persistent..."));
+		return &_persistent;
+	}
+	LOG(("AuthKey Error: attempt by nonce not found."));
+	return nullptr;
+}
+
+void DcKeyCreator::handleAnswer(gsl::span<const mtpPrime> answer) {
+	if (const auto resPQ = readNotSecureResponse<MTPReq_pq>(answer)) {
+		const auto nonce = resPQ->match([](const auto &data) {
+			return data.vnonce();
+		});
+		if (const auto attempt = attemptByNonce(nonce)) {
+			DEBUG_LOG(("AuthKey Info: receiving Req_pq answer..."));
+			return pqAnswered(attempt, *resPQ);
+		}
+	} else if (const auto resDH = readNotSecureResponse<MTPReq_DH_params>(answer)) {
+		const auto nonce = resDH->match([](const auto &data) {
+			return data.vnonce();
+		});
+		if (const auto attempt = attemptByNonce(nonce)) {
+			DEBUG_LOG(("AuthKey Info: receiving Req_DH_params answer..."));
+			return dhParamsAnswered(attempt, *resDH);
+		}
+	} else if (const auto result = readNotSecureResponse<MTPSet_client_DH_params>(answer)) {
+		const auto nonce = result->match([](const auto &data) {
+			return data.vnonce();
+		});
+		if (const auto attempt = attemptByNonce(nonce)) {
+			DEBUG_LOG(("AuthKey Info: receiving Req_client_DH_params answer..."));
+			return dhClientParamsAnswered(attempt, *result);
+		}
+	}
+	LOG(("AuthKey Error: Unknown answer received."));
+	failed();
+}
+
+void DcKeyCreator::pqSend(not_null<Attempt*> attempt, TimeId expiresIn) {
+	DEBUG_LOG(("AuthKey Info: sending Req_pq for %1..."
+		).arg(expiresIn ? "temporary" : "persistent"));
+	attempt->stage = Stage::WaitingPQ;
+	attempt->expiresIn = expiresIn;
+	attempt->data.nonce = openssl::RandomValue<MTPint128>();
+	sendNotSecureRequest(MTPReq_pq_multi(attempt->data.nonce));
+}
+
+void DcKeyCreator::pqAnswered(
+		not_null<Attempt*> attempt,
+		const MTPresPQ &data) {
+	data.match([&](const MTPDresPQ &data) {
+		Expects(data.vnonce() == attempt->data.nonce);
+
+		if (attempt->stage != Stage::WaitingPQ) {
+			LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
+			return failed();
+		}
+		const auto rsaKey = _dcOptions->getDcRSAKey(
+			_dcId,
+			data.vserver_public_key_fingerprints().v);
+		if (!rsaKey.valid()) {
+			return failed(Error::UnknownPublicKey);
+		}
+
+		attempt->data.server_nonce = data.vserver_nonce();
+		attempt->data.new_nonce = openssl::RandomValue<MTPint256>();
+
+		const auto &pq = data.vpq().v;
+		const auto parsed = ParsePQ(data.vpq().v);
+		if (parsed.p.isEmpty() || parsed.q.isEmpty()) {
+			LOG(("AuthKey Error: could not factor pq!"));
+			DEBUG_LOG(("AuthKey Error: problematic pq: %1").arg(Logs::mb(pq.constData(), pq.length()).str()));
+			return failed();
+		}
+
+		const auto dhEncString = [&] {
+			return (attempt->expiresIn == 0)
+				? EncryptPQInnerRSA(
+					MTP_p_q_inner_data_dc(
+						data.vpq(),
+						MTP_bytes(parsed.p),
+						MTP_bytes(parsed.q),
+						attempt->data.nonce,
+						attempt->data.server_nonce,
+						attempt->data.new_nonce,
+						MTP_int(_protocolDcId)),
+					rsaKey)
+				: EncryptPQInnerRSA(
+					MTP_p_q_inner_data_temp_dc(
+						data.vpq(),
+						MTP_bytes(parsed.p),
+						MTP_bytes(parsed.q),
+						attempt->data.nonce,
+						attempt->data.server_nonce,
+						attempt->data.new_nonce,
+						MTP_int(_protocolDcId),
+						MTP_int(attempt->expiresIn)),
+					rsaKey);
+		}();
+		if (dhEncString.empty()) {
+			return failed();
+		}
+
+		attempt->stage = Stage::WaitingDH;
+		DEBUG_LOG(("AuthKey Info: sending Req_DH_params..."));
+		sendNotSecureRequest(MTPReq_DH_params(
+			attempt->data.nonce,
+			attempt->data.server_nonce,
+			MTP_bytes(parsed.p),
+			MTP_bytes(parsed.q),
+			MTP_long(rsaKey.fingerprint()),
+			MTP_bytes(dhEncString)));
+	});
+}
+
+void DcKeyCreator::dhParamsAnswered(
+		not_null<Attempt*> attempt,
+		const MTPserver_DH_Params &data) {
+	if (attempt->stage != Stage::WaitingDH) {
+		LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
+		return failed();
+	}
+	data.match([&](const MTPDserver_DH_params_ok &data) {
+		Expects(data.vnonce() == attempt->data.nonce);
+
+		if (data.vserver_nonce() != attempt->data.server_nonce) {
+			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_ok)!"));
+			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str()).arg(Logs::mb(&attempt->data.server_nonce, 16).str()));
+			return failed();
+		}
+
+		auto &encDHStr = data.vencrypted_answer().v;
+		uint32 encDHLen = encDHStr.length(), encDHBufLen = encDHLen >> 2;
+		if ((encDHLen & 0x03) || encDHBufLen < 6) {
+			LOG(("AuthKey Error: bad encrypted data length %1 (in server_DH_params_ok)!").arg(encDHLen));
+			DEBUG_LOG(("AuthKey Error: received encrypted data %1").arg(Logs::mb(encDHStr.constData(), encDHLen).str()));
+			return failed();
+		}
+
+		const auto nlen = sizeof(attempt->data.new_nonce);
+		const auto slen = sizeof(attempt->data.server_nonce);
+		auto tmp_aes_buffer = bytes::array<1024>();
+		const auto tmp_aes = bytes::make_span(tmp_aes_buffer);
+		bytes::copy(tmp_aes, bytes::object_as_span(&attempt->data.new_nonce));
+		bytes::copy(tmp_aes.subspan(nlen), bytes::object_as_span(&attempt->data.server_nonce));
+		bytes::copy(tmp_aes.subspan(nlen + slen), bytes::object_as_span(&attempt->data.new_nonce));
+		bytes::copy(tmp_aes.subspan(nlen + slen + nlen), bytes::object_as_span(&attempt->data.new_nonce));
+		const auto sha1ns = openssl::Sha1(tmp_aes.subspan(0, nlen + slen));
+		const auto sha1sn = openssl::Sha1(tmp_aes.subspan(nlen, nlen + slen));
+		const auto sha1nn = openssl::Sha1(tmp_aes.subspan(nlen + slen, nlen + nlen));
+
+		mtpBuffer decBuffer;
+		decBuffer.resize(encDHBufLen);
+
+		const auto aesKey = bytes::make_span(attempt->data.aesKey);
+		const auto aesIV = bytes::make_span(attempt->data.aesIV);
+		bytes::copy(aesKey, bytes::make_span(sha1ns).subspan(0, 20));
+		bytes::copy(aesKey.subspan(20), bytes::make_span(sha1sn).subspan(0, 12));
+		bytes::copy(aesIV, bytes::make_span(sha1sn).subspan(12, 8));
+		bytes::copy(aesIV.subspan(8), bytes::make_span(sha1nn).subspan(0, 20));
+		bytes::copy(aesIV.subspan(28), bytes::object_as_span(&attempt->data.new_nonce).subspan(0, 4));
+
+		aesIgeDecryptRaw(encDHStr.constData(), &decBuffer[0], encDHLen, aesKey.data(), aesIV.data());
+
+		const mtpPrime *from(&decBuffer[5]), *to(from), *end(from + (encDHBufLen - 5));
+		MTPServer_DH_inner_data dh_inner;
+		if (!dh_inner.read(to, end)) {
+			LOG(("AuthKey Error: could not decrypt server_DH_inner_data!"));
+			return failed();
+		}
+		const auto &dh_inner_data(dh_inner.c_server_DH_inner_data());
+		if (dh_inner_data.vnonce() != attempt->data.nonce) {
+			LOG(("AuthKey Error: received nonce <> sent nonce (in server_DH_inner_data)!"));
+			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&dh_inner_data.vnonce(), 16).str()).arg(Logs::mb(&attempt->data.nonce, 16).str()));
+			return failed();
+		}
+		if (dh_inner_data.vserver_nonce() != attempt->data.server_nonce) {
+			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_inner_data)!"));
+			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&dh_inner_data.vserver_nonce(), 16).str()).arg(Logs::mb(&attempt->data.server_nonce, 16).str()));
+			return failed();
+		}
+		const auto sha1Buffer = openssl::Sha1(
+			bytes::make_span(decBuffer).subspan(
+				5 * sizeof(mtpPrime),
+				(to - from) * sizeof(mtpPrime)));
+		const auto sha1Dec = bytes::make_span(decBuffer).subspan(
+			0,
+			openssl::kSha1Size);
+		if (bytes::compare(sha1Dec, sha1Buffer)) {
+			LOG(("AuthKey Error: sha1 hash of encrypted part did not match!"));
+			DEBUG_LOG(("AuthKey Error: sha1 did not match, server_nonce: %1, new_nonce %2, encrypted data %3").arg(Logs::mb(&attempt->data.server_nonce, 16).str()).arg(Logs::mb(&attempt->data.new_nonce, 16).str()).arg(Logs::mb(encDHStr.constData(), encDHLen).str()));
+			return failed();
+		}
+		base::unixtime::update(dh_inner_data.vserver_time().v);
+
+		// check that dhPrime and (dhPrime - 1) / 2 are really prime
+		if (!IsPrimeAndGood(bytes::make_span(dh_inner_data.vdh_prime().v), dh_inner_data.vg().v)) {
+			LOG(("AuthKey Error: bad dh_prime primality!"));
+			return failed();
+		}
+
+		attempt->dhPrime = bytes::make_vector(
+			dh_inner_data.vdh_prime().v);
+		attempt->data.g = dh_inner_data.vg().v;
+		attempt->g_a = bytes::make_vector(dh_inner_data.vg_a().v);
+		attempt->data.retry_id = MTP_long(0);
+		attempt->retries = 0;
+		dhClientParamsSend(attempt);
+	}, [&](const MTPDserver_DH_params_fail &data) {
+		Expects(data.vnonce() == attempt->data.nonce);
+
+		if (data.vserver_nonce() != attempt->data.server_nonce) {
+			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in server_DH_params_fail)!"));
+			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str()).arg(Logs::mb(&attempt->data.server_nonce, 16).str()));
+			return failed();
+		}
+		if (data.vnew_nonce_hash() != NonceDigest(bytes::object_as_span(&attempt->data.new_nonce))) {
+			LOG(("AuthKey Error: received new_nonce_hash did not match!"));
+			DEBUG_LOG(("AuthKey Error: received new_nonce_hash: %1, new_nonce: %2").arg(Logs::mb(&data.vnew_nonce_hash(), 16).str()).arg(Logs::mb(&attempt->data.new_nonce, 32).str()));
+			return failed();
+		}
+		LOG(("AuthKey Error: server_DH_params_fail received!"));
+		failed();
+	});
+}
+
+void DcKeyCreator::dhClientParamsSend(not_null<Attempt*> attempt) {
+	if (++attempt->retries > 5) {
+		LOG(("AuthKey Error: could not create auth_key for %1 retries").arg(attempt->retries - 1));
+		return failed();
+	}
+
+	// gen rand 'b'
+	auto randomSeed = bytes::vector(ModExpFirst::kRandomPowerSize);
+	bytes::set_random(randomSeed);
+	auto g_b_data = CreateModExp(attempt->data.g, attempt->dhPrime, randomSeed);
+	if (g_b_data.modexp.empty()) {
+		LOG(("AuthKey Error: could not generate good g_b."));
+		return failed();
+	}
+
+	auto computedAuthKey = CreateAuthKey(attempt->g_a, g_b_data.randomPower, attempt->dhPrime);
+	if (computedAuthKey.empty()) {
+		LOG(("AuthKey Error: could not generate auth_key."));
+		return failed();
+	}
+	AuthKey::FillData(attempt->authKey, computedAuthKey);
+
+	auto auth_key_sha = openssl::Sha1(attempt->authKey);
+	memcpy(&attempt->data.auth_key_aux_hash, auth_key_sha.data(), 8);
+	memcpy(&attempt->data.auth_key_hash, auth_key_sha.data() + 12, 8);
+
+	const auto client_dh_inner = MTP_client_DH_inner_data(
+		attempt->data.nonce,
+		attempt->data.server_nonce,
+		attempt->data.retry_id,
+		MTP_bytes(g_b_data.modexp));
+
+	auto sdhEncString = EncryptClientDHInner(
+		client_dh_inner,
+		attempt->data.aesKey.data(),
+		attempt->data.aesIV.data());
+
+	attempt->stage = Stage::WaitingDone;
+	DEBUG_LOG(("AuthKey Info: sending Req_client_DH_params..."));
+	sendNotSecureRequest(MTPSet_client_DH_params(
+		attempt->data.nonce,
+		attempt->data.server_nonce,
+		MTP_string(std::move(sdhEncString))));
+}
+
+void DcKeyCreator::dhClientParamsAnswered(
+		not_null<Attempt*> attempt,
+		const MTPset_client_DH_params_answer &data) {
+	if (attempt->stage != Stage::WaitingDone) {
+		LOG(("AuthKey Error: Unexpected stage %1").arg(int(attempt->stage)));
+		return failed();
+	}
+
+	data.match([&](const MTPDdh_gen_ok &data) {
+		if (data.vnonce() != attempt->data.nonce) {
+			LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_ok)!"));
+			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str()).arg(Logs::mb(&attempt->data.nonce, 16).str()));
+			return failed();
+		}
+		if (data.vserver_nonce() != attempt->data.server_nonce) {
+			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_ok)!"));
+			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str()).arg(Logs::mb(&attempt->data.server_nonce, 16).str()));
+			return failed();
+		}
+		attempt->data.new_nonce_buf[32] = bytes::type(1);
+		if (data.vnew_nonce_hash1() != NonceDigest(attempt->data.new_nonce_buf)) {
+			LOG(("AuthKey Error: received new_nonce_hash1 did not match!"));
+			DEBUG_LOG(("AuthKey Error: received new_nonce_hash1: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash1(), 16).str()).arg(Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
+			return failed();
+		}
+
+		uint64 salt1 = attempt->data.new_nonce.l.l, salt2 = attempt->data.server_nonce.l;
+		attempt->data.doneSalt = salt1 ^ salt2;
+		attempt->stage = Stage::Ready;
+		done();
+	}, [&](const MTPDdh_gen_retry &data) {
+		if (data.vnonce() != attempt->data.nonce) {
+			LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_retry)!"));
+			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str()).arg(Logs::mb(&attempt->data.nonce, 16).str()));
+			return failed();
+		}
+		if (data.vserver_nonce() != attempt->data.server_nonce) {
+			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_retry)!"));
+			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str()).arg(Logs::mb(&attempt->data.server_nonce, 16).str()));
+			return failed();
+		}
+		attempt->data.new_nonce_buf[32] = bytes::type(2);
+		uchar sha1Buffer[20];
+		if (data.vnew_nonce_hash2() != NonceDigest(attempt->data.new_nonce_buf)) {
+			LOG(("AuthKey Error: received new_nonce_hash2 did not match!"));
+			DEBUG_LOG(("AuthKey Error: received new_nonce_hash2: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash2(), 16).str()).arg(Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
+			return failed();
+		}
+		attempt->data.retry_id = attempt->data.auth_key_aux_hash;
+		dhClientParamsSend(attempt);
+	}, [&](const MTPDdh_gen_fail &data) {
+		if (data.vnonce() != attempt->data.nonce) {
+			LOG(("AuthKey Error: received nonce <> sent nonce (in dh_gen_fail)!"));
+			DEBUG_LOG(("AuthKey Error: received nonce: %1, sent nonce: %2").arg(Logs::mb(&data.vnonce(), 16).str()).arg(Logs::mb(&attempt->data.nonce, 16).str()));
+			return failed();
+		}
+		if (data.vserver_nonce() != attempt->data.server_nonce) {
+			LOG(("AuthKey Error: received server_nonce <> sent server_nonce (in dh_gen_fail)!"));
+			DEBUG_LOG(("AuthKey Error: received server_nonce: %1, sent server_nonce: %2").arg(Logs::mb(&data.vserver_nonce(), 16).str()).arg(Logs::mb(&attempt->data.server_nonce, 16).str()));
+			return failed();
+		}
+		attempt->data.new_nonce_buf[32] = bytes::type(3);
+		uchar sha1Buffer[20];
+		if (data.vnew_nonce_hash3() != NonceDigest(attempt->data.new_nonce_buf)) {
+			LOG(("AuthKey Error: received new_nonce_hash3 did not match!"));
+			DEBUG_LOG(("AuthKey Error: received new_nonce_hash3: %1, new_nonce_buf: %2").arg(Logs::mb(&data.vnew_nonce_hash3(), 16).str()).arg(Logs::mb(attempt->data.new_nonce_buf.data(), 41).str()));
+			return failed();
+		}
+		LOG(("AuthKey Error: dh_gen_fail received!"));
+		failed();
+	});
 }
 
 void DcKeyCreator::failed(Error error) {
@@ -576,13 +601,28 @@ void DcKeyCreator::failed(Error error) {
 	onstack(tl::unexpected(error));
 }
 
-void DcKeyCreator::done(uint64 serverSalt) {
+void DcKeyCreator::done() {
+	Expects(_temporary.stage != Stage::None);
+
+	if (_persistent.stage != Stage::None && _persistent.stage != Stage::Ready) {
+		return;
+	} else if (_temporary.stage != Stage::Ready) {
+		return;
+	}
+
 	auto result = Result();
-	result.key = std::make_shared<AuthKey>(
-		AuthKey::Type::Generated,
+	result.temporaryKey = std::make_shared<AuthKey>(
+		AuthKey::Type::Temporary,
 		_dcId,
-		_authKey);
-	result.serverSalt = serverSalt;
+		_temporary.authKey);
+	result.temporaryServerSalt = _temporary.data.doneSalt;
+	if (_persistent.stage == Stage::Ready) {
+		result.persistentKey = std::make_shared<AuthKey>(
+			AuthKey::Type::Generated,
+			_dcId,
+			_persistent.authKey);
+		result.persistentServerSalt = _persistent.data.doneSalt;
+	}
 
 	stopReceiving();
 	auto onstack = base::take(_delegate.done);
diff --git a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
index 23dcadbd6..e98312694 100644
--- a/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
+++ b/Telegram/SourceFiles/mtproto/details/mtproto_dc_key_creator.h
@@ -23,13 +23,19 @@ using namespace ::MTP::internal;
 
 class DcKeyCreator final {
 public:
+	struct Request {
+		TimeId temporaryExpiresIn = 0;
+		bool persistentNeeded = false;
+	};
 	enum class Error {
 		UnknownPublicKey,
 		Other,
 	};
 	struct Result {
-		AuthKeyPtr key;
-		uint64 serverSalt = 0;
+		AuthKeyPtr persistentKey;
+		AuthKeyPtr temporaryKey;
+		uint64 temporaryServerSalt = 0;
+		uint64 persistentServerSalt = 0;
 	};
 	struct Delegate {
 		FnMut<void(base::expected<Result, Error>)> done;
@@ -43,11 +49,17 @@ public:
 		not_null<AbstractConnection*> connection,
 		not_null<DcOptions*> dcOptions,
 		Delegate delegate,
-		TimeId expireIn = 0); // 0 - persistent, > 0 - temporary
+		Request request);
 	~DcKeyCreator();
 
 private:
-	// Auth key creation fields and methods
+	enum class Stage {
+		None,
+		WaitingPQ,
+		WaitingDH,
+		WaitingDone,
+		Ready,
+	};
 	struct Data {
 		Data()
 		: new_nonce(*(MTPint256*)((uchar*)new_nonce_buf.data()))
@@ -61,7 +73,6 @@ private:
 		MTPint256 &new_nonce;
 		MTPlong &auth_key_aux_hash;
 
-		uint32 retries = 0;
 		MTPlong retry_id;
 
 		int32 g = 0;
@@ -69,35 +80,59 @@ private:
 		bytes::array<32> aesKey;
 		bytes::array<32> aesIV;
 		MTPlong auth_key_hash;
+		uint64 doneSalt = 0;
+	};
+	struct Attempt {
+		~Attempt();
+
+		Data data;
+		bytes::vector dhPrime;
+		bytes::vector g_a;
+		AuthKey::Data authKey = { { gsl::byte{} } };
+		TimeId expiresIn = 0;
+		uint32 retries = 0;
+		Stage stage = Stage::None;
 	};
 
-	template <typename Request>
-	void sendNotSecureRequest(const Request &request);
+	template <typename RequestType>
+	void sendNotSecureRequest(const RequestType &request);
 
-	template <typename Response>
-	[[nodiscard]] bool readNotSecureResponse(Response &response);
+	template <
+		typename RequestType,
+		typename Response = typename RequestType::ResponseType>
+	[[nodiscard]] std::optional<Response> readNotSecureResponse(
+			gsl::span<const mtpPrime> answer);
 
-	void pqSend();
-	void pqAnswered();
-	void dhParamsAnswered();
-	void dhClientParamsSend();
-	void dhClientParamsAnswered();
+	Attempt *attemptByNonce(const MTPint128 &nonce);
+
+	void answered();
+	void handleAnswer(gsl::span<const mtpPrime> answer);
+	void pqSend(not_null<Attempt*> attempt, TimeId expiresIn);
+	void pqAnswered(
+		not_null<Attempt*> attempt,
+		const MTPresPQ &data);
+	void dhParamsAnswered(
+		not_null<Attempt*> attempt,
+		const MTPserver_DH_Params &data);
+	void dhClientParamsSend(not_null<Attempt*> attempt);
+	void dhClientParamsAnswered(
+		not_null<Attempt*> attempt,
+		const MTPset_client_DH_params_answer &data);
 
 	void stopReceiving();
 	void failed(Error error = Error::Other);
-	void done(uint64 serverSalt);
+	void done();
 
 	const not_null<AbstractConnection*> _connection;
 	const not_null<DcOptions*> _dcOptions;
 	const DcId _dcId = 0;
 	const int16 _protocolDcId = 0;
-	const TimeId _expireIn = 0;
+	const Request _request;
 	Delegate _delegate;
 
-	Data _data;
-	bytes::vector _dhPrime;
-	bytes::vector _g_a;
-	AuthKey::Data _authKey = { { gsl::byte{} } };
+	Attempt _temporary;
+	Attempt _persistent;
+
 	FnMut<void(base::expected<Result, Error>)> _done;
 
 };
diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.cpp b/Telegram/SourceFiles/mtproto/mtp_instance.cpp
index 320f1b77c..0f73de8da 100644
--- a/Telegram/SourceFiles/mtproto/mtp_instance.cpp
+++ b/Telegram/SourceFiles/mtproto/mtp_instance.cpp
@@ -65,8 +65,9 @@ public:
 	void setMainDcId(DcId mainDcId);
 	[[nodiscard]] DcId mainDcId() const;
 
-	void dcKeyChanged(DcId dcId, const AuthKeyPtr &key);
-	[[nodiscard]] rpl::producer<DcId> dcKeyChanged() const;
+	void dcPersistentKeyChanged(DcId dcId, const AuthKeyPtr &persistentKey);
+	void dcTemporaryKeyChanged(DcId dcId);
+	[[nodiscard]] rpl::producer<DcId> dcTemporaryKeyChanged() const;
 	[[nodiscard]] AuthKeysList getKeysForWrite() const;
 	void addKeysForDestroy(AuthKeysList &&keys);
 
@@ -209,7 +210,7 @@ private:
 	bool _mainDcIdForced = false;
 	base::flat_map<DcId, std::unique_ptr<Dcenter>> _dcenters;
 	std::vector<std::unique_ptr<Dcenter>> _dcentersToDestroy;
-	rpl::event_stream<DcId> _dcKeyChanged;
+	rpl::event_stream<DcId> _dcTemporaryKeyChanged;
 
 	Session *_mainSession = nullptr;
 	base::flat_map<ShiftedDcId, std::unique_ptr<Session>> _sessions;
@@ -225,10 +226,8 @@ private:
 	crl::time _lastConfigLoadedTime = 0;
 	crl::time _configExpiresAt = 0;
 
-	std::map<DcId, AuthKeyPtr> _keysForWrite;
-	mutable QReadWriteLock _keysForWriteLock;
-
-	std::map<ShiftedDcId, mtpRequestId> _logoutGuestRequestIds;
+	base::flat_map<DcId, AuthKeyPtr> _keysForWrite;
+	base::flat_map<ShiftedDcId, mtpRequestId> _logoutGuestRequestIds;
 
 	// holds dcWithShift for request to this dc or -dc for request to main dc
 	std::map<mtpRequestId, ShiftedDcId> _requestsByDc;
@@ -619,23 +618,22 @@ void Instance::Private::logout(
 
 void Instance::Private::logoutGuestDcs() {
 	auto dcIds = std::vector<DcId>();
-	{
-		QReadLocker lock(&_keysForWriteLock);
-		dcIds.reserve(_keysForWrite.size());
-		for (auto &key : _keysForWrite) {
-			dcIds.push_back(key.first);
-		}
+	dcIds.reserve(_keysForWrite.size());
+	for (const auto &key : _keysForWrite) {
+		dcIds.push_back(key.first);
 	}
-	for (auto dcId : dcIds) {
-		if (dcId != mainDcId() && dcOptions()->dcType(dcId) != DcType::Cdn) {
-			auto shiftedDcId = MTP::logoutDcId(dcId);
-			auto requestId = _instance->send(MTPauth_LogOut(), rpcDone([this](mtpRequestId requestId) {
-				logoutGuestDone(requestId);
-			}), rpcFail([this](mtpRequestId requestId) {
-				return logoutGuestDone(requestId);
-			}), shiftedDcId);
-			_logoutGuestRequestIds.emplace(shiftedDcId, requestId);
+	for (const auto dcId : dcIds) {
+		if (dcId == mainDcId() || dcOptions()->dcType(dcId) == DcType::Cdn) {
+			continue;
 		}
+		const auto shiftedDcId = MTP::logoutDcId(dcId);
+		const auto requestId = _instance->send(MTPauth_LogOut(), rpcDone([=](
+				mtpRequestId requestId) {
+			logoutGuestDone(requestId);
+		}), rpcFail([=](mtpRequestId requestId) {
+			return logoutGuestDone(requestId);
+		}), shiftedDcId);
+		_logoutGuestRequestIds.emplace(shiftedDcId, requestId);
 	}
 }
 
@@ -695,35 +693,45 @@ not_null<Dcenter*> Instance::Private::getDcById(
 	return addDc(dcId);
 }
 
-void Instance::Private::dcKeyChanged(DcId dcId, const AuthKeyPtr &key) {
-	_dcKeyChanged.fire_copy(dcId);
+void Instance::Private::dcPersistentKeyChanged(
+		DcId dcId,
+		const AuthKeyPtr &persistentKey) {
+	dcTemporaryKeyChanged(dcId);
 
 	if (isTemporaryDcId(dcId)) {
 		return;
 	}
 
-	QWriteLocker lock(&_keysForWriteLock);
-	if (key) {
-		_keysForWrite[dcId] = key;
-	} else {
-		_keysForWrite.erase(dcId);
+	const auto i = _keysForWrite.find(dcId);
+	if (i != _keysForWrite.end() && i->second == persistentKey) {
+		return;
+	} else if (i == _keysForWrite.end() && !persistentKey) {
+		return;
 	}
-	crl::on_main(_instance, [=] {
-		DEBUG_LOG(("AuthKey Info: writing auth keys, called by dc %1").arg(dcId));
-		Local::writeMtpData();
-	});
+	if (!persistentKey) {
+		_keysForWrite.erase(i);
+	} else if (i != _keysForWrite.end()) {
+		i->second = persistentKey;
+	} else {
+		_keysForWrite.emplace(dcId, persistentKey);
+	}
+	DEBUG_LOG(("AuthKey Info: writing auth keys, called by dc %1").arg(dcId));
+	Local::writeMtpData();
 }
 
-rpl::producer<DcId> Instance::Private::dcKeyChanged() const {
-	return _dcKeyChanged.events();
+void Instance::Private::dcTemporaryKeyChanged(DcId dcId) {
+	_dcTemporaryKeyChanged.fire_copy(dcId);
+}
+
+rpl::producer<DcId> Instance::Private::dcTemporaryKeyChanged() const {
+	return _dcTemporaryKeyChanged.events();
 }
 
 AuthKeysList Instance::Private::getKeysForWrite() const {
 	auto result = AuthKeysList();
 
-	QReadLocker lock(&_keysForWriteLock);
 	result.reserve(_keysForWrite.size());
-	for (auto &key : _keysForWrite) {
+	for (const auto &key : _keysForWrite) {
 		result.push_back(key.second);
 	}
 	return result;
@@ -736,15 +744,12 @@ void Instance::Private::addKeysForDestroy(AuthKeysList &&keys) {
 		const auto dcId = key->dcId();
 		auto shiftedDcId = MTP::destroyKeyNextDcId(dcId);
 
-		{
-			QWriteLocker lock(&_keysForWriteLock);
-			// There could be several keys for one dc if we're destroying them.
-			// Place them all in separate shiftedDcId so that they won't conflict.
-			while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) {
-				shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
-			}
-			_keysForWrite[shiftedDcId] = key;
+		// There could be several keys for one dc if we're destroying them.
+		// Place them all in separate shiftedDcId so that they won't conflict.
+		while (_keysForWrite.find(shiftedDcId) != _keysForWrite.cend()) {
+			shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
 		}
+		_keysForWrite[shiftedDcId] = key;
 
 		addDc(shiftedDcId, std::move(key));
 		startSession(shiftedDcId);
@@ -1352,7 +1357,8 @@ bool Instance::Private::onErrorDefault(mtpRequestId requestId, const RPCError &e
 		checkDelayedRequests();
 
 		return true;
-	} else if (code == 401 || (badGuestDc && _badGuestDcRequests.find(requestId) == _badGuestDcRequests.cend())) {
+	} else if ((code == 401 && err != "AUTH_KEY_PERM_EMPTY")
+		|| (badGuestDc && _badGuestDcRequests.find(requestId) == _badGuestDcRequests.cend())) {
 		auto dcWithShift = ShiftedDcId(0);
 		if (const auto shiftedDcId = queryRequestByDc(requestId)) {
 			dcWithShift = *shiftedDcId;
@@ -1572,10 +1578,7 @@ void Instance::Private::completedKeyDestroy(ShiftedDcId shiftedDcId) {
 	Expects(isKeysDestroyer());
 
 	removeDc(shiftedDcId);
-	{
-		QWriteLocker lock(&_keysForWriteLock);
-		_keysForWrite.erase(shiftedDcId);
-	}
+	_keysForWrite.erase(shiftedDcId);
 	killSession(shiftedDcId);
 	if (_dcenters.empty()) {
 		emit _instance->allKeysDestroyed();
@@ -1588,19 +1591,15 @@ void Instance::Private::checkMainDcKey() {
 	if (findSession(shiftedDcId)) {
 		return;
 	}
-	const auto key = [&] {
-		QReadLocker lock(&_keysForWriteLock);
-		const auto i = _keysForWrite.find(id);
-		return (i != end(_keysForWrite)) ? i->second : AuthKeyPtr();
-	}();
-	if (!key) {
+	const auto i = _keysForWrite.find(id);
+	if (i == end(_keysForWrite)) {
 		return;
 	}
-	const auto lastCheckTime = key->lastCheckTime();
+	const auto lastCheckTime = i->second->lastCheckTime();
 	if (lastCheckTime > 0 && lastCheckTime + kCheckKeyEach >= crl::now()) {
 		return;
 	}
-	_instance->sendDcKeyCheck(shiftedDcId, key);
+	_instance->sendDcKeyCheck(shiftedDcId, i->second);
 }
 
 void Instance::Private::keyDestroyedOnServer(DcId dcId, uint64 keyId) {
@@ -1608,7 +1607,7 @@ void Instance::Private::keyDestroyedOnServer(DcId dcId, uint64 keyId) {
 	if (const auto dc = findDc(dcId)) {
 		if (dc->destroyConfirmedForgottenKey(keyId)) {
 			LOG(("Key destroyed!"));
-			dcKeyChanged(dcId, nullptr);
+			dcPersistentKeyChanged(dcId, nullptr);
 		} else {
 			LOG(("Key already is different."));
 		}
@@ -1761,12 +1760,18 @@ void Instance::logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) {
 	_private->logout(onDone, onFail);
 }
 
-void Instance::dcKeyChanged(DcId dcId, const AuthKeyPtr &key) {
-	_private->dcKeyChanged(dcId, key);
+void Instance::dcPersistentKeyChanged(
+		DcId dcId,
+		const AuthKeyPtr &persistentKey) {
+	_private->dcPersistentKeyChanged(dcId, persistentKey);
 }
 
-rpl::producer<DcId> Instance::dcKeyChanged() const {
-	return _private->dcKeyChanged();
+void Instance::dcTemporaryKeyChanged(DcId dcId) {
+	_private->dcTemporaryKeyChanged(dcId);
+}
+
+rpl::producer<DcId> Instance::dcTemporaryKeyChanged() const {
+	return _private->dcTemporaryKeyChanged();
 }
 
 AuthKeysList Instance::getKeysForWrite() const {
@@ -1881,13 +1886,11 @@ void Instance::sendRequest(
 }
 
 void Instance::sendAnything(ShiftedDcId shiftedDcId, crl::time msCanWait) {
-	const auto session = _private->getSession(shiftedDcId);
-	session->sendAnything(msCanWait);
+	_private->getSession(shiftedDcId)->sendAnything(msCanWait);
 }
 
 void Instance::sendDcKeyCheck(ShiftedDcId shiftedDcId, const AuthKeyPtr &key) {
-	const auto session = _private->getSession(shiftedDcId);
-	session->sendDcKeyCheck(key);
+	_private->getSession(shiftedDcId)->sendDcKeyCheck(key);
 }
 
 Instance::~Instance() {
diff --git a/Telegram/SourceFiles/mtproto/mtp_instance.h b/Telegram/SourceFiles/mtproto/mtp_instance.h
index bf4ddc117..dc400e8b6 100644
--- a/Telegram/SourceFiles/mtproto/mtp_instance.h
+++ b/Telegram/SourceFiles/mtproto/mtp_instance.h
@@ -65,8 +65,9 @@ public:
 	[[nodiscard]] QString systemVersion() const;
 
 	// Main thread.
-	void dcKeyChanged(DcId dcId, const AuthKeyPtr &key);
-	[[nodiscard]] rpl::producer<DcId> dcKeyChanged() const;
+	void dcPersistentKeyChanged(DcId dcId, const AuthKeyPtr &persistentKey);
+	void dcTemporaryKeyChanged(DcId dcId);
+	[[nodiscard]] rpl::producer<DcId> dcTemporaryKeyChanged() const;
 	[[nodiscard]] AuthKeysList getKeysForWrite() const;
 	void addKeysForDestroy(AuthKeysList &&keys);
 
diff --git a/Telegram/SourceFiles/mtproto/mtproto_auth_key.cpp b/Telegram/SourceFiles/mtproto/mtproto_auth_key.cpp
index b2991a880..ef0693085 100644
--- a/Telegram/SourceFiles/mtproto/mtproto_auth_key.cpp
+++ b/Telegram/SourceFiles/mtproto/mtproto_auth_key.cpp
@@ -123,6 +123,16 @@ void AuthKey::setLastCheckTime(crl::time time) {
 	_lastCheckTime = time;
 }
 
+TimeId AuthKey::expiresAt() const {
+	return _expiresAt;
+}
+
+void AuthKey::setExpiresAt(TimeId expiresAt) {
+	Expects(_type == Type::Temporary);
+
+	_expiresAt = expiresAt;
+}
+
 void AuthKey::FillData(Data &authKey, bytes::const_span computedAuthKey) {
 	auto computedAuthKeySize = computedAuthKey.size();
 	Assert(computedAuthKeySize <= kSize);
diff --git a/Telegram/SourceFiles/mtproto/mtproto_auth_key.h b/Telegram/SourceFiles/mtproto/mtproto_auth_key.h
index 7120cfa48..082d47310 100644
--- a/Telegram/SourceFiles/mtproto/mtproto_auth_key.h
+++ b/Telegram/SourceFiles/mtproto/mtproto_auth_key.h
@@ -21,6 +21,7 @@ public:
 
 	enum class Type {
 		Generated,
+		Temporary,
 		ReadFromFile,
 		Local,
 	};
@@ -45,6 +46,8 @@ public:
 
 	[[nodiscard]] crl::time lastCheckTime() const;
 	void setLastCheckTime(crl::time time);
+	[[nodiscard]] TimeId expiresAt() const;
+	void setExpiresAt(TimeId expiresAt);
 
 	static void FillData(Data &authKey, bytes::const_span computedAuthKey);
 
@@ -56,6 +59,7 @@ private:
 	Data _key = { { gsl::byte{} } };
 	KeyId _keyId = 0;
 	crl::time _lastCheckTime = 0;
+	TimeId _expiresAt = 0;
 
 };
 
diff --git a/Telegram/SourceFiles/mtproto/session.cpp b/Telegram/SourceFiles/mtproto/session.cpp
index 99feebb62..f1ee15fcb 100644
--- a/Telegram/SourceFiles/mtproto/session.cpp
+++ b/Telegram/SourceFiles/mtproto/session.cpp
@@ -7,7 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 */
 #include "mtproto/session.h"
 
-#include "mtproto/details/mtproto_dc_key_checker.h"
 #include "mtproto/connection.h"
 #include "mtproto/dcenter.h"
 #include "mtproto/mtproto_auth_key.h"
@@ -222,9 +221,14 @@ bool SessionData::connectionInited() const {
 	return _owner ? _owner->connectionInited() : false;
 }
 
-AuthKeyPtr SessionData::getKey() const {
+AuthKeyPtr SessionData::getTemporaryKey() const {
 	QMutexLocker lock(&_ownerMutex);
-	return _owner ? _owner->getKey() : nullptr;
+	return _owner ? _owner->getTemporaryKey() : nullptr;
+}
+
+AuthKeyPtr SessionData::getPersistentKey() const {
+	QMutexLocker lock(&_ownerMutex);
+	return _owner ? _owner->getPersistentKey() : nullptr;
 }
 
 bool SessionData::acquireKeyCreation() {
@@ -232,10 +236,12 @@ bool SessionData::acquireKeyCreation() {
 	return _owner ? _owner->acquireKeyCreation() : false;
 }
 
-void SessionData::releaseKeyCreationOnDone(const AuthKeyPtr &key) {
+void SessionData::releaseKeyCreationOnDone(
+		const AuthKeyPtr &temporaryKey,
+		const AuthKeyPtr &persistentKey) {
 	QMutexLocker lock(&_ownerMutex);
 	if (_owner) {
-		_owner->releaseKeyCreationOnDone(key);
+		_owner->releaseKeyCreationOnDone(temporaryKey, persistentKey);
 	}
 }
 
@@ -246,10 +252,10 @@ void SessionData::releaseKeyCreationOnFail() {
 	}
 }
 
-void SessionData::destroyCdnKey(uint64 keyId) {
+void SessionData::destroyTemporaryKey(uint64 keyId) {
 	QMutexLocker lock(&_ownerMutex);
 	if (_owner) {
-		_owner->destroyCdnKey(keyId);
+		_owner->destroyTemporaryKey(keyId);
 	}
 }
 
@@ -278,7 +284,7 @@ Session::Session(
 }
 
 void Session::watchDcKeyChanges() {
-	_instance->dcKeyChanged(
+	_instance->dcTemporaryKeyChanged(
 	) | rpl::filter([=](DcId dcId) {
 		return (dcId == _shiftedDcId) || (dcId == BareDcId(_shiftedDcId));
 	}) | rpl::start_with_next([=] {
@@ -684,6 +690,29 @@ bool Session::acquireKeyCreation() {
 	return true;
 }
 
+void Session::releaseKeyCreationOnDone(
+		const AuthKeyPtr &temporaryKey,
+		const AuthKeyPtr &persistentKey) {
+	Expects(_myKeyCreation);
+
+	DEBUG_LOG(("AuthKey Info: Session key bound, setting, dcWithShift %1"
+		).arg(_shiftedDcId));
+	_dc->releaseKeyCreationOnDone(temporaryKey, persistentKey);
+	_myKeyCreation = false;
+
+	if (sharedDc()) {
+		const auto dcId = _dc->id();
+		const auto instance = _instance;
+		InvokeQueued(instance, [=] {
+			if (persistentKey) {
+				instance->dcPersistentKeyChanged(dcId, persistentKey);
+			} else {
+				instance->dcTemporaryKeyChanged(dcId);
+			}
+		});
+	}
+}
+
 void Session::releaseKeyCreationOnFail() {
 	Expects(_myKeyCreation);
 
@@ -691,36 +720,20 @@ void Session::releaseKeyCreationOnFail() {
 	_myKeyCreation = false;
 }
 
-void Session::releaseKeyCreationOnDone(const AuthKeyPtr &key) {
-	Expects(_myKeyCreation);
-
-	DEBUG_LOG(("AuthKey Info: Session key created, setting, dcWithShift %1").arg(_shiftedDcId));
-	_dc->releaseKeyCreationOnDone(key);
-	_myKeyCreation = false;
-
-	if (sharedDc()) {
-		const auto dcId = _dc->id();
-		const auto instance = _instance;
-		InvokeQueued(instance, [=] {
-			instance->dcKeyChanged(dcId, key);
-		});
-	}
-}
-
 void Session::notifyDcConnectionInited() {
 	DEBUG_LOG(("MTP Info: emitting MTProtoDC::connectionWasInited(), dcWithShift %1").arg(_shiftedDcId));
 	_dc->setConnectionInited();
 }
 
-void Session::destroyCdnKey(uint64 keyId) {
-	if (!_dc->destroyCdnKey(keyId)) {
+void Session::destroyTemporaryKey(uint64 keyId) {
+	if (!_dc->destroyTemporaryKey(keyId)) {
 		return;
 	}
 	if (sharedDc()) {
 		const auto dcId = _dc->id();
 		const auto instance = _instance;
 		InvokeQueued(instance, [=] {
-			instance->dcKeyChanged(dcId, nullptr);
+			instance->dcTemporaryKeyChanged(dcId);
 		});
 	}
 }
@@ -729,8 +742,12 @@ int32 Session::getDcWithShift() const {
 	return _shiftedDcId;
 }
 
-AuthKeyPtr Session::getKey() const {
-	return _dc->getKey();
+AuthKeyPtr Session::getTemporaryKey() const {
+	return _dc->getTemporaryKey();
+}
+
+AuthKeyPtr Session::getPersistentKey() const {
+	return _dc->getPersistentKey();
 }
 
 bool Session::connectionInited() const {
diff --git a/Telegram/SourceFiles/mtproto/session.h b/Telegram/SourceFiles/mtproto/session.h
index a0075a214..1eb307e40 100644
--- a/Telegram/SourceFiles/mtproto/session.h
+++ b/Telegram/SourceFiles/mtproto/session.h
@@ -283,11 +283,14 @@ public:
 		bool sendMsgStateInfo);
 
 	[[nodiscard]] bool connectionInited() const;
-	[[nodiscard]] AuthKeyPtr getKey() const;
+	[[nodiscard]] AuthKeyPtr getPersistentKey() const;
+	[[nodiscard]] AuthKeyPtr getTemporaryKey() const;
 	[[nodiscard]] bool acquireKeyCreation();
-	void releaseKeyCreationOnDone(const AuthKeyPtr &key);
+	void releaseKeyCreationOnDone(
+		const AuthKeyPtr &temporaryKey,
+		const AuthKeyPtr &persistentKey);
 	void releaseKeyCreationOnFail();
-	void destroyCdnKey(uint64 keyId);
+	void destroyTemporaryKey(uint64 keyId);
 
 	void detach();
 
@@ -351,14 +354,17 @@ public:
 
 	// Thread-safe.
 	[[nodiscard]] ShiftedDcId getDcWithShift() const;
-	[[nodiscard]] AuthKeyPtr getKey() const;
+	[[nodiscard]] AuthKeyPtr getPersistentKey() const;
+	[[nodiscard]] AuthKeyPtr getTemporaryKey() const;
 	[[nodiscard]] bool connectionInited() const;
 
 	// Connection thread.
 	[[nodiscard]] bool acquireKeyCreation();
+	void releaseKeyCreationOnDone(
+		const AuthKeyPtr &temporaryKey,
+		const AuthKeyPtr &persistentKey);
 	void releaseKeyCreationOnFail();
-	void releaseKeyCreationOnDone(const AuthKeyPtr &key);
-	void destroyCdnKey(uint64 keyId);
+	void destroyTemporaryKey(uint64 keyId);
 
 	void notifyDcConnectionInited();
 
diff --git a/Telegram/gyp/lib_mtproto.gyp b/Telegram/gyp/lib_mtproto.gyp
index 99ec10634..83fb05965 100644
--- a/Telegram/gyp/lib_mtproto.gyp
+++ b/Telegram/gyp/lib_mtproto.gyp
@@ -34,8 +34,8 @@
       '<(src_loc)',
     ],
     'sources': [
-      '<(src_loc)/mtproto/details/mtproto_dc_key_checker.cpp',
-      '<(src_loc)/mtproto/details/mtproto_dc_key_checker.h',
+      '<(src_loc)/mtproto/details/mtproto_dc_key_binder.cpp',
+      '<(src_loc)/mtproto/details/mtproto_dc_key_binder.h',
       '<(src_loc)/mtproto/details/mtproto_dc_key_creator.cpp',
       '<(src_loc)/mtproto/details/mtproto_dc_key_creator.h',
       '<(src_loc)/mtproto/details/mtproto_dump_to_text.cpp',