- 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:
Vitor Pamplona
2025-09-22 19:44:51 -04:00
parent 7949fc66b2
commit 7a280a3cde
1195 changed files with 12671 additions and 5183 deletions
+1 -1
View File
@@ -24,7 +24,7 @@ android {
targetCompatibility JavaVersion.VERSION_21
}
sourceSets.androidTest.resources.srcDirs += ["../quartz/src/androidDeviceTest/resources"]
sourceSets.androidTest.resources.srcDirs += ["../quartz/src/androidInstrumentedTest/resources"]
testBuildType = "benchmark"
buildTypes {
@@ -29,10 +29,10 @@ import com.vitorpamplona.amethyst.commons.richtext.HashTagSegment
import com.vitorpamplona.amethyst.commons.richtext.ImageSegment
import com.vitorpamplona.amethyst.commons.richtext.LinkSegment
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
import com.vitorpamplona.quartz.nip01Core.core.EmptyTagList
import com.vitorpamplona.quartz.nip01Core.core.ImmutableListOfLists
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -23,8 +23,8 @@ package com.vitorpamplona.quartz.benchmark
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
import com.vitorpamplona.quartz.utils.LargeCache
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.utils.cache.LargeCache
import org.junit.Assert.assertTrue
import java.util.function.Consumer
import java.util.zip.GZIPInputStream
@@ -33,9 +33,9 @@ open class BaseLargeCacheBenchmark {
companion object {
fun getEventDB(): List<Event> {
// This file includes duplicates
val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json")
val fullDBInputStream = javaClass.classLoader!!.getResourceAsStream("nostr_vitor_startup_data.json")
return JsonMapper.mapper.readValue<ArrayList<Event>>(
return JacksonMapper.mapper.readValue<ArrayList<Event>>(
GZIPInputStream(fullDBInputStream),
)
}
@@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.nip19Bech32.bech32.bechToBytes
import com.vitorpamplona.quartz.nip19Bech32.toNpub
import com.vitorpamplona.quartz.utils.Hex
import junit.framework.TestCase.assertEquals
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -26,8 +26,8 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
import com.vitorpamplona.quartz.utils.LargeCache
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.utils.cache.LargeCache
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
@@ -43,7 +43,7 @@ open class BaseCacheBenchmark {
// This file includes duplicates
val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json")
return JsonMapper.mapper.readValue<ArrayList<Event>>(
return JacksonMapper.mapper.readValue<ArrayList<Event>>(
GZIPInputStream(fullDBInputStream),
)
}
@@ -58,12 +58,15 @@ class ChaCha20Benchmark {
}
}
/*
Removed in the conversion to KMP
@Test
fun encryptNative() {
benchmarkRule.measureRepeated {
chaCha.encryptNative(padded, messageKeys.chachaNonce, messageKeys.chachaKey)
}
}
*/
@Test
fun decryptLibSodium() {
@@ -72,10 +75,13 @@ class ChaCha20Benchmark {
}
}
/*
Removed in the conversion to KMP
@Test
fun decryptNative() {
benchmarkRule.measureRepeated {
chaCha.decryptNative(padded, messageKeys.chachaNonce, messageKeys.chachaKey)
}
}
*/
}
@@ -25,7 +25,7 @@ import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.utils.DualCase
import com.vitorpamplona.quartz.utils.containsAny
import junit.framework.TestCase.assertTrue
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -24,7 +24,7 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.utils.ensure
import junit.framework.TestCase.assertTrue
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -45,7 +45,6 @@ class EnsureTest {
"0123456789abcdefABCDEF".forEach { this[it.code] = true }
}
@JvmStatic
fun String.isHex() = all { isHexChar[it.code] }
}
@@ -23,14 +23,17 @@ package com.vitorpamplona.quartz.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.EventFactory
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
import com.vitorpamplona.quartz.nip01Core.verify
import com.vitorpamplona.quartz.nip01Core.verifyId
import com.vitorpamplona.quartz.nip01Core.verifySignature
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
import com.vitorpamplona.quartz.nip01Core.crypto.verify
import com.vitorpamplona.quartz.nip01Core.crypto.verifyId
import com.vitorpamplona.quartz.nip01Core.crypto.verifySignature
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.utils.EventFactory
import com.vitorpamplona.quartz.utils.TimeUtils
import junit.framework.TestCase.assertTrue
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -48,28 +51,26 @@ class EventBenchmark {
@Test
fun parseComplete() {
benchmarkRule.measureRepeated {
val tree = JsonMapper.mapper.readTree(reqResponseEvent)
val event = JsonMapper.fromJson(tree[2])
val event = (OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) as EventMessage).event
assertTrue(event.verify())
}
}
@Test
fun parseREQString() {
benchmarkRule.measureRepeated { JsonMapper.mapper.readTree(reqResponseEvent) }
benchmarkRule.measureRepeated { JacksonMapper.mapper.readTree(reqResponseEvent) }
}
@Test
fun parseEvent() {
val msg = JsonMapper.mapper.readTree(reqResponseEvent)
val msg = JacksonMapper.mapper.readTree(reqResponseEvent)
benchmarkRule.measureRepeated { JsonMapper.fromJson(msg[2]) }
benchmarkRule.measureRepeated { JacksonMapper.fromJson(msg[2]) }
}
@Test
fun checkId() {
val msg = JsonMapper.mapper.readTree(reqResponseEvent)
val event = JsonMapper.fromJson(msg[2])
val event = (OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) as EventMessage).event
benchmarkRule.measureRepeated {
// Should pass
assertTrue(event.verifyId())
@@ -78,8 +79,7 @@ class EventBenchmark {
@Test
fun checkSignature() {
val msg = JsonMapper.mapper.readTree(reqResponseEvent)
val event = JsonMapper.fromJson(msg[2])
val event = (OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) as EventMessage).event
benchmarkRule.measureRepeated {
// Should pass
assertTrue(event.verifySignature())
@@ -24,12 +24,13 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.generateId
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
import com.vitorpamplona.quartz.nip01Core.crypto.generateId
import com.vitorpamplona.quartz.nip01Core.crypto.verifyId
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.verifyId
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -46,7 +47,7 @@ class EventCmdHasherBenchmark {
@Test
fun checkIDHashKind1WithoutTags() {
val event = EventMessage.parse(JsonMapper.mapper.readTree(reqResponseEvent)).event
val event = (OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) as EventMessage).event
benchmarkRule.measureRepeated {
// Should pass
@@ -24,7 +24,7 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasherSerializer
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -44,7 +44,7 @@ class EventVerifySerializer {
fun serializeToCheckId() {
val event = Event.fromJson(largeKind1Event)
benchmarkRule.measureRepeated {
EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
EventHasherSerializer.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
}
}
@@ -52,7 +52,7 @@ class EventVerifySerializer {
fun fastSerializeToCheckId() {
val event = Event.fromJson(largeKind1Event)
benchmarkRule.measureRepeated {
EventHasher.fastMakeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
EventHasherSerializer.fastMakeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
}
}
}
@@ -23,9 +23,9 @@ package com.vitorpamplona.quartz.benchmark
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.nip01Core.checkSignature
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.checkSignature
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip17Dm.NIP17Factory
@@ -26,11 +26,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
import com.vitorpamplona.quartz.nip01Core.crypto.verifyId
import com.vitorpamplona.quartz.nip01Core.crypto.verifySignature
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip01Core.verifyId
import com.vitorpamplona.quartz.nip01Core.verifySignature
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
import com.vitorpamplona.quartz.nip17Dm.messages.changeSubject
import com.vitorpamplona.quartz.nip36SensitiveContent.contentWarning
@@ -31,7 +31,6 @@ import com.vitorpamplona.quartz.utils.RandomInstance
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import java.nio.charset.Charset
@RunWith(AndroidJUnit4::class)
class HintIndexerBenchmark {
@@ -46,10 +45,10 @@ class HintIndexerBenchmark {
}
val relays =
javaClass.classLoader
?.getResourceAsStream("relayDB.txt")!!
.readBytes()
.toString(Charset.forName("utf-8"))
javaClass.classLoader!!
.getResourceAsStream("relayDB.txt")
.bufferedReader()
.use { it.readText() }
.split("\n")
.mapNotNull(RelayUrlNormalizer::normalizeOrNull)
}
@@ -61,6 +61,7 @@ class HkdfBenchmark {
}
}
/*
@Test
fun hkdfExpandOld() {
val hkdf = Hkdf()
@@ -68,6 +69,7 @@ class HkdfBenchmark {
hkdf.expand(sharedKey, encrypted.nonce, 76)
}
}
*/
@Test
fun hkdfExtract() {
@@ -22,7 +22,6 @@ package com.vitorpamplona.quartz.benchmark
import android.content.Context
import android.database.sqlite.SQLiteException
import android.util.Log
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.core.app.ApplicationProvider
@@ -31,6 +30,7 @@ import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
import com.vitorpamplona.quartz.nip01Core.store.sqlite.EventStore
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
import com.vitorpamplona.quartz.nip40Expiration.isExpired
import com.vitorpamplona.quartz.utils.Log
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -49,7 +49,7 @@ class LargeDBInsertBenchmark : BaseLargeCacheBenchmark() {
val context = ApplicationProvider.getApplicationContext<Context>()
benchmarkRule.measureRepeated {
val db =
this.runWithTimingDisabled {
this.runWithMeasurementDisabled {
val db = EventStore(context, "test1.db")
db.store.clearDB()
db
@@ -61,7 +61,7 @@ class LargeDBInsertBenchmark : BaseLargeCacheBenchmark() {
Log.w("LargeDBInsertBenchmark", "Error inserting event: ${e.message} for event: $event")
}
}
this.runWithTimingDisabled {
this.runWithMeasurementDisabled {
db.store.clearDB()
db.close()
}
@@ -82,7 +82,7 @@ class LargeDBInsertBenchmark : BaseLargeCacheBenchmark() {
benchmarkRule.measureRepeated {
val db =
this.runWithTimingDisabled {
this.runWithMeasurementDisabled {
val db = EventStore(context, "test1.db")
db.store.clearDB()
toBeDeletedEvents.forEach { event ->
@@ -104,7 +104,7 @@ class LargeDBInsertBenchmark : BaseLargeCacheBenchmark() {
}
}
runWithTimingDisabled {
runWithMeasurementDisabled {
db.store.clearDB()
db.close()
}
@@ -22,7 +22,6 @@ package com.vitorpamplona.quartz.benchmark
import android.content.Context
import android.database.sqlite.SQLiteException
import android.util.Log
import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.core.app.ApplicationProvider
@@ -32,6 +31,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
import com.vitorpamplona.quartz.nip01Core.store.sqlite.EventStore
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.utils.Log
import org.junit.After
import org.junit.Before
import org.junit.Rule
@@ -24,7 +24,7 @@ import androidx.benchmark.junit4.BenchmarkRule
import androidx.benchmark.junit4.measureRepeated
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasherSerializer
import com.vitorpamplona.quartz.utils.sha256.sha256
import junit.framework.TestCase.assertNotNull
import org.junit.Rule
@@ -46,7 +46,7 @@ class Sha256Benchmark {
@Test
fun sha256Pool() {
val event = Event.fromJson(largeKind1Event)
val byteArray = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray()
val byteArray = EventHasherSerializer.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray()
benchmarkRule.measureRepeated {
// Should pass
@@ -57,7 +57,7 @@ class Sha256Benchmark {
@Test
fun sha256NewEachTime() {
val event = Event.fromJson(largeKind1Event)
val byteArray = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray()
val byteArray = EventHasherSerializer.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray()
benchmarkRule.measureRepeated {
val digest = MessageDigest.getInstance("SHA-256")
@@ -68,7 +68,7 @@ class Sha256Benchmark {
@Test
fun sha256Reuse() {
val event = Event.fromJson(largeKind1Event)
val byteArray = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray()
val byteArray = EventHasherSerializer.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content).toByteArray()
val digest = MessageDigest.getInstance("SHA-256")
@@ -28,7 +28,7 @@ import com.vitorpamplona.quartz.nip01Core.crypto.Nip01
import com.vitorpamplona.quartz.utils.RandomInstance
import com.vitorpamplona.quartz.utils.sha256.sha256
import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith