- Migrates Quartz from Android to CommonMain
- Fully converts OpenTimestamp Java codebase to Kotlin, migrating the sync and async HTTP call interfaces to OkHttp and coroutines - Redesigns parsing of relay commands, messages and filters for performance in Jackson. - Starts the use of KotlinX Serialization when speed is not a requirement - Migrates all Jackson field annotations to Kotlin Serialization - Migrates Regex use in Quarts to Kotlin's Regex class - Migrates Base64 from Android to Kotlin - Migrates UUID from Android/Java to Kotlin - Migrates LRUCache usage from Android/Java to Kotlin collections - Migrates all String to bytearray conversions to Kotlin methods - Migrates all System.arraycopy calls to kotlin native ones. - Separates parsing code from the data classes Companion objects - Exposes Rfc3986 normalizations to each platform. - Exposes URI parsing classes to each platform. - Exposes URL Encoders to each platform. - Exposes BigDecimal to each platform. - Exposes the Url Detector to each platform. - Exposes MacInstances to each platform - Exposes Diggest instances to each platform. - Exposes a BitSet to each platform. - Exposes GZip to each platform. - Exposes Secp256k1 to each platform. - Exposes SecureRandom to each platform. - Exposes Time in seconds to each platform. - Exposes the LargeCache to each platform. - Exposes AES CBC and AES GCM encryption/decryption to each platform - Migrate test assertions to Kotlin Tests - Exposes Address class to each platform because of the Parceleable - Creates our own ByteArrayOutputStream. - Removes Lock features inside the Bloomfilters because we don't need data consistency/ - Migrates UserMetadata parser from Jackson to Kotlin serialization - Removes the need for Static methods in each tag. - Adds an event template serializer - Adds KotlinX Datetime to migrate some of the date-based logs - Adds support for LibSodium in the JVM platform - Creates a shared test build for iOS targets - Fixes several usages of Reflection when serializing classes - Fixes a bug on loading RelayDB for the HintBloom filter test - Increases the Bloom filter space to better use hints in the app. - Removes support for iOS in x86 - Creates a Jackson mapper just for NIP-55, which stays in the Android build only. - Keeps the event store in the android build as well. - Removes @Syncronized tags in favor of Mutexes. - Improved sendAndWaitForResponse NostrClient method to properly account for returns from each relay. - Removes the need for GlobalScope and async calls in the downloadFirstEvent method. - Restructures the parser and serialization of the relay messages and commands for performance with Jackson - Removes the dependency on Jackson's error classes across the codebase. - Moves the hint to quote tag extension methods to their own packages. - Speeds up the generation of Bech32 addresses - Migrates NIP-6 and Blossom uploads to use Kotlin Serialization
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.core
|
||||
|
||||
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
|
||||
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
|
||||
|
||||
actual data class Address actual constructor(
|
||||
actual val kind: Kind,
|
||||
actual val pubKeyHex: HexKey,
|
||||
actual val dTag: String,
|
||||
) : Comparable<Address> {
|
||||
actual fun toValue() = assemble(kind, pubKeyHex, dTag)
|
||||
|
||||
actual fun countMemory(): Int =
|
||||
3 * pointerSizeInBytes +
|
||||
8 + // kind
|
||||
pubKeyHex.bytesUsedInMemory() +
|
||||
dTag.bytesUsedInMemory()
|
||||
|
||||
actual override fun compareTo(other: Address): Int {
|
||||
val result = kind.compareTo(other.kind)
|
||||
return if (result == 0) {
|
||||
val result2 = pubKeyHex.compareTo(other.pubKeyHex)
|
||||
if (result2 == 0) {
|
||||
dTag.compareTo(other.dTag)
|
||||
} else {
|
||||
result2
|
||||
}
|
||||
} else {
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
actual companion object {
|
||||
actual fun assemble(
|
||||
kind: Int,
|
||||
pubKeyHex: HexKey,
|
||||
dTag: String,
|
||||
) = AddressSerializer.assemble(kind, pubKeyHex, dTag)
|
||||
|
||||
actual fun parse(addressId: String) = AddressSerializer.parse(addressId)
|
||||
|
||||
actual fun isOfKind(
|
||||
addressId: String,
|
||||
kind: String,
|
||||
) = addressId.startsWith(kind) && addressId[kind.length] == ':'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
import com.goterl.lazysodium.LazySodiumJava
|
||||
import com.goterl.lazysodium.SodiumJava
|
||||
|
||||
actual object LibSodiumInstance {
|
||||
private val libSodium = SodiumJava()
|
||||
private val lazySodium = LazySodiumJava(libSodium)
|
||||
|
||||
actual fun cryptoAeadXChaCha20Poly1305IetfDecrypt(
|
||||
message: ByteArray,
|
||||
nSec: ByteArray,
|
||||
ciphertext: ByteArray,
|
||||
ad: ByteArray,
|
||||
nPub: ByteArray,
|
||||
k: ByteArray,
|
||||
): Boolean =
|
||||
lazySodium.cryptoAeadXChaCha20Poly1305IetfDecrypt(
|
||||
message,
|
||||
longArrayOf(message.size.toLong()),
|
||||
nSec,
|
||||
ciphertext,
|
||||
ciphertext.size.toLong(),
|
||||
ad,
|
||||
ad.size.toLong(),
|
||||
nPub,
|
||||
k,
|
||||
)
|
||||
|
||||
actual fun cryptoAeadXChaCha20Poly1305IetfEncrypt(
|
||||
ciphertext: ByteArray,
|
||||
message: ByteArray,
|
||||
ad: ByteArray,
|
||||
nSec: ByteArray,
|
||||
nPub: ByteArray,
|
||||
k: ByteArray,
|
||||
): Boolean =
|
||||
lazySodium.cryptoAeadXChaCha20Poly1305IetfEncrypt(
|
||||
ciphertext,
|
||||
longArrayOf(ciphertext.size.toLong()),
|
||||
message,
|
||||
message.size.toLong(),
|
||||
ad,
|
||||
ad.size.toLong(),
|
||||
nSec,
|
||||
nPub,
|
||||
k,
|
||||
)
|
||||
|
||||
actual fun cryptoStreamChaCha20IetfXor(
|
||||
message: ByteArray,
|
||||
nonce: ByteArray?,
|
||||
key: ByteArray?,
|
||||
): ByteArray {
|
||||
val ciphertext = ByteArray(message.size)
|
||||
lazySodium.cryptoStreamChaCha20IetfXor(ciphertext, message, message.size.toLong(), nonce, key)
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
actual fun cryptoStreamXChaCha20Xor(
|
||||
messageBytes: ByteArray,
|
||||
nonce: ByteArray,
|
||||
key: ByteArray,
|
||||
): ByteArray {
|
||||
val cipher = ByteArray(messageBytes.size)
|
||||
val k2 = ByteArray(32)
|
||||
|
||||
val nonceChaCha = nonce.drop(16).toByteArray()
|
||||
assert(nonceChaCha.size == 8)
|
||||
|
||||
libSodium.crypto_core_hchacha20(k2, nonce, key, null)
|
||||
val resultCode =
|
||||
libSodium.crypto_stream_chacha20_xor_ic(
|
||||
cipher,
|
||||
messageBytes,
|
||||
messageBytes.size.toLong(),
|
||||
nonceChaCha,
|
||||
0,
|
||||
k2,
|
||||
)
|
||||
|
||||
return if (resultCode == 0) cipher else throw IllegalStateException("Could not decrypt message")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
actual object Log {
|
||||
actual fun w(
|
||||
tag: String,
|
||||
message: String,
|
||||
throwable: Throwable?,
|
||||
) {
|
||||
if (throwable != null) {
|
||||
println("WARN: [$tag] $message. Throwable: ${throwable.message}")
|
||||
} else {
|
||||
println("WARN: [$tag] $message")
|
||||
}
|
||||
}
|
||||
|
||||
actual fun e(
|
||||
tag: String,
|
||||
message: String,
|
||||
throwable: Throwable?,
|
||||
) {
|
||||
if (throwable != null) {
|
||||
println("ERROR: [$tag] $message. Throwable: ${throwable.message}")
|
||||
} else {
|
||||
println("ERROR: [$tag] $message")
|
||||
}
|
||||
}
|
||||
|
||||
actual fun d(
|
||||
tag: String,
|
||||
message: String,
|
||||
) {
|
||||
println("DEBUG: [$tag] $message")
|
||||
}
|
||||
|
||||
actual fun i(
|
||||
tag: String,
|
||||
message: String,
|
||||
) {
|
||||
println("INFO: [$tag] $message")
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -18,6 +18,8 @@
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
actual fun platform() = "JVM"
|
||||
|
||||
actual fun currentTimeSeconds() = System.currentTimeMillis() / 1000
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
import fr.acinq.secp256k1.Secp256k1
|
||||
|
||||
actual object Secp256k1Instance {
|
||||
private val h02 = Hex.decode("02")
|
||||
private val secp256k1 = Secp256k1.get()
|
||||
|
||||
actual fun compressedPubKeyFor(privKey: ByteArray) = secp256k1.pubKeyCompress(secp256k1.pubkeyCreate(privKey))
|
||||
|
||||
actual fun isPrivateKeyValid(il: ByteArray): Boolean = secp256k1.secKeyVerify(il)
|
||||
|
||||
actual fun signSchnorr(
|
||||
data: ByteArray,
|
||||
privKey: ByteArray,
|
||||
nonce: ByteArray?,
|
||||
): ByteArray = secp256k1.signSchnorr(data, privKey, nonce)
|
||||
|
||||
actual fun signSchnorr(
|
||||
data: ByteArray,
|
||||
privKey: ByteArray,
|
||||
): ByteArray = secp256k1.signSchnorr(data, privKey, null)
|
||||
|
||||
actual fun verifySchnorr(
|
||||
signature: ByteArray,
|
||||
hash: ByteArray,
|
||||
pubKey: ByteArray,
|
||||
): Boolean = secp256k1.verifySchnorr(signature, hash, pubKey)
|
||||
|
||||
actual fun privateKeyAdd(
|
||||
first: ByteArray,
|
||||
second: ByteArray,
|
||||
): ByteArray = secp256k1.privKeyTweakAdd(first, second)
|
||||
|
||||
actual fun pubKeyTweakMulCompact(
|
||||
pubKey: ByteArray,
|
||||
privateKey: ByteArray,
|
||||
): ByteArray = secp256k1.pubKeyTweakMul(h02 + pubKey, privateKey).copyOfRange(1, 33)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.utils
|
||||
|
||||
import java.net.URI
|
||||
import java.net.URLDecoder
|
||||
|
||||
actual class UriParser actual constructor(
|
||||
uri: String,
|
||||
) {
|
||||
val myUri = URI.create(uri)
|
||||
val queryParameters: Map<String, String> by lazy {
|
||||
myUri.query?.ifBlank { null }?.let { query ->
|
||||
query.split('&').associate { paramValue ->
|
||||
val parts = paramValue.split("=", limit = 2)
|
||||
if (parts.size == 2) {
|
||||
parts[0] to parts[1]
|
||||
} else {
|
||||
parts[0] to "" // Handle parameters without a value, e.g., "param&other=value"
|
||||
}
|
||||
}
|
||||
} ?: emptyMap()
|
||||
}
|
||||
|
||||
val fragments: Map<String, String> by lazy {
|
||||
myUri.rawFragment?.ifBlank { null }?.let { keyValuePair ->
|
||||
keyValuePair.split('&').associate { paramValue ->
|
||||
val parts = paramValue.split("=", limit = 2)
|
||||
if (parts.size == 2) {
|
||||
parts[0] to URLDecoder.decode(parts[1], "UTF-8")
|
||||
} else {
|
||||
parts[0] to "" // Handle parameters without a value, e.g., "param&other=value"
|
||||
}
|
||||
}
|
||||
} ?: emptyMap()
|
||||
}
|
||||
|
||||
actual fun scheme(): String? = myUri.scheme
|
||||
|
||||
actual fun host(): String? = myUri.host
|
||||
|
||||
actual fun port(): Int? {
|
||||
// java.net.URI.getPort() returns -1 if the port is not set, so we handle that case.
|
||||
val port = myUri.port
|
||||
return if (port == -1) null else port
|
||||
}
|
||||
|
||||
actual fun path(): String? = myUri.path
|
||||
|
||||
actual fun queryParameterNames(): Set<String> = queryParameters.keys
|
||||
|
||||
actual fun getQueryParameter(param: String): String? = queryParameters[param]
|
||||
|
||||
actual fun fragments() = fragments
|
||||
}
|
||||
Reference in New Issue
Block a user