Merge branch 'vitorpamplona:main' into add-feed-indicator-to-live-bubbles
This commit is contained in:
+23
@@ -27,13 +27,17 @@ import com.vitorpamplona.amethyst.model.AccountSettings
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.service.location.LocationState
|
||||
import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket
|
||||
import com.vitorpamplona.amethyst.service.relayClient.reqCommand.nwc.NWCPaymentFilterAssembler
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.dal.ThreadFeedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag
|
||||
import com.vitorpamplona.quartz.nip01Core.verify
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OkHttpOtsResolverBuilder
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OtsBlockHeightCache
|
||||
import junit.framework.TestCase
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -67,6 +71,25 @@ class ThreadDualAxisChartAssemblerTest {
|
||||
},
|
||||
CoroutineScope(Dispatchers.IO + SupervisorJob()),
|
||||
),
|
||||
nwcFilterAssembler =
|
||||
NWCPaymentFilterAssembler(
|
||||
NostrClient(
|
||||
OkHttpWebSocket.Builder {
|
||||
OkHttpClient
|
||||
.Builder()
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.build()
|
||||
},
|
||||
CoroutineScope(Dispatchers.IO + SupervisorJob()),
|
||||
),
|
||||
),
|
||||
otsResolverBuilder =
|
||||
OkHttpOtsResolverBuilder(
|
||||
okHttpClient = { OkHttpClient.Builder().build() },
|
||||
isTorActive = { false },
|
||||
cache = OtsBlockHeightCache(),
|
||||
),
|
||||
)
|
||||
|
||||
val db =
|
||||
|
||||
-1
@@ -27,7 +27,6 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.vitorpamplona.amethyst.ui.actions.MediaSaverToDisk.save
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorSettings
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorSettingsFlow
|
||||
import com.vitorpamplona.amethyst.ui.tor.TorType
|
||||
|
||||
-1
@@ -34,7 +34,6 @@ import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlin.collections.ifEmpty
|
||||
|
||||
class AccountsTorStateConnector(
|
||||
accountsCache: AccountCacheState,
|
||||
|
||||
+1
-1
@@ -172,7 +172,7 @@ class LightningAddressResolver {
|
||||
val messageNode = tree.get("message")
|
||||
val statusNode = tree.get("status")
|
||||
|
||||
if (tree.get("error").isBoolean && messageNode != null) {
|
||||
if (tree.get("error") != null && tree.get("error").isBoolean && messageNode != null) {
|
||||
if (errorNode.asBoolean()) {
|
||||
return messageNode.asText()
|
||||
}
|
||||
|
||||
-1
@@ -29,7 +29,6 @@ import com.vitorpamplona.amethyst.LocalPreferences
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.LocalCache
|
||||
import com.vitorpamplona.amethyst.model.LocalCache.chatroomList
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendDMNotification
|
||||
import com.vitorpamplona.amethyst.service.notifications.NotificationUtils.sendZapNotification
|
||||
|
||||
+22
-14
@@ -20,6 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.service.okhttp
|
||||
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import okhttp3.Dispatcher
|
||||
import okhttp3.OkHttpClient
|
||||
import java.net.InetSocketAddress
|
||||
@@ -35,6 +37,22 @@ class OkHttpClientFactory(
|
||||
const val DEFAULT_IS_MOBILE: Boolean = false
|
||||
const val DEFAULT_TIMEOUT_ON_WIFI_SECS: Int = 10
|
||||
const val DEFAULT_TIMEOUT_ON_MOBILE_SECS: Int = 30
|
||||
|
||||
private fun isEmulator(): Boolean =
|
||||
Build.FINGERPRINT.startsWith("generic") ||
|
||||
Build.FINGERPRINT.lowercase().contains("emulator") ||
|
||||
Build.MODEL.contains("google_sdk") ||
|
||||
Build.MODEL.lowercase().contains("droid4x") ||
|
||||
Build.MODEL.contains("Emulator") ||
|
||||
Build.MODEL.contains("Android SDK built for x86") ||
|
||||
Build.MANUFACTURER.contains("Genymotion") ||
|
||||
(Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) ||
|
||||
"google_sdk" == Build.PRODUCT ||
|
||||
Build.HARDWARE.contains("goldfish") ||
|
||||
Build.HARDWARE.contains("ranchu") ||
|
||||
Build.HARDWARE.contains("vbox86") ||
|
||||
Build.HARDWARE.contains("nox") ||
|
||||
Build.HARDWARE.contains("cuttlefish")
|
||||
}
|
||||
|
||||
val logging = LoggingInterceptor()
|
||||
@@ -42,22 +60,12 @@ class OkHttpClientFactory(
|
||||
|
||||
val myDispatcher =
|
||||
Dispatcher().apply {
|
||||
maxRequests = 512
|
||||
}
|
||||
|
||||
/*
|
||||
DEBUG OK HTTP connections here.
|
||||
init {
|
||||
if (isDebug) {
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
while (true) {
|
||||
Log.d("OkHttpClientFactory", "Active threads ${myDispatcher.runningCallsCount()}")
|
||||
delay(5000)
|
||||
}
|
||||
if (!isEmulator()) {
|
||||
maxRequests = 512
|
||||
} else {
|
||||
Log.i("OkHttpClientFactory", "Emulator detected, using default maxRequests: 64.")
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
private val rootClient =
|
||||
OkHttpClient
|
||||
|
||||
-1
@@ -26,7 +26,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.vitorpamplona.amethyst.model.Channel
|
||||
import com.vitorpamplona.amethyst.model.ChannelState
|
||||
import com.vitorpamplona.amethyst.model.LocalCache.notes
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel
|
||||
import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel
|
||||
|
||||
@@ -28,7 +28,6 @@ import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.Amethyst
|
||||
import com.vitorpamplona.amethyst.debugState
|
||||
|
||||
-1
@@ -33,7 +33,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.torSettings
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.topbars.SavingTopBar
|
||||
import com.vitorpamplona.amethyst.ui.tor.PrivacySettingsBody
|
||||
|
||||
-2
@@ -41,12 +41,10 @@ import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.collections.map
|
||||
|
||||
@Stable
|
||||
class RelayFeedViewModel :
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.relays
|
||||
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
-1
@@ -21,7 +21,6 @@
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
|
||||
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
||||
-1
@@ -32,7 +32,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.preferences.AccountPreferenceStores.Companion.torSettings
|
||||
import com.vitorpamplona.amethyst.ui.components.appendLink
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.tor.ConnectTorDialog
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* 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.benchmark
|
||||
|
||||
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 org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
/**
|
||||
* Benchmark, which will execute on an Android device.
|
||||
*
|
||||
* The body of [BenchmarkRule.measureRepeated] is measured in a loop, and Studio will output the
|
||||
* result. Modify your code to see how it affects performance.
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class EventVerifySerializer {
|
||||
@get:Rule
|
||||
val benchmarkRule = BenchmarkRule()
|
||||
|
||||
@Test
|
||||
fun serializeToCheckId() {
|
||||
val event = Event.fromJson(largeKind1Event)
|
||||
benchmarkRule.measureRepeated {
|
||||
EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fastSerializeToCheckId() {
|
||||
val event = Event.fromJson(largeKind1Event)
|
||||
benchmarkRule.measureRepeated {
|
||||
EventHasher.fastMakeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,13 @@ class HexBenchmark {
|
||||
fr.acinq.secp256k1.Hex
|
||||
.decode(hex)
|
||||
|
||||
@Test
|
||||
fun hexIsEqual() {
|
||||
r.measureRepeated {
|
||||
assert(Hex.isEqual(hex, bytes))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun hexDecodeOurs() {
|
||||
r.measureRepeated {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[versions]
|
||||
accompanistAdaptive = "0.37.3"
|
||||
activityCompose = "1.10.1"
|
||||
activityCompose = "1.11.0"
|
||||
agp = "8.13.0"
|
||||
android-compileSdk = "36"
|
||||
android-minSdk = "26"
|
||||
@@ -9,25 +9,25 @@ androidKotlinGeohash = "b481c6a64e"
|
||||
androidxJunit = "1.3.0"
|
||||
appcompat = "1.7.1"
|
||||
audiowaveform = "1.1.2"
|
||||
benchmark = "1.4.0"
|
||||
benchmarkJunit4 = "1.4.0"
|
||||
benchmark = "1.4.1"
|
||||
benchmarkJunit4 = "1.4.1"
|
||||
biometricKtx = "1.2.0-alpha05"
|
||||
coil = "3.3.0"
|
||||
composeBom = "2025.08.01"
|
||||
composeBom = "2025.09.00"
|
||||
coreKtx = "1.17.0"
|
||||
datastore = "1.1.7"
|
||||
espressoCore = "3.7.0"
|
||||
firebaseBom = "34.2.0"
|
||||
fragmentKtx = "1.8.9"
|
||||
gms = "4.4.3"
|
||||
jacksonModuleKotlin = "2.19.2"
|
||||
jacksonModuleKotlin = "2.20.0"
|
||||
jna = "5.17.0"
|
||||
jtorctl = "0.4.5.7"
|
||||
junit = "4.13.2"
|
||||
kotlin = "2.2.10"
|
||||
kotlin = "2.2.20"
|
||||
kotlinxCollectionsImmutable = "0.4.0"
|
||||
kotlinxSerialization = "1.9.0"
|
||||
kotlinxSerializationPlugin = "2.2.10"
|
||||
kotlinxSerializationPlugin = "2.2.20"
|
||||
languageId = "17.0.6"
|
||||
lazysodiumAndroid = "5.2.0"
|
||||
lifecycleRuntimeKtx = "2.9.3"
|
||||
@@ -36,7 +36,7 @@ markdown = "e1151c8"
|
||||
media3 = "1.8.0"
|
||||
mockk = "1.14.5"
|
||||
kotlinx-coroutines-test = "1.10.2"
|
||||
navigationCompose = "2.9.3"
|
||||
navigationCompose = "2.9.4"
|
||||
okhttp = "5.1.0"
|
||||
runner = "1.7.0"
|
||||
rfc3986 = "0.1.2"
|
||||
@@ -53,7 +53,7 @@ zoomable = "2.8.1"
|
||||
zxing = "3.5.3"
|
||||
zxingAndroidEmbedded = "4.3.0"
|
||||
windowCoreAndroid = "1.4.0"
|
||||
androidxCamera = "1.4.2"
|
||||
androidxCamera = "1.5.0"
|
||||
kotlinStdlib = "2.2.10"
|
||||
kotlinTest = "2.2.10"
|
||||
core = "1.7.0"
|
||||
|
||||
+101
@@ -21,7 +21,14 @@
|
||||
package com.vitorpamplona.quartz.nip01Core
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.vitorpamplona.quartz.EventFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@@ -58,4 +65,98 @@ class EventSigCheck {
|
||||
// Should pass
|
||||
event.checkSignature()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSerializationWithEmojis() {
|
||||
val event =
|
||||
EventFactory.create<ReactionEvent>(
|
||||
id = "ac2fb0c9b72a6fefe60262fbce6eb8740380b7f964200cb8efdd2e72fcb1ddb0",
|
||||
pubKey = "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c",
|
||||
createdAt = 1690288395,
|
||||
kind = 7,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("e", "2cc5b6e012c5a64fcf580fc1b53bed25ed0d7f785fd896744524b1a114dcc86e"),
|
||||
arrayOf("p", "c80b5248fbe8f392bc3ba45091fb4e6e2b5872387601bf90f53992366b30d720"),
|
||||
),
|
||||
content = "🚀",
|
||||
sig = "6df6aeef98c21a0508a788cbe6a2a6825e5cc69e57dd843dc6e6d4ce2c28dd042947723fda3d8b24489305d86b1e81f9f66b390d6f7dabf9742cd3775e17e53f",
|
||||
)
|
||||
|
||||
val old = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
val new = EventHasher.fastMakeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
|
||||
assertEquals(old.toByteArray().joinToString(), new.joinToString())
|
||||
assertTrue(event.verifySignature())
|
||||
assertTrue(event.verifyId())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSerializationWithUnicode() {
|
||||
val event =
|
||||
EventFactory.create<ClassifiedsEvent>(
|
||||
id = "e395a1a4cad5b9cf930533c48c2dd175a40331a1d33af8f11b776a0970bc1446",
|
||||
pubKey = "3fa2504f693c7f0fe71bded634339ae5383a8b14b4164d7c4935830b048dce12",
|
||||
createdAt = 1707335824,
|
||||
kind = 30402,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("d", "b5dd96da-eb92-463a-9cfd-70f1c5ca7e64"),
|
||||
arrayOf("title", "Google Pixel 7A (NUOVO)\n8GB RAM/128GB MEMORIA"),
|
||||
arrayOf("summary", "📱Google Pixel 7A (NUOVO)\n👉 8GB RAM/128GB MEMORIA \n\nRete : 5G\nColore : Grigio antracite\n\n👤Sistema operativo : GrapheneOS \n\n🔥Riacquista la tua privacy🔥\n\nℹ️Spacchettato solo per modifica ROM.\n\nNessuna app google e servizi google\n\nSolo app opensource \n\nSet-up privacy oriented incluso dedicato in base a vostre esigenze con Profili dedicati personale/lavoro, Vpn e Tor\n\n🔸️Android 14\n🔸️GrapheneOS ROM !!!!!!!\n🔸️Adattatore incluso Usb c / Usb per passaggio dati\n🔸️Ricarica con connettore USB Type-C \n🔸Dual SIM (nano SIM singola ed eSIM)\n🔸️Schermo da 6.1 pollici\n🔸️Fotocamera da 64 megapixel\n🔸️8 gb memoria ram\n🔸️128 gb memoria interna\n🔸Dimensioni: 155 x 152 x 72.9 mm\n🔸Sblocco con l'impronta tramite sensore di impronte digitali integrato nel display\n🔸️Batteria da 4385 mAh (Ricarica veloce e Ricarica wireless)\n🔸Materiali: Rivestimento in vetro Corning Gorilla Glass 3 resistente ai graffi\n🔸Resistenza all'acqua e alla polvere di grado IP67 \n\n✅️Cover in slicone in omaggio🔥\n\n💶PREZZO💶 \n\n💰380 € ( SOLO IN BITCOIN) \n\n🚚spedizione privacy oriented da Punto di ritiro a Punto di ritiro (SOLO ITALIA) inclusa, solo scrivendo allo Zio il ref: \"Ziophone21\" \n\n🚚Spedizione Full Privacy ( PREMIUM) \nNessun dato da parte utente, per chi necessitasse di questa sped, la differenza si paga a parte.\n\nAccettati pagamenti:\nBTC 🔗\nBTC ⚡️\nBTC💧\nhttps://image.nostr.build/87c79c56f270ca04607bc6d72b21786837f81344a960a3787820dc0c482c6660.jpg#m=image%2Fjpeg&dim=1254x1280&blurhash=%7CWHB--yGi%5E4mR3IVV%40RhIT%25LIpf5t6RjofafofWBMcMxbH%25MbJofofogt7j%5Bt7azWBoeWVj%5BayayD%24RikCt8t8off7j%5DogWBoej%5BWCofayazayoeROV%40j%5DogfloebHa%7DkCj%3Fayj%5Bj%5Bj%5Bayj%5BWVj%40e-fPa%7Dj%5BWVj%40j%5Ba%23j%5B&x=17b7ff98875a6e6d6e6bddee30e0a1c18ccf4281db940ee8b5bcc0736d2137c6"),
|
||||
arrayOf("price", "1000", "SATS"),
|
||||
arrayOf("t", "Electronics"),
|
||||
arrayOf("location", "Brescia Italia"),
|
||||
arrayOf("publishedAt", "1707335824"),
|
||||
arrayOf("condition", "like new"),
|
||||
arrayOf("image", "https://image.nostr.build/87c79c56f270ca04607bc6d72b21786837f81344a960a3787820dc0c482c6660.jpg#m=image%2Fjpeg&dim=1254x1280&blurhash=%7CWHB--yGi%5E4mR3IVV%40RhIT%25LIpf5t6RjofafofWBMcMxbH%25MbJofofogt7j%5Bt7azWBoeWVj%5BayayD%24RikCt8t8off7j%5DogWBoej%5BWCofayazayoeROV%40j%5DogfloebHa%7DkCj%3Fayj%5Bj%5Bj%5Bayj%5BWVj%40e-fPa%7Dj%5BWVj%40j%5Ba%23j%5B&x=17b7ff98875a6e6d6e6bddee30e0a1c18ccf4281db940ee8b5bcc0736d2137c6"),
|
||||
arrayOf("r", "https://image.nostr.build/87c79c56f270ca04607bc6d72b21786837f81344a960a3787820dc0c482c6660.jpg#m=image%2Fjpeg&dim=1254x1280&blurhash=%7CWHB--yGi%5E4mR3IVV%40RhIT%25LIpf5t6RjofafofWBMcMxbH%25MbJofofogt7j%5Bt7azWBoeWVj%5BayayD%24RikCt8t8off7j%5DogWBoej%5BWCofayazayoeROV%40j%5DogfloebHa%7DkCj%3Fayj%5Bj%5Bj%5Bayj%5BWVj%40e-fPa%7Dj%5BWVj%40j%5Ba%23j%5B&x=17b7ff98875a6e6d6e6bddee30e0a1c18ccf4281db940ee8b5bcc0736d2137c6"),
|
||||
arrayOf("alt", "Classifieds listing"),
|
||||
),
|
||||
content = "📱Google Pixel 7A (NUOVO)\n👉 8GB RAM/128GB MEMORIA \n\nRete : 5G\nColore : Grigio antracite\n\n👤Sistema operativo : GrapheneOS \n\n🔥Riacquista la tua privacy🔥\n\nℹ️Spacchettato solo per modifica ROM.\n\nNessuna app google e servizi google\n\nSolo app opensource \n\nSet-up privacy oriented incluso dedicato in base a vostre esigenze con Profili dedicati personale/lavoro, Vpn e Tor\n\n🔸️Android 14\n🔸️GrapheneOS ROM !!!!!!!\n🔸️Adattatore incluso Usb c / Usb per passaggio dati\n🔸️Ricarica con connettore USB Type-C \n🔸Dual SIM (nano SIM singola ed eSIM)\n🔸️Schermo da 6.1 pollici\n🔸️Fotocamera da 64 megapixel\n🔸️8 gb memoria ram\n🔸️128 gb memoria interna\n🔸Dimensioni: 155 x 152 x 72.9 mm\n🔸Sblocco con l'impronta tramite sensore di impronte digitali integrato nel display\n🔸️Batteria da 4385 mAh (Ricarica veloce e Ricarica wireless)\n🔸Materiali: Rivestimento in vetro Corning Gorilla Glass 3 resistente ai graffi\n🔸Resistenza all'acqua e alla polvere di grado IP67 \n\n✅️Cover in slicone in omaggio🔥\n\n💶PREZZO💶 \n\n💰380 € ( SOLO IN BITCOIN) \n\n🚚spedizione privacy oriented da Punto di ritiro a Punto di ritiro (SOLO ITALIA) inclusa, solo scrivendo allo Zio il ref: \"Ziophone21\" \n\n🚚Spedizione Full Privacy ( PREMIUM) \nNessun dato da parte utente, per chi necessitasse di questa sped, la differenza si paga a parte.\n\nAccettati pagamenti:\nBTC 🔗\nBTC ⚡️\nBTC💧\nhttps://image.nostr.build/87c79c56f270ca04607bc6d72b21786837f81344a960a3787820dc0c482c6660.jpg#m=image%2Fjpeg&dim=1254x1280&blurhash=%7CWHB--yGi%5E4mR3IVV%40RhIT%25LIpf5t6RjofafofWBMcMxbH%25MbJofofogt7j%5Bt7azWBoeWVj%5BayayD%24RikCt8t8off7j%5DogWBoej%5BWCofayazayoeROV%40j%5DogfloebHa%7DkCj%3Fayj%5Bj%5Bj%5Bayj%5BWVj%40e-fPa%7Dj%5BWVj%40j%5Ba%23j%5B&x=17b7ff98875a6e6d6e6bddee30e0a1c18ccf4281db940ee8b5bcc0736d2137c6",
|
||||
sig = "e03c81ec2543e777583bfcf93c0d82d3055a47a541b1a961db010ed2390e56ddc59e4f891505672757b7ac0d52e891854fdd61f1a4e13b4e6aa563e94cb9368e",
|
||||
)
|
||||
|
||||
val old = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
val new = EventHasher.fastMakeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
|
||||
println(old)
|
||||
println(String(new))
|
||||
|
||||
assertEquals(old, String(new))
|
||||
assertTrue(event.verifySignature())
|
||||
assertTrue(event.verifyId())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkSerializationLargeZap() {
|
||||
val event =
|
||||
EventFactory.create<LnZapEvent>(
|
||||
id = "e7f09fddf39fc6cb604708b6af7b4d4adbb07b412847ebb004064040fe8c4b1e",
|
||||
pubKey = "79f00d3f5a19ec806189fcab03c1be4ff81d18ee4f653c88fac41fe03570f432",
|
||||
createdAt = 1728921077,
|
||||
kind = 9735,
|
||||
tags =
|
||||
arrayOf(
|
||||
arrayOf("p", "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c"),
|
||||
arrayOf("e", "58f22b06a219f92fb535c71f7098e076751cf2798208acabbbdefbcb950585f5"),
|
||||
arrayOf("P", "21335073401a310cc9179fe3a77e9666710cfdf630dfd840f972c183a244b1ad"),
|
||||
arrayOf("bolt11", "lnbc420n1pns600jdqjfah8wctjvss0p8at5ynp4qtfc238rdkzsj26waa3l8zgag9damzltzsqcrlscj9gvpc7ch2qs2pp5ks03qwm8laa78hnh0xy0p78l6wmyuj3zfqas3gzc2a52lj2zrmtqsp539528j3tvvfzpk8n5v966zccvj3pq2l3etxqxh5qsp8emh29yw3q9qyysgqcqpcxqyz5vqrzjqvdnqyc82a9maxu6c7mee0shqr33u4z9z04wpdwhf96gxzpln8jcrapyqqqqqqp2rcqqqqlgqqqqqzsq2qrzjqw9fu4j39mycmg440ztkraa03u5qhtuc5zfgydsv6ml38qd4azymlapyqqqqqqqp9sqqqqlgqqqq86qqjqrzjq26922n6s5n5undqrf78rjjhgpcczafws45tx8237y7pzx3fg8wwxrgayyqq2mgqqqqqqqqqqqqqqqqq2quc90y7tgfxuauh0vjfvhxjgektaycfesne76jcuk4u9mt6a9l39pddzk3muwy03sjvfk0w8390xxnpzu2656jf2l73ya59ye2yx9aaqpdgxmaq"),
|
||||
arrayOf("preimage", "0718bf6ba9f503917a2568dac7fe7a5b9361eeb708cd5f57b700060a173ed652"),
|
||||
arrayOf("description", "{\"id\":\"1110d1216fff3200ed562439556be508a42e4202f66b55f09c9abeed06307d57\",\"pubkey\":\"21335073401a310cc9179fe3a77e9666710cfdf630dfd840f972c183a244b1ad\",\"created_at\":1728921073,\"kind\":9734,\"tags\":[[\"p\",\"460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c\"],[\"e\",\"58f22b06a219f92fb535c71f7098e076751cf2798208acabbbdefbcb950585f5\"],[\"amount\",\"42000\"],[\"relays\",\"wss://christpill.nostr1.com\",\"wss://relay.nostr.bg\",\"wss://relay.noderunners.network\",\"wss://relay.nostrplebs.com\",\"wss://relay.utxo.one\",\"wss://filter.nostr.wine\",\"wss://pyramid.fiatjaf.com\",\"wss://wot.utxo.one\",\"wss://relay.mostr.pub\",\"wss://relay.nostr.band\",\"wss://relay.primal.net\",\"wss://relay.snort.social\",\"wss://purplepag.es\",\"wss://wot.nostr.party\",\"wss://catstrr.swarmstr.com\",\"wss://nostr-relay.derekross.me\",\"wss://relay.damus.io\",\"wss://nos.lol\",\"wss://nostr.wine\",\"wss://relay.bitcoinpark.com\",\"wss://sendit.nosflare.com\",\"wss://nostrelites.org\",\"wss://relay.momostr.pink\",\"ws://localhost:4869\"]],\"content\":\"Onward 🫡\",\"sig\":\"82333a70103b6541f125ac64379a32ad0e9a51eb2769cf899c88df8406bdfa339693d530e640d1c701f4d9d7d847806e1ee2118655b7ba3f9795ce56747eff1b\"}"),
|
||||
),
|
||||
content = "Onward \uD83E\uDEE1",
|
||||
sig = "81d08765524bd8b774585a57c76d25b1d2c09eaa23efcb075226ba8b64f42e3d9d9403e5edf888e0e58702a24abc084259e21b97e24a275021c5e36186c65f0c",
|
||||
)
|
||||
|
||||
val old = EventHasher.makeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
val new = EventHasher.fastMakeJsonForId(event.pubKey, event.createdAt, event.kind, event.tags, event.content)
|
||||
|
||||
println(old)
|
||||
println(String(new))
|
||||
|
||||
assertEquals(old.toByteArray().joinToString(), new.joinToString())
|
||||
assertTrue(event.verifySignature())
|
||||
assertTrue(event.verifyId())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ fun Event.generateId(): String = EventHasher.hashId(pubKey, createdAt, kind, tag
|
||||
|
||||
fun Event.verifyId(): Boolean {
|
||||
if (id.isEmpty()) return false
|
||||
return id == generateId()
|
||||
return EventHasher.hashIdEquals(id, pubKey, createdAt, kind, tags, content)
|
||||
}
|
||||
|
||||
fun Event.verifySignature(): Boolean {
|
||||
|
||||
+66
-1
@@ -20,12 +20,20 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.crypto
|
||||
|
||||
import com.fasterxml.jackson.core.JsonEncoding
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import com.fasterxml.jackson.core.JsonProcessingException
|
||||
import com.fasterxml.jackson.core.util.BufferRecycler
|
||||
import com.fasterxml.jackson.core.util.ByteArrayBuilder
|
||||
import com.fasterxml.jackson.databind.JsonMappingException
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import com.vitorpamplona.quartz.utils.sha256.sha256
|
||||
import java.io.IOException
|
||||
|
||||
class EventHasher {
|
||||
companion object {
|
||||
@@ -63,13 +71,58 @@ class EventHasher {
|
||||
content: String,
|
||||
): String = JsonMapper.toJson(makeJsonObjectForId(pubKey, createdAt, kind, tags, content))
|
||||
|
||||
fun fastMakeJsonForId(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): ByteArray {
|
||||
val br: BufferRecycler = JsonMapper.mapper.factory._getBufferRecycler()
|
||||
try {
|
||||
ByteArrayBuilder(br).use { bb ->
|
||||
val generator = JsonMapper.mapper.createGenerator(bb, JsonEncoding.UTF8)
|
||||
generator.enable(JsonGenerator.Feature.COMBINE_UNICODE_SURROGATES_IN_UTF8)
|
||||
generator.use {
|
||||
it.writeStartArray()
|
||||
it.writeNumber(0)
|
||||
it.writeString(pubKey)
|
||||
it.writeNumber(createdAt)
|
||||
it.writeNumber(kind)
|
||||
it.writeStartArray()
|
||||
tags.forEach { tag ->
|
||||
it.writeStartArray()
|
||||
tag.forEach { value ->
|
||||
it.writeString(value)
|
||||
}
|
||||
it.writeEndArray()
|
||||
}
|
||||
it.writeEndArray()
|
||||
it.writeString(content)
|
||||
it.writeEndArray()
|
||||
}
|
||||
|
||||
val result = bb.toByteArray()
|
||||
bb.release()
|
||||
return result
|
||||
}
|
||||
} catch (e: JsonProcessingException) {
|
||||
throw e
|
||||
} catch (e: IOException) {
|
||||
// shouldn't really happen, but is declared as possibility so:
|
||||
throw JsonMappingException.fromUnexpectedIOE(e)
|
||||
} finally {
|
||||
br.releaseToPool()
|
||||
}
|
||||
}
|
||||
|
||||
fun hashIdBytes(
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): ByteArray = sha256(makeJsonForId(pubKey, createdAt, kind, tags, content).toByteArray())
|
||||
): ByteArray = sha256(fastMakeJsonForId(pubKey, createdAt, kind, tags, content))
|
||||
|
||||
fun hashId(serializedJsonAsBytes: ByteArray): String = sha256(serializedJsonAsBytes).toHexKey()
|
||||
|
||||
@@ -80,5 +133,17 @@ class EventHasher {
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): String = hashIdBytes(pubKey, createdAt, kind, tags, content).toHexKey()
|
||||
|
||||
fun hashIdEquals(
|
||||
id: HexKey,
|
||||
pubKey: HexKey,
|
||||
createdAt: Long,
|
||||
kind: Int,
|
||||
tags: Array<Array<String>>,
|
||||
content: String,
|
||||
): Boolean {
|
||||
val outId = hashIdBytes(pubKey, createdAt, kind, tags, content)
|
||||
return Hex.isEqual(id, outId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
|
||||
object RelayStats {
|
||||
private val innerCache =
|
||||
object : LruCache<NormalizedRelayUrl, RelayStat>(100) {
|
||||
object : LruCache<NormalizedRelayUrl, RelayStat>(1000) {
|
||||
override fun create(key: NormalizedRelayUrl?) = RelayStat()
|
||||
}
|
||||
|
||||
|
||||
@@ -79,13 +79,13 @@ class PoWMiner(
|
||||
|
||||
val bytes =
|
||||
EventHasher
|
||||
.makeJsonForId(
|
||||
pubKey,
|
||||
template.createdAt,
|
||||
template.kind,
|
||||
template.tags + PoWTag.assemble(initialNonce, desiredPoW),
|
||||
template.content,
|
||||
).toByteArray()
|
||||
.fastMakeJsonForId(
|
||||
pubKey = pubKey,
|
||||
createdAt = template.createdAt,
|
||||
kind = template.kind,
|
||||
tags = template.tags + PoWTag.assemble(initialNonce, desiredPoW),
|
||||
content = template.content,
|
||||
)
|
||||
|
||||
val startIndex = bytes.indexOf(initialNonce.toByteArray())
|
||||
|
||||
|
||||
+4
@@ -23,6 +23,7 @@ package com.vitorpamplona.quartz.nip28PublicChat.admin
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.fasterxml.jackson.core.JsonParseException
|
||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||
@@ -68,6 +69,9 @@ class ChannelCreateEvent(
|
||||
} catch (e: JsonParseException) {
|
||||
Log.w("ChannelCreateEvent", "Failure to parse ${this.toJson()}", e)
|
||||
ChannelDataNorm()
|
||||
} catch (e: MismatchedInputException) {
|
||||
Log.w("ChannelCreateEvent", "Failure to parse ${this.toJson()}", e)
|
||||
ChannelDataNorm()
|
||||
}
|
||||
|
||||
cache = newInfo
|
||||
|
||||
@@ -47,7 +47,6 @@ import com.vitorpamplona.quartz.nip19Bech32.eventIds
|
||||
import com.vitorpamplona.quartz.nip19Bech32.pubKeyHints
|
||||
import com.vitorpamplona.quartz.nip19Bech32.pubKeys
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
|
||||
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag.Companion.parse
|
||||
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||
import com.vitorpamplona.quartz.nip50Search.SearchableEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
|
||||
-1
@@ -27,7 +27,6 @@ import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.dTags.dTag
|
||||
import com.vitorpamplona.quartz.nip22Comments.RootScope
|
||||
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||
import com.vitorpamplona.quartz.nip71Video.videoIMetas
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.util.UUID
|
||||
|
||||
|
||||
@@ -74,4 +74,21 @@ object Hex {
|
||||
}
|
||||
return String(out)
|
||||
}
|
||||
|
||||
fun isEqual(
|
||||
id: String,
|
||||
ourId: ByteArray,
|
||||
): Boolean {
|
||||
var charIndex = 0
|
||||
for (i in 0 until ourId.size) {
|
||||
val chars = byteToHex[ourId[i].toInt() and 0xFF]
|
||||
if (
|
||||
id[charIndex++] != (chars shr 8).toChar() ||
|
||||
id[charIndex++] != (chars and 0xFF).toChar()
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user