From 47a4ed6fe6bdca30fb58716236c83b3faf8eecc5 Mon Sep 17 00:00:00 2001 From: Alex Sokol / y9san9 Date: Wed, 11 Oct 2023 14:52:06 +0300 Subject: [PATCH] feat: initDH method implemented --- README.md | 32 ++++++- .../kotlin/kmp-library-convention.gradle.kts | 2 +- client/build.gradle.kts | 1 + client/ktor/build.gradle.kts | 1 + .../client/ktor/DataCenter.kt | 2 +- .../ktor/websocket/KtorWebsocketTransport.kt | 2 +- .../client/ktor/DataCenter.ios.kt | 2 +- .../client/ktor/DataCenter.js.kt | 2 +- .../kotlin/kotl/client/ktor/ClientMain.kt | 41 -------- .../kotlin/ktproto/client/ktor/ClientMain.kt | 71 ++++++++++++++ .../client/ktor/DataCenter.jvm.kt | 2 +- .../client/ktor/socket/KtorSocketTransport.kt | 2 +- .../kotlin/ktproto/client/MTProtoClient.kt | 8 +- .../kotlin/ktproto/client/MTProtoRequest.kt | 9 -- .../authorization/CreateAuthorizationKey.kt | 51 ++++++++-- .../client/plain/PlainMTProtoClient.kt | 14 +-- .../ktproto/client/requests/RequestPQ.kt | 18 ++-- .../kotlin/ktproto/client/rsa/RsaPublicKey.kt | 51 ++++++++++ .../ktproto/client/rsa/TLRsaPublicKey.kt | 10 ++ .../client/serialization/MTProtoClient.kt | 18 ++-- .../client/serialization/MTProtoRequest.kt | 3 + .../serialization/MTProtoRequestContainer.kt | 3 - gradle/libs.versions.toml | 3 +- libs/crypto/build.gradle.kts | 4 + .../kotlin/kotl/crypto/dh/PollardRhoBrent.kt | 55 ----------- .../kotlin/ktproto/crypto/asn1/Asn1Object.kt | 34 +++++++ .../kotlin/ktproto/crypto/asn1/Asn1Parser.kt | 71 ++++++++++++++ .../crypto/factorization/PollardRhoBrent.kt | 96 +++++++++++++++++++ .../kotlin/ktproto/crypto/rsa/RsaPublicKey.kt | 60 ++++++++++++ .../kotlin/ktproto/crypto/sha/Sha1.kt | 3 + .../kotlin/ktproto/crypto/sha/TestSha1.kt | 15 +++ .../kotlin/ktproto/crypto/sha/Crypto.kt | 30 ++++++ .../kotlin/ktproto/crypto/sha/Sha1.js.kt | 6 ++ .../kotlin/ktproto/crypto/sha/Sha1.jvm.kt | 13 +++ .../kotlin/ktproto/crypto/sha/Sha1.native.kt | 23 +++++ .../kotlin/ktproto/io/memory/Read.kt | 9 ++ .../kotlin/ktproto/stdlib/bytes/Pad.kt | 8 ++ .../kotlin/ktproto/stdlib/int/BigInt.kt | 65 ------------- .../kotlin/ktproto/stdlib/int/BigIntRange.kt | 28 ------ .../kotlin/ktproto/stdlib/int/BigInt.js.kt | 82 ---------------- .../jsMain/kotlin/ktproto/stdlib/int/Main.kt | 7 -- .../ktproto/stdlib/platform/JsPlatform.kt | 14 +++ .../src/jsTest/kotlin/BigIntTest.kt | 20 ---- .../kotlin/ktproto/stdlib/int/BigInt.jvm.kt | 68 ------------- .../ktproto/session/MessageIdProvider.kt | 2 +- types/src/commonMain/kotlin/kotl/_ | 0 .../kotlin/{kotl => ktproto}/time/Clock.kt | 2 +- .../{kotl => ktproto}/time/Clock.ios.kt | 3 +- .../kotlin/{kotl => ktproto}/time/Clock.js.kt | 5 +- .../{kotl => ktproto}/time/Clock.jvm.kt | 3 +- 50 files changed, 648 insertions(+), 426 deletions(-) rename client/ktor/src/commonMain/kotlin/{kotl => ktproto}/client/ktor/DataCenter.kt (96%) rename client/ktor/src/commonMain/kotlin/{kotl => ktproto}/client/ktor/websocket/KtorWebsocketTransport.kt (98%) rename client/ktor/src/iosMain/kotlin/{kotl => ktproto}/client/ktor/DataCenter.ios.kt (61%) rename client/ktor/src/jsMain/kotlin/{kotl => ktproto}/client/ktor/DataCenter.js.kt (61%) delete mode 100644 client/ktor/src/jvmMain/kotlin/kotl/client/ktor/ClientMain.kt create mode 100644 client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/ClientMain.kt rename client/ktor/src/jvmMain/kotlin/{kotl => ktproto}/client/ktor/DataCenter.jvm.kt (61%) rename client/ktor/src/jvmMain/kotlin/{kotl => ktproto}/client/ktor/socket/KtorSocketTransport.kt (97%) delete mode 100644 client/src/commonMain/kotlin/ktproto/client/MTProtoRequest.kt create mode 100644 client/src/commonMain/kotlin/ktproto/client/rsa/RsaPublicKey.kt create mode 100644 client/src/commonMain/kotlin/ktproto/client/rsa/TLRsaPublicKey.kt create mode 100644 client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequest.kt delete mode 100644 client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequestContainer.kt delete mode 100644 libs/crypto/src/commonMain/kotlin/kotl/crypto/dh/PollardRhoBrent.kt create mode 100644 libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Object.kt create mode 100644 libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Parser.kt create mode 100644 libs/crypto/src/commonMain/kotlin/ktproto/crypto/factorization/PollardRhoBrent.kt create mode 100644 libs/crypto/src/commonMain/kotlin/ktproto/crypto/rsa/RsaPublicKey.kt create mode 100644 libs/crypto/src/commonMain/kotlin/ktproto/crypto/sha/Sha1.kt create mode 100644 libs/crypto/src/commonTest/kotlin/ktproto/crypto/sha/TestSha1.kt create mode 100644 libs/crypto/src/jsMain/kotlin/ktproto/crypto/sha/Crypto.kt create mode 100644 libs/crypto/src/jsMain/kotlin/ktproto/crypto/sha/Sha1.js.kt create mode 100644 libs/crypto/src/jvmMain/kotlin/ktproto/crypto/sha/Sha1.jvm.kt create mode 100644 libs/crypto/src/nativeMain/kotlin/ktproto/crypto/sha/Sha1.native.kt delete mode 100644 libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigInt.kt delete mode 100644 libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigIntRange.kt delete mode 100644 libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/BigInt.js.kt delete mode 100644 libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/Main.kt create mode 100644 libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/platform/JsPlatform.kt delete mode 100644 libs/stdlib-extensions/src/jsTest/kotlin/BigIntTest.kt delete mode 100644 libs/stdlib-extensions/src/jvmMain/kotlin/ktproto/stdlib/int/BigInt.jvm.kt delete mode 100644 types/src/commonMain/kotlin/kotl/_ rename types/src/commonMain/kotlin/{kotl => ktproto}/time/Clock.kt (89%) rename types/src/iosMain/kotlin/{kotl => ktproto}/time/Clock.ios.kt (79%) rename types/src/jsMain/kotlin/{kotl => ktproto}/time/Clock.js.kt (72%) rename types/src/jvmMain/kotlin/{kotl => ktproto}/time/Clock.jvm.kt (73%) diff --git a/README.md b/README.md index f87f5c1..1eefccc 100644 --- a/README.md +++ b/README.md @@ -1 +1,31 @@ -# TODO \ No newline at end of file +# ktproto - Kotlin Library for MTProto Protocol + +⚠️ ALL OF THIS IS WORK IN PROGRESS ⚠️ + +`ktproto` is a Kotlin library designed to simplify working with Telegram's MTProto protocol. This library provides the tools you need to establish connections, perform authentication, and interact with the Telegram API using the MTProto protocol. + +## Features + +- Establish connections to Telegram's servers. +- Interact with the Telegram API using MTProto protocol. +- Built-in integration with TL (Maintained in a separate repo: https://github.com/kotlin-telegram/ktproto) + +## Usage + +```kotlin +@OngoingConnection +private suspend fun main(): Unit = weakCoroutineScope { + val transport = openKtorSocketTransport( + hostname = "149.154.167.51", + port = 443 + ) + val client = plainMTProtoClient( + transport = transport, + scope = this + ) + repeat(10) { + createAuthorizationKey(client, keys) + } + // Sending encrypted requests are not supported ATM +} +``` diff --git a/build-logic/src/main/kotlin/kmp-library-convention.gradle.kts b/build-logic/src/main/kotlin/kmp-library-convention.gradle.kts index f38f84b..c4de319 100644 --- a/build-logic/src/main/kotlin/kmp-library-convention.gradle.kts +++ b/build-logic/src/main/kotlin/kmp-library-convention.gradle.kts @@ -12,7 +12,7 @@ kotlin { jvm { jvmToolchain(8) } - js(IR) { + js { browser() nodejs() } diff --git a/client/build.gradle.kts b/client/build.gradle.kts index bef8ce1..4d3cccd 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -6,6 +6,7 @@ plugins { version = libs.versions.ktprotoVersion.get() dependencies { + commonMainImplementation(projects.libs.crypto) commonMainImplementation(libs.koTL.serialization) commonMainImplementation(libs.kotlinxSerialization) commonMainApi(projects.session) diff --git a/client/ktor/build.gradle.kts b/client/ktor/build.gradle.kts index eeb9215..85009d0 100644 --- a/client/ktor/build.gradle.kts +++ b/client/ktor/build.gradle.kts @@ -12,6 +12,7 @@ dependencies { commonMainImplementation(projects.types) commonMainImplementation(libs.koTL.serialization) commonMainApi(libs.ktor.client) + commonMainApi(libs.kotlinxSerialization) jvmMainImplementation(libs.ktor.client.cio) jvmMainImplementation(libs.ktor.client.logging) } diff --git a/client/ktor/src/commonMain/kotlin/kotl/client/ktor/DataCenter.kt b/client/ktor/src/commonMain/kotlin/ktproto/client/ktor/DataCenter.kt similarity index 96% rename from client/ktor/src/commonMain/kotlin/kotl/client/ktor/DataCenter.kt rename to client/ktor/src/commonMain/kotlin/ktproto/client/ktor/DataCenter.kt index 72f5b27..34bf784 100644 --- a/client/ktor/src/commonMain/kotlin/kotl/client/ktor/DataCenter.kt +++ b/client/ktor/src/commonMain/kotlin/ktproto/client/ktor/DataCenter.kt @@ -1,4 +1,4 @@ -package kotl.client.ktor +package ktproto.client.ktor // todo: move file to kotel, since it's telegram, not MTProto // ktproto may also be used for TON (lib name kton) diff --git a/client/ktor/src/commonMain/kotlin/kotl/client/ktor/websocket/KtorWebsocketTransport.kt b/client/ktor/src/commonMain/kotlin/ktproto/client/ktor/websocket/KtorWebsocketTransport.kt similarity index 98% rename from client/ktor/src/commonMain/kotlin/kotl/client/ktor/websocket/KtorWebsocketTransport.kt rename to client/ktor/src/commonMain/kotlin/ktproto/client/ktor/websocket/KtorWebsocketTransport.kt index 84f1b12..29da708 100644 --- a/client/ktor/src/commonMain/kotlin/kotl/client/ktor/websocket/KtorWebsocketTransport.kt +++ b/client/ktor/src/commonMain/kotlin/ktproto/client/ktor/websocket/KtorWebsocketTransport.kt @@ -1,4 +1,4 @@ -package kotl.client.ktor.websocket +package ktproto.client.ktor.websocket import io.ktor.client.* import io.ktor.client.plugins.websocket.* diff --git a/client/ktor/src/iosMain/kotlin/kotl/client/ktor/DataCenter.ios.kt b/client/ktor/src/iosMain/kotlin/ktproto/client/ktor/DataCenter.ios.kt similarity index 61% rename from client/ktor/src/iosMain/kotlin/kotl/client/ktor/DataCenter.ios.kt rename to client/ktor/src/iosMain/kotlin/ktproto/client/ktor/DataCenter.ios.kt index dbff4a1..319dae2 100644 --- a/client/ktor/src/iosMain/kotlin/kotl/client/ktor/DataCenter.ios.kt +++ b/client/ktor/src/iosMain/kotlin/ktproto/client/ktor/DataCenter.ios.kt @@ -1,3 +1,3 @@ -package kotl.client.ktor +package ktproto.client.ktor internal actual fun isJS(): Boolean = false diff --git a/client/ktor/src/jsMain/kotlin/kotl/client/ktor/DataCenter.js.kt b/client/ktor/src/jsMain/kotlin/ktproto/client/ktor/DataCenter.js.kt similarity index 61% rename from client/ktor/src/jsMain/kotlin/kotl/client/ktor/DataCenter.js.kt rename to client/ktor/src/jsMain/kotlin/ktproto/client/ktor/DataCenter.js.kt index 0f2565a..c48cf00 100644 --- a/client/ktor/src/jsMain/kotlin/kotl/client/ktor/DataCenter.js.kt +++ b/client/ktor/src/jsMain/kotlin/ktproto/client/ktor/DataCenter.js.kt @@ -1,3 +1,3 @@ -package kotl.client.ktor +package ktproto.client.ktor internal actual fun isJS(): Boolean = true diff --git a/client/ktor/src/jvmMain/kotlin/kotl/client/ktor/ClientMain.kt b/client/ktor/src/jvmMain/kotlin/kotl/client/ktor/ClientMain.kt deleted file mode 100644 index b4b8ac1..0000000 --- a/client/ktor/src/jvmMain/kotlin/kotl/client/ktor/ClientMain.kt +++ /dev/null @@ -1,41 +0,0 @@ -package kotl.client.ktor - -import io.ktor.client.* -import io.ktor.client.plugins.logging.* -import kotl.client.ktor.socket.openKtorSocketTransport -import kotl.serialization.int.Int128 -import kotlinx.coroutines.* -import ktproto.client.authorization.createAuthorizationKey -import ktproto.client.plain.plainMTProtoClient -import ktproto.client.requests.TLRequestPQ -import ktproto.client.serialization.execute -import ktproto.io.annotation.OngoingConnection -import ktproto.stdlib.scope.weakCoroutineScope -import kotlin.random.Random - -private object DC : DataCenter { - override val name: String = "pluto" - override val isTest: Boolean = false -} - -private val httpClient = HttpClient { - Logging { - level = LogLevel.ALL - logger = object : Logger { - override fun log(message: String) = println(message) - } - } -} - -@OngoingConnection -private suspend fun main(): Unit = weakCoroutineScope { - val transport = openKtorSocketTransport( - hostname = "149.154.167.51", - port = 443 - ) - val client = plainMTProtoClient( - transport = transport, - scope = this - ) - createAuthorizationKey(client) -} diff --git a/client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/ClientMain.kt b/client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/ClientMain.kt new file mode 100644 index 0000000..6ec7b3e --- /dev/null +++ b/client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/ClientMain.kt @@ -0,0 +1,71 @@ +package ktproto.client.ktor + +import io.ktor.client.* +import io.ktor.client.plugins.logging.* +import kotl.serialization.TL +import kotl.serialization.bare.bare +import kotl.serialization.bytes.Bytes +import kotlinx.serialization.encodeToByteArray +import ktproto.client.authorization.createAuthorizationKey +import ktproto.client.ktor.socket.openKtorSocketTransport +import ktproto.client.plain.plainMTProtoClient +import ktproto.client.rsa.RsaPublicKey +import ktproto.client.rsa.TLRsaPublicKey +import ktproto.client.rsa.fingerprint +import ktproto.io.annotation.OngoingConnection +import ktproto.stdlib.scope.weakCoroutineScope + +private object DC : DataCenter { + override val name: String = "pluto" + override val isTest: Boolean = false +} + +private val httpClient = HttpClient { + Logging { + level = LogLevel.ALL + logger = object : Logger { + override fun log(message: String) = println(message) + } + } +} + +// Fingerprint: d09d1d85de64fd85 +private val productionKey = RsaPublicKey(""" + -----BEGIN RSA PUBLIC KEY----- + MIIBCgKCAQEA6LszBcC1LGzyr992NzE0ieY+BSaOW622Aa9Bd4ZHLl+TuFQ4lo4g + 5nKaMBwK/BIb9xUfg0Q29/2mgIR6Zr9krM7HjuIcCzFvDtr+L0GQjae9H0pRB2OO + 62cECs5HKhT5DZ98K33vmWiLowc621dQuwKWSQKjWf50XYFw42h21P2KXUGyp2y/ + +aEyZ+uVgLLQbRA1dEjSDZ2iGRy12Mk5gpYc397aYp438fsJoHIgJ2lgMv5h7WY9 + t6N/byY9Nw9p21Og3AoXSL2q/2IJ1WRUhebgAdGVMlV1fkuOQoEzR7EdpqtQD9Cs + 5+bfo3Nhmcyvk5ftB0WkJ9z6bNZ7yxrP8wIDAQAB + -----END RSA PUBLIC KEY----- +""".trimIndent()) + +// Fingerprint: b25898df208d2603 +private val testKey = RsaPublicKey(""" + -----BEGIN RSA PUBLIC KEY----- + MIIBCgKCAQEAyMEdY1aR+sCR3ZSJrtztKTKqigvO/vBfqACJLZtS7QMgCGXJ6XIR + yy7mx66W0/sOFa7/1mAZtEoIokDP3ShoqF4fVNb6XeqgQfaUHd8wJpDWHcR2OFwv + plUUI1PLTktZ9uW2WE23b+ixNwJjJGwBDJPQEQFBE+vfmH0JP503wr5INS1poWg/ + j25sIWeYPHYeOrFp/eXaqhISP6G+q2IeTaWTXpwZj4LzXq5YOpk4bYEQ6mvRq7D1 + aHWfYmlEGepfaYR8Q0YqvvhYtMte3ITnuSJs171+GDqpdKcSwHnd6FudwGO4pcCO + j4WcDuXc2CTHgH8gFTNhp/Y8/SpDOhvn9QIDAQAB + -----END RSA PUBLIC KEY----- +""".trimIndent()) + +private val keys = listOf(productionKey, testKey) + +@OngoingConnection +private suspend fun main(): Unit = weakCoroutineScope { + val transport = openKtorSocketTransport( + hostname = "149.154.167.51", + port = 443 + ) + val client = plainMTProtoClient( + transport = transport, + scope = this + ) + repeat(10) { + createAuthorizationKey(client, keys) + } +} diff --git a/client/ktor/src/jvmMain/kotlin/kotl/client/ktor/DataCenter.jvm.kt b/client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/DataCenter.jvm.kt similarity index 61% rename from client/ktor/src/jvmMain/kotlin/kotl/client/ktor/DataCenter.jvm.kt rename to client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/DataCenter.jvm.kt index dbff4a1..319dae2 100644 --- a/client/ktor/src/jvmMain/kotlin/kotl/client/ktor/DataCenter.jvm.kt +++ b/client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/DataCenter.jvm.kt @@ -1,3 +1,3 @@ -package kotl.client.ktor +package ktproto.client.ktor internal actual fun isJS(): Boolean = false diff --git a/client/ktor/src/jvmMain/kotlin/kotl/client/ktor/socket/KtorSocketTransport.kt b/client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/socket/KtorSocketTransport.kt similarity index 97% rename from client/ktor/src/jvmMain/kotlin/kotl/client/ktor/socket/KtorSocketTransport.kt rename to client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/socket/KtorSocketTransport.kt index 4646eae..46cf3a7 100644 --- a/client/ktor/src/jvmMain/kotlin/kotl/client/ktor/socket/KtorSocketTransport.kt +++ b/client/ktor/src/jvmMain/kotlin/ktproto/client/ktor/socket/KtorSocketTransport.kt @@ -1,4 +1,4 @@ -package kotl.client.ktor.socket +package ktproto.client.ktor.socket import io.ktor.network.selector.* import io.ktor.network.sockets.* diff --git a/client/src/commonMain/kotlin/ktproto/client/MTProtoClient.kt b/client/src/commonMain/kotlin/ktproto/client/MTProtoClient.kt index 1d9d388..717f4b5 100644 --- a/client/src/commonMain/kotlin/ktproto/client/MTProtoClient.kt +++ b/client/src/commonMain/kotlin/ktproto/client/MTProtoClient.kt @@ -1,9 +1,15 @@ package ktproto.client +import kotl.core.descriptor.TLExpressionDescriptor import kotl.core.element.TLExpression +import kotl.core.element.TLFunction import kotlinx.coroutines.flow.Flow public interface MTProtoClient { public val updates: Flow - public suspend fun execute(request: MTProtoRequest): TLExpression + + public suspend fun execute( + function: TLFunction, + responseDescriptor: TLExpressionDescriptor + ): TLExpression } diff --git a/client/src/commonMain/kotlin/ktproto/client/MTProtoRequest.kt b/client/src/commonMain/kotlin/ktproto/client/MTProtoRequest.kt deleted file mode 100644 index fc4f250..0000000 --- a/client/src/commonMain/kotlin/ktproto/client/MTProtoRequest.kt +++ /dev/null @@ -1,9 +0,0 @@ -package ktproto.client - -import kotl.core.descriptor.TLExpressionDescriptor -import kotl.core.element.TLFunction - -public data class MTProtoRequest( - public val function: TLFunction, - public val responseDescriptor: TLExpressionDescriptor -) diff --git a/client/src/commonMain/kotlin/ktproto/client/authorization/CreateAuthorizationKey.kt b/client/src/commonMain/kotlin/ktproto/client/authorization/CreateAuthorizationKey.kt index 0187eac..bf118f3 100644 --- a/client/src/commonMain/kotlin/ktproto/client/authorization/CreateAuthorizationKey.kt +++ b/client/src/commonMain/kotlin/ktproto/client/authorization/CreateAuthorizationKey.kt @@ -1,20 +1,57 @@ package ktproto.client.authorization +import ktproto.crypto.factorization.PollardRhoBrent import kotl.serialization.int.Int128 import ktproto.client.MTProtoClient -import ktproto.client.requests.TLRequestPQ -import ktproto.client.serialization.execute +import ktproto.client.requests.requestPQ +import ktproto.client.rsa.RsaPublicKey +import ktproto.client.rsa.fingerprint +import ktproto.stdlib.bytes.decodeLong import kotlin.random.Random public suspend fun createAuthorizationKey( - client: MTProtoClient + client: MTProtoClient, + keys: List ) { + val (p, q, publicKey, serverNonce) = initDH(client, keys) + +} + +private data class InitDHResult( + val p: ULong, + val q: ULong, + val publicKey: RsaPublicKey, + val serverNonce: Int128 +) + +private suspend fun initDH( + client: MTProtoClient, + keys: List +): InitDHResult { + // 1) val nonce = Random.nextInt128() - val request = client.execute(TLRequestPQ(nonce)) - require(request.nonce.data.contentEquals(nonce.data)) { - "Server responded with invalid nonce (actual: $nonce, expected: ${request.nonce})" + val response = client.requestPQ(nonce) + + require(response.nonce.data.contentEquals(nonce.data)) { + "Server responded with invalid nonce (actual: $nonce, expected: ${response.nonce})" + } + require(response.pq.payload.size <= 8) { + "Resulted payload size is more than is supported by ktproto. This should never happen, but if it did, then that is not your fault, just report it to https://github.com/ktproto/issues" } - println(request) + + // 2) + val publicKey = keys.firstOrNull { key -> + key.fingerprint() in response.serverPublicKeyFingerprints + } ?: error("Couldn't match any server_public_key_fingerprints. Response: ${response.serverPublicKeyFingerprints}, Expected: ${keys.map { key -> key.fingerprint() }}") + + // 3) + val pq = response.pq.payload + .apply(ByteArray::reverse) + .decodeLong() + .toULong() + + val (p, q) = PollardRhoBrent.factorize(pq) + return InitDHResult(p, q, publicKey, response.serverNonce) } private fun Random.nextInt128() = Int128( diff --git a/client/src/commonMain/kotlin/ktproto/client/plain/PlainMTProtoClient.kt b/client/src/commonMain/kotlin/ktproto/client/plain/PlainMTProtoClient.kt index 59d3e8d..8b7947d 100644 --- a/client/src/commonMain/kotlin/ktproto/client/plain/PlainMTProtoClient.kt +++ b/client/src/commonMain/kotlin/ktproto/client/plain/PlainMTProtoClient.kt @@ -1,16 +1,16 @@ package ktproto.client.plain import kotl.core.decoder.decodeFromByteArray +import kotl.core.descriptor.TLExpressionDescriptor import kotl.core.element.TLExpression +import kotl.core.element.TLFunction import kotl.core.encoder.encodeToByteArray -import kotl.time.Clock +import ktproto.time.Clock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flow import ktproto.client.MTProtoClient -import ktproto.client.MTProtoRequest import ktproto.io.annotation.OngoingConnection import ktproto.session.MTProtoSession import ktproto.session.SessionConnector @@ -61,12 +61,14 @@ private class PlainMTProtoClient( override val updates: Flow = flow { awaitCancellation() } + @OptIn(ExperimentalStdlibApi::class) override suspend fun execute( - request: MTProtoRequest + function: TLFunction, + responseDescriptor: TLExpressionDescriptor ): TLExpression { - val bytes = request.function.encodeToByteArray() + val bytes = function.encodeToByteArray() val message = MTProtoSession.Message(bytes) val response = session.sendRequest(message) { it } - return request.responseDescriptor.decodeFromByteArray(response.bytes) + return responseDescriptor.decodeFromByteArray(response.bytes) } } diff --git a/client/src/commonMain/kotlin/ktproto/client/requests/RequestPQ.kt b/client/src/commonMain/kotlin/ktproto/client/requests/RequestPQ.kt index b04d346..43e31d8 100644 --- a/client/src/commonMain/kotlin/ktproto/client/requests/RequestPQ.kt +++ b/client/src/commonMain/kotlin/ktproto/client/requests/RequestPQ.kt @@ -2,24 +2,30 @@ package ktproto.client.requests import kotl.serialization.annotation.Crc32 import kotl.serialization.annotation.TLRpc -import kotl.serialization.annotation.TLSize +import kotl.serialization.bytes.Bytes import kotl.serialization.int.Int128 import kotlinx.serialization.Serializable -import ktproto.client.serialization.MTProtoRequestContainer +import ktproto.client.MTProtoClient +import ktproto.client.serialization.MTProtoRequest +import ktproto.client.serialization.execute // req_pq_multi#be7e8ef1 nonce:int128 = ResPQ; @Serializable @TLRpc(crc32 = 0xbe7e8ef1_u) public data class TLRequestPQ( public val nonce: Int128 -) : MTProtoRequestContainer +) : MTProtoRequest -// resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector long = ResPQ; +// resPQ#05162463 nonce:int128 server_nonce:int128 pq:bytes server_public_key_fingerprints:Vector long = ResPQ; @Serializable @Crc32(value = 0x05162463_u) public data class TLResponsePQ( public val nonce: Int128, public val serverNonce: Int128, - public val pq: String, - public val serverPublicKey: List + public val pq: Bytes, + public val serverPublicKeyFingerprints: List ) + +public suspend fun MTProtoClient.requestPQ( + nonce: Int128 +): TLResponsePQ = execute(TLRequestPQ(nonce)) diff --git a/client/src/commonMain/kotlin/ktproto/client/rsa/RsaPublicKey.kt b/client/src/commonMain/kotlin/ktproto/client/rsa/RsaPublicKey.kt new file mode 100644 index 0000000..8f15d91 --- /dev/null +++ b/client/src/commonMain/kotlin/ktproto/client/rsa/RsaPublicKey.kt @@ -0,0 +1,51 @@ +package ktproto.client.rsa + +import kotl.serialization.TL +import kotl.serialization.bytes.Bytes +import kotlinx.serialization.encodeToByteArray +import ktproto.crypto.sha.sha1 +import ktproto.io.memory.MemoryArena +import ktproto.io.memory.drop +import ktproto.io.memory.scanLong +import kotl.serialization.bare.bare +import ktproto.crypto.rsa.RsaPublicKey as InternalRsaPublicKey + +public data class RsaPublicKey( + public val n: ByteArray, + public val e: ByteArray +) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is RsaPublicKey) return false + + if (!n.contentEquals(other.n)) return false + if (!e.contentEquals(other.e)) return false + + return true + } + + override fun hashCode(): Int { + var result = n.contentHashCode() + result = 31 * result + e.contentHashCode() + return result + } +} + +public fun RsaPublicKey(string: String): RsaPublicKey { + val (n, e) = InternalRsaPublicKey(string) + return RsaPublicKey(n, e) +} + +public fun RsaPublicKey(bytes: ByteArray): RsaPublicKey { + val (n, e) = InternalRsaPublicKey(bytes) + return RsaPublicKey(n, e) +} + +public suspend fun RsaPublicKey.fingerprint(): Long { + val n = Bytes(n) + val e = Bytes(e) + val key = TLRsaPublicKey(n, e).bare + val bytes = TL.encodeToByteArray(key) + val sha1 = MemoryArena.of(bytes.sha1()) + return sha1.drop(n = 12).scanLong() +} diff --git a/client/src/commonMain/kotlin/ktproto/client/rsa/TLRsaPublicKey.kt b/client/src/commonMain/kotlin/ktproto/client/rsa/TLRsaPublicKey.kt new file mode 100644 index 0000000..c9db76b --- /dev/null +++ b/client/src/commonMain/kotlin/ktproto/client/rsa/TLRsaPublicKey.kt @@ -0,0 +1,10 @@ +package ktproto.client.rsa + +import kotl.serialization.bytes.Bytes +import kotlinx.serialization.Serializable + +@Serializable +public class TLRsaPublicKey( + public val n: Bytes, + public val e: Bytes +) diff --git a/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoClient.kt b/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoClient.kt index 1d907f4..847a86a 100644 --- a/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoClient.kt +++ b/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoClient.kt @@ -2,28 +2,26 @@ package ktproto.client.serialization import kotl.core.element.TLFunction import kotl.serialization.TL -import kotl.serialization.decodeFromTLElement import kotl.serialization.extensions.asTLDescriptor import kotlinx.serialization.serializer import ktproto.client.MTProtoClient -import ktproto.client.MTProtoRequest +import kotlin.reflect.KType import kotlin.reflect.typeOf -public suspend inline fun , reified R> MTProtoClient.execute(request: T): R { - val descriptor = MTProtoRequestDescriptor(typeOf(), typeOf()) - return execute(request, descriptor) +public suspend inline fun , reified R> MTProtoClient.execute(request: T): R { + return execute(request, typeOf(), typeOf()) } @Suppress("UNCHECKED_CAST") public suspend fun MTProtoClient.execute( request: T, - descriptor: MTProtoRequestDescriptor + requestType: KType, + responseType: KType ): R { - val function = TL.encodeToTLElement(serializer(descriptor.functionType), request) + val function = TL.encodeToTLElement(serializer(requestType), request) if (function !is TLFunction) error("Can only execute TLFunction, but got $function") - val responseSerializer = serializer(descriptor.returnType) + val responseSerializer = serializer(responseType) val responseDescriptor = responseSerializer.descriptor.asTLDescriptor() - val mtProtoRequest = MTProtoRequest(function, responseDescriptor) - val expression = execute(mtProtoRequest) + val expression = execute(function, responseDescriptor) return TL.decodeFromTLElement(responseSerializer, expression) as R } diff --git a/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequest.kt b/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequest.kt new file mode 100644 index 0000000..776deef --- /dev/null +++ b/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequest.kt @@ -0,0 +1,3 @@ +package ktproto.client.serialization + +public interface MTProtoRequest diff --git a/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequestContainer.kt b/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequestContainer.kt deleted file mode 100644 index f1fb102..0000000 --- a/client/src/commonMain/kotlin/ktproto/client/serialization/MTProtoRequestContainer.kt +++ /dev/null @@ -1,3 +0,0 @@ -package ktproto.client.serialization - -public interface MTProtoRequestContainer diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ba44394..e3524e1 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] kotlin = "1.9.20-Beta2" -kotlVersion = "0.0.5" +kotlVersion = "0.0.14" ktprotoVersion = "0.0.1" kotlinxSerialization = "1.5.0" kotlinxCoroutines = "1.7.3" @@ -15,6 +15,7 @@ ktor-client = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" } ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" } kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" } +kotlinxCoroutinesTest = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-core", version.ref = "kotlinxSerialization" } # Gradle plugins diff --git a/libs/crypto/build.gradle.kts b/libs/crypto/build.gradle.kts index 0ca350d..0e8d906 100644 --- a/libs/crypto/build.gradle.kts +++ b/libs/crypto/build.gradle.kts @@ -6,5 +6,9 @@ plugins { version = libs.versions.ktprotoVersion.get() dependencies { + commonMainImplementation(libs.kotlinxCoroutines) commonMainImplementation(projects.libs.stdlibExtensions) + commonMainImplementation(projects.libs.io) + commonTestImplementation(libs.kotlinxCoroutinesTest) + commonTestImplementation(kotlin("test")) } diff --git a/libs/crypto/src/commonMain/kotlin/kotl/crypto/dh/PollardRhoBrent.kt b/libs/crypto/src/commonMain/kotlin/kotl/crypto/dh/PollardRhoBrent.kt deleted file mode 100644 index d9dd82e..0000000 --- a/libs/crypto/src/commonMain/kotlin/kotl/crypto/dh/PollardRhoBrent.kt +++ /dev/null @@ -1,55 +0,0 @@ -package kotl.crypto.dh - - -import ktproto.stdlib.int.* -import kotlin.random.Random - -public object PollardRhoBrent { - public fun factorize(pq: BigInt): Pair { - if (pq % 2.bi == 0.bi) return Pair(2.bi, pq / 2.bi) - - var y = Random.nextBigInt(1.bi, pq - 1.bi) - var c = Random.nextBigInt(1.bi, pq - 1.bi) - var m = Random.nextBigInt(1.bi, pq - 1.bi) - - var g = 1.bi - var r = 1.bi - var q = 1.bi - - var ys: BigInt = 0.bi - - while (g == 1.bi) { - val x = y - repeat(r) { - y = ((y * y) % pq + c) % pq - } - var k = 0.bi - while (k < r && g == 1.bi) { - ys = y - repeat(min(m, r - k)) { - y = ((y * y) % pq + c) % pq - q = q * (abs(x - y)) % pq - } - g = gcd(q, pq) - k += m - } - r *= r - } - - if (g == pq) { - while (true) { - ys = ((ys * ys) % pq + c) % pq - g = gcd(abs(y - ys), pq) - if (g > 1.bi) break - } - } - - // p, q - return g to pq / g - } - - private tailrec fun gcd(a: BigInt, b: BigInt): BigInt { - if (b == 0.bi) return a - return gcd(b, a % b) - } -} diff --git a/libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Object.kt b/libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Object.kt new file mode 100644 index 0000000..dc57ad7 --- /dev/null +++ b/libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Object.kt @@ -0,0 +1,34 @@ +package ktproto.crypto.asn1 + +public sealed interface Asn1Object { + public val bytes: ByteArray? + public val children: List? + + public data class Container( + override val children: List + ) : Asn1Object { + override val bytes: Nothing? = null + } + + public data class Value( + override val bytes: ByteArray? + ) : Asn1Object { + override val children: Nothing? = null + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Value) return false + + if (bytes != null) { + if (other.bytes == null) return false + if (!bytes.contentEquals(other.bytes)) return false + } else if (other.bytes != null) return false + + return true + } + + override fun hashCode(): Int { + return bytes?.contentHashCode() ?: 0 + } + } +} diff --git a/libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Parser.kt b/libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Parser.kt new file mode 100644 index 0000000..4686b4f --- /dev/null +++ b/libs/crypto/src/commonMain/kotlin/ktproto/crypto/asn1/Asn1Parser.kt @@ -0,0 +1,71 @@ +package ktproto.crypto.asn1 + +import ktproto.io.memory.* +import ktproto.stdlib.bytes.decodeInt +import ktproto.stdlib.bytes.padEnd + +public fun parseAsn1Der(data: ByteArray): Asn1Object { + val (_, result) = readAsn1Der(MemoryArena.of(data)) + return result +} + +private const val SEQUENCE: UByte = 0x30u + +private fun readAsn1Der(memory: MemoryArena): Pair { + val tag: Byte + val dropTag = memory.readByte { tag = it } + return when (tag.toUByte()) { + SEQUENCE -> readAsn1Container(dropTag) + else -> readAsn1Value(dropTag) + } +} + +private fun readAsn1Container( + memory: MemoryArena +): Pair { + val (dropLength, length) = readAsn1DerLength(memory) + var mutable = dropLength.take(length) + val children = buildList { + while (mutable.size > 0) { + val (remaining, element) = readAsn1Der(mutable) + mutable = remaining + add(element) + } + } + return dropLength.drop(length) to Asn1Object.Container(children) +} + +private fun readAsn1Value( + memory: MemoryArena +): Pair { + val (remaining, length) = readAsn1DerLength(memory) + val bytes = remaining.take(length) + val result = Asn1Object.Value(bytes.toByteArray()) + return remaining.drop(length) to result +} + +private fun readAsn1DerLength(memory: MemoryArena): Pair { + val lengthFirstByte: Byte + val dropFirst = memory.readByte { byte -> + lengthFirstByte = byte + } + + // Short-Form + if (lengthFirstByte >= 0) { + return dropFirst to lengthFirstByte.toInt() + } + + // Remove sign-bit + val lengthOfTheLength = lengthFirstByte.toInt() and 0b01111111 + + val length: Int + + val remaining = dropFirst.readBytes(lengthOfTheLength) { bytes -> + val intBytes = bytes + .apply { reverse() } + .padEnd(4) + length = intBytes.decodeInt() + } + + return remaining to length +} diff --git a/libs/crypto/src/commonMain/kotlin/ktproto/crypto/factorization/PollardRhoBrent.kt b/libs/crypto/src/commonMain/kotlin/ktproto/crypto/factorization/PollardRhoBrent.kt new file mode 100644 index 0000000..2453578 --- /dev/null +++ b/libs/crypto/src/commonMain/kotlin/ktproto/crypto/factorization/PollardRhoBrent.kt @@ -0,0 +1,96 @@ +package ktproto.crypto.factorization + +import kotlin.math.min +import kotlin.random.Random +import kotlin.random.nextULong + +public object PollardRhoBrent { + // Code was rewritten to Kotlin from CPP: + // https://github.com/tdlib/td/blob/master/tdutils/td/utils/crypto.cpp#L103 + public fun factorize(pq: ULong): Pair { + if (pq and 1uL == 0uL) { + return 2uL to pq / 2uL + } + + var g = 0uL + + var i = 0 + var iter = 0 + + while (i < 3 || iter < 1000) { + val q = Random.nextULong(from = 17uL, until = 32u) % (pq - 1uL) + var x = Random.nextULong() % (pq - 1uL) + var y = x + val lim = 1 shl (min(5, i) + 18) + for (j in 1.. 1uL && g < pq) { + break + } + i++ + } + + val p: ULong + val q: ULong + + if (g == 0uL) return 1uL to pq + + val other = pq / g + + if (other < g) { + p = other + q = g + } else { + p = g + q = other + } + + return p to q + } +} + +private fun pqAddMul( + c: ULong, + a: ULong, + b: ULong, + pq: ULong +): ULong { + var cVar = c + var aVar = a + var bVar = b + + while (bVar != 0uL) { + if ((bVar and 1uL) == 1uL) { + cVar += aVar + if (cVar >= pq) { + cVar -= pq + } + } + aVar += aVar + if (aVar >= pq) { + aVar -= pq + } + bVar = bVar shr 1 + } + + return cVar +} + +private tailrec fun gcd(a: ULong, b: ULong): ULong { + if (b == 0uL) return a + return gcd(b, a % b) +} + +private inline fun repeat(n: ULong, block: (ULong) -> Unit) { + for (i in 0uL.. +} + +internal suspend fun Subtle.digest( + algorithm: String, + data: ByteArray +): ByteArray { + val buffer = digestInterop(algorithm, data).await() + return Uint8Array(buffer).unsafeCast() +} + +internal val JsPlatform.crypto: Crypto get() = when (this) { + JsPlatform.Browser -> window.asDynamic().crypto + JsPlatform.Node -> eval("require")("crypto") +}.unsafeCast() ?: throw UnsupportedOperationException("Web Crypto API not available") diff --git a/libs/crypto/src/jsMain/kotlin/ktproto/crypto/sha/Sha1.js.kt b/libs/crypto/src/jsMain/kotlin/ktproto/crypto/sha/Sha1.js.kt new file mode 100644 index 0000000..b8574f4 --- /dev/null +++ b/libs/crypto/src/jsMain/kotlin/ktproto/crypto/sha/Sha1.js.kt @@ -0,0 +1,6 @@ +package ktproto.crypto.sha + +import ktproto.stdlib.platform.platform + +public actual suspend fun ByteArray.sha1(): ByteArray = + platform.crypto.subtle.digest("SHA-1", this) diff --git a/libs/crypto/src/jvmMain/kotlin/ktproto/crypto/sha/Sha1.jvm.kt b/libs/crypto/src/jvmMain/kotlin/ktproto/crypto/sha/Sha1.jvm.kt new file mode 100644 index 0000000..f004b6a --- /dev/null +++ b/libs/crypto/src/jvmMain/kotlin/ktproto/crypto/sha/Sha1.jvm.kt @@ -0,0 +1,13 @@ +package ktproto.crypto.sha + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +public actual suspend fun ByteArray.sha1(): ByteArray { + val input = this + return withContext(Dispatchers.Default) { + val messageDigest = java.security.MessageDigest.getInstance("SHA-1") + val hashBytes = messageDigest.digest(input) + hashBytes + } +} diff --git a/libs/crypto/src/nativeMain/kotlin/ktproto/crypto/sha/Sha1.native.kt b/libs/crypto/src/nativeMain/kotlin/ktproto/crypto/sha/Sha1.native.kt new file mode 100644 index 0000000..ebef0e9 --- /dev/null +++ b/libs/crypto/src/nativeMain/kotlin/ktproto/crypto/sha/Sha1.native.kt @@ -0,0 +1,23 @@ +package ktproto.crypto.sha + +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.addressOf +import kotlinx.cinterop.convert +import kotlinx.cinterop.usePinned +import platform.CoreCrypto.CC_SHA1 +import platform.CoreCrypto.CC_SHA1_DIGEST_LENGTH + +@OptIn(ExperimentalForeignApi::class) +public actual suspend fun ByteArray.sha1(): ByteArray { + val digest = UByteArray(CC_SHA1_DIGEST_LENGTH) + this.usePinned { input -> + digest.usePinned { digest -> + CC_SHA1( + input.addressOf(index = 0), + this.size.convert(), + digest.addressOf(index = 0) + ) + } + } + return digest.toByteArray() +} diff --git a/libs/io/src/commonMain/kotlin/ktproto/io/memory/Read.kt b/libs/io/src/commonMain/kotlin/ktproto/io/memory/Read.kt index a0f829b..0ca051a 100644 --- a/libs/io/src/commonMain/kotlin/ktproto/io/memory/Read.kt +++ b/libs/io/src/commonMain/kotlin/ktproto/io/memory/Read.kt @@ -42,6 +42,15 @@ public inline fun MemoryArena.readBytes( return readMemory(n) { memory -> block(memory.toByteArray()) } } +public inline fun MemoryArena.readByte( + block: (Byte) -> Unit +): MemoryArena { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return readBytes(n = 1) { bytes -> block(bytes[0]) } +} + public fun MemoryArena.scanInt(): Int { val result: Int readInt { result = it } diff --git a/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/bytes/Pad.kt b/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/bytes/Pad.kt index b19d7d2..e026f71 100644 --- a/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/bytes/Pad.kt +++ b/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/bytes/Pad.kt @@ -1,5 +1,13 @@ package ktproto.stdlib.bytes +public fun ByteArray.padStart( + desiredLength: Int, + padByte: Byte = 0 +): ByteArray { + val padSize = desiredLength - this.size + return ByteArray(padSize) { padByte } + this +} + public fun ByteArray.padEnd( desiredLength: Int, padByte: Byte = 0 diff --git a/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigInt.kt b/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigInt.kt deleted file mode 100644 index 5817a4b..0000000 --- a/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigInt.kt +++ /dev/null @@ -1,65 +0,0 @@ -package ktproto.stdlib.int - -import ktproto.stdlib.bytes.encodeToByteArray -import kotlin.random.Random - -public expect class BigInt : Comparable { - public operator fun plus(other: BigInt): BigInt - public operator fun minus(other: BigInt): BigInt - public operator fun times(other: BigInt): BigInt - public operator fun div(other: BigInt): BigInt - public operator fun rem(other: BigInt): BigInt - - public operator fun unaryPlus(): BigInt - public operator fun unaryMinus(): BigInt - - public operator fun inc(): BigInt - public operator fun dec(): BigInt - - public override operator fun compareTo(other: BigInt): Int - - public infix fun pow(other: BigInt): BigInt - - public val bytesSize: Int - public fun toByteArrayLE(): ByteArray - public fun toByteArrayBE(): ByteArray - - public companion object { - public fun ofLE(bytes: ByteArray): BigInt - public fun ofBE(bytes: ByteArray): BigInt - } -} - -private val ZERO = BigInt.ofLE(0.encodeToByteArray()) -private val ONE = BigInt.ofLE(1.encodeToByteArray()) - -public val BigInt.Companion.ZERO: BigInt get() = ktproto.stdlib.int.ZERO -public val BigInt.Companion.ONE: BigInt get() = ktproto.stdlib.int.ONE - -public val Int.bi: BigInt get() = when (this) { - 0 -> BigInt.ZERO - 1 -> BigInt.ONE - else -> BigInt.ofLE(encodeToByteArray()) -} -public fun Random.nextBigInt(min: BigInt, max: BigInt): BigInt { - val randomBytes = nextBytes(max.bytesSize) - val random = BigInt.ofLE(randomBytes) - val range = max - min - return min + random % range -} - -public inline fun repeat(int: BigInt, block: (BigInt) -> Unit) { - for (i in 0.bi..= 0.bi -> x - else -> -x -} - -public fun min(x: BigInt, y: BigInt): BigInt = when { - x < y -> x - else -> y -} diff --git a/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigIntRange.kt b/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigIntRange.kt deleted file mode 100644 index ff11589..0000000 --- a/libs/stdlib-extensions/src/commonMain/kotlin/ktproto/stdlib/int/BigIntRange.kt +++ /dev/null @@ -1,28 +0,0 @@ -package ktproto.stdlib.int - -public class BigIntRange( - override val start: BigInt, - override val endInclusive: BigInt -) : ClosedRange, OpenEndRange, Iterable { - override val endExclusive: BigInt get() = endInclusive + 1.bi - - override fun isEmpty(): Boolean = start > endInclusive - - override fun iterator(): Iterator = iterator { - var start = start - while (start <= endInclusive) { - yield(start) - start++ - } - } - - @Suppress("ConvertTwoComparisonsToRangeCheck") - override fun contains(value: BigInt): Boolean = - start <= value && value <= endInclusive -} - -public operator fun BigInt.rangeTo(other: BigInt): BigIntRange = - BigIntRange(start = this, endInclusive = other) - -public operator fun BigInt.rangeUntil(other: BigInt): BigIntRange = - BigIntRange(start = this, endInclusive = other - 1.bi) diff --git a/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/BigInt.js.kt b/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/BigInt.js.kt deleted file mode 100644 index c132804..0000000 --- a/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/BigInt.js.kt +++ /dev/null @@ -1,82 +0,0 @@ -package ktproto.stdlib.int - -public actual class BigInt private constructor( - any: Any? -) : Comparable { - private val underlying = js("BigInt")(any) - - public actual operator fun plus(other: BigInt): BigInt { - return BigInt(any = underlying + other.underlying) - } - - public actual operator fun minus(other: BigInt): BigInt { - TODO("Not yet implemented") - } - - public actual operator fun times(other: BigInt): BigInt { - TODO("Not yet implemented") - } - - public actual operator fun div(other: BigInt): BigInt { - TODO("Not yet implemented") - } - - public actual operator fun rem(other: BigInt): BigInt { - TODO("Not yet implemented") - } - - public actual operator fun unaryPlus(): BigInt { - TODO("Not yet implemented") - } - - public actual operator fun unaryMinus(): BigInt { - TODO("Not yet implemented") - } - - public actual operator fun inc(): BigInt { - TODO("Not yet implemented") - } - - public actual operator fun dec(): BigInt { - TODO("Not yet implemented") - } - - public actual override fun compareTo(other: BigInt): Int { - TODO("Not yet implemented") - } - - public actual infix fun pow(other: BigInt): BigInt { - TODO("Not yet implemented") - } - - public actual val bytesSize: Int - get() = toByteArrayLE().size - - @OptIn(ExperimentalStdlibApi::class) - public actual fun toByteArrayLE(): ByteArray { - var hex = underlying.toString(16) as String - hex = hex.padStart(length = hex.length.nearestMultipleOf(n = 2), padChar = '0') - println(hex) - return hex.hexToByteArray().also { println(it) } - } - - public actual fun toByteArrayBE(): ByteArray { - TODO("Not yet implemented") - } - - override fun toString(): String = underlying.toString() - - public actual companion object { - public actual fun ofLE(bytes: ByteArray): BigInt { - return ofBE(bytes.reversedArray()) - } - - public actual fun ofBE(bytes: ByteArray): BigInt { - val string = "0x" + bytes.joinToString(separator = "") { byte -> - byte.toUByte().toString(radix = 16).padStart(length = 2, padChar = '0') - } - return BigInt(string) - } - } - -} \ No newline at end of file diff --git a/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/Main.kt b/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/Main.kt deleted file mode 100644 index 707c3dc..0000000 --- a/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/int/Main.kt +++ /dev/null @@ -1,7 +0,0 @@ -package ktproto.stdlib.int - -public fun main() { - val bigInt = BigInt.ONE - console.log(bigInt) - console.log("Hello") -} diff --git a/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/platform/JsPlatform.kt b/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/platform/JsPlatform.kt new file mode 100644 index 0000000..d4cd3e7 --- /dev/null +++ b/libs/stdlib-extensions/src/jsMain/kotlin/ktproto/stdlib/platform/JsPlatform.kt @@ -0,0 +1,14 @@ +package ktproto.stdlib.platform + +public sealed interface JsPlatform { + public data object Node : JsPlatform + public data object Browser : JsPlatform +} + +public val platform: JsPlatform by lazy { + if (js("typeof window") === "undefined") { + JsPlatform.Node + } else { + JsPlatform.Browser + } +} diff --git a/libs/stdlib-extensions/src/jsTest/kotlin/BigIntTest.kt b/libs/stdlib-extensions/src/jsTest/kotlin/BigIntTest.kt deleted file mode 100644 index e5ca362..0000000 --- a/libs/stdlib-extensions/src/jsTest/kotlin/BigIntTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -import ktproto.stdlib.bytes.decodeInt -import ktproto.stdlib.bytes.encodeToByteArray -import ktproto.stdlib.int.BigInt -import kotlin.random.Random -import kotlin.test.Test - -class BigIntTest { - @Test - fun `creationOfBbigint`() { -// val number = Random.nextInt() - val number = 1024 - val bytes = number.encodeToByteArray() - println(bytes) - val bigInt = BigInt.ofLE(bytes) - println(bigInt) - val decoded = bigInt.toByteArrayLE().decodeInt() - println(decoded) - require(number == decoded) { "number: $number, decoded: $decoded" } - } -} diff --git a/libs/stdlib-extensions/src/jvmMain/kotlin/ktproto/stdlib/int/BigInt.jvm.kt b/libs/stdlib-extensions/src/jvmMain/kotlin/ktproto/stdlib/int/BigInt.jvm.kt deleted file mode 100644 index ba1f165..0000000 --- a/libs/stdlib-extensions/src/jvmMain/kotlin/ktproto/stdlib/int/BigInt.jvm.kt +++ /dev/null @@ -1,68 +0,0 @@ -package ktproto.stdlib.int - -import java.math.BigInteger - -public actual class BigInt( - private val underlying: BigInteger -) : Comparable { - public actual operator fun plus(other: BigInt): BigInt { - return (underlying + other.underlying).bi - } - - public actual operator fun minus(other: BigInt): BigInt { - return (underlying - other.underlying).bi - } - - public actual operator fun times(other: BigInt): BigInt { - return (underlying * other.underlying).bi - } - - public actual operator fun div(other: BigInt): BigInt { - return (underlying / other.underlying).bi - } - - public actual operator fun rem(other: BigInt): BigInt { - return (underlying % other.underlying).bi - } - - public actual operator fun unaryPlus(): BigInt { - return this - } - - public actual operator fun unaryMinus(): BigInt { - return (-underlying).bi - } - - public actual operator fun inc(): BigInt { - return this + 1.bi - } - - public actual operator fun dec(): BigInt { - return this - 1.bi - } - - public actual override fun compareTo(other: BigInt): Int { - return underlying.compareTo(other.underlying) - } - - public actual infix fun pow(other: BigInt): BigInt { - return underlying.pow(other.underlying.toInt()).bi - } - - public actual val bytesSize: Int get() = underlying.toByteArray().size - - public actual fun toByteArrayBE(): ByteArray = - underlying.toByteArray() - - public actual fun toByteArrayLE(): ByteArray = - underlying.toByteArray().apply { reverse() } - - public actual companion object { - public actual fun ofLE(bytes: ByteArray): BigInt = - BigInteger(bytes.reversedArray()).bi - public actual fun ofBE(bytes: ByteArray): BigInt = - BigInteger(bytes).bi - } -} - -private val BigInteger.bi: BigInt get() = BigInt(underlying = this) diff --git a/session/src/commonMain/kotlin/ktproto/session/MessageIdProvider.kt b/session/src/commonMain/kotlin/ktproto/session/MessageIdProvider.kt index 1d349f1..3a07f79 100644 --- a/session/src/commonMain/kotlin/ktproto/session/MessageIdProvider.kt +++ b/session/src/commonMain/kotlin/ktproto/session/MessageIdProvider.kt @@ -1,6 +1,6 @@ package ktproto.session -import kotl.time.Clock +import ktproto.time.Clock import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import ktproto.io.memory.MemoryArena diff --git a/types/src/commonMain/kotlin/kotl/_ b/types/src/commonMain/kotlin/kotl/_ deleted file mode 100644 index e69de29..0000000 diff --git a/types/src/commonMain/kotlin/kotl/time/Clock.kt b/types/src/commonMain/kotlin/ktproto/time/Clock.kt similarity index 89% rename from types/src/commonMain/kotlin/kotl/time/Clock.kt rename to types/src/commonMain/kotlin/ktproto/time/Clock.kt index a381d58..afa78c0 100644 --- a/types/src/commonMain/kotlin/kotl/time/Clock.kt +++ b/types/src/commonMain/kotlin/ktproto/time/Clock.kt @@ -1,4 +1,4 @@ -package kotl.time +package ktproto.time @Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_EXPERIMENTAL_WARNING") public expect interface Clock { diff --git a/types/src/iosMain/kotlin/kotl/time/Clock.ios.kt b/types/src/iosMain/kotlin/ktproto/time/Clock.ios.kt similarity index 79% rename from types/src/iosMain/kotlin/kotl/time/Clock.ios.kt rename to types/src/iosMain/kotlin/ktproto/time/Clock.ios.kt index 52247bc..fa95aac 100644 --- a/types/src/iosMain/kotlin/kotl/time/Clock.ios.kt +++ b/types/src/iosMain/kotlin/ktproto/time/Clock.ios.kt @@ -1,9 +1,8 @@ -package kotl.time +package ktproto.time import platform.Foundation.NSDate import platform.Foundation.timeIntervalSince1970 -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_EXPERIMENTAL_WARNING") public actual interface Clock { public actual fun currentTimeMillis(): Long diff --git a/types/src/jsMain/kotlin/kotl/time/Clock.js.kt b/types/src/jsMain/kotlin/ktproto/time/Clock.js.kt similarity index 72% rename from types/src/jsMain/kotlin/kotl/time/Clock.js.kt rename to types/src/jsMain/kotlin/ktproto/time/Clock.js.kt index c7287b4..5ec8209 100644 --- a/types/src/jsMain/kotlin/kotl/time/Clock.js.kt +++ b/types/src/jsMain/kotlin/ktproto/time/Clock.js.kt @@ -1,12 +1,11 @@ -package kotl.time +package ktproto.time import kotlin.js.Date -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_EXPERIMENTAL_WARNING") public actual interface Clock { public actual fun currentTimeMillis(): Long public actual object System : Clock { override fun currentTimeMillis(): Long = Date.now().toLong() } -} \ No newline at end of file +} diff --git a/types/src/jvmMain/kotlin/kotl/time/Clock.jvm.kt b/types/src/jvmMain/kotlin/ktproto/time/Clock.jvm.kt similarity index 73% rename from types/src/jvmMain/kotlin/kotl/time/Clock.jvm.kt rename to types/src/jvmMain/kotlin/ktproto/time/Clock.jvm.kt index 8774fda..b92b170 100644 --- a/types/src/jvmMain/kotlin/kotl/time/Clock.jvm.kt +++ b/types/src/jvmMain/kotlin/ktproto/time/Clock.jvm.kt @@ -1,6 +1,5 @@ -package kotl.time +package ktproto.time -@Suppress("EXPECT_ACTUAL_CLASSIFIERS_ARE_EXPERIMENTAL_WARNING") public actual interface Clock { public actual fun currentTimeMillis(): Long