diff --git a/.gitignore b/.gitignore index f527be1420..b4ec9c6450 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ /.idea/other.xml /.idea/runConfigurations.xml /.idea/ChatHistory_schema_v2.xml +/.idea/artifacts/* .DS_Store /build /captures diff --git a/README.md b/README.md index f322784272..d1bf11d231 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -
+
Amethyst Logo diff --git a/amethyst/build.gradle b/amethyst/build.gradle index 5c72709ddc..4733679989 100644 --- a/amethyst/build.gradle +++ b/amethyst/build.gradle @@ -44,8 +44,8 @@ android { applicationId = "com.vitorpamplona.amethyst" minSdk = libs.versions.android.minSdk.get().toInteger() targetSdk = libs.versions.android.targetSdk.get().toInteger() - versionCode = 424 - versionName = generateVersionName("1.00.5") + versionCode = 425 + versionName = generateVersionName("1.01.0") buildConfigField "String", "RELEASE_NOTES_ID", "\"08abe267baf5d7ce14db7975866f929e2794cc23484171aef0816c60a2416597\"" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt index d039911477..91f63f94d3 100644 --- a/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt +++ b/amethyst/src/androidTest/java/com/vitorpamplona/amethyst/OkHttpOtsTest.kt @@ -21,14 +21,14 @@ package com.vitorpamplona.amethyst import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.vitorpamplona.amethyst.service.ots.OkHttpBitcoinExplorer -import com.vitorpamplona.amethyst.service.ots.OkHttpCalendarBuilder -import com.vitorpamplona.amethyst.service.ots.OtsBlockHeightCache import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver +import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OkHttpBitcoinExplorer +import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OkHttpCalendarBuilder +import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OtsBlockHeightCache import junit.framework.TestCase.assertEquals import kotlinx.coroutines.runBlocking import okhttp3.OkHttpClient diff --git a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt index b6e13bc24f..9862203d48 100644 --- a/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt +++ b/amethyst/src/fdroid/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt @@ -22,6 +22,8 @@ package com.vitorpamplona.amethyst.ui.components import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import com.vitorpamplona.amethyst.ui.navigation.navs.INav @@ -52,3 +54,11 @@ fun TranslatableRichTextViewer( accountViewModel, nav, ) + +@Composable +fun TranslatableRichTextViewer( + content: String, + id: String, + accountViewModel: AccountViewModel, + displayText: @Composable (String) -> Unit, +) = displayText(content) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt index 70e36a3f52..b0e7de2fcf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/Amethyst.kt @@ -38,7 +38,6 @@ import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager import com.vitorpamplona.amethyst.service.okhttp.EncryptionKeyCache import com.vitorpamplona.amethyst.service.okhttp.OkHttpWebSocket import com.vitorpamplona.amethyst.service.okhttp.ProxySettingsAnchor -import com.vitorpamplona.amethyst.service.ots.OtsBlockHeightCache import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCache import com.vitorpamplona.amethyst.service.playback.diskCache.VideoCacheFactory import com.vitorpamplona.amethyst.service.relayClient.CacheClientConnector @@ -51,6 +50,7 @@ import com.vitorpamplona.amethyst.service.uploads.nip95.Nip95CacheFactory import com.vitorpamplona.amethyst.ui.tor.TorManager import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache +import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OtsBlockHeightCache import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index d8943b4682..5ecf4f660e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -89,7 +89,6 @@ import com.vitorpamplona.amethyst.model.topNavFeeds.IFeedTopNavFilter import com.vitorpamplona.amethyst.model.topNavFeeds.OutboxLoaderState import com.vitorpamplona.amethyst.model.torState.TorRelayState import com.vitorpamplona.amethyst.service.location.LocationState -import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder import com.vitorpamplona.amethyst.service.uploads.FileHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.lists.FollowSet import com.vitorpamplona.quartz.experimental.bounties.BountyAddValueEvent @@ -137,6 +136,7 @@ import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUserIds import com.vitorpamplona.quartz.nip01Core.tags.references.references +import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OkHttpOtsResolverBuilder import com.vitorpamplona.quartz.nip04Dm.PrivateDMCache import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent @@ -187,6 +187,8 @@ import com.vitorpamplona.quartz.nip68Picture.pictureIMeta import com.vitorpamplona.quartz.nip71Video.VideoMeta import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent import com.vitorpamplona.quartz.nip71Video.VideoShortEvent +import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent import com.vitorpamplona.quartz.nip92IMeta.IMetaTag import com.vitorpamplona.quartz.nip92IMeta.imetas @@ -323,7 +325,9 @@ class Account( val otsResolverBuilder: OkHttpOtsResolverBuilder = OkHttpOtsResolverBuilder( - Amethyst.instance.okHttpClients, + { + Amethyst.instance.okHttpClients.getHttpClient(privacyState.shouldUseTorForMoneyOperations(it)) + }, privacyState::shouldUseTorForMoneyOperations, Amethyst.instance.otsBlockHeightCache, ) @@ -879,6 +883,24 @@ class Account( suspend fun unfollowGeohash(geohash: String) = sendMyPublicAndPrivateOutbox(geohashList.unfollow(geohash)) + suspend fun approveCommunityPost( + post: Note, + community: AddressableNote, + ) { + val commEvent = community.event as? CommunityDefinitionEvent ?: return + val postHint = post.toEventHint() ?: return + val communityHint = community.toEventHint() ?: return + + val template = CommunityPostApprovalEvent.build(postHint, communityHint) + + val signedEvent = signer.sign(template) + + val relays = outboxRelays.flow.value + commEvent.relayUrls() + community.relays + (post.author?.inboxRelays() ?: emptyList()) + + cache.justConsumeMyOwnEvent(signedEvent) + client.send(signedEvent, relays) + } + fun sendAutomatic(events: List) = events.forEach { sendAutomatic(it) } fun sendAutomatic(event: Event?) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Constants.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Constants.kt index d34c7d42c6..461faca450 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Constants.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Constants.kt @@ -34,7 +34,6 @@ object Constants { val elites = RelayUrlNormalizer.normalize("wss://nostrelites.org") val bitcoiner = RelayUrlNormalizer.normalize("wss://nostr.bitcoiner.social") - val bg = RelayUrlNormalizer.normalize("wss://relay.nostr.bg") val oxtr = RelayUrlNormalizer.normalize("wss://nostr.oxtr.dev") val fmtwiz = RelayUrlNormalizer.normalize("wss://nostr.fmt.wiz.biz") @@ -47,7 +46,7 @@ object Constants { val coracle = RelayUrlNormalizer.normalize("wss://indexer.coracle.social") val userkinds = RelayUrlNormalizer.normalize("wss://user.kindpag.es") - val eventFinderRelays = setOf(band, wine, damus, primal, mom, nos, bitcoiner, oxtr, fmtwiz, bg) + val eventFinderRelays = setOf(band, wine, damus, primal, mom, nos, bitcoiner, oxtr, fmtwiz) val defaultSearchRelaySet = setOf(band, wine, where) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 3b401ac631..40f17f4d50 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -1453,6 +1453,8 @@ object LocalCache : ILocalCache { // Counts the replies repliesTo.forEach { it.addBoost(note) } + eventsApproved.forEach { it.addBoost(note) } + event.containedPost()?.let { checkDeletionAndConsume(it, relay, false) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 4d59a2efc8..b813dcfc04 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -50,6 +50,7 @@ import com.vitorpamplona.quartz.nip18Reposts.RepostEvent import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress import com.vitorpamplona.quartz.nip19Bech32.entities.NEvent import com.vitorpamplona.quartz.nip22Comments.CommentEvent +import com.vitorpamplona.quartz.nip28PublicChat.base.IsInPublicChatChannel import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent @@ -64,6 +65,7 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nip59Giftwrap.WrappedEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.anyAsync import com.vitorpamplona.quartz.utils.containsAny @@ -201,6 +203,16 @@ open class Note( } fun relayHintUrl(): NormalizedRelayUrl? { + val noteEvent = event + val communityPostRelays = + when (noteEvent) { + is CommunityDefinitionEvent -> noteEvent.relayUrls().ifEmpty { null }?.toSet() + is IsInPublicChatChannel -> LocalCache.getAnyChannel(this)?.relays() + else -> null + } + + if (!communityPostRelays.isNullOrEmpty()) return communityPostRelays.firstOrNull() + val currentOutbox = author?.outboxRelays()?.toSet() return if (relays.isNotEmpty()) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt index b013507614..7026aca5a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/ThreadAssembler.kt @@ -36,12 +36,14 @@ class ThreadAssembler { ): Note? { if (note.replyTo == null || note.replyTo?.isEmpty() == true) return note - if (note.event is RepostEvent || note.event is GenericRepostEvent) return note + val noteEvent = note.event + + if (noteEvent is RepostEvent || noteEvent is GenericRepostEvent) return note testedNotes.add(note) val markedAsRoot = - note.event + noteEvent ?.tags ?.firstOrNull { it[0] == "e" && it.size > 3 && it[3] == "root" } ?.getOrNull(1) @@ -52,7 +54,10 @@ class ThreadAssembler { } } - val hasNoReplyTo = note.replyTo?.reversed()?.firstOrNull { it.replyTo?.isEmpty() == true } + val hasNoReplyTo = + note.replyTo?.lastOrNull { + it.replyTo?.isEmpty() == true + } if (hasNoReplyTo != null) return hasNoReplyTo // recursive diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt index 44f316caad..763d724194 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/nip03Timestamp/OtsState.kt @@ -24,10 +24,10 @@ import android.util.Log import com.vitorpamplona.amethyst.model.AccountSettings import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note -import com.vitorpamplona.amethyst.service.ots.OkHttpOtsResolverBuilder import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent +import com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp.OkHttpOtsResolverBuilder import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.update import java.util.Base64 diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt index 960018f896..665d4fe799 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/EventObservers.kt @@ -29,6 +29,11 @@ import com.vitorpamplona.amethyst.model.NoteState import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.quartz.nip01Core.core.Event +import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent +import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip72ModCommunities.isForCommunity import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.FlowPreview @@ -356,3 +361,49 @@ fun observeNoteEdits( .stateFlow .collectAsStateWithLifecycle() } + +@Composable +fun observeCommunityApprovalNeedStatus( + note: Note, + community: Note, + accountViewModel: AccountViewModel, +): State { + // Subscribe in the relay for changes in this note. + EventFinderFilterAssemblerSubscription(note, accountViewModel) + + // Subscribe in the LocalCache for changes that arrive in the device + val flow = + remember(note, community) { + combine( + community.flow().metadata.stateFlow, + note.flow().boosts.stateFlow, + ) { communityMetadata, boosts -> + (communityMetadata.note.event as? CommunityDefinitionEvent)?.let { communityDefEvent -> + val moderators = communityDefEvent.moderatorKeys().toSet() + + if (note.author?.pubkeyHex in moderators) { + false + } else { + val isModerator = accountViewModel.account.userProfile().pubkeyHex in moderators + + if (isModerator) { + val wasAlreadyApproved = + note.boosts.any { + val approvalEvent = it.event + (approvalEvent is CommunityPostApprovalEvent || approvalEvent is RepostEvent || approvalEvent is GenericRepostEvent) && + approvalEvent.pubKey in moderators && + approvalEvent.isForCommunity(community.idHex) + } + !wasAlreadyApproved + } else { + false + } + } + } + }.distinctUntilChanged() + .flowOn(Dispatchers.Default) + } + + // Subscribe in the LocalCache for changes that arrive in the device + return flow.collectAsStateWithLifecycle(false) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt index b010546033..5e5b5343be 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToAddresses.kt @@ -29,6 +29,7 @@ import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent import com.vitorpamplona.quartz.nip56Reports.ReportEvent @@ -45,6 +46,7 @@ val RepliesAndReactionsToAddressesKinds1 = ReportEvent.KIND, LnZapEvent.KIND, PollNoteEvent.KIND, + CommentEvent.KIND, ) val PostsAndChatMessagesToAddresses = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt index a6c6d05949..dd76df8f64 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/event/watchers/FilterRepliesAndReactionsToNotes.kt @@ -31,6 +31,7 @@ import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip34Git.reply.GitReplyEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent @@ -51,7 +52,7 @@ val RepliesAndReactionsKinds = PollNoteEvent.KIND, OtsEvent.KIND, TextNoteModificationEvent.KIND, - GitReplyEvent.KIND, + CommentEvent.KIND, ) val RepliesAndReactionsKinds2 = @@ -60,6 +61,7 @@ val RepliesAndReactionsKinds2 = NIP90ContentDiscoveryResponseEvent.KIND, NIP90StatusEvent.KIND, TorrentCommentEvent.KIND, + GitReplyEvent.KIND, ) fun filterRepliesAndReactionsToNotes( @@ -108,7 +110,7 @@ fun filterRepliesAndReactionsToNotes( relay = relay, filter = Filter( - kinds = listOf(TextNoteEvent.KIND), + kinds = listOf(TextNoteEvent.KIND, CommentEvent.KIND), tags = mapOf("q" to sortedList), since = since, limit = 1000, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt index 17181f90c9..540e2d2fd4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/SensitivityWarning.kt @@ -56,6 +56,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding +import com.vitorpamplona.amethyst.ui.theme.PaddingHorizontal12Modifier import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip36SensitiveContent.isSensitiveOrNSFW @@ -113,7 +114,7 @@ fun SensitivityWarning( @Composable fun ContentWarningNote(onDismiss: () -> Unit) { Column { - Row(modifier = Modifier.padding(horizontal = 12.dp)) { + Row(modifier = PaddingHorizontal12Modifier) { Column(modifier = Modifier.padding(start = 10.dp)) { Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) { Box( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/AdditiveFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/AdditiveFeedFilter.kt index 8bbf5e91b9..5e77294815 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/AdditiveFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/dal/AdditiveFeedFilter.kt @@ -40,11 +40,6 @@ abstract class AdditiveFeedFilter : oldList } } - - fun removeNotes( - oldList: List, - deletedNotes: Set, - ): List = oldList - deletedNotes } interface IAdditiveFeedFilter : IFeedFilter { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/ChannelFeedContentState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/ChannelFeedContentState.kt index fa561cbf6b..c49976e690 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/ChannelFeedContentState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/ChannelFeedContentState.kt @@ -105,6 +105,8 @@ class ChannelFeedContentState( } } + fun deleteFromFeed(deletedNotes: Set) {} + fun refreshFromOldState(newItems: Set) { val oldNotesState = _feedContent.value if (oldNotesState is ChannelFeedState.Loaded) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt index eae3d24525..1792ef4d0c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt @@ -29,7 +29,6 @@ import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.navigation.navs.INav @@ -49,8 +48,6 @@ fun FeedLoaded( ) { val items by loaded.feed.collectAsStateWithLifecycle() - val scope = rememberCoroutineScope() - LazyColumn( contentPadding = FeedPadding, state = listState, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/layouts/DisappearingScaffold.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/layouts/DisappearingScaffold.kt index d9fd8f4135..9685177157 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/layouts/DisappearingScaffold.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/layouts/DisappearingScaffold.kt @@ -64,9 +64,10 @@ fun DisappearingScaffold( bottomBar: (@Composable () -> Unit)? = null, floatingButton: (@Composable () -> Unit)? = null, accountViewModel: AccountViewModel, + isActive: () -> Boolean = { true }, mainContent: @Composable (padding: PaddingValues) -> Unit, ) { - val shouldShow = remember { mutableStateOf(true) } + val shouldShow = remember { mutableStateOf(isActive()) } val modifier = if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) { @@ -80,6 +81,13 @@ fun DisappearingScaffold( available: Offset, source: NestedScrollSource, ): Offset { + if (!isActive()) { + if (!shouldShow.value) { + shouldShow.value = true + } + return Offset.Zero + } + val newOffset = bottomBarOffsetHeightPx.floatValue + available.y if (accountViewModel.settings.automaticallyHideNavigationBars == BooleanType.ALWAYS) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index 4bdf1b7b56..13f4c542f8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -28,9 +28,11 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.defaultMinSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -56,6 +58,7 @@ import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.nip28PublicChats.PublicChatChannel import com.vitorpamplona.amethyst.service.relayClient.reqCommand.channel.observeChannelPicture +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeCommunityApprovalNeedStatus import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEdits import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent import com.vitorpamplona.amethyst.ui.components.GenericLoadable @@ -139,7 +142,9 @@ import com.vitorpamplona.amethyst.ui.theme.HalfPadding import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding import com.vitorpamplona.amethyst.ui.theme.RowColSpacing10dp import com.vitorpamplona.amethyst.ui.theme.RowColSpacing5dp +import com.vitorpamplona.amethyst.ui.theme.Size10dp import com.vitorpamplona.amethyst.ui.theme.Size25dp +import com.vitorpamplona.amethyst.ui.theme.Size30dp import com.vitorpamplona.amethyst.ui.theme.Size34dp import com.vitorpamplona.amethyst.ui.theme.Size55Modifier import com.vitorpamplona.amethyst.ui.theme.Size55dp @@ -164,7 +169,6 @@ import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent -import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableKind import com.vitorpamplona.quartz.nip01Core.tags.geohash.geoHashOrScope import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent @@ -212,7 +216,9 @@ import com.vitorpamplona.quartz.nip71Video.VideoNormalEvent import com.vitorpamplona.quartz.nip71Video.VideoShortEvent import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent +import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip72ModCommunities.isACommunityPost import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent @@ -552,6 +558,8 @@ fun InnerNoteWithReactions( accountViewModel = accountViewModel, nav = nav, ) + + RenderApprovalIfNeeded(baseNote, accountViewModel, nav) } } @@ -577,6 +585,47 @@ fun InnerNoteWithReactions( } } +@Composable +private fun RenderApprovalIfNeeded( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + if (baseNote.isNewThread()) { + val communityAddress = + remember(baseNote) { + baseNote.event?.communityAddress() + } + communityAddress?.let { + LoadAddressableNote(it, accountViewModel) { community -> + if (community != null) { + val showApproveButton by observeCommunityApprovalNeedStatus(baseNote, community, accountViewModel) + if (showApproveButton == true) { + RenderApproveButton(baseNote, community, accountViewModel) + } + } + } + } + } +} + +@Composable +fun RenderApproveButton( + post: Note, + community: AddressableNote, + accountViewModel: AccountViewModel, +) { + OutlinedButton( + modifier = Modifier.fillMaxWidth().defaultMinSize(minWidth = Size10dp, minHeight = Size30dp), + contentPadding = PaddingValues(0.dp), + onClick = { + accountViewModel.approveCommunityPost(post, community) + }, + ) { + Text("Approve") + } +} + @Composable fun NoteBody( baseNote: Note, @@ -1090,14 +1139,7 @@ fun FirstUserInfoRow( ) { Row(verticalAlignment = CenterVertically, modifier = UserNameRowHeight) { val isRepost = baseNote.event is RepostEvent || baseNote.event is GenericRepostEvent - - val isCommunityPost by - remember(baseNote) { - derivedStateOf { - baseNote.event?.isTaggedAddressableKind(CommunityDefinitionEvent.KIND) == true - } - } - + val isDraft = baseNote.isDraft() val textColor = if (isRepost) MaterialTheme.colorScheme.grayText else Color.Unspecified if (showAuthorPicture) { @@ -1108,10 +1150,28 @@ fun FirstUserInfoRow( NoteUsernameDisplay(baseNote, Modifier.weight(1f), textColor = textColor, accountViewModel = accountViewModel) } - if (isCommunityPost) { - DisplayFollowingCommunityInPost(baseNote, accountViewModel, nav) + if (isDraft) { + ObserveDraftEvent(baseNote, accountViewModel) { draftNote -> + val isCommunityPost by remember(draftNote) { + derivedStateOf { draftNote.event?.isACommunityPost() == true } + } + + if (isCommunityPost) { + DisplayFollowingCommunityInPost(draftNote, accountViewModel, nav) + } else { + DisplayFollowingHashtagsInPost(draftNote, accountViewModel, nav) + } + } } else { - DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav) + val isCommunityPost by remember(baseNote) { + derivedStateOf { baseNote.event?.isACommunityPost() == true } + } + + if (isCommunityPost) { + DisplayFollowingCommunityInPost(baseNote, accountViewModel, nav) + } else { + DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav) + } } if (isRepost) { @@ -1120,7 +1180,7 @@ fun FirstUserInfoRow( CheckAndDisplayEditStatus(editState) - if (baseNote.isDraft()) { + if (isDraft) { DisplayDraft() } @@ -1192,7 +1252,7 @@ fun observeEdits( } @Composable -private fun BadgeBox( +fun BadgeBox( baseNote: Note, accountViewModel: AccountViewModel, nav: INav, @@ -1207,7 +1267,7 @@ private fun BadgeBox( } @Composable -private fun RenderAuthorImages( +fun RenderAuthorImages( baseNote: Note, nav: INav, accountViewModel: AccountViewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt index 877b417d41..d33c8a1211 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/elements/DisplayCommunity.kt @@ -34,9 +34,8 @@ import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.Route import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding -import com.vitorpamplona.quartz.nip01Core.tags.addressables.ATag -import com.vitorpamplona.quartz.nip01Core.tags.addressables.getTagOfAddressableKind -import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress @Composable fun DisplayFollowingCommunityInPost( @@ -55,11 +54,15 @@ private fun DisplayCommunity( nav: INav, ) { val communityTag = - remember(note) { note.event?.getTagOfAddressableKind(CommunityDefinitionEvent.KIND) } ?: return + remember(note) { note.event?.communityAddress() } ?: return val displayTag = remember(note) { - buildLinkString(getCommunityShortName(communityTag)) { nav.nav(Route.Community(communityTag.kind, communityTag.pubKeyHex, communityTag.dTag)) } + buildLinkString( + getCommunityShortName(communityTag), + ) { + nav.nav(Route.Community(communityTag.kind, communityTag.pubKeyHex, communityTag.dTag)) + } } Text( @@ -75,12 +78,12 @@ private fun DisplayCommunity( ) } -private fun getCommunityShortName(communityTag: ATag): String { +fun getCommunityShortName(communityAddress: Address): String { val name = - if (communityTag.dTag.length > 10) { - communityTag.dTag.take(10) + "..." + if (communityAddress.dTag.length > 10) { + communityAddress.dTag.take(10) + "..." } else { - communityTag.dTag.take(10) + communityAddress.dTag.take(10) } return "/n/$name" diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt index 6c05d97144..43ce9e53b5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/CommentPostViewModel.kt @@ -86,6 +86,7 @@ import com.vitorpamplona.quartz.nip37Drafts.DraftWrapEvent import com.vitorpamplona.quartz.nip57Zaps.splits.zapSplits import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiser import com.vitorpamplona.quartz.nip57Zaps.zapraiser.zapraiserAmount +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId import com.vitorpamplona.quartz.nip73ExternalIds.scope import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder @@ -252,8 +253,7 @@ open class CommentPostViewModel : } private fun loadFromDraft(draftEvent: CommentEvent) { - val scope = draftEvent.scope() ?: return - this.externalIdentity = scope + this.externalIdentity = draftEvent.scope() canAddInvoice = accountViewModel.userProfile().info?.lnAddress() != null canAddZapRaiser = accountViewModel.userProfile().info?.lnAddress() != null @@ -278,8 +278,14 @@ open class CommentPostViewModel : zapRaiserAmount.value = zapraiser } - draftEvent.replyingTo()?.let { - replyingTo = LocalCache.getOrCreateNote(it) + val replyAddress = draftEvent.replyAddress() + + if (replyAddress.isNotEmpty()) { + replyingTo = LocalCache.getOrCreateAddressableNote(replyAddress.first()) + } else { + draftEvent.replyingTo()?.let { + replyingTo = LocalCache.getOrCreateNote(it) + } } wantsToAddGeoHash = draftEvent.hasGeohashes() @@ -317,7 +323,7 @@ open class CommentPostViewModel : cancel() accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast) - accountViewModel.viewModelScope.launch { + accountViewModel.viewModelScope.launch(Dispatchers.Default) { accountViewModel.account.deleteDraftIgnoreErrors(version) } } @@ -356,6 +362,7 @@ open class CommentPostViewModel : val contentWarningReason = if (wantsToMarkAsSensitive) "" else null val replyingTo = replyingTo + val replyingToEvent = replyingTo?.event val template = if (replyingTo != null) { @@ -365,7 +372,22 @@ open class CommentPostViewModel : msg = tagger.message, replyingTo = eventHint, ) { - tagger.pTags?.let { pTagList -> notify(pTagList.map { it.toPTag() }) } + val notifyPTags = tagger.pTags?.let { pTagList -> pTagList.map { it.toPTag() } } ?: emptyList() + + val extraNotificationAuthors = + if (replyingToEvent is CommunityDefinitionEvent) { + replyingToEvent.moderatorKeys().mapNotNull { + if (it != replyingToEvent.pubKey) { + accountViewModel.checkGetOrCreateUser(it)?.toPTag() + } else { + null + } + } + } else { + emptyList() + } + + notify((notifyPTags + extraNotificationAuthors).distinctBy { it.pubKey }) hashtags(findHashtags(tagger.message)) references(findURLs(tagger.message)) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt index d4b8eb9585..cde5a69ea3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/nip22Comments/GenericCommentPostScreen.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.ui.note.nip22Comments import android.net.Uri +import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -135,6 +136,14 @@ fun GenericCommentPostScreen( ) { WatchAndLoadMyEmojiList(accountViewModel) + BackHandler { + accountViewModel.runIOCatching { + postViewModel.sendDraftSync() + postViewModel.cancel() + } + nav.popBack() + } + Scaffold( topBar = { PostingTopBar( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt index 6278738351..9c74cc139c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt @@ -20,16 +20,27 @@ */ package com.vitorpamplona.amethyst.ui.note.types +import android.R.attr.onClick +import android.content.Intent +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Share import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.FilledTonalIconButton +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -37,14 +48,18 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote @@ -52,8 +67,11 @@ import com.vitorpamplona.amethyst.model.FeatureSetType import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent +import com.vitorpamplona.amethyst.ui.components.MyAsyncImage +import com.vitorpamplona.amethyst.ui.components.RichTextViewer import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer +import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav.nav import com.vitorpamplona.amethyst.ui.navigation.navs.INav import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture @@ -62,9 +80,10 @@ import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.ZapReaction +import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeader +import com.vitorpamplona.amethyst.ui.note.elements.DefaultImageHeaderBackground import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags -import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton -import com.vitorpamplona.amethyst.ui.note.elements.NormalTimeAgo +import com.vitorpamplona.amethyst.ui.note.externalLinkForNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.equalImmutableLists import com.vitorpamplona.amethyst.ui.stringRes @@ -74,6 +93,7 @@ import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier import com.vitorpamplona.amethyst.ui.theme.RowColSpacing import com.vitorpamplona.amethyst.ui.theme.Size10dp +import com.vitorpamplona.amethyst.ui.theme.Size18Modifier import com.vitorpamplona.amethyst.ui.theme.Size25dp import com.vitorpamplona.amethyst.ui.theme.Size35dp import com.vitorpamplona.amethyst.ui.theme.Size5dp @@ -81,12 +101,12 @@ import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.innerPostModifier import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags import com.vitorpamplona.quartz.nip02FollowList.EmptyTagList -import com.vitorpamplona.quartz.nip14Subject.subject +import com.vitorpamplona.quartz.nip02FollowList.toImmutableListOfLists import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.tags.ModeratorTag import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import java.util.Locale +import kotlinx.collections.immutable.toImmutableList @Composable fun RenderCommunity( @@ -110,6 +130,16 @@ fun RenderCommunity( } } +@Composable +fun Title(title: String) { + Text( + text = title, + fontSize = 16.sp, + fontWeight = FontWeight.W600, + textAlign = TextAlign.Start, + ) +} + @Composable fun LongCommunityHeader( baseNote: AddressableNote, @@ -118,142 +148,169 @@ fun LongCommunityHeader( nav: INav, ) { val noteEvent by observeNoteEvent(baseNote, accountViewModel) + val callbackUri = baseNote.toNostrUri() - Row( - lineModifier, - ) { - val rulesLabel = stringRes(id = R.string.rules) - val summary = - remember(noteEvent) { - val subject = noteEvent?.subject()?.ifEmpty { null } - val body = noteEvent?.description()?.ifBlank { null } - val rules = noteEvent?.rules()?.ifBlank { null } + val defaultBackground = MaterialTheme.colorScheme.background + val background = remember { mutableStateOf(defaultBackground) } - if (!subject.isNullOrBlank() && body?.split("\n")?.get(0)?.contains(subject) == false) { - if (rules == null) { - "### $subject\n$body" - } else { - "### $subject\n$body\n\n### $rulesLabel\n\n$rules" - } - } else { - if (rules == null) { - body - } else { - "$body\n\n$rulesLabel\n$rules" - } + val description = noteEvent?.description()?.ifBlank { null } + val guidelines = noteEvent?.rules()?.ifBlank { null } + val image = noteEvent?.image()?.imageUrl + val tagList = noteEvent?.tags?.toImmutableListOfLists() ?: EmptyTagList + + Column(Modifier.verticalScroll(rememberScrollState())) { + Box(contentAlignment = Alignment.TopEnd) { + image?.let { + MyAsyncImage( + imageUrl = it, + contentDescription = + stringRes( + R.string.preview_card_image_for, + it, + ), + contentScale = ContentScale.FillWidth, + mainImageModifier = Modifier, + loadedImageModifier = Modifier.fillMaxWidth(), + accountViewModel = accountViewModel, + onLoadingBackground = { DefaultImageHeaderBackground(baseNote, accountViewModel) }, + onError = { DefaultImageHeader(baseNote, accountViewModel) }, + ) + } ?: run { + DefaultImageHeader(baseNote, accountViewModel) + } + Box( + modifier = Modifier.padding(5.dp), + ) { + LongCommunityActionOptions(baseNote, accountViewModel, nav) + } + } + + Column(lineModifier) { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = noteEvent?.name() ?: baseNote.dTag(), + fontSize = 20.sp, + fontWeight = FontWeight.W600, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(8.dp)) + Title(title = stringRes(R.string.about_us)) + Spacer(modifier = Modifier.height(16.dp)) + + Column(Modifier.padding(16.dp, 0.dp, 16.dp, 0.dp)) { + TranslatableRichTextViewer( + content = description ?: stringRes(id = R.string.community_no_descriptor), + id = baseNote.idHex + "description", + accountViewModel = accountViewModel, + ) { + RichTextViewer( + it, + modifier = Modifier.fillMaxWidth(), + canPreview = false, + quotesLeft = 1, + tags = tagList, + backgroundColor = background, + callbackUri = callbackUri, + accountViewModel = accountViewModel, + nav = nav, + ) } } - Column( - Modifier.weight(1f), - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - val defaultBackground = MaterialTheme.colorScheme.background - val background = remember { mutableStateOf(defaultBackground) } + guidelines?.let { + Spacer(modifier = Modifier.height(24.dp)) + Title(title = stringRes(R.string.guidelines)) + Spacer(modifier = Modifier.height(16.dp)) - TranslatableRichTextViewer( - content = summary ?: stringRes(id = R.string.community_no_descriptor), - canPreview = false, - quotesLeft = 1, - tags = EmptyTagList, - backgroundColor = background, - id = baseNote.idHex, - accountViewModel = accountViewModel, - nav = nav, - ) + Column(Modifier.padding(16.dp, 0.dp, 16.dp, 0.dp)) { + TranslatableRichTextViewer( + content = guidelines, + id = baseNote.idHex + "guidelines", + accountViewModel = accountViewModel, + ) { + RichTextViewer( + it, + modifier = Modifier.fillMaxWidth(), + canPreview = false, + quotesLeft = 1, + tags = tagList, + backgroundColor = background, + callbackUri = callbackUri, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } } noteEvent?.let { if (it.hasHashtags()) { DisplayUncitedHashtags( event = it, - content = summary ?: "", + content = description ?: "", accountViewModel = accountViewModel, nav = nav, ) } } - } - Column { - Row { + Spacer(modifier = Modifier.height(24.dp)) + Title(title = stringRes(R.string.owner)) + Spacer(modifier = Modifier.height(16.dp)) + + Row( + Modifier.fillMaxWidth().padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + NoteAuthorPicture(baseNote, Size25dp, accountViewModel = accountViewModel, nav = nav) Spacer(DoubleHorzSpacer) - LongCommunityActionOptions(baseNote, accountViewModel, nav) + NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel) } - } - } - Row( - lineModifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringRes(id = R.string.owner), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.width(75.dp), - ) - Spacer(DoubleHorzSpacer) - NoteAuthorPicture(baseNote, Size25dp, accountViewModel = accountViewModel, nav = nav) - Spacer(DoubleHorzSpacer) - NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel) - } + var participantUsers by + remember(baseNote) { + mutableStateOf>>( + persistentListOf(), + ) + } - var participantUsers by - remember(baseNote) { - mutableStateOf>>( - persistentListOf(), - ) - } + LaunchedEffect(key1 = noteEvent) { + val participants = noteEvent?.moderators() - LaunchedEffect(key1 = noteEvent) { - val participants = noteEvent?.moderators() - - if (participants != null) { - accountViewModel.loadParticipants(participants) { newParticipantUsers -> - if (!equalImmutableLists(newParticipantUsers, participantUsers)) { - participantUsers = newParticipantUsers + if (participants != null) { + accountViewModel.loadParticipants(participants) { newParticipantUsers -> + val noOwner = newParticipantUsers.filter { it.second != baseNote.author }.toImmutableList() + if (!equalImmutableLists(noOwner, participantUsers)) { + participantUsers = noOwner + } + } } } - } - } - participantUsers.forEach { - Row( - lineModifier.clickable { nav.nav(routeFor(it.second)) }, - verticalAlignment = Alignment.CenterVertically, - ) { - it.first.role?.let { it1 -> - Text( - text = - it1.replaceFirstChar { - if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() - }, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.width(75.dp), - ) + if (participantUsers.isNotEmpty()) { + Spacer(modifier = Modifier.height(24.dp)) + Title(title = stringRes(R.string.moderators)) + Spacer(modifier = Modifier.height(16.dp)) + + participantUsers.forEach { + Column(Modifier.padding(vertical = 5.dp).clickable { nav.nav(routeFor(it.second)) }) { + Row( + Modifier.fillMaxWidth().padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + ClickableUserPicture(it.second, Size25dp, accountViewModel) + Spacer(DoubleHorzSpacer) + UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel) + } + } + } } - Spacer(DoubleHorzSpacer) - ClickableUserPicture(it.second, Size25dp, accountViewModel) - Spacer(DoubleHorzSpacer) - UsernameDisplay(it.second, Modifier.weight(1f), accountViewModel = accountViewModel) - } - } - Row( - lineModifier, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = stringRes(id = R.string.created_at), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.width(75.dp), - ) - Spacer(DoubleHorzSpacer) - NormalTimeAgo(baseNote = baseNote, Modifier.weight(1f)) - MoreOptionsButton(baseNote, null, accountViewModel, nav) + Spacer(modifier = Modifier.height(15.dp)) + } } } @@ -308,7 +365,47 @@ fun ShortCommunityHeader( } @Composable -private fun ShortCommunityActionOptions( +fun ShortCommunityHeaderNoActions( + baseNote: AddressableNote, + accountViewModel: AccountViewModel, + nav: INav, +) { + val noteEvent by observeNoteEvent(baseNote, accountViewModel) + + Row(verticalAlignment = Alignment.CenterVertically) { + noteEvent?.image()?.let { + RobohashFallbackAsyncImage( + robot = baseNote.idHex, + model = it.imageUrl, + contentDescription = stringRes(R.string.profile_image), + contentScale = ContentScale.Crop, + modifier = HeaderPictureModifier, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE, + ) + } + + Column( + modifier = + Modifier + .padding(start = 10.dp) + .height(Size35dp) + .weight(1f), + verticalArrangement = Arrangement.Center, + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text( + text = noteEvent?.name() ?: baseNote.dTag(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + } + } + } +} + +@Composable +fun ShortCommunityActionOptions( note: AddressableNote, accountViewModel: AccountViewModel, nav: INav, @@ -346,9 +443,12 @@ private fun LongCommunityActionOptions( accountViewModel: AccountViewModel, nav: INav, ) { - WatchAddressableNoteFollows(note, accountViewModel) { isFollowing -> - if (isFollowing) { - LeaveCommunityButton(accountViewModel, note, nav) + Row { + ShareCommunityButton(accountViewModel, note, nav) + WatchAddressableNoteFollows(note, accountViewModel) { isFollowing -> + if (isFollowing) { + LeaveCommunityButton(accountViewModel, note, nav) + } } } } @@ -388,18 +488,48 @@ fun LeaveCommunityButton( note: AddressableNote, nav: INav, ) { - val scope = rememberCoroutineScope() - - Button( + FilledTonalButton( modifier = Modifier.padding(horizontal = 3.dp), onClick = { accountViewModel.unfollow(note) }, - shape = ButtonBorder, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.primary, - ), contentPadding = ButtonPadding, ) { - Text(text = stringRes(R.string.leave), color = Color.White) + Text(text = stringRes(R.string.leave)) + } +} + +@Composable +fun ShareCommunityButton( + accountViewModel: AccountViewModel, + note: AddressableNote, + nav: INav, +) { + val actContext = LocalContext.current + + FilledTonalIconButton( + onClick = { + val sendIntent = + Intent().apply { + action = Intent.ACTION_SEND + type = "text/plain" + putExtra( + Intent.EXTRA_TEXT, + externalLinkForNote(note), + ) + putExtra( + Intent.EXTRA_TITLE, + stringRes(actContext, R.string.quick_action_share_browser_link), + ) + } + + val shareIntent = + Intent.createChooser(sendIntent, stringRes(actContext, R.string.quick_action_share)) + ContextCompat.startActivity(actContext, shareIntent, null) + }, + ) { + Icon( + imageVector = Icons.Default.Share, + modifier = Size18Modifier, + contentDescription = stringRes(R.string.quick_action_share), + ) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 8015e2fb08..1225b2bec0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -33,6 +33,7 @@ import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.feeds.FeedContentState import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.FeedLoaded import com.vitorpamplona.amethyst.ui.feeds.FeedState import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox @@ -102,8 +103,7 @@ fun RenderFeedState( nav: INav, routeForLastRead: String?, onLoaded: @Composable (FeedState.Loaded) -> Unit = { - com.vitorpamplona.amethyst.ui.feeds - .FeedLoaded(it, listState, routeForLastRead, accountViewModel, nav) + FeedLoaded(it, listState, routeForLastRead, accountViewModel, nav) }, onEmpty: @Composable () -> Unit = { FeedEmpty { viewModel.invalidateData() } }, onError: @Composable (String) -> Unit = { FeedError(it) { viewModel.invalidateData() } }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FollowListState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FollowListState.kt index 9f7d4becb2..361988be8f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FollowListState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FollowListState.kt @@ -44,6 +44,7 @@ import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip51Lists.PinListEvent import com.vitorpamplona.quartz.nip51Lists.followList.FollowListEvent @@ -123,36 +124,40 @@ class FollowListState( val livePeopleListsFlow = MutableStateFlow(emptyList()) + fun hasItemInNoteList(notes: Set): Boolean = + notes.any { it -> + val noteEvent = it.event + + noteEvent?.pubKey == account.userProfile().pubkeyHex && + ( + ( + noteEvent is PeopleListEvent || + noteEvent is FollowListEvent || + noteEvent is MuteListEvent || + noteEvent is ContactListEvent + ) || + ( + noteEvent is DeletionEvent && + ( + noteEvent.deleteEventIds().any { LocalCache.getNoteIfExists(it)?.event is PeopleListEvent } || + noteEvent.deleteAddresses().any { it.kind == PeopleListEvent.KIND } + ) + ) + ) + } + + fun deleteFromFeed(deletedNotes: Set) { + checkNotInMainThread() + + if (hasItemInNoteList(deletedNotes)) { + livePeopleListsFlow.tryEmit(getPeopleLists()) + } + } + fun updateFeedWith(newNotes: Set) { checkNotInMainThread() - val hasNewList = - newNotes.any { it -> - val noteEvent = it.event - - noteEvent?.pubKey == account.userProfile().pubkeyHex && - ( - ( - noteEvent is PeopleListEvent || - noteEvent is FollowListEvent || - noteEvent is MuteListEvent || - noteEvent is ContactListEvent - ) || - ( - noteEvent is DeletionEvent && - ( - noteEvent.deleteEventIds().any { - LocalCache.getNoteIfExists(it)?.event is PeopleListEvent - } || - noteEvent.deleteAddresses().any { - it.kind == PeopleListEvent.KIND - } - ) - ) - ) - } - - if (hasNewList) { + if (hasItemInNoteList(newNotes)) { livePeopleListsFlow.tryEmit(getPeopleLists()) } } @@ -163,7 +168,7 @@ class FollowListState( checkNotInMainThread() val communities = - communityList.mapNotNull { + communityList.map { LocalCache.getOrCreateAddressableNote(it.address).let { communityNote -> TagFeedDefinition( "Community/${communityNote.idHex}", @@ -377,6 +382,7 @@ val DEFAULT_FEED_KINDS = val DEFAULT_COMMUNITY_FEEDS = listOf( TextNoteEvent.KIND, + CommentEvent.KIND, LongTextNoteEvent.KIND, ClassifiedsEvent.KIND, HighlightEvent.KIND, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt index 962a1009d2..2264d833ff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountFeedContentStates.kt @@ -104,6 +104,34 @@ class AccountFeedContentStates( drafts.updateFeedWith(newNotes) } + fun deleteNotes(newNotes: Set) { + checkNotInMainThread() + + homeLive.deleteFromFeed(newNotes) + homeNewThreads.deleteFromFeed(newNotes) + homeReplies.deleteFromFeed(newNotes) + + dmKnown.updateFeedWith(newNotes) + dmNew.updateFeedWith(newNotes) + + videoFeed.deleteFromFeed(newNotes) + + discoverMarketplace.deleteFromFeed(newNotes) + discoverFollowSets.deleteFromFeed(newNotes) + discoverReads.deleteFromFeed(newNotes) + discoverDVMs.deleteFromFeed(newNotes) + discoverLive.deleteFromFeed(newNotes) + discoverCommunities.deleteFromFeed(newNotes) + discoverPublicChats.deleteFromFeed(newNotes) + + notifications.deleteFromFeed(newNotes) + notificationSummary.invalidateInsertData(newNotes) + + feedListOptions.deleteFromFeed(newNotes) + + drafts.deleteFromFeed(newNotes) + } + fun destroy() { notifications.destroy() notificationSummary.destroy() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 5815003018..3e7c758ded 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -774,6 +774,11 @@ class AccountViewModel( } } + fun approveCommunityPost( + post: Note, + community: AddressableNote, + ) = runIOCatching { account.approveCommunityPost(post, community) } + fun follow(community: AddressableNote) = runIOCatching { account.follow(community) } fun follow(channel: PublicChatChannel) = runIOCatching { account.follow(channel) } @@ -1188,6 +1193,7 @@ class AccountViewModel( } logTime("AccountViewModel deletedEventBundle Update with ${newNotes.size} new notes") { newNotesPreProcessor.runDeleted(newNotes) + feedStates.deleteNotes(newNotes) } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt index 056787bbb7..796f5145af 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/ChatNewMessageViewModel.kt @@ -351,7 +351,7 @@ class ChatNewMessageViewModel : val version = draftTag.current innerSendPost(null) cancel() - accountViewModel.viewModelScope.launch { + accountViewModel.viewModelScope.launch(Dispatchers.Default) { accountViewModel.account.deleteDraftIgnoreErrors(version) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt index cde640df8b..eb7996ddb3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/NewGroupDMScreen.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send import android.net.Uri +import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy import androidx.compose.foundation.layout.Box @@ -165,6 +166,14 @@ fun NewGroupDMScreen( WatchAndLoadMyEmojiList(accountViewModel) + BackHandler { + accountViewModel.runIOCatching { + postViewModel.sendDraftSync() + postViewModel.cancel() + } + nav.popBack() + } + Scaffold( topBar = { PostingTopBar( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt index cc557c2d06..072328acc7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/privateDM/send/PrivateMessageEditFieldRow.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -79,6 +80,14 @@ fun PrivateMessageEditFieldRow( onSendNewMessage: () -> Unit, nav: INav, ) { + BackHandler { + accountViewModel.runIOCatching { + channelScreenModel.sendDraftSync() + channelScreenModel.cancel() + } + nav.popBack() + } + channelScreenModel.replyTo.value?.let { DisplayReplyingToNote(it, accountViewModel, nav) { channelScreenModel.clearReply() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt index bc775bac9b..8eb6ba01dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/ChannelNewMessageViewModel.kt @@ -276,7 +276,7 @@ open class ChannelNewMessageViewModel : cancel() accountViewModel.account.signAndSendPrivately(template, channelRelays) - accountViewModel.viewModelScope.launch { + accountViewModel.viewModelScope.launch(Dispatchers.Default) { accountViewModel.account.deleteDraftIgnoreErrors(version) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt index 0a2e462557..0b6085faad 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chats/publicChannels/send/EditFieldRow.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.publicChannels.send +import androidx.activity.compose.BackHandler import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -60,6 +61,14 @@ fun EditFieldRow( onSendNewMessage: suspend () -> Unit, nav: INav, ) { + BackHandler { + accountViewModel.runIOCatching { + channelScreenModel.sendDraftSync() + channelScreenModel.cancel() + } + nav.popBack() + } + channelScreenModel.replyTo.value?.let { DisplayReplyingToNote(it, accountViewModel, nav) { channelScreenModel.clearReply() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/CommunityScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/CommunityScreen.kt index f36fbf65cf..dffc55dee9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/CommunityScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/CommunityScreen.kt @@ -20,29 +20,56 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.communities +import android.R.attr.onClick +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextFieldDefaults.contentPadding +import androidx.compose.material3.Surface +import androidx.compose.material3.Tab +import androidx.compose.material3.TabRow +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel +import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.navigation.navs.INav -import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton +import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar +import com.vitorpamplona.amethyst.ui.navigation.topbars.TitleIconModifier +import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.types.LongCommunityHeader -import com.vitorpamplona.amethyst.ui.note.types.ShortCommunityHeader +import com.vitorpamplona.amethyst.ui.note.types.ShortCommunityActionOptions +import com.vitorpamplona.amethyst.ui.note.types.ShortCommunityHeaderNoActions import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.dal.CommunityFeedViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.dal.CommunityModerationFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.datasource.CommunityFilterAssemblerSubscription -import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.Size35dp +import com.vitorpamplona.amethyst.ui.theme.TabRowHeight +import com.vitorpamplona.amethyst.ui.theme.isLight import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import kotlinx.coroutines.launch @Composable fun CommunityScreen( @@ -77,61 +104,116 @@ fun PrepareViewModelsCommunityScreen( ), ) - CommunityScreen(note, followsFeedViewModel, accountViewModel, nav) + val modFeedViewModel: CommunityModerationFeedViewModel = + viewModel( + key = note.idHex + "CommunityModerationFeedViewModel", + factory = + CommunityModerationFeedViewModel.Factory( + note, + accountViewModel.account, + ), + ) + + CommunityScreen(note, followsFeedViewModel, modFeedViewModel, accountViewModel, nav) } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun CommunityScreen( note: AddressableNote, feedViewModel: CommunityFeedViewModel, + modFeedViewModel: CommunityModerationFeedViewModel, accountViewModel: AccountViewModel, nav: INav, ) { WatchLifecycleAndUpdateModel(feedViewModel) + WatchLifecycleAndUpdateModel(modFeedViewModel) CommunityFilterAssemblerSubscription(note, accountViewModel.dataSources().community) + val pagerState = rememberForeverPagerState(note.idHex + "CommunityScreenPagerState") { 2 } + val expanded = remember { mutableStateOf(false) } + DisappearingScaffold( isInvertedLayout = false, + isActive = { !expanded.value }, topBar = { - CommunityTopBar(note.address, accountViewModel, nav) + Column( + Modifier.clickable { expanded.value = !expanded.value }, + ) { + ShorterTopAppBar( + title = { + ShortCommunityHeaderNoActions(note, accountViewModel, nav) + }, + navigationIcon = { + Row(TitleIconModifier, verticalAlignment = Alignment.CenterVertically) { + IconButton(onClick = nav::popBack) { ArrowBackIcon() } + } + }, + actions = { + Row( + modifier = + Modifier + .height(Size35dp) + .padding(start = 5.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + ShortCommunityActionOptions(note, accountViewModel, nav) + } + }, + ) + + if (expanded.value) { + val elevation = if (MaterialTheme.colorScheme.isLight) 1.dp else 10.dp + Surface(modifier = Modifier.fillMaxWidth(), tonalElevation = elevation) { + LongCommunityHeader(baseNote = note, accountViewModel = accountViewModel, nav = nav) + } + } + } + + TabRow( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onBackground, + modifier = TabRowHeight, + selectedTabIndex = pagerState.currentPage, + ) { + val coroutineScope = rememberCoroutineScope() + Tab( + selected = pagerState.currentPage == 0, + text = { Text(text = stringRes(R.string.feed)) }, + onClick = { coroutineScope.launch { pagerState.animateScrollToPage(0) } }, + ) + Tab( + selected = pagerState.currentPage == 1, + text = { Text(text = stringRes(R.string.mod_queue)) }, + onClick = { coroutineScope.launch { pagerState.animateScrollToPage(1) } }, + ) + } }, floatingButton = { NewCommunityNoteButton(note.idHex, accountViewModel, nav) }, accountViewModel = accountViewModel, ) { - Column( - modifier = Modifier.padding(it).consumeWindowInsets(it), - ) { - RefresheableFeedView( - feedViewModel, - null, - accountViewModel = accountViewModel, - nav = nav, - ) - } - } -} - -@Composable -fun CommunityTopBar( - id: Address, - accountViewModel: AccountViewModel, - nav: INav, -) { - LoadAddressableNote(id, accountViewModel) { baseNote -> - if (baseNote != null) { - TopBarExtensibleWithBackButton( - title = { ShortCommunityHeader(baseNote, accountViewModel, nav) }, - extendableRow = { - Column(Modifier.verticalScroll(rememberScrollState())) { - LongCommunityHeader(baseNote = baseNote, accountViewModel = accountViewModel, nav = nav) - } - }, - popBack = nav::popBack, - ) - } else { - Spacer(BottomTopHeight) + HorizontalPager( + contentPadding = it, + state = pagerState, + ) { page -> + when (page) { + 0 -> + RefresheableFeedView( + feedViewModel, + null, + accountViewModel = accountViewModel, + nav = nav, + ) + 1 -> + RefresheableFeedView( + modFeedViewModel, + null, + accountViewModel = accountViewModel, + nav = nav, + ) + } } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt index dcf84afab8..12e14fd8d2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/NewCommunityNoteButton.kt @@ -57,8 +57,8 @@ fun NewCommunityNoteButton( FloatingActionButton( onClick = { val route = - Route.NewShortNote( - baseReplyTo = note.idHex, + Route.GenericCommentPost( + replyTo = note.idHex, ) nav.nav(route) }, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityFeedFilter.kt index 14aa925b40..53e23842c6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityFeedFilter.kt @@ -26,21 +26,28 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder -import com.vitorpamplona.quartz.nip01Core.core.HexKey -import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableNote +import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip72ModCommunities.isForCommunity +import kotlin.collections.toSet class CommunityFeedFilter( - val note: AddressableNote, + val communityDefNote: AddressableNote, val account: Account, ) : AdditiveFeedFilter() { - override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + note.idHex + val communityDefEvent = communityDefNote.event as? CommunityDefinitionEvent + val moderators = communityDefEvent?.moderatorKeys()?.toSet() ?: emptySet() + + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + communityDefNote.idHex override fun feed(): List { - val myPubKey = account.userProfile().pubkeyHex + if (communityDefEvent == null) return emptyList() + val result = LocalCache.notes.mapFlattenIntoSet { _, it -> - filterMap(it, myPubKey) + filterMap(it) } return sort(result) @@ -49,35 +56,39 @@ class CommunityFeedFilter( override fun applyFilter(newItems: Set): Set = innerApplyFilter(newItems) private fun innerApplyFilter(collection: Collection): Set { - val myPubKey = account.userProfile().pubkeyHex + if (communityDefEvent == null) return emptySet() return collection .mapNotNull { - filterMap(it, myPubKey) + filterMap(it) }.flatten() .toSet() } - private fun filterMap( - note: Note, - myPubKey: HexKey, - ): List? = - if ( + private fun filterMap(note: Note): List? { + val noteEvent = note.event ?: return null + + return if ( // Only Approvals - note.event is CommunityPostApprovalEvent && - // Of the given community - note.event?.isTaggedAddressableNote(this.note.idHex) == true + (noteEvent is TextNoteEvent || noteEvent is CommentEvent || noteEvent is CommunityPostApprovalEvent) && + noteEvent.pubKey in moderators && + noteEvent.isForCommunity(this.communityDefNote.idHex) ) { - // if it is my post, bring on - if (note.author?.pubkeyHex == myPubKey && note.isNewThread()) { - listOf(note) + if (noteEvent is CommunityPostApprovalEvent) { + note.replyTo?.filter { + it.isNewThread() + } } else { - // brings the actual posts, not the approvals - note.replyTo?.filter { it.isNewThread() } + if (note.isNewThread()) { + listOf(note) + } else { + null + } } } else { null } + } override fun sort(items: Set): List = items.sortedWith(DefaultFeedOrder) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityModerationFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityModerationFeedFilter.kt new file mode 100644 index 0000000000..114fb2f670 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityModerationFeedFilter.kt @@ -0,0 +1,126 @@ +/** + * 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.amethyst.ui.screen.loggedIn.communities.dal + +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter +import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder +import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent +import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent +import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent +import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip72ModCommunities.isForCommunity + +class CommunityModerationFeedFilter( + val communityDefNote: AddressableNote, + val account: Account, +) : AdditiveFeedFilter() { + val approvedFilter = CommunityFeedFilter(communityDefNote, account) + + val communityDefEvent = communityDefNote.event as? CommunityDefinitionEvent + val moderators = communityDefEvent?.moderatorKeys()?.toSet() ?: emptySet() + + override fun feedKey(): String = account.userProfile().pubkeyHex + "-" + communityDefNote.idHex + + override fun feed(): List { + if (communityDefEvent == null) return emptyList() + + val result = + LocalCache.notes.mapFlattenIntoSet { _, it -> + filterMap(it) + } + + return sort(result) + } + + override fun updateListWith( + oldList: List, + newItems: Set, + ): List { + val approved = approvedFilter.applyFilter(newItems) + + val newOldList = + if (approved.isNotEmpty()) { + oldList - approved + } else { + oldList + } + + val newItemsToBeAdded = applyFilter(newItems) + return if (newItemsToBeAdded.isNotEmpty()) { + val newList = newOldList.toSet() + newItemsToBeAdded + sort(newList).take(limit()) + } else { + newOldList + } + } + + override fun applyFilter(newItems: Set): Set = innerApplyFilter(newItems) + + private fun innerApplyFilter(collection: Collection): Set { + if (communityDefEvent == null) return emptySet() + + return collection + .mapNotNull { + filterMap(it) + }.flatten() + .toSet() + } + + private fun wasApprovedByCommunity(note: Note): Boolean = + note.boosts.any { + val approvalEvent = it.event + (approvalEvent is CommunityPostApprovalEvent || approvalEvent is RepostEvent || approvalEvent is GenericRepostEvent) && + approvalEvent.pubKey in moderators && + approvalEvent.isForCommunity(this.communityDefNote.idHex) + } + + private fun filterMap(note: Note): List? { + val noteEvent = note.event ?: return null + + return if ( + (noteEvent is TextNoteEvent || noteEvent is CommentEvent || noteEvent is CommunityPostApprovalEvent) && + noteEvent.isForCommunity(this.communityDefNote.idHex) && + noteEvent.pubKey !in moderators + ) { + if (noteEvent is CommunityPostApprovalEvent) { + note.replyTo?.filter { + it.isNewThread() && !wasApprovedByCommunity(it) + } + } else { + if (note.isNewThread() && !wasApprovedByCommunity(note)) { + listOf(note) + } else { + null + } + } + } else { + null + } + } + + override fun sort(items: Set): List = items.sortedWith(DefaultFeedOrder) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityModerationFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityModerationFeedViewModel.kt new file mode 100644 index 0000000000..14b423fac6 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/dal/CommunityModerationFeedViewModel.kt @@ -0,0 +1,40 @@ +/** + * 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.amethyst.ui.screen.loggedIn.communities.dal + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.vitorpamplona.amethyst.model.Account +import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.ui.screen.FeedViewModel + +class CommunityModerationFeedViewModel( + val note: AddressableNote, + val account: Account, +) : FeedViewModel(CommunityModerationFeedFilter(note, account)) { + class Factory( + val note: AddressableNote, + val account: Account, + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T = CommunityModerationFeedViewModel(note, account) as T + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/CommunityFeedFilterSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/CommunityFeedFilterSubAssembler.kt index bd7db1129b..2b891ae8ea 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/CommunityFeedFilterSubAssembler.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/CommunityFeedFilterSubAssembler.kt @@ -26,6 +26,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap import com.vitorpamplona.quartz.nip01Core.relay.client.NostrClient import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.utils.mapOfSet class CommunityFeedFilterSubAssembler( client: NostrClient, @@ -40,14 +41,34 @@ class CommunityFeedFilterSubAssembler( return keys.flatMap { val commEvent = it.community.event if (commEvent is CommunityDefinitionEvent) { - val relays = - commEvent.relayUrls().ifEmpty { null } - ?: LocalCache.relayHints.hintsForAddress(commEvent.addressTag()).ifEmpty { null } - ?: it.community.relayUrls() + val communityRelays = + ( + commEvent.relayUrls().ifEmpty { + LocalCache.relayHints.hintsForAddress(commEvent.addressTag()) + } + it.community.relayUrls() + ).toSet() - relays.toSet().map { - filterCommunityPosts(it, commEvent, since?.get(it)?.time) - } + val moderatorsOutbox = + mapOfSet { + commEvent.moderatorKeys().forEach { modKey -> + // hits the relay of each moderator for approvals + LocalCache.checkGetOrCreateUser(modKey)?.outboxRelays()?.forEach { relay -> + add(relay, modKey) + } + + // static relay hints from this community + communityRelays.forEach { relay -> + add(relay, modKey) + } + } + } + + moderatorsOutbox.map { (relay, authors) -> + filterCommunityPostsFromModerators(relay, authors, commEvent, since?.get(relay)?.time) + } + + communityRelays.map { relay -> + filterCommunityPostsFromEverybody(relay, commEvent, since?.get(relay)?.time) + } } else { emptyList() } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/FilterCommunityPosts.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/FilterCommunityPosts.kt index c728a95267..2f72f81ca9 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/FilterCommunityPosts.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/communities/datasource/FilterCommunityPosts.kt @@ -20,11 +20,29 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.communities.datasource +import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent +import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent +import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent +import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent + +val CommunityPostKinds = + listOf( + TextNoteEvent.KIND, + CommentEvent.KIND, + RepostEvent.KIND, + GenericRepostEvent.KIND, + ClassifiedsEvent.KIND, + LongTextNoteEvent.KIND, + CommunityPostApprovalEvent.KIND, + ) fun filterCommunityPosts( relay: NormalizedRelayUrl, @@ -35,10 +53,44 @@ fun filterCommunityPosts( relay = relay, filter = Filter( - authors = community.moderators().map { it.pubKey }.plus(community.pubKey), + authors = community.moderatorKeys(), tags = mapOf("a" to listOf(community.addressTag())), - kinds = CommunityPostApprovalEvent.KIND_LIST, + kinds = CommunityPostKinds, limit = 500, since = since, ), ) + +fun filterCommunityPostsFromModerators( + relay: NormalizedRelayUrl, + authors: Set, + community: CommunityDefinitionEvent, + since: Long?, +): RelayBasedFilter = + RelayBasedFilter( + relay = relay, + filter = + Filter( + authors = authors.sorted(), + tags = mapOf("a" to listOf(community.addressTag())), + kinds = CommunityPostKinds, + limit = 100, + since = since, + ), + ) + +fun filterCommunityPostsFromEverybody( + relay: NormalizedRelayUrl, + community: CommunityDefinitionEvent, + since: Long?, +): RelayBasedFilter = + RelayBasedFilter( + relay = relay, + filter = + Filter( + tags = mapOf("a" to listOf(community.addressTag())), + kinds = CommunityPostKinds, + limit = 100, + since = since, + ), + ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt index bd30e46660..d58e7c48a7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductScreen.kt @@ -21,6 +21,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.nip99Classifieds import android.net.Uri +import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -136,6 +137,14 @@ fun NewProductScreen( ) { WatchAndLoadMyEmojiList(accountViewModel) + BackHandler { + accountViewModel.runIOCatching { + postViewModel.sendDraftSync() + postViewModel.cancel() + } + nav.popBack() + } + Scaffold( topBar = { PostingTopBar( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt index 265f6d544d..8f37a64f60 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip99Classifieds/NewProductViewModel.kt @@ -297,7 +297,7 @@ open class NewProductViewModel : cancel() accountViewModel.account.signAndSendPrivatelyOrBroadcast(template, relayList = { relayList }) - accountViewModel.viewModelScope.launch { + accountViewModel.viewModelScope.launch(Dispatchers.Default) { accountViewModel.account.deleteDraftIgnoreErrors(version) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt index d8aa344efb..2d95deecc3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostScreen.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.home import android.content.Intent import android.net.Uri import android.os.Parcelable +import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -164,6 +165,14 @@ private fun NewPostScreenInner( ) { WatchAndLoadMyEmojiList(accountViewModel) + BackHandler { + accountViewModel.runIOCatching { + postViewModel.sendDraftSync() + postViewModel.cancel() + } + nav.popBack() + } + Scaffold( topBar = { PostingTopBar( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt index 5b975b6bfb..3dc9a0cd67 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/ShortNotePostViewModel.kt @@ -483,7 +483,7 @@ open class ShortNotePostViewModel : cancel() accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast) - accountViewModel.viewModelScope.launch { + accountViewModel.viewModelScope.launch(Dispatchers.Default) { accountViewModel.account.deleteDraftIgnoreErrors(version) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt index a22766245f..175e0bec94 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt @@ -340,6 +340,9 @@ class CardFeedContentState( } } + fun deleteFromFeed(deletedNotes: Set) { + } + private fun refreshFromOldState(newItems: Set) { val oldNotesState = _feedContent.value diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt index 7aeb8571dc..1f80d52305 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/dal/NotificationFeedFilter.kt @@ -33,8 +33,10 @@ import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent +import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelCreateEvent import com.vitorpamplona.quartz.nip28PublicChat.admin.ChannelMetadataEvent @@ -50,6 +52,8 @@ import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent +import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent @@ -157,7 +161,26 @@ class NotificationFeedFilter( } if (event is BaseThreadedEvent) { - if (note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true) return true + if (note.replyTo?.any { it.author?.pubkeyHex == authorHex } == true) { + return true + } + + if ((event is TextNoteEvent || event is CommentEvent)) { + val community = + event + .communityAddress() + ?.let { + LocalCache.getAddressableNoteIfExists(it) + }?.event as? CommunityDefinitionEvent + if (community != null) { + val moderators = community.moderatorKeys().toSet() + val isModerator = moderators.contains(authorHex) + + if (isModerator && event.pubKey !in moderators) { + return true + } + } + } val isAuthoredPostCited = event.findCitations().any { LocalCache.getNoteIfExists(it)?.author?.pubkeyHex == authorHex } val isAuthorDirectlyCited = event.citedUsers().contains(authorHex) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt index bd99422ba6..4f78893ec5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageScreen.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.publicMessages +import androidx.activity.compose.BackHandler import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement.Absolute.spacedBy import androidx.compose.foundation.layout.Column @@ -128,6 +129,14 @@ fun NewPublicMessageScreen( WatchAndLoadMyEmojiList(accountViewModel) + BackHandler { + accountViewModel.runIOCatching { + postViewModel.sendDraftSync() + postViewModel.cancel() + } + nav.popBack() + } + Scaffold( topBar = { PostingTopBar( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt index 3c42d85e1e..c0340ba1f5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/publicMessages/NewPublicMessageViewModel.kt @@ -321,7 +321,7 @@ class NewPublicMessageViewModel : cancel() accountViewModel.account.signAndComputeBroadcast(template, extraNotesToBroadcast) - accountViewModel.viewModelScope.launch { + accountViewModel.viewModelScope.launch(Dispatchers.Default) { accountViewModel.account.deleteDraftIgnoreErrors(version) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt index 7e054d3153..3b4b1b7277 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/profile/newthreads/dal/UserProfileNewThreadFeedFilter.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent @@ -70,6 +71,7 @@ class UserProfileNewThreadFeedFilter( it.author == user && ( it.event is TextNoteEvent || + it.event is CommentEvent || it.event is ClassifiedsEvent || it.event is RepostEvent || it.event is GenericRepostEvent || diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index 1beaef7334..c290cc8636 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -49,6 +49,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.State +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -75,6 +76,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.richtext.MediaUrlImage import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeCommunityApprovalNeedStatus import com.vitorpamplona.amethyst.ui.components.AutoNonlazyGrid import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.components.LoadNote @@ -96,7 +98,9 @@ import com.vitorpamplona.amethyst.ui.note.LongPressToQuickAction import com.vitorpamplona.amethyst.ui.note.NoteAuthorPicture import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.NoteUsernameDisplay +import com.vitorpamplona.amethyst.ui.note.ObserveDraftEvent import com.vitorpamplona.amethyst.ui.note.ReactionsRow +import com.vitorpamplona.amethyst.ui.note.RenderApproveButton import com.vitorpamplona.amethyst.ui.note.RenderDraft import com.vitorpamplona.amethyst.ui.note.RenderRepost import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent @@ -168,6 +172,7 @@ import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier import com.vitorpamplona.amethyst.ui.theme.FeedPadding +import com.vitorpamplona.amethyst.ui.theme.PaddingHorizontal12Modifier import com.vitorpamplona.amethyst.ui.theme.Size55dp import com.vitorpamplona.amethyst.ui.theme.Size5dp import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer @@ -189,7 +194,6 @@ import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent import com.vitorpamplona.quartz.nip01Core.core.Event -import com.vitorpamplona.quartz.nip01Core.tags.addressables.isTaggedAddressableKind import com.vitorpamplona.quartz.nip01Core.tags.geohash.geoHashOrScope import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent import com.vitorpamplona.quartz.nip13Pow.strongPoWOrNull @@ -227,7 +231,8 @@ import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent import com.vitorpamplona.quartz.nip68Picture.PictureEvent import com.vitorpamplona.quartz.nip71Video.VideoEvent import com.vitorpamplona.quartz.nip72ModCommunities.approval.CommunityPostApprovalEvent -import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.nip72ModCommunities.communityAddress +import com.vitorpamplona.quartz.nip72ModCommunities.isACommunityPost import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent @@ -435,6 +440,7 @@ private fun FullBleedNoteCompose( ) { val noteEvent = baseNote.event ?: return + val isDraft = baseNote.isDraft() val backgroundColor = calculateBackgroundColor( baseNote.createdAt(), @@ -467,15 +473,28 @@ private fun FullBleedNoteCompose( Row(verticalAlignment = Alignment.CenterVertically) { NoteUsernameDisplay(baseNote, Modifier.weight(1f), accountViewModel = accountViewModel) - val isCommunityPost = - remember(baseNote) { - baseNote.event?.isTaggedAddressableKind(CommunityDefinitionEvent.KIND) == true + if (isDraft) { + ObserveDraftEvent(baseNote, accountViewModel) { draftNote -> + val isCommunityPost by remember(draftNote) { + derivedStateOf { draftNote.event?.isACommunityPost() == true } + } + + if (isCommunityPost) { + DisplayFollowingCommunityInPost(draftNote, accountViewModel, nav) + } else { + DisplayFollowingHashtagsInPost(draftNote, accountViewModel, nav) + } + } + } else { + val isCommunityPost by remember(baseNote) { + derivedStateOf { baseNote.event?.isACommunityPost() == true } } - if (isCommunityPost) { - DisplayFollowingCommunityInPost(baseNote, accountViewModel, nav) - } else { - DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav) + if (isCommunityPost) { + DisplayFollowingCommunityInPost(baseNote, accountViewModel, nav) + } else { + DisplayFollowingHashtagsInPost(baseNote, accountViewModel, nav) + } } CheckAndDisplayEditStatus(editState) @@ -508,7 +527,7 @@ private fun FullBleedNoteCompose( DisplayPoW(pow) } - if (baseNote.isDraft()) { + if (isDraft) { DisplayDraft() } @@ -527,9 +546,7 @@ private fun FullBleedNoteCompose( } Row( - modifier = - modifier - .padding(horizontal = 12.dp), + modifier = PaddingHorizontal12Modifier, ) { Column { if (noteEvent is ChannelCreateEvent) { @@ -753,17 +770,43 @@ private fun FullBleedNoteCompose( val zapSplits = remember(noteEvent) { noteEvent.hasZapSplitSetup() } if (zapSplits) { Spacer(modifier = DoubleVertSpacer) - Row( - modifier = Modifier.padding(horizontal = 12.dp), - ) { + Row(PaddingHorizontal12Modifier) { DisplayZapSplits(noteEvent, false, accountViewModel, nav) } } + RenderApprovalIfNeeded(baseNote, accountViewModel, nav) + ReactionsRow(baseNote, showReactionDetail = true, addPadding = true, editState = editState, accountViewModel = accountViewModel, nav = nav) } } +@Composable +private fun RenderApprovalIfNeeded( + baseNote: Note, + accountViewModel: AccountViewModel, + nav: INav, +) { + if (baseNote.isNewThread()) { + val communityAddress = + remember(baseNote) { + baseNote.event?.communityAddress() + } + communityAddress?.let { + LoadAddressableNote(it, accountViewModel) { community -> + if (community != null) { + val showApproveButton by observeCommunityApprovalNeedStatus(baseNote, community, accountViewModel) + if (showApproveButton == true) { + Row(PaddingHorizontal12Modifier) { + RenderApproveButton(baseNote, community, accountViewModel) + } + } + } + } + } + } +} + @Composable private fun RenderClassifiedsReaderForThread( noteEvent: ClassifiedsEvent, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt index af3b6678f3..19508f850e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/theme/Shape.kt @@ -358,3 +358,5 @@ val BadgePictureModifier = Modifier.size(35.dp).clip(shape = CutCornerShape(20)) val MaxWidthPaddingTop5dp = Modifier.fillMaxWidth().padding(top = 5.dp) val VoiceHeightModifier = Modifier.fillMaxWidth().height(100.dp) + +val PaddingHorizontal12Modifier = Modifier.padding(horizontal = 12.dp) diff --git a/amethyst/src/main/res/values-pl-rPL/strings.xml b/amethyst/src/main/res/values-pl-rPL/strings.xml index 6c651da040..068f9a9d9e 100644 --- a/amethyst/src/main/res/values-pl-rPL/strings.xml +++ b/amethyst/src/main/res/values-pl-rPL/strings.xml @@ -101,7 +101,7 @@ "Błąd analizowania podglądu dla %1$s : %2$s" "Podgląd obrazu karty dla %1$s" Nowy kanał - Nazwa Grupy + Nazwa Nazwa Grupy Adres Url obrazka Opis @@ -159,10 +159,11 @@ Edytuje metadane kanału Dołącz Znajomi - Nowe Kontakty + Nowe Zablokowani użytkownicy Nowe Wpisy Komentarze + Moderacja Wpisy Odpowiedzi Twoje @@ -263,9 +264,9 @@ Zgłoś mowę nienawiści Zgłoś Nagość / Pornografię inne - Oznacz wszystkie popularne jako przeczytane - Oznacz wszystkie nowe jako przeczytane - Oznacz wszystkie jako przeczytane + Zaznacz wszystkie popularne jako przeczytane + Zaznacz wszystkie nowe jako przeczytane + Zaznacz wszystkie jako przeczytane Klucze kopii zapasowej ## Porady dotyczące kopii zapasowej i bezpieczeństwa \n\nTwoje konto jest zabezpieczone tajnym kluczem. Kluczem jest długa sekwencja znaków, zaczynająca się od **nsec1**. Każdy, kto ma dostęp do tego tajnego klucza może publikować i zmieniać Twoją tożsamość. @@ -456,7 +457,7 @@ Użyj Tor dla dowolnego adresu .onion Transmitery DM Zmuś Tor do wysyłania i odbierania DM - Niepewne Transmitery + Niewiarygodne Transmitery Wymuś Tor na transmiterach skrzynki odbiorczej/nadawczej Zaufane Transmitery Wymuś Tor na wszystkich transmiterach na listach @@ -471,10 +472,10 @@ Operacje finansowe Wymuś Tor przy zapsach, transferach lightning and cashu Weryfikacja adresu Nostr - Wymuś Tor przy weryfikacji adresów NIP-05 + Wymuś Tor podczas weryfikacji adresów NIP-05 Wgrywanie mediów Wymuś Tor podczas wgrywania zawartości - Wewnętrzny + Wewnętrznie Orbot Wył. Podstawowe @@ -484,7 +485,7 @@ Niestandardowy Użyj Tor, gdy jest wymagany przez serwer Ukryj swój adres IP przed przypadkowymi transmiterami - Ukryj swój adres IP od wszystkiego oprócz zdjęć i filmów + Ukryj swój adres IP wszędzie oprócz zdjęć i filmów Ukryj swój adres IP przy wszystkich połączeniach Stwórz swój własny Nieprawidłowy numer portu @@ -520,7 +521,7 @@ Ostrzeżenie o zgłoszeniach Wyświetla komunikat ostrzegawczy, gdy posty zostały zgłoszone przez 5 lub więcej obserwowanych osób Pokaż wrażliwe treści - Pokazuje komunikat ostrzegawczy, gdy autor wpisu oznaczył go jako wrażliwy + Pokazuje komunikat ostrzegawczy, gdy autor oznaczył wpis jako wrażliwy Nowy Symbol Odzewu Brak wstępnie wybranych typów reakcji dla tego użytkownika. Przytrzymaj przycisk serce, aby zmienić Zapraiser @@ -645,7 +646,7 @@ Do Temat Temat dyskusji - "\@Użytkownik1, @Użytkownik2, @Użytkownik3" + "\@Użytkownik1, @Użytkownik2" Nie można wysłać Najpierw podaj adresata wiadomości Członkowie tej grupy @@ -669,6 +670,9 @@ Pliki dodane do Twojej galerii profilu Utworzono Zasady + O nas + Wskazówki + Moderatorzy Zaloguj się za pomocą Amber Zaktualizuj swój status Błąd podczas analizowania komunikatu o błędzie @@ -903,7 +907,7 @@ Transmitery, którym ufasz, nie potrzebujesz połączenia Tor Transmitery Proxy Transmitery Proxy - Transmitery agregatora, z których aplikacja musi pobierać kanały, takie jak filter.nostr.wine. To zastępuje model skrzynki nadawczej i sprawia, że aplikacja łączy się tylko z transmiterami z listy. + Transmitery agregatora, z których aplikacja musi pobierać kanały, takie jak filter.nostr.wine. To zastępuje model skrzynki nadawczej i sprawia, że aplikacja łączy się tylko z transmiterami z Twojej listy. Transmitery Nadawcze Transmitery Nadawania Transmitery, które specjalizują się w przesyłaniu wpisów do wszystkich innych transmiterów, takich jak sendit.nosflare.com. Amethyst doda ten transmiter do wszystkich nowych wydarzeń utworzonych przez użytkownika @@ -1011,4 +1015,6 @@ Języki wyświetlane tutaj nie będą tłumaczone. Wybierz język, aby usunąć go z listy języków nietłumaczonych. Pauza Odtwórz + Otwórz rozwijane menu + Opcja %1$s dla %2$s diff --git a/amethyst/src/main/res/values-zh-rCN/strings.xml b/amethyst/src/main/res/values-zh-rCN/strings.xml index b598945356..8760de5916 100644 --- a/amethyst/src/main/res/values-zh-rCN/strings.xml +++ b/amethyst/src/main/res/values-zh-rCN/strings.xml @@ -163,6 +163,8 @@ 已隐藏用户 新动态 会话 + + Mod 队列 动态 回复 你的 @@ -672,6 +674,9 @@ 媒体已添加到个人资料相册中 创建于 规则 + 关于我们 + 指南 + 审核者 使用 Amber 登录 更新你的状态 错误解析错误消息 @@ -1014,4 +1019,8 @@ 此处显示的语言不会被翻译,请选择一种目标语言重新翻译并去除这个提示。 暂停 播放 + 打开下拉菜单 + 第 %1$s 个选项,共 %2$s 个 + 源过滤器,选中了 %1$s + 源过滤器,%1$s diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index c4cbe33a6c..fd1af1236a 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -175,6 +175,8 @@ Blocked Users New Threads Conversations + Feed + Mod Queue Notes Replies Yours @@ -806,6 +808,10 @@ Created at Rules + About Us + Guidelines + Moderators + Login with Amber Update your status diff --git a/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt b/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt index 03b7d5f790..70b6c2de8d 100644 --- a/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt +++ b/amethyst/src/play/java/com/vitorpamplona/amethyst/ui/components/TranslatableRichTextViewer.kt @@ -81,50 +81,13 @@ fun TranslatableRichTextViewer( accountViewModel: AccountViewModel, nav: INav, ) { - var translatedTextState by translateAndWatchLanguageChanges(content, id, accountViewModel) - - CrossfadeIfEnabled(targetState = translatedTextState, accountViewModel = accountViewModel) { - RenderText( - translatedTextState = it, - content = content, - canPreview = canPreview, - quotesLeft = quotesLeft, - modifier = modifier, - tags = tags, - backgroundColor = backgroundColor, - id = id, - callbackUri = callbackUri, - accountViewModel = accountViewModel, - nav = nav, - ) - } -} - -@Composable -private fun RenderText( - translatedTextState: TranslationConfig, - content: String, - canPreview: Boolean, - quotesLeft: Int, - modifier: Modifier, - tags: ImmutableListOfLists, - backgroundColor: MutableState, - id: String, - callbackUri: String? = null, - accountViewModel: AccountViewModel, - nav: INav, -) { - var showOriginal by - remember(translatedTextState) { mutableStateOf(translatedTextState.showOriginal) } - - val toBeViewed by - remember(translatedTextState) { - derivedStateOf { if (showOriginal) content else translatedTextState.result ?: content } - } - - Column { + TranslatableRichTextViewer( + content = content, + id = id, + accountViewModel = accountViewModel, + ) { ExpandableRichTextViewer( - toBeViewed, + it, canPreview, quotesLeft, modifier, @@ -135,6 +98,45 @@ private fun RenderText( accountViewModel, nav, ) + } +} + +@Composable +fun TranslatableRichTextViewer( + content: String, + id: String, + accountViewModel: AccountViewModel, + displayText: @Composable (String) -> Unit, +) { + var translatedTextState by translateAndWatchLanguageChanges(content, id, accountViewModel) + + CrossfadeIfEnabled(targetState = translatedTextState, accountViewModel = accountViewModel) { + RenderTextWithTranslateOptions( + translatedTextState = it, + content = content, + accountViewModel = accountViewModel, + displayText = displayText, + ) + } +} + +@Composable +private fun RenderTextWithTranslateOptions( + translatedTextState: TranslationConfig, + content: String, + accountViewModel: AccountViewModel, + displayText: @Composable (String) -> Unit, +) { + var showOriginal by + remember(translatedTextState) { mutableStateOf(translatedTextState.showOriginal) } + + val toBeViewed by + remember(translatedTextState) { + derivedStateOf { if (showOriginal) content else translatedTextState.result ?: content } + } + + Column { + displayText(toBeViewed) if ( translatedTextState.sourceLang != null && @@ -325,7 +327,7 @@ fun translateAndWatchLanguageChanges( id: String, accountViewModel: AccountViewModel, ): MutableState { - var translatedTextState = remember(id) { mutableStateOf(TranslationsCache.get(content)) } + val translatedTextState = remember(id) { mutableStateOf(TranslationsCache.get(content)) } TranslateAndWatchLanguageChanges( content, diff --git a/ammolite/build.gradle b/ammolite/build.gradle index 9c8f5dc4e9..3578fb6267 100644 --- a/ammolite/build.gradle +++ b/ammolite/build.gradle @@ -54,10 +54,10 @@ dependencies { implementation libs.androidx.core.ktx // Compose's @Immutable and @Stable classes - implementation libs.androidx.runtime.runtime - + implementation libs.androidx.compose.runtime implementation project(path: ':quartz') + testImplementation libs.junit androidTestImplementation libs.androidx.junit diff --git a/benchmark/build.gradle b/benchmark/build.gradle index 866d4e71be..d008883b7c 100644 --- a/benchmark/build.gradle +++ b/benchmark/build.gradle @@ -24,7 +24,7 @@ android { targetCompatibility JavaVersion.VERSION_21 } - sourceSets.androidTest.assets.srcDirs += ["../quartz/src/androidTest/assets"] + sourceSets.androidTest.resources.srcDirs += ["../quartz/src/androidDeviceTest/resources"] testBuildType = "benchmark" buildTypes { diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/MetaTagsParserBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/MetaTagsParserBenchmark.kt index 6b6aea7d23..7159ec9a10 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/MetaTagsParserBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/amethyst/benchmark/MetaTagsParserBenchmark.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.benchmark import androidx.benchmark.junit4.BenchmarkRule import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.vitorpamplona.amethyst.commons.preview.MetaTagsParser import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull @@ -35,9 +34,8 @@ import java.nio.charset.Charset @RunWith(AndroidJUnit4::class) class MetaTagsParserBenchmark { private val html = - getInstrumentation() - .context.assets - .open("github_amethyst.html") + javaClass.classLoader!! + .getResourceAsStream("github_amethyst.html") .readBytes() .toString(Charset.forName("utf-8")) diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/BaseLargeCacheBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/BaseLargeCacheBenchmark.kt index 3f502cb4db..b64308936f 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/BaseLargeCacheBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/BaseLargeCacheBenchmark.kt @@ -20,7 +20,6 @@ */ package com.vitorpamplona.quartz.benchmark -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.fasterxml.jackson.module.kotlin.readValue import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -34,7 +33,7 @@ open class BaseLargeCacheBenchmark { companion object { fun getEventDB(): List { // This file includes duplicates - val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json") + val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json") return JsonMapper.mapper.readValue>( GZIPInputStream(fullDBInputStream), diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/CacheBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/CacheBenchmark.kt index 2e0bf231a4..2fbe69d92d 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/CacheBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/CacheBenchmark.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.quartz.benchmark import androidx.benchmark.junit4.BenchmarkRule import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.fasterxml.jackson.module.kotlin.readValue import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -42,7 +41,7 @@ import java.util.zip.GZIPInputStream open class BaseCacheBenchmark { fun getEventDB(): List { // This file includes duplicates - val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json") + val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json") return JsonMapper.mapper.readValue>( GZIPInputStream(fullDBInputStream), diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventCmdHasherBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventCmdHasherBenchmark.kt index b343ff0c1d..5b07307dd8 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventCmdHasherBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/EventCmdHasherBenchmark.kt @@ -26,6 +26,7 @@ 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.relay.commands.toClient.EventMessage import com.vitorpamplona.quartz.nip01Core.verifyId import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue @@ -44,8 +45,8 @@ class EventCmdHasherBenchmark { @get:Rule val benchmarkRule = BenchmarkRule() @Test - fun checkIDHashKind1WihtoutTags() { - val event = JsonMapper.fromJson(JsonMapper.mapper.readTree(reqResponseEvent)) + fun checkIDHashKind1WithoutTags() { + val event = EventMessage.parse(JsonMapper.mapper.readTree(reqResponseEvent)).event benchmarkRule.measureRepeated { // Should pass diff --git a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HintIndexerBenchmark.kt b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HintIndexerBenchmark.kt index 80c16fdfe9..28ea473b66 100644 --- a/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HintIndexerBenchmark.kt +++ b/benchmark/src/androidTest/java/com/vitorpamplona/quartz/benchmark/HintIndexerBenchmark.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.quartz.benchmark import androidx.benchmark.junit4.BenchmarkRule import androidx.benchmark.junit4.measureRepeated import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.nip01Core.hints.HintIndexer @@ -47,9 +46,8 @@ class HintIndexerBenchmark { } val relays = - getInstrumentation() - .context.assets - .open("relayDB.txt") + javaClass.classLoader + ?.getResourceAsStream("relayDB.txt")!! .readBytes() .toString(Charset.forName("utf-8")) .split("\n") diff --git a/build.gradle b/build.gradle index 7f204f9e88..4acc357f81 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,8 @@ plugins { alias(libs.plugins.diffplugSpotless) alias(libs.plugins.googleServices) apply false alias(libs.plugins.jetbrainsComposeCompiler) apply false + alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.androidKotlinMultiplatformLibrary) apply false } allprojects { @@ -39,9 +41,18 @@ allprojects { subprojects { afterEvaluate { - tasks.named("preBuild") { - dependsOn("spotlessApply") + try { + tasks.named("preBuild") { + dependsOn("spotlessApply") + } + } catch (UnknownTaskException ignored) { + tasks.matching { + it.name.startsWith("pre") && it.name.endsWith("Build") + }.configureEach { + dependsOn("spotlessApply") + } } + } } diff --git a/commons/build.gradle b/commons/build.gradle index cba6f3a55b..b37d1acc4b 100644 --- a/commons/build.gradle +++ b/commons/build.gradle @@ -51,7 +51,6 @@ composeCompiler { dependencies { implementation project(path: ':quartz') - // Import @Immutable and @Stable implementation platform(libs.androidx.compose.bom) implementation libs.androidx.ui diff --git a/commons/proguard-rules.pro b/commons/proguard-rules.pro index 0c1559397c..283e8b07f3 100644 --- a/commons/proguard-rules.pro +++ b/commons/proguard-rules.pro @@ -20,4 +20,6 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --keep class com.vitorpamplona.amethyst.commons.** { *; } \ No newline at end of file +-keep class com.vitorpamplona.quartz.** { *; } +-keep class com.vitorpamplona.amethyst.** { *; } +-keep class com.vitorpamplona.ammolite.** { *; } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f44afee032..dbd7474fe0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -54,6 +54,9 @@ zxing = "3.5.3" zxingAndroidEmbedded = "4.3.0" windowCoreAndroid = "1.4.0" androidxCamera = "1.4.2" +kotlinStdlib = "2.2.10" +kotlinTest = "2.2.10" +core = "1.7.0" [libraries] abedElazizShe-image-compressor = { group = "com.github.AbedElazizShe", name = "LightCompressor", version.ref = "lightcompressor" } @@ -69,11 +72,12 @@ androidx-camera-extensions = { module = "androidx.camera:camera-extensions", ver androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidxCamera" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidxCamera" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } +androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" } -androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" } androidx-junit-ktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "androidxJunit" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleRuntimeKtx" } @@ -89,7 +93,6 @@ androidx-media3-session = { group = "androidx.media3", name = "media3-session", androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } androidx-runner = { group = "androidx.test", name = "runner", version.ref = "runner" } -androidx-runtime-runtime = { group = "androidx.compose.runtime", name = "runtime" } androidx-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "securityCryptoKtx" } androidx-ui = { group = "androidx.compose.ui", name = "ui" } androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } @@ -121,8 +124,12 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } okhttpCoroutines = { group = "com.squareup.okhttp3", name = "okhttp-coroutines", version.ref = "okhttp" } rfc3986-normalizer = { group = "org.czeal", name = "rfc3986", version.ref = "rfc3986" } +secp256k1-kmp-common = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp", version.ref = "secp256k1KmpJniAndroid" } secp256k1-kmp-jni-android = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp-jni-android", version.ref = "secp256k1KmpJniAndroid" } secp256k1-kmp-jni-jvm = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp-jni-jvm", version.ref = "secp256k1KmpJniAndroid" } +secp256k1-kmp-jni-jvm-linux = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp-jni-jvm-linux", version.ref = "secp256k1KmpJniAndroid" } +secp256k1-kmp-jni-jvm-darwin = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp-jni-jvm-darwin", version.ref = "secp256k1KmpJniAndroid" } +secp256k1-kmp-jni-jvm-mingw = { group = "fr.acinq.secp256k1", name = "secp256k1-kmp-jni-jvm-mingw", version.ref = "secp256k1KmpJniAndroid" } tor-android = { module = "info.guardianproject:tor-android", version.ref = "torAndroid" } unifiedpush = { group = "com.github.UnifiedPush", name = "android-connector", version.ref = "unifiedpush" } url-detector = { group = "io.github.url-detector", name = "url-detector", version.ref = "urlDetector" } @@ -135,6 +142,9 @@ zoomable = { group = "net.engawapg.lib", name = "zoomable", version.ref = "zooma zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" } zxing-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxingAndroidEmbedded" } androidx-window-core-android = { group = "androidx.window", name = "window-core-android", version.ref = "windowCoreAndroid" } +kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlinStdlib" } +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test", version.ref = "kotlinTest" } +androidx-core = { group = "androidx.test", name = "core", version.ref = "core" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } @@ -146,4 +156,6 @@ jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "k jetbrainsKotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } jetbrainsComposeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } serialization = { id = 'org.jetbrains.kotlin.plugin.serialization', version.ref = 'kotlinxSerializationPlugin' } -parcelize = { id = "kotlin-parcelize" } \ No newline at end of file +parcelize = { id = "kotlin-parcelize" } +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +androidKotlinMultiplatformLibrary = { id = "com.android.kotlin.multiplatform.library", version.ref = "agp" } \ No newline at end of file diff --git a/quartz/build.gradle b/quartz/build.gradle deleted file mode 100644 index f4e91c4d09..0000000000 --- a/quartz/build.gradle +++ /dev/null @@ -1,88 +0,0 @@ -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -plugins { - alias(libs.plugins.androidLibrary) - alias(libs.plugins.jetbrainsKotlinAndroid) - alias(libs.plugins.parcelize) -} - -android { - namespace = 'com.vitorpamplona.quartz' - compileSdk = libs.versions.android.compileSdk.get().toInteger() - - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInteger() - targetSdk = libs.versions.android.targetSdk.get().toInteger() - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled = true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - create("benchmark") { - initWith(getByName("release")) - signingConfig = signingConfigs.debug - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_21 - targetCompatibility JavaVersion.VERSION_21 - } - packagingOptions { - resources { - excludes += ['**/libscrypt.dylib'] - } - } -} - -kotlin { - compilerOptions { - jvmTarget = JvmTarget.JVM_21 - freeCompilerArgs.add("-Xstring-concat=inline") - } -} - -dependencies { - implementation libs.androidx.core.ktx - implementation platform(libs.androidx.compose.bom) - - // @Immutable and @Stable - implementation libs.androidx.runtime.runtime - - // Bitcoin secp256k1 bindings to Android - api libs.secp256k1.kmp.jni.android - - // LibSodium for ChaCha encryption (NIP-44) - // Wait for @aar support in version catalogs - - // implementation 'com.goterl:lazysodium-android:5.2.0@aar' - // implementation 'net.java.dev.jna:jna:5.17.0@aar' - - implementation variantOf(libs.lazysodium.android) { artifactType("aar") } - implementation variantOf(libs.jna) { artifactType("aar") } - - // Performant Parser of JSONs into Events - api libs.jackson.module.kotlin - - // immutable collections to avoid recomposition - api libs.kotlinx.collections.immutable - - // Parses URLs from Text: - api libs.url.detector - - // Normalizes URLs - api libs.rfc3986.normalizer - - // Websockets API - implementation libs.okhttp - - testImplementation libs.junit - testImplementation libs.secp256k1.kmp.jni.jvm - androidTestImplementation platform(libs.androidx.compose.bom) - androidTestImplementation libs.androidx.junit - androidTestImplementation libs.androidx.espresso.core -} \ No newline at end of file diff --git a/quartz/build.gradle.kts b/quartz/build.gradle.kts new file mode 100644 index 0000000000..500a818efd --- /dev/null +++ b/quartz/build.gradle.kts @@ -0,0 +1,167 @@ +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidKotlinMultiplatformLibrary) +} + +kotlin { + jvm() + + // Target declarations - add or remove as needed below. These define + // which platforms this KMP module supports. + // See: https://kotlinlang.org/docs/multiplatform-discover-project.html#targets + androidLibrary { + namespace = "com.vitorpamplona.quartz" + compileSdk = libs.versions.android.compileSdk.get().toInt() + minSdk = libs.versions.android.minSdk.get().toInt() + + withHostTestBuilder { + } + + withDeviceTestBuilder { + sourceSetTreeName = "test" + }.configure { + instrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + optimization { + consumerKeepRules.publish = true + consumerKeepRules.files.add(File(project.projectDir, "proguard-rules.pro")) + } + + packaging { + resources { + excludes.add("**/libscrypt.dylib") + } + } + + compilerOptions { + freeCompilerArgs.add("-Xstring-concat=inline") + } + } + + // For iOS targets, this is also where you should + // configure native binary output. For more information, see: + // https://kotlinlang.org/docs/multiplatform-build-native-binaries.html#build-xcframeworks + + // A step-by-step guide on how to include this library in an XCode + // project can be found here: + // https://developer.android.com/kotlin/multiplatform/migrate + val xcfName = "quartz-kmpKit" + + iosX64 { + binaries.framework { + baseName = xcfName + } + } + + iosArm64 { + binaries.framework { + baseName = xcfName + } + } + + iosSimulatorArm64 { + binaries.framework { + baseName = xcfName + } + } + + // Source set declarations. + // Declaring a target automatically creates a source set with the same name. By default, the + // Kotlin Gradle Plugin creates additional source sets that depend on each other, since it is + // common to share sources between related targets. + // See: https://kotlinlang.org/docs/multiplatform-hierarchy.html + sourceSets { + commonMain { + dependencies { + implementation(libs.kotlin.stdlib) + implementation(project.dependencies.platform(libs.androidx.compose.bom)) + + // @Immutable and @Stable + implementation(libs.androidx.compose.runtime) + + api(libs.secp256k1.kmp.common) + } + } + + jvmMain { + dependencies { + // Bitcoin secp256k1 bindings + api(libs.secp256k1.kmp.jni.jvm) + + // Performant Parser of JSONs into Events + api(libs.jackson.module.kotlin) + + // immutable collections to avoid recomposition + api(libs.kotlinx.collections.immutable) + + // Parses URLs from Text: + api(libs.url.detector) + + // Normalizes URLs + api(libs.rfc3986.normalizer) + + // Websockets API + implementation(libs.okhttp) + } + } + + commonTest { + dependencies { + implementation(libs.kotlin.test) + // Bitcoin secp256k1 bindings + api(libs.secp256k1.kmp.jni.jvm) + } + } + + androidMain { + dependencies { + implementation(libs.androidx.core.ktx) + + // @Immutable and @Stable + implementation(libs.androidx.compose.runtime) + + // Bitcoin secp256k1 bindings to Android + api(libs.secp256k1.kmp.jni.android) + + // LibSodium for ChaCha encryption (NIP-44) + implementation ("com.goterl:lazysodium-android:5.2.0@aar") + implementation ("net.java.dev.jna:jna:5.17.0@aar") + + // Performant Parser of JSONs into Events + api(libs.jackson.module.kotlin) + + // immutable collections to avoid recomposition + api(libs.kotlinx.collections.immutable) + + // Parses URLs from Text: + api(libs.url.detector) + + // Normalizes URLs + api(libs.rfc3986.normalizer) + + // Websockets API + implementation(libs.okhttp) + } + } + + getByName("androidDeviceTest") { + dependencies { + implementation(libs.androidx.runner) + implementation(libs.androidx.core) + implementation(libs.androidx.junit) + implementation(libs.androidx.espresso.core) + } + } + + iosMain { + dependencies { + // Add iOS-specific dependencies here. This a source set created by Kotlin Gradle + // Plugin (KGP) that each specific iOS target (e.g., iosX64) depends on as + // part of KMP’s default source set hierarchy. Note that this source set depends + // on common by default and will correctly pull the iOS artifacts of any + // KMP dependencies declared in commonMain. + } + } + } +} \ No newline at end of file diff --git a/quartz/notebooks/Kind1Test.ipynb b/quartz/notebooks/Kind1Test.ipynb deleted file mode 100644 index 707a981a83..0000000000 --- a/quartz/notebooks/Kind1Test.ipynb +++ /dev/null @@ -1,147 +0,0 @@ -{ - "cells": [ - { - "metadata": {}, - "cell_type": "markdown", - "source": "## Creating a new Kind 1 post" - }, - { - "metadata": { - "jupyter": { - "is_executing": true - } - }, - "cell_type": "code", - "source": [ - "USE {\n", - " dependencies(\"fr.acinq.secp256k1:secp256k1-kmp-jni-jvm:0.17.3\")\n", - " dependencies(\"com.goterl:lazysodium-java:5.1.4\")\n", - " dependencies(\"net.java.dev.jna:jna:5.17.0\")\n", - "\n", - " import(\"com.vitorpamplona.quartz.*\")\n", - "}" - ], - "outputs": [], - "execution_count": null - }, - { - "cell_type": "code", - "metadata": { - "collapsed": true, - "ExecuteTime": { - "end_time": "2025-06-03T14:42:40.051027Z", - "start_time": "2025-06-03T14:42:40.023052Z" - } - }, - "source": [ - "import fr.acinq.secp256k1.Secp256k1\n", - "\n", - "import com.vitorpamplona.quartz.nip01Core.crypto.KeyPair\n", - "import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerInternal\n", - "import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync\n", - "import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent\n", - "\n", - "val signer = NostrSignerSync()\n", - "val kind1Template = TextNoteEvent.build(\"New kind 1 Post\")\n", - "val signedEvent = signer.sign(kind1Template)\n", - "\n", - "println(signedEvent)" - ], - "outputs": [ - { - "ename": "org.jetbrains.kotlinx.jupyter.exceptions.ReplCompilerException", - "evalue": "at Cell In[5], line 3, column 43: Unresolved reference: crypto\nat Cell In[5], line 4, column 43: Unresolved reference: signers\nat Cell In[5], line 5, column 43: Unresolved reference: signers\nat Cell In[5], line 6, column 33: Unresolved reference: nip10Notes\nat Cell In[5], line 8, column 14: Unresolved reference: NostrSignerSync\nat Cell In[5], line 9, column 21: Unresolved reference: TextNoteEvent\nat Cell In[5], line 12, column 1: Overload resolution ambiguity: \npublic inline fun println(message: Any?): Unit defined in kotlin.io\npublic inline fun println(message: Boolean): Unit defined in kotlin.io\npublic inline fun println(message: Byte): Unit defined in kotlin.io\npublic inline fun println(message: Char): Unit defined in kotlin.io\npublic inline fun println(message: CharArray): Unit defined in kotlin.io\npublic inline fun println(message: Double): Unit defined in kotlin.io\npublic inline fun println(message: Float): Unit defined in kotlin.io\npublic inline fun println(message: Int): Unit defined in kotlin.io\npublic inline fun println(message: Long): Unit defined in kotlin.io\npublic inline fun println(message: Short): Unit defined in kotlin.io", - "output_type": "error", - "traceback": [ - "org.jetbrains.kotlinx.jupyter.exceptions.ReplCompilerException: at Cell In[5], line 3, column 43: Unresolved reference: crypto", - "at Cell In[5], line 4, column 43: Unresolved reference: signers", - "at Cell In[5], line 5, column 43: Unresolved reference: signers", - "at Cell In[5], line 6, column 33: Unresolved reference: nip10Notes", - "at Cell In[5], line 8, column 14: Unresolved reference: NostrSignerSync", - "at Cell In[5], line 9, column 21: Unresolved reference: TextNoteEvent", - "at Cell In[5], line 12, column 1: Overload resolution ambiguity: ", - "public inline fun println(message: Any?): Unit defined in kotlin.io", - "public inline fun println(message: Boolean): Unit defined in kotlin.io", - "public inline fun println(message: Byte): Unit defined in kotlin.io", - "public inline fun println(message: Char): Unit defined in kotlin.io", - "public inline fun println(message: CharArray): Unit defined in kotlin.io", - "public inline fun println(message: Double): Unit defined in kotlin.io", - "public inline fun println(message: Float): Unit defined in kotlin.io", - "public inline fun println(message: Int): Unit defined in kotlin.io", - "public inline fun println(message: Long): Unit defined in kotlin.io", - "public inline fun println(message: Short): Unit defined in kotlin.io", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.JupyterCompilerImpl.compileSync(JupyterCompilerImpl.kt:208)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.InternalEvaluatorImpl.eval(InternalEvaluatorImpl.kt:126)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$1$result$1.invoke(CellExecutorImpl.kt:80)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl$execute$1$result$1.invoke(CellExecutorImpl.kt:78)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.withHost(ReplForJupyterImpl.kt:791)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.CellExecutorImpl.execute-L4Nmkdk(CellExecutorImpl.kt:78)", - "\tat org.jetbrains.kotlinx.jupyter.repl.execution.CellExecutor$DefaultImpls.execute-L4Nmkdk$default(CellExecutor.kt:13)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.evaluateUserCode-wNURfNM(ReplForJupyterImpl.kt:613)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.evalExImpl(ReplForJupyterImpl.kt:471)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.access$evalExImpl(ReplForJupyterImpl.kt:143)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl$evalEx$1.invoke(ReplForJupyterImpl.kt:464)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl$evalEx$1.invoke(ReplForJupyterImpl.kt:463)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.withEvalContext(ReplForJupyterImpl.kt:444)", - "\tat org.jetbrains.kotlinx.jupyter.repl.impl.ReplForJupyterImpl.evalEx(ReplForJupyterImpl.kt:463)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1$1.invoke(IdeCompatibleMessageRequestProcessor.kt:159)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1$1.invoke(IdeCompatibleMessageRequestProcessor.kt:158)", - "\tat org.jetbrains.kotlinx.jupyter.streams.BlockingSubstitutionEngine.withDataSubstitution(SubstitutionEngine.kt:70)", - "\tat org.jetbrains.kotlinx.jupyter.streams.StreamSubstitutionManager.withSubstitutedStreams(StreamSubstitutionManager.kt:118)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.withForkedIn(IdeCompatibleMessageRequestProcessor.kt:335)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.access$withForkedIn(IdeCompatibleMessageRequestProcessor.kt:54)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$evalWithIO$1$1.invoke(IdeCompatibleMessageRequestProcessor.kt:349)", - "\tat org.jetbrains.kotlinx.jupyter.streams.BlockingSubstitutionEngine.withDataSubstitution(SubstitutionEngine.kt:70)", - "\tat org.jetbrains.kotlinx.jupyter.streams.StreamSubstitutionManager.withSubstitutedStreams(StreamSubstitutionManager.kt:118)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.withForkedErr(IdeCompatibleMessageRequestProcessor.kt:324)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.access$withForkedErr(IdeCompatibleMessageRequestProcessor.kt:54)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$evalWithIO$1.invoke(IdeCompatibleMessageRequestProcessor.kt:348)", - "\tat org.jetbrains.kotlinx.jupyter.streams.BlockingSubstitutionEngine.withDataSubstitution(SubstitutionEngine.kt:70)", - "\tat org.jetbrains.kotlinx.jupyter.streams.StreamSubstitutionManager.withSubstitutedStreams(StreamSubstitutionManager.kt:118)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.withForkedOut(IdeCompatibleMessageRequestProcessor.kt:316)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor.evalWithIO(IdeCompatibleMessageRequestProcessor.kt:347)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1.invoke(IdeCompatibleMessageRequestProcessor.kt:158)", - "\tat org.jetbrains.kotlinx.jupyter.messaging.IdeCompatibleMessageRequestProcessor$processExecuteRequest$1$response$1.invoke(IdeCompatibleMessageRequestProcessor.kt:157)", - "\tat org.jetbrains.kotlinx.jupyter.execution.JupyterExecutorImpl$Task.execute(JupyterExecutorImpl.kt:41)", - "\tat org.jetbrains.kotlinx.jupyter.execution.JupyterExecutorImpl$executorThread$1.invoke(JupyterExecutorImpl.kt:83)", - "\tat org.jetbrains.kotlinx.jupyter.execution.JupyterExecutorImpl$executorThread$1.invoke(JupyterExecutorImpl.kt:80)", - "\tat kotlin.concurrent.ThreadsKt$thread$thread$1.run(Thread.kt:30)", - "" - ] - } - ], - "execution_count": 5 - }, - { - "metadata": {}, - "cell_type": "code", - "source": "", - "outputs": [], - "execution_count": null - } - ], - "metadata": { - "kernelspec": { - "display_name": "Kotlin", - "language": "kotlin", - "name": "kotlin" - }, - "language_info": { - "name": "kotlin", - "version": "1.9.23", - "mimetype": "text/x-kotlin", - "file_extension": ".kt", - "pygments_lexer": "kotlin", - "codemirror_mode": "text/x-kotlin", - "nbconvert_exporter": "" - }, - "ktnbPluginMetadata": { - "projectDependencies": [ - "Amethyst.amethyst.unitTest" - ], - "projectLibraries": false - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/quartz/proguard-rules.pro b/quartz/proguard-rules.pro index 5dc1d419ec..4d9f471e28 100644 --- a/quartz/proguard-rules.pro +++ b/quartz/proguard-rules.pro @@ -58,4 +58,5 @@ } # JSON parsing --keep class com.vitorpamplona.quartz.** { *; } \ No newline at end of file +-keep class com.vitorpamplona.quartz.** { *; } + diff --git a/quartz/src/androidTest/AndroidManifest.xml b/quartz/src/androidDeviceTest/AndroidManifest.xml similarity index 100% rename from quartz/src/androidTest/AndroidManifest.xml rename to quartz/src/androidDeviceTest/AndroidManifest.xml diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/LargeDBSignatureCheck.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/LargeDBSignatureCheck.kt similarity index 90% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/LargeDBSignatureCheck.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/LargeDBSignatureCheck.kt index b40314fd7d..ed5257c58c 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/LargeDBSignatureCheck.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/LargeDBSignatureCheck.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.quartz import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.fasterxml.jackson.module.kotlin.readValue import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper @@ -39,7 +38,7 @@ class LargeDBSignatureCheck { @Test fun insertDatabaseSample() = runBlocking { - val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_short.json") + val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_short.json") val eventArray = JsonMapper.mapper.readValue>( @@ -59,7 +58,7 @@ class LargeDBSignatureCheck { fun insertStartupDatabase() = runBlocking { // This file includes duplicates - val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json") + val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json") val eventArray = JsonMapper.mapper.readValue>( diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/bloom/BloomFilter.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/bloom/BloomFilter.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/bloom/BloomFilter.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/bloom/BloomFilter.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/lightning/LnInvoiceUtilTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/lightning/LnInvoiceUtilTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/lightning/LnInvoiceUtilTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/lightning/LnInvoiceUtilTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/EventSigCheck.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/Nip01Test.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/Nip01Test.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/Nip01Test.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/Nip01Test.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/crypto/Nip01CryptoTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/Nip01CryptoTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/crypto/Nip01CryptoTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/Nip01CryptoTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/AssertUtils.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/AssertUtils.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/AssertUtils.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/AssertUtils.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/BasicTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/BasicTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/BasicTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/BasicTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/LargeDBTests.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/LargeDBTests.kt similarity index 95% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/LargeDBTests.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/LargeDBTests.kt index ac62d9ff33..8b4d1a2767 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/LargeDBTests.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/LargeDBTests.kt @@ -25,7 +25,6 @@ import android.database.sqlite.SQLiteException import android.util.Log import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.fasterxml.jackson.module.kotlin.readValue import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.jackson.JsonMapper @@ -42,7 +41,7 @@ class LargeDBTests { companion object { fun getEventDB(): List { // This file includes duplicates - val fullDBInputStream = getInstrumentation().context.assets.open("nostr_vitor_startup_data.json") + val fullDBInputStream = javaClass.classLoader?.getResourceAsStream("nostr_vitor_startup_data.json") return JsonMapper.mapper.readValue>( GZIPInputStream(fullDBInputStream), diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/SearchTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SearchTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/SearchTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SearchTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip03Timestamp/ots/OtsTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/OtsTest.kt similarity index 96% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip03Timestamp/ots/OtsTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/OtsTest.kt index 0cbd18b877..1a852eaea0 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip03Timestamp/ots/OtsTest.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/OtsTest.kt @@ -29,10 +29,7 @@ import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.fail import kotlinx.coroutines.runBlocking -import org.junit.Assert import org.junit.Test -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit class OtsTest { val resolver = OtsResolver() @@ -75,12 +72,8 @@ class OtsTest { val signer = NostrSignerInternal(KeyPair()) - val countDownLatch = CountDownLatch(1) - val newOts = runBlocking { signer.sign(OtsEvent.build(eventId, upgraded!!)) } - Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS)) - println(newOts.toJson()) println(resolver.info(newOts.otsByteArray())) @@ -91,15 +84,11 @@ class OtsTest { fun createOTSEventAndVerify() { val signer = NostrSignerInternal(KeyPair()) - val countDownLatch = CountDownLatch(1) - val ots = runBlocking { signer.sign(OtsEvent.build(otsEvent2Digest, OtsEvent.stamp(otsEvent2Digest, resolver))) } - Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS)) - println(ots.toJson()) println(resolver.info(ots.otsByteArray())) diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip04Dm/EncryptionTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip04Dm/EncryptionTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip04Dm/EncryptionTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip04Dm/EncryptionTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip04Dm/Nip04Test.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip04Dm/Nip04Test.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip04Dm/Nip04Test.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip04Dm/Nip04Test.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivationTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivationTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivationTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivationTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPathTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPathTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPathTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPathTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39MnemonicsTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39MnemonicsTest.kt similarity index 95% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39MnemonicsTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39MnemonicsTest.kt index ebdafca34c..7b0dd25bdd 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39MnemonicsTest.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39MnemonicsTest.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.quartz.nip06KeyDerivation import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.quartz.nip01Core.core.toHexKey import com.vitorpamplona.quartz.utils.Hex @@ -36,12 +35,7 @@ import org.junit.runner.RunWith class Bip39MnemonicsTest { private val tests = jacksonObjectMapper() - .readTree( - InstrumentationRegistry - .getInstrumentation() - .context.assets - .open("bip39.vectors.json"), - ) + .readTree(javaClass.classLoader?.getResourceAsStream("bip39.vectors.json")) @Test fun toSeed() { diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06Test.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06Test.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06Test.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06Test.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip10Notes/CitationTests.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip10Notes/CitationTests.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip10Notes/CitationTests.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip10Notes/CitationTests.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip10Notes/ThreadingTests.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip10Notes/ThreadingTests.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip10Notes/ThreadingTests.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip10Notes/ThreadingTests.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWMinerTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip13Pow/PoWMinerTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWMinerTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip13Pow/PoWMinerTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankEvaluatorTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip13Pow/PoWRankEvaluatorTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip13Pow/PoWRankEvaluatorTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip13Pow/PoWRankEvaluatorTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip17Dm/AESGCMTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip17Dm/AESGCMTest.kt similarity index 90% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip17Dm/AESGCMTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip17Dm/AESGCMTest.kt index 2d3eb66c14..94cae91122 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip17Dm/AESGCMTest.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip17Dm/AESGCMTest.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.quartz.nip17Dm import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip17Dm.files.encryption.AESGCM import junit.framework.TestCase.assertEquals @@ -46,9 +45,9 @@ class AESGCMTest { @Test fun imageTest() { val image = - getInstrumentation().context.assets.open("ovxxk2vz.jpg").use { + javaClass.classLoader?.getResourceAsStream("ovxxk2vz.jpg")?.use { it.readAllBytes() - } + }!! val decrypted = cipher.decrypt(image) @@ -64,9 +63,9 @@ class AESGCMTest { ) val encrypted = - getInstrumentation().context.assets.open("trouble_video").use { + javaClass.classLoader?.getResourceAsStream("trouble_video")?.use { it.readAllBytes() - } + }!! val decrypted = myCipher.decrypt(encrypted) diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip17Dm/ChatroomKeyTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip17Dm/ChatroomKeyTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip17Dm/ChatroomKeyTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip17Dm/ChatroomKeyTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip19Bech32/NIP19EmbedTests.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/NIP19EmbedTests.kt similarity index 97% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip19Bech32/NIP19EmbedTests.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/NIP19EmbedTests.kt index 347ecf81f9..f6c574544d 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip19Bech32/NIP19EmbedTests.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/NIP19EmbedTests.kt @@ -33,11 +33,8 @@ import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertTrue import kotlinx.coroutines.runBlocking -import org.junit.Assert import org.junit.Test import org.junit.runner.RunWith -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit @RunWith(AndroidJUnit4::class) class NIP19EmbedTests { @@ -57,7 +54,7 @@ class NIP19EmbedTests { assertNotNull(textNote) - val bech32 = NEmbed.create(textNote!!) + val bech32 = NEmbed.create(textNote) println(bech32) @@ -65,7 +62,7 @@ class NIP19EmbedTests { assertTrue(decodedNote.verify()) - assertEquals(textNote!!.toJson(), decodedNote.toJson()) + assertEquals(textNote.toJson(), decodedNote.toJson()) } @Test @@ -101,15 +98,11 @@ class NIP19EmbedTests { KeyPair(Hex.decode("e8e7197ccc53c9ed4cf9b1c8dce085475fa1ffdd71f2c14e44fe23d0bdf77598")), ) - val countDownLatch = CountDownLatch(1) - val eyeglassesPrescriptionEvent = runBlocking { signer.sign(FhirResourceEvent.build(visionPrescriptionBundle)) } - Assert.assertTrue(countDownLatch.await(1, TimeUnit.SECONDS)) - assertNotNull(eyeglassesPrescriptionEvent) val bech32 = NEmbed.create(eyeglassesPrescriptionEvent) diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip22Comments/GeolocatedComments.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip22Comments/GeolocatedComments.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip22Comments/GeolocatedComments.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip22Comments/GeolocatedComments.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1Test.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v1Test.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1Test.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v1Test.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v2Test.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v2Test.kt similarity index 97% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v2Test.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v2Test.kt index e0dcddbfe3..bf87b85cf7 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v2Test.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v2Test.kt @@ -21,7 +21,6 @@ package com.vitorpamplona.quartz.nip44Encryption import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray import com.vitorpamplona.quartz.nip01Core.core.toHexKey @@ -41,7 +40,7 @@ class Nip44v2Test { private val vectors: VectorFile = jacksonObjectMapper() .readValue( - getInstrumentation().context.assets.open("nip44.vectors.json"), + javaClass.classLoader?.getResourceAsStream("nip44.vectors.json"), VectorFile::class.java, ) diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip44Encryption/TestPackageClasses.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip46RemoteSigner/Nip46Test.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/Nip46Test.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip46RemoteSigner/Nip46Test.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/Nip46Test.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/NIP49Test.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/NIP49Test.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/NIP49Test.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/NIP49Test.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip55AndroidSigner/SignStringTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/SignStringTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip55AndroidSigner/SignStringTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip55AndroidSigner/SignStringTest.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip57Zaps/PrivateZapTests.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip57Zaps/PrivateZapTests.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip57Zaps/PrivateZapTests.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip57Zaps/PrivateZapTests.kt diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip59GiftWraps/GiftWrapEventTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip59GiftWraps/GiftWrapEventTest.kt similarity index 99% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/nip59GiftWraps/GiftWrapEventTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip59GiftWraps/GiftWrapEventTest.kt index 1b244a2b30..aa21965ee2 100644 --- a/quartz/src/androidTest/java/com/vitorpamplona/quartz/nip59GiftWraps/GiftWrapEventTest.kt +++ b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/nip59GiftWraps/GiftWrapEventTest.kt @@ -237,7 +237,7 @@ class GiftWrapEventTest { fail("Should not be able to decrypt msg for the receiver decrypted by the sender") } - giftWrapToReceiver.unwrapOrNull(receiver)?.let { _ -> + giftWrapToSender.unwrapOrNull(receiver)?.let { _ -> fail("Should not be able to decrypt msg for the sender decrypted by the receiver") } diff --git a/quartz/src/androidTest/java/com/vitorpamplona/quartz/utils/HexEncodingTest.kt b/quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/utils/HexEncodingTest.kt similarity index 100% rename from quartz/src/androidTest/java/com/vitorpamplona/quartz/utils/HexEncodingTest.kt rename to quartz/src/androidDeviceTest/kotlin/com/vitorpamplona/quartz/utils/HexEncodingTest.kt diff --git a/quartz/src/androidTest/assets/bip39.vectors.json b/quartz/src/androidDeviceTest/resources/bip39.vectors.json similarity index 100% rename from quartz/src/androidTest/assets/bip39.vectors.json rename to quartz/src/androidDeviceTest/resources/bip39.vectors.json diff --git a/quartz/src/androidTest/assets/github_amethyst.html b/quartz/src/androidDeviceTest/resources/github_amethyst.html similarity index 100% rename from quartz/src/androidTest/assets/github_amethyst.html rename to quartz/src/androidDeviceTest/resources/github_amethyst.html diff --git a/quartz/src/androidTest/assets/nip44.vectors.json b/quartz/src/androidDeviceTest/resources/nip44.vectors.json similarity index 100% rename from quartz/src/androidTest/assets/nip44.vectors.json rename to quartz/src/androidDeviceTest/resources/nip44.vectors.json diff --git a/quartz/src/androidTest/assets/nostr_vitor_short.json b/quartz/src/androidDeviceTest/resources/nostr_vitor_short.json similarity index 100% rename from quartz/src/androidTest/assets/nostr_vitor_short.json rename to quartz/src/androidDeviceTest/resources/nostr_vitor_short.json diff --git a/quartz/src/androidTest/assets/nostr_vitor_startup_data.json b/quartz/src/androidDeviceTest/resources/nostr_vitor_startup_data.json similarity index 100% rename from quartz/src/androidTest/assets/nostr_vitor_startup_data.json rename to quartz/src/androidDeviceTest/resources/nostr_vitor_startup_data.json diff --git a/quartz/src/androidTest/assets/ovxxk2vz.jpg b/quartz/src/androidDeviceTest/resources/ovxxk2vz.jpg similarity index 100% rename from quartz/src/androidTest/assets/ovxxk2vz.jpg rename to quartz/src/androidDeviceTest/resources/ovxxk2vz.jpg diff --git a/quartz/src/androidTest/assets/relayDB.txt b/quartz/src/androidDeviceTest/resources/relayDB.txt similarity index 100% rename from quartz/src/androidTest/assets/relayDB.txt rename to quartz/src/androidDeviceTest/resources/relayDB.txt diff --git a/quartz/src/androidTest/assets/trouble_video b/quartz/src/androidDeviceTest/resources/trouble_video similarity index 100% rename from quartz/src/androidTest/assets/trouble_video rename to quartz/src/androidDeviceTest/resources/trouble_video diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/lightning/Lud06Test.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/lightning/Lud06Test.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/lightning/Lud06Test.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/lightning/Lud06Test.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/BloomFilterMurMur3Test.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/BloomFilterMurMur3Test.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/BloomFilterMurMur3Test.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/BloomFilterMurMur3Test.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/HintIndexerTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/HintIndexerTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/HintIndexerTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/HintIndexerTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/MurMur3Test.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/MurMur3Test.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/MurMur3Test.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/MurMur3Test.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/relayDB.txt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/relayDB.txt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/hints/relayDB.txt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/hints/relayDB.txt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializerTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializerTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializerTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializerTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinterTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinterTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinterTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinterTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/metadata/UpdateMetadataTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/UpdateMetadataTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/metadata/UpdateMetadataTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/UpdateMetadataTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/relay/RelayUrlFormatterTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/relay/RelayUrlFormatterTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/relay/RelayUrlFormatterTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/relay/RelayUrlFormatterTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventDbQueryAssemblerTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventDbQueryAssemblerTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventDbQueryAssemblerTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventDbQueryAssemblerTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05Test.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05Test.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05Test.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05Test.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip19Bech32/NIP19ParserTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/NIP19ParserTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip19Bech32/NIP19ParserTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/NIP19ParserTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip19Bech32/TlvIntegerTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/TlvIntegerTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip19Bech32/TlvIntegerTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip19Bech32/TlvIntegerTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip30CustomEmoji/Nip30Test.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/Nip30Test.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip30CustomEmoji/Nip30Test.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/Nip30Test.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip51Lists/TagArrayExt.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip51Lists/TagArrayExt.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip51Lists/TagArrayExt.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip51Lists/TagArrayExt.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip65RelayList/RelayListRecommendationProcessorTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip65RelayList/RelayListRecommendationProcessorTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip65RelayList/RelayListRecommendationProcessorTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip65RelayList/RelayListRecommendationProcessorTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/nip96FileStorage/Nip96Test.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip96FileStorage/Nip96Test.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/nip96FileStorage/Nip96Test.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/nip96FileStorage/Nip96Test.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/utils/HexEncodingTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/HexEncodingTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/utils/HexEncodingTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/HexEncodingTest.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/utils/RuntimeExt.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/RuntimeExt.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/utils/RuntimeExt.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/RuntimeExt.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/utils/SignerUtils.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/SignerUtils.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/utils/SignerUtils.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/SignerUtils.kt diff --git a/quartz/src/test/java/com/vitorpamplona/quartz/utils/StringUtilsTest.kt b/quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/StringUtilsTest.kt similarity index 100% rename from quartz/src/test/java/com/vitorpamplona/quartz/utils/StringUtilsTest.kt rename to quartz/src/androidHostTest/kotlin/com/vitorpamplona/quartz/utils/StringUtilsTest.kt diff --git a/quartz/src/androidMain/AndroidManifest.xml b/quartz/src/androidMain/AndroidManifest.xml new file mode 100644 index 0000000000..a5918e68ab --- /dev/null +++ b/quartz/src/androidMain/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/EventFactory.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/EventFactory.kt diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/Platform.android.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/Platform.android.kt new file mode 100644 index 0000000000..5a6ac25b4c --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/Platform.android.kt @@ -0,0 +1,23 @@ +/** + * 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 + +actual fun platform() = "Android" diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/AudioHeaderEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/tags/DownloadUrlTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/tags/StreamUrlTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/header/tags/WaveformTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/AudioTrackEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/AudioTrackEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/AudioTrackEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/AudioTrackEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/CoverTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/MediaTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/ParticipantTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/PriceTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/audio/track/tags/TypeTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/BountyAddValueEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/BountyAddValueEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/BountyAddValueEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/BountyAddValueEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/RewardTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/bounties/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/bounties/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/DecoupledCipher.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/EncryptionKeyDerivation.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/setup/EncryptionKeyListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/setup/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/decoupling/setup/tags/KeyTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/TextNoteModificationEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/edits/TextNoteModificationEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/edits/TextNoteModificationEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/edits/TextNoteModificationEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/EphemeralChatEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/EphemeralChatEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/EphemeralChatEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/EphemeralChatEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/RoomId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/RoomId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/RoomId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/RoomId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RelayTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RelayTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RelayTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RelayTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RoomTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RoomTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RoomTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/chat/tags/RoomTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoom.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoom.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoom.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoom.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoomCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoomCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoomCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/db/EphemeralRoomCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/EphemeralChatListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/tags/RoomIdTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/tags/RoomIdTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/ephemChat/list/tags/RoomIdTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/ephemChat/list/tags/RoomIdTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/BaseThreadedEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/forks/BaseThreadedEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/BaseThreadedEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/forks/BaseThreadedEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/forks/MarkedETagExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/inlineMetadata/Nip54InlineMetadata.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/inlineMetadata/Nip54InlineMetadata.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/inlineMetadata/Nip54InlineMetadata.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/inlineMetadata/Nip54InlineMetadata.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryBaseEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryBaseEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryBaseEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryBaseEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryPrologueEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryPrologueEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryPrologueEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryPrologueEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryReadingStateEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryReadingStateEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryReadingStateEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStoryReadingStateEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStorySceneEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStorySceneEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStorySceneEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/InteractiveStorySceneEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/tags/ReadStatusTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/tags/RootSceneTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/interactiveStories/tags/StoryOptionTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/limits/LimitProcessor.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/Limits.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/limits/Limits.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/limits/Limits.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/limits/Limits.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/medical/FhirResourceEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/medical/FhirResourceEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/medical/FhirResourceEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/medical/FhirResourceEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/data/FileStorageEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/data/FileStorageEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/data/FileStorageEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/data/FileStorageEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/data/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/data/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/data/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/data/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/header/FileStorageHeaderEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/header/FileStorageHeaderEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/header/FileStorageHeaderEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/header/FileStorageHeaderEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/header/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/header/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nip95/header/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nip95/header/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/NNSEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/NNSEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/NNSEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/NNSEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/tags/IPv4Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/tags/IPv6Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/nns/tags/VersionTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/profileGallery/GalleryListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/profileGallery/GalleryListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/profileGallery/GalleryListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/profileGallery/GalleryListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/profileGallery/ProfileGalleryEntryEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/profileGallery/ProfileGalleryEntryEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/profileGallery/ProfileGalleryEntryEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/profileGallery/ProfileGalleryEntryEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/profileGallery/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/profileGallery/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/profileGallery/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/profileGallery/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/publicMessages/PublicMessageEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/publicMessages/PublicMessageEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/publicMessages/PublicMessageEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/publicMessages/PublicMessageEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/publicMessages/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/publicMessages/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/publicMessages/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/publicMessages/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/publicMessages/tags/ReceiverTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/publicMessages/tags/ReceiverTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/publicMessages/tags/ReceiverTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/publicMessages/tags/ReceiverTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/RelationshipStatusEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/RelationshipStatusEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/RelationshipStatusEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/RelationshipStatusEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetNameTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetNameTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetNameTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/PetNameTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/RankTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/relationshipStatus/tags/SummaryTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/PollNoteEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/PollNoteEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/PollNoteEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/PollNoteEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/ClosedAtTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/ConsensusThresholdTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/MaximumTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/MinimumTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/experimental/zapPolls/tags/PollOptionTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/lightning/LnInvoiceUtil.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/lightning/LnInvoiceUtil.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/lightning/LnInvoiceUtil.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/lightning/LnInvoiceUtil.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/lightning/LnWithdrawalUtil.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/lightning/LnWithdrawalUtil.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/lightning/LnWithdrawalUtil.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/lightning/LnWithdrawalUtil.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/lightning/Lud06.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/lightning/Lud06.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/lightning/Lud06.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/lightning/Lud06.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/AddressableEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/AddressableEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/AddressableEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/AddressableEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/BaseAddressableEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/BaseAddressableEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/BaseAddressableEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/BaseAddressableEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/BaseReplaceableEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/BaseReplaceableEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/BaseReplaceableEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/BaseReplaceableEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Event.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Event.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Event.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Event.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/HexKey.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/HexKey.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/HexKey.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/HexKey.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/IEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/IEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/IEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/IEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Kind.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Kind.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Kind.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Kind.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArray.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/TagArray.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArray.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/TagArray.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/core/TagArrayBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/DeterministicSigner.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/DeterministicSigner.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/DeterministicSigner.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/DeterministicSigner.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/EventAssembler.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/EventAssembler.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/EventAssembler.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/EventAssembler.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/EventHasher.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/EventHasher.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/EventHasher.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/EventHasher.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/KeyPair.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/Nip01.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/Nip01.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/crypto/Nip01.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/crypto/Nip01.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/EventHintBundle.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/EventHintBundle.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/EventHintBundle.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/EventHintBundle.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/HintIndexer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/HintIndexer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/HintIndexer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/HintIndexer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/HintProviders.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/HintProviders.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/HintProviders.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/HintProviders.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/bloom/BitSetExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/bloom/BitSetExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/bloom/BitSetExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/bloom/BitSetExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/bloom/BloomFilterMurMur3.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/bloom/BloomFilterMurMur3.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/bloom/BloomFilterMurMur3.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/bloom/BloomFilterMurMur3.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/bloom/MurmurHash3.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/bloom/MurmurHash3.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/bloom/MurmurHash3.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/bloom/MurmurHash3.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/AddressHint.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/AddressHint.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/AddressHint.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/AddressHint.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/EventIdHint.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/EventIdHint.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/EventIdHint.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/EventIdHint.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/Hint.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/Hint.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/Hint.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/Hint.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/PubKeyHint.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/PubKeyHint.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/hints/types/PubKeyHint.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/hints/types/PubKeyHint.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventManualDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventManualDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventManualDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventManualDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventManualSerializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventManualSerializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventManualSerializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventManualSerializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventSerializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventSerializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/EventSerializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/EventSerializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinter.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinter.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinter.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/InliningTagArrayPrettyPrinter.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/JacksonExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/JacksonExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JacksonExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/JsonMapper.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JsonMapper.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/JsonMapper.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/JsonMapper.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayManualDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayManualDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayManualDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/TagArrayManualDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/TagArraySerializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/TagArraySerializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/jackson/TagArraySerializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/jackson/TagArraySerializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/MetadataEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/MetadataEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/MetadataEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/MetadataEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/UserMetadata.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/UserMetadata.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/UserMetadata.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/UserMetadata.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/AboutTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/AboutTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/AboutTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/AboutTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/BannerTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/BannerTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/BannerTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/BannerTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/DisplayNameTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/DisplayNameTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/DisplayNameTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/DisplayNameTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud06Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud06Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud06Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud06Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud16Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud16Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud16Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/Lud16Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/NameTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/NameTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/NameTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/NameTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/Nip05Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/Nip05Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/Nip05Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/Nip05Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/PictureTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/PictureTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/PictureTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/PictureTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/PronounsTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/PronounsTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/PronounsTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/PronounsTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/WebsiteTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/WebsiteTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/metadata/tags/WebsiteTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/metadata/tags/WebsiteTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClient.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClientSubscription.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClientSubscription.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClientSubscription.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/NostrClientSubscription.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/EventCollector.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/EventCollector.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/EventCollector.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/EventCollector.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSendAndWaitExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSendAndWaitExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSendAndWaitExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSendAndWaitExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/NostrClientSingleDownloadExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayAuthenticator.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayAuthenticator.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayAuthenticator.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayAuthenticator.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayInsertConfirmationCollector.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayInsertConfirmationCollector.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayInsertConfirmationCollector.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayInsertConfirmationCollector.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayLogger.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayLogger.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayLogger.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayLogger.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayNotifier.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayNotifier.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayNotifier.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/accessories/RelayNotifier.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/IRelayClientListener.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/IRelayClientListener.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/IRelayClientListener.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/IRelayClientListener.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/RedirectRelayClientListener.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/RedirectRelayClientListener.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/RedirectRelayClientListener.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/listeners/RedirectRelayClientListener.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutbox.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutboxRepository.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutboxRepository.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutboxRepository.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolEventOutboxRepository.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolSubscriptionRepository.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolSubscriptionRepository.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolSubscriptionRepository.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/PoolSubscriptionRepository.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayBasedFilter.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayBasedFilter.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayBasedFilter.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayBasedFilter.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/pool/RelayPool.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/IRelayClient.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/IRelayClient.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/IRelayClient.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/IRelayClient.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/Subscription.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/Subscription.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/Subscription.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/Subscription.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/basic/BasicRelayClient.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/OutboxCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/OutboxCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/OutboxCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/OutboxCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/SimpleRelayClient.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/SimpleRelayClient.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/SimpleRelayClient.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/single/simple/SimpleRelayClient.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStat.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStat.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStat.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStat.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStats.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStats.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStats.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/stats/RelayStats.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/Subscription.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/Subscription.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/Subscription.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/Subscription.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionController.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionStats.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionStats.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionStats.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/client/subscriptions/SubscriptionStats.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/AuthMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/AuthMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/AuthMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/AuthMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ClosedMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ClosedMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ClosedMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ClosedMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EoseMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EoseMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EoseMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EoseMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EventMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EventMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EventMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/EventMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/Message.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/Message.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/Message.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/Message.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NoticeMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NoticeMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NoticeMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NoticeMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NotifyMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NotifyMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NotifyMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/NotifyMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/OkMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/OkMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/OkMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/OkMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ToClientParser.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ToClientParser.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ToClientParser.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toClient/ToClientParser.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/AuthCmd.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/AuthCmd.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/AuthCmd.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/AuthCmd.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CloseCmd.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CloseCmd.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CloseCmd.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CloseCmd.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/Command.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/Command.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/Command.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/Command.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CountCmd.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CountCmd.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CountCmd.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/CountCmd.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/EventCmd.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/EventCmd.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/EventCmd.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/EventCmd.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ReqCmd.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ReqCmd.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ReqCmd.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ReqCmd.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ToRelayParser.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ToRelayParser.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ToRelayParser.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/commands/toRelay/ToRelayParser.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/Filter.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterMatcher.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterMatcher.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterMatcher.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterMatcher.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterSerializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterSerializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterSerializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/filters/FilterSerializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/normalizer/NormalizedRelayUrl.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/normalizer/NormalizedRelayUrl.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/normalizer/NormalizedRelayUrl.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/normalizer/NormalizedRelayUrl.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/normalizer/RelayUrlNormalizer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/normalizer/RelayUrlNormalizer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/normalizer/RelayUrlNormalizer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/normalizer/RelayUrlNormalizer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocket.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocket.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocket.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocket.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocketListener.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocketListener.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocketListener.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebSocketListener.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilderFactory.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilderFactory.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilderFactory.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/WebsocketBuilderFactory.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/relay/sockets/okhttp/BasicOkHttpWebSocket.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/EventTemplate.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateManualDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateManualDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateManualDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/EventTemplateManualDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/NostrSigner.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerInternal.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/NostrSignerSync.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/SignerExceptions.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/SignerExceptions.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/SignerExceptions.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/SignerExceptions.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/caches/CacheResults.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/caches/CacheResults.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/caches/CacheResults.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/caches/CacheResults.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/caches/DecryptCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/caches/DecryptCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/signers/caches/DecryptCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/signers/caches/DecryptCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/README.md b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/README.md similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/README.md rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/README.md diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/AddressableModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionRequestModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionRequestModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionRequestModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/DeletionRequestModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EphemeralModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EphemeralModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EphemeralModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EphemeralModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventIndexesModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/EventStore.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ExpirationModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/FullTextSearchModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/FullTextSearchModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/FullTextSearchModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/FullTextSearchModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/ReplaceableModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishModule.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishModule.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishModule.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/RightToVanishModule.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/SQLiteEventStore.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/StatementCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/StatementCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/StatementCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/StatementCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/Condition.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/Condition.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/Condition.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/Condition.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/SqlSelectionBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/SqlSelectionBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/SqlSelectionBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/SqlSelectionBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/WhereClauseBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/WhereClauseBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/WhereClauseBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/store/sqlite/sql/WhereClauseBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/ATag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt similarity index 77% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt index 8c5edc6d2d..b055aab09d 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/Address.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.quartz.nip01Core.tags.addressables +import android.os.Parcel import android.os.Parcelable import android.util.Log import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -29,9 +30,7 @@ import com.vitorpamplona.quartz.nip19Bech32.entities.NAddress import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.bytesUsedInMemory import com.vitorpamplona.quartz.utils.pointerSizeInBytes -import kotlinx.parcelize.Parcelize -@Parcelize data class Address( val kind: Kind, val pubKeyHex: HexKey, @@ -61,6 +60,19 @@ data class Address( } companion object { + // ----------- + // Manual Parcelable implementation to avoid issues between kmp and the parcelize plugin + // ----------- + @JvmField + val CREATOR = + object : Parcelable.Creator
{ + // Create an instance from the Parcel + override fun createFromParcel(parcel: Parcel): Address = Address(parcel) + + // Create a new array of the Parcelable class + override fun newArray(size: Int): Array = arrayOfNulls(size) + } + fun assemble( kind: Int, pubKeyHex: HexKey, @@ -99,4 +111,25 @@ data class Address( kind: String, ) = addressId.startsWith(kind) && addressId[kind.length] == ':' } + + // ----------- + // Manual Parcelable implementation to avoid issues between kmp and the parcelize plugin + // ----------- + override fun writeToParcel( + parcel: Parcel, + flags: Int, + ) { + parcel.writeInt(kind) + parcel.writeString(pubKeyHex) + parcel.writeString(dTag) + } + + override fun describeContents() = 0 + + // 4. Primary constructor for deserialization + constructor(parcel: Parcel) : this( + kind = parcel.readInt(), + pubKeyHex = parcel.readString() ?: "", + dTag = parcel.readString() ?: "", + ) } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/EventExt.kt similarity index 91% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/EventExt.kt index ef28d4089b..ee1c611fac 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/EventExt.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/EventExt.kt @@ -32,8 +32,12 @@ fun Event.isTaggedAddressableNotes(addressIds: Set) = tags.isTaggedAddre fun Event.isTaggedAddressableKind(kind: Int) = tags.isTaggedAddressableKind(kind) +fun Event.isTaggedAddressableKind(kindStr: String) = tags.isTaggedAddressableKind(kindStr) + fun Event.getTagOfAddressableKind(kind: Int) = tags.getTagOfAddressableKind(kind) +fun Event.getTagOfAddressableKind(kindStr: String) = tags.getTagOfAddressableKind(kindStr) + fun Event.taggedATags() = tags.taggedATags() fun Event.firstTaggedATag() = tags.firstTaggedATag() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayExt.kt similarity index 91% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayExt.kt index 11f6e514d4..bf36f24541 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayExt.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/addressables/TagArrayExt.kt @@ -35,8 +35,12 @@ fun TagArray.isTaggedAddressableNotes(addressIds: Set) = this.any(ATag:: fun TagArray.isTaggedAddressableKind(kind: Int) = this.any(ATag::isTaggedWithKind, kind.toString()) +fun TagArray.isTaggedAddressableKind(kindStr: String) = this.any(ATag::isTaggedWithKind, kindStr) + fun TagArray.getTagOfAddressableKind(kind: Int) = this.fastFirstNotNullOfOrNull(ATag::parseIfOfKind, kind.toString()) +fun TagArray.getTagOfAddressableKind(kindStr: String) = this.fastFirstNotNullOfOrNull(ATag::parseIfOfKind, kindStr.toString()) + fun TagArray.taggedATags() = this.mapNotNull(ATag::parse) fun TagArray.firstTaggedATag() = this.fastFirstNotNullOfOrNull(ATag::parse) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/DTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/DTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/DTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/DTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/dTags/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/ETag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/EventReference.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/EventReference.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/EventReference.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/EventReference.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/GenericETag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/GenericETag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/GenericETag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/GenericETag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/events/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeoHashTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeoHashTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeoHashTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeoHashTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeohashPrecision.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeohashPrecision.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeohashPrecision.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/GeohashPrecision.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/geohash/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/HashtagTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/MipMapHashTags.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/MipMapHashTags.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/MipMapHashTags.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/MipMapHashTags.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/hashtags/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/KindTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/kinds/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/EntityExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/EntityExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/EntityExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/EntityExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/PTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PubKeyReferenceTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/PubKeyReferenceTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/PubKeyReferenceTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/PubKeyReferenceTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/people/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/publishedAt/PublishedAtProvider.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/publishedAt/PublishedAtProvider.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/publishedAt/PublishedAtProvider.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/publishedAt/PublishedAtProvider.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/ReferenceTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip01Core/tags/references/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ContactListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/ImmutableListOfLists.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ImmutableListOfLists.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/ImmutableListOfLists.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/ImmutableListOfLists.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/RelaySet.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/RelaySet.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/RelaySet.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/RelaySet.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip02FollowList/tags/ContactTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/OtsEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/OtsEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/OtsEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/OtsEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/OtsResolver.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/OtsResolver.kt similarity index 90% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/OtsResolver.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/OtsResolver.kt index 4d560b1e9d..169f282e06 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/OtsResolver.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/OtsResolver.kt @@ -22,14 +22,14 @@ package com.vitorpamplona.quartz.nip03Timestamp import android.util.Log import com.vitorpamplona.quartz.nip03Timestamp.ots.BitcoinExplorer -import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockstreamExplorer import com.vitorpamplona.quartz.nip03Timestamp.ots.CalendarBuilder -import com.vitorpamplona.quartz.nip03Timestamp.ots.CalendarPureJavaBuilder import com.vitorpamplona.quartz.nip03Timestamp.ots.DetachedTimestampFile import com.vitorpamplona.quartz.nip03Timestamp.ots.Hash import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps import com.vitorpamplona.quartz.nip03Timestamp.ots.VerifyResult import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException +import com.vitorpamplona.quartz.nip03Timestamp.ots.http.BlockstreamExplorer +import com.vitorpamplona.quartz.nip03Timestamp.ots.http.CalendarPureJavaBuilder import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256 import kotlinx.coroutines.CancellationException @@ -42,10 +42,10 @@ class OtsResolver( fun info(otsState: ByteArray): String = ots.info(DetachedTimestampFile.deserialize(otsState)) fun stamp(data: ByteArray): ByteArray { - val hash = Hash(data, OpSHA256._TAG) + val hash = Hash(data, OpSHA256.TAG) val file = DetachedTimestampFile.from(hash) val timestamp = ots.stamp(file) - val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp) + val detachedToSerialize = DetachedTimestampFile(hash.op, timestamp) return detachedToSerialize.serialize() } @@ -78,7 +78,7 @@ class OtsResolver( ): VerificationState { try { val result = ots.verify(detachedOts, data) - if (result == null || result.isEmpty()) { + if (result.isEmpty()) { return VerificationState.Error("Verification hashmap is empty") } else { val time = result.get(VerifyResult.Chains.BITCOIN)?.timestamp @@ -90,7 +90,7 @@ class OtsResolver( } } catch (e: Exception) { if (e is CancellationException) throw e - Log.e("OpenTimeStamps", "Failed to verify", e) + Log.w("OpenTimeStamps", "Failed to verify", e) return if (e is UrlException) { VerificationState.NetworkError(e.message ?: e.cause?.message ?: "Failed to verify") } else { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/OtsResolverBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/OtsResolverBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/OtsResolverBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/OtsResolverBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/VerificationState.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/VerificationState.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/VerificationState.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/VerificationState.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/VerificationStateCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/VerificationStateCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/VerificationStateCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/VerificationStateCache.kt diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/BitcoinExplorer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/BitcoinExplorer.kt new file mode 100644 index 0000000000..a3c094c6f6 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/BitcoinExplorer.kt @@ -0,0 +1,43 @@ +/** + * 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.nip03Timestamp.ots + +interface BitcoinExplorer { + /** + * Retrieve the block information from the block hash. + * + * @param hash Hash of the block. + * @return the blockheader of the hash + * @throws Exception desc + */ + @Throws(Exception::class) + fun block(hash: String): BlockHeader + + /** + * Retrieve the block hash from the block height. + * + * @param height Height of the block. + * @return the hash of the block at height height + * @throws Exception desc + */ + @Throws(Exception::class) + fun blockHash(height: Int): String +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockHeader.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockHeader.kt new file mode 100644 index 0000000000..ca4235dfcf --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockHeader.kt @@ -0,0 +1,29 @@ +/** + * 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.nip03Timestamp.ots + +data class BlockHeader( + val merkleRoot: String, + val blockHash: String, + val time: String, +) { + fun getTime(): Long = time.toLong() +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarBuilder.kt new file mode 100644 index 0000000000..5b317e2bdd --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarBuilder.kt @@ -0,0 +1,30 @@ +/** + * 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.nip03Timestamp.ots + +interface CalendarBuilder { + fun newSyncCalendar(url: String): ICalendar + + fun newAsyncCalendar( + url: String, + digest: ByteArray, + ): ICalendarAsyncSubmit +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/DetachedTimestampFile.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/DetachedTimestampFile.kt new file mode 100644 index 0000000000..52699b94e9 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/DetachedTimestampFile.kt @@ -0,0 +1,272 @@ +/** + * 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.nip03Timestamp.ots + +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256 +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.security.NoSuchAlgorithmException + +/** + * Class representing Detached com.vitorpamplona.quartz.ots.Timestamp File. + * A file containing a timestamp for another file. + * Contains a timestamp, along with a header and the digest of the file. + */ +class DetachedTimestampFile( + val fileHashOp: Op, + val timestamp: Timestamp, +) { + /** + * The digest of the file that was timestamped. + * + * @return The message inside the timestamp. + */ + fun fileDigest(): ByteArray? = this.timestamp.digest + + /** + * Serialize a com.vitorpamplona.quartz.ots.Timestamp File. + * + * @param ctx The stream serialization context. + */ + fun serialize(ctx: StreamSerializationContext) { + ctx.writeBytes(HEADER_MAGIC) + ctx.writeVaruint(MAJOR_VERSION.toInt()) + this.fileHashOp.serialize(ctx) + ctx.writeBytes(this.timestamp.digest) + this.timestamp.serialize(ctx) + } + + /** + * Serialize a com.vitorpamplona.quartz.ots.Timestamp File. + * + * @return The byte array of serialized data. + */ + fun serialize(): ByteArray { + val ctx = StreamSerializationContext() + this.serialize(ctx) + + return ctx.output + } + + /** + * Print the object. + * + * @return The output. + */ + override fun toString(): String { + var output = "com.vitorpamplona.quartz.ots.DetachedTimestampFile\n" + output += "fileHashOp: " + this.fileHashOp.toString() + '\n' + output += "timestamp: " + this.timestamp.toString() + '\n' + + return output + } + + companion object { + /** + * Header magic bytes Designed to be give the user some information in a hexdump, while being + * identified as 'data' by the file utility. + * + * @default \x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94 + */ + val HEADER_MAGIC: ByteArray = + byteArrayOf( + 0x00.toByte(), + 0x4f.toByte(), + 0x70.toByte(), + 0x65.toByte(), + 0x6e.toByte(), + 0x54.toByte(), + 0x69.toByte(), + 0x6d.toByte(), + 0x65.toByte(), + 0x73.toByte(), + 0x74.toByte(), + 0x61.toByte(), + 0x6d.toByte(), + 0x70.toByte(), + 0x73.toByte(), + 0x00.toByte(), + 0x00.toByte(), + 0x50.toByte(), + 0x72.toByte(), + 0x6f.toByte(), + 0x6f.toByte(), + 0x66.toByte(), + 0x00.toByte(), + 0xbf.toByte(), + 0x89.toByte(), + 0xe2.toByte(), + 0xe8.toByte(), + 0x84.toByte(), + 0xe8.toByte(), + 0x92.toByte(), + 0x94.toByte(), + ) + + /** + * While the git commit timestamps have a minor version, probably better to + * leave it out here: unlike Git commits round-tripping is an issue when + * timestamps are upgraded, and we could end up with bugs related to not + * saving/updating minor version numbers correctly. + * + * @default 1 + */ + const val MAJOR_VERSION: Byte = 1 + + /** + * Deserialize a com.vitorpamplona.quartz.ots.Timestamp File. + * + * @param ctx The stream deserialization context. + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + */ + @Throws(DeserializationException::class) + fun deserialize(ctx: StreamDeserializationContext): DetachedTimestampFile { + ctx.assertMagic(HEADER_MAGIC) + ctx.readVaruint() + + val fileHashOp = Op.deserialize(ctx) as OpCrypto + val fileHash = ctx.readBytes(fileHashOp.digestLength()) + val timestamp = Timestamp.deserialize(ctx, fileHash) + + ctx.assertEof() + + return DetachedTimestampFile(fileHashOp, timestamp) + } + + /** + * Deserialize a com.vitorpamplona.quartz.ots.Timestamp File. + * + * @param ots The byte array of deserialization DetachedFileTimestamped. + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + */ + @Throws(DeserializationException::class) + fun deserialize(ots: ByteArray): DetachedTimestampFile { + val ctx = StreamDeserializationContext(ots) + + return deserialize(ctx) + } + + /** + * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from bytes. + * + * @param fileHashOp The file hash operation. + * @param ctx The stream deserialization context. + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + * @throws NoSuchAlgorithmException desc + */ + @Throws(NoSuchAlgorithmException::class) + fun from( + fileHashOp: OpCrypto, + ctx: StreamDeserializationContext, + ): DetachedTimestampFile { + val fdHash = fileHashOp.hashFd(ctx) + + return DetachedTimestampFile(fileHashOp, Timestamp(fdHash)) + } + + /** + * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from bytes. + * + * @param fileHashOp The file hash operation. + * @param bytes The byte array of data to hash + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + * @throws NoSuchAlgorithmException desc + */ + @Throws(Exception::class) + fun from( + fileHashOp: OpCrypto, + bytes: ByteArray, + ): DetachedTimestampFile { + val fdHash = fileHashOp.hashFd(bytes) + + return DetachedTimestampFile(fileHashOp, Timestamp(fdHash)) + } + + /** + * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from hash. + * + * @param inputStream The InputStream of the file to hash + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + * @throws Exception if the input stream is null + */ + @Throws(Exception::class) + fun from(inputStream: InputStream): DetachedTimestampFile { + try { + val fileTimestamp: DetachedTimestampFile = + from(OpSHA256(), inputStream) // Read from file reader stream + return fileTimestamp + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + throw Exception() + } + } + + /** + * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from InputStream. + * + * @param fileHashOp The file hash operation. + * @param inputStream The input stream file. + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + * @throws IOException desc + * @throws NoSuchAlgorithmException desc + */ + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun from( + fileHashOp: OpCrypto, + inputStream: InputStream, + ): DetachedTimestampFile { + val fdHash = fileHashOp.hashFd(inputStream) + + return DetachedTimestampFile(fileHashOp, Timestamp(fdHash)) + } + + /** + * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from hash. + * + * @param hash The hash of the file. + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + */ + fun from(hash: Hash): DetachedTimestampFile = DetachedTimestampFile(hash.op, Timestamp(hash.value)) + + /** + * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from File. + * + * @param fileHashOp The file hash operation. + * @param file The hash file. + * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. + * @throws IOException desc + * @throws NoSuchAlgorithmException desc + */ + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun from( + fileHashOp: OpCrypto, + file: File?, + ): DetachedTimestampFile { + val fdHash = fileHashOp.hashFd(file) + + return DetachedTimestampFile(fileHashOp, Timestamp(fdHash)) + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Hash.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Hash.kt new file mode 100644 index 0000000000..0dccda379a --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Hash.kt @@ -0,0 +1,170 @@ +/** + * 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.nip03Timestamp.ots + +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpKECCAK256 +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpRIPEMD160 +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA1 +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256 +import com.vitorpamplona.quartz.utils.Hex.encode +import java.io.File +import java.io.IOException +import java.io.InputStream +import java.security.NoSuchAlgorithmException + +/** + * Create a Hash object. + * + * @param value - The byte array of the hash + * @param algorithm - The hashlib tag of crypto operation + */ +class Hash( + val value: ByteArray, + val algorithm: Byte, +) { + /** + * Create a Hash object. + * + * @param value - The byte array of the hash + * @param label - The hashlib name of crypto operation + */ + constructor(value: ByteArray, label: String) : this(value, getOp(label).tag()) + + val op: OpCrypto = + when (this.algorithm) { + OpSHA1.TAG -> OpSHA1() + OpSHA256.TAG -> OpSHA256() + OpRIPEMD160.TAG -> OpRIPEMD160() + OpKECCAK256.TAG -> OpKECCAK256() + else -> OpSHA256() + } + + /** + * Print the object. + * + * @return The output. + */ + override fun toString(): String { + var output = "com.vitorpamplona.quartz.ots.Hash\n" + output += "algorithm: " + this.op.hashLibName() + '\n' + output += "value: " + encode(this.value) + '\n' + + return output + } + + companion object { + /** + * Get Crypto operation from hashlib tag. + * + * @param algorithm The hashlib tag. + * @return The generated com.vitorpamplona.quartz.ots.OpCrypto object. + */ + fun getOp(algorithm: Byte): OpCrypto = + when (algorithm) { + OpSHA1.TAG -> OpSHA1() + OpSHA256.TAG -> OpSHA256() + OpRIPEMD160.TAG -> OpRIPEMD160() + OpKECCAK256.TAG -> OpKECCAK256() + else -> OpSHA256() + } + + /** + * Get Crypto operation from hashlib name. + * + * @param label The hashlib name. + * @return The generated com.vitorpamplona.quartz.ots.OpCrypto object. + */ + fun getOp(label: String): OpCrypto { + if (label.lowercase() == OpSHA1().tagName()) { + return OpSHA1() + } else if (label.lowercase() == OpSHA256().tagName()) { + return OpSHA256() + } else if (label.lowercase() == OpRIPEMD160().tagName()) { + return OpRIPEMD160() + } else if (label.lowercase() == OpKECCAK256().tagName()) { + return OpKECCAK256() + } + + return OpSHA256() + } + + /** + * Build hash from data. + * + * @param bytes The byte array of data to hash. + * @param algorithm The hash file. + * @return The generated com.vitorpamplona.quartz.ots.Hash object. + * @throws IOException desc + * @throws NoSuchAlgorithmException desc + */ + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun from( + bytes: ByteArray, + algorithm: Byte, + ): Hash { + val opCrypto: OpCrypto = getOp(algorithm) + val value = opCrypto.hashFd(bytes) + + return Hash(value, algorithm) + } + + /** + * Build hash from File. + * + * @param file The File of data to hash. + * @param algorithm The hash file. + * @return The generated com.vitorpamplona.quartz.ots.Hash object. + * @throws IOException desc + * @throws NoSuchAlgorithmException desc + */ + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun from( + file: File?, + algorithm: Byte, + ): Hash { + val opCrypto: OpCrypto = getOp(algorithm) + val value = opCrypto.hashFd(file) + + return Hash(value, algorithm) + } + + /** + * Build hash from InputStream. + * + * @param inputStream The InputStream of data to hash. + * @param algorithm The hash file. + * @return The generated com.vitorpamplona.quartz.ots.Hash object. + * @throws IOException desc + * @throws NoSuchAlgorithmException desc + */ + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun from( + inputStream: InputStream, + algorithm: Byte, + ): Hash { + val opCrypto: OpCrypto = getOp(algorithm) + val value = opCrypto.hashFd(inputStream) + + return Hash(value, algorithm) + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendar.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendar.kt new file mode 100644 index 0000000000..8ddc0d9c52 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendar.kt @@ -0,0 +1,39 @@ +/** + * 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.nip03Timestamp.ots + +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException + +interface ICalendar { + @Throws(ExceededSizeException::class, UrlException::class, DeserializationException::class) + fun submit(digest: ByteArray): Timestamp + + @Throws( + DeserializationException::class, + ExceededSizeException::class, + CommitmentNotFoundException::class, + UrlException::class, + ) + fun getTimestamp(commitment: ByteArray): Timestamp +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendarAsyncSubmit.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendarAsyncSubmit.kt new file mode 100644 index 0000000000..b80e2238d7 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendarAsyncSubmit.kt @@ -0,0 +1,29 @@ +/** + * 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.nip03Timestamp.ots + +import java.util.Optional +import java.util.concurrent.Callable + +interface ICalendarAsyncSubmit : Callable> { + @Throws(Exception::class) + override fun call(): Optional +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Merkle.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Merkle.kt new file mode 100644 index 0000000000..4d96a22d7d --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Merkle.kt @@ -0,0 +1,118 @@ +/** + * 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.nip03Timestamp.ots + +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpPrepend +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256 + +/** + * Utility functions for merkle trees + */ +object Merkle { + /** + * Concatenate left and right, then perform a unary operation on them left and right can be either timestamps or bytes. + * Appropriate intermediary append/prepend operations will be created as needed for left and right. + * + * @param left the left timestamp parameter + * @param right the right timestamp parameter + * @return the concatenation of left and right + */ + fun catThenUnaryOp( + left: Timestamp, + right: Timestamp, + ): Timestamp? { + // rightPrependStamp = right.ops.add(OpPrepend(left.msg)) + val rightPrependStamp = right.add(OpPrepend(left.digest)) + + // Left and right should produce the same thing, so we can set the timestamp of the left to the right. + // left.ops[OpAppend(right.msg)] = right_prepend_stamp + // leftAppendStamp = left.ops.add(OpAppend(right.msg)) + // Timestamp leftPrependStamp = left.add(new OpAppend(right.msg)); + left.ops.put(OpAppend(right.digest), rightPrependStamp!!) + + // return rightPrependStamp.ops.add(unaryOpCls()) + val res = rightPrependStamp.add(OpSHA256()) + return res + } + + fun catSha256( + left: Timestamp, + right: Timestamp, + ): Timestamp = catThenUnaryOp(left, right)!! + + fun catSha256d( + left: Timestamp, + right: Timestamp, + ): Timestamp? { + val sha256Timestamp = catSha256(left, right) + // res = sha256Timestamp.ops.add(OpSHA256()); + val res = sha256Timestamp.add(OpSHA256()) + return res + } + + /** + * Merkelize a set of timestamps. + * A merkle tree of all the timestamps is built in-place using binop() to + * timestamp each pair of timestamps. The exact algorithm used is structurally + * identical to a merkle-mountain-range, although leaf sums aren't committed. + * As this function is under the consensus-critical core, it's guaranteed that + * the algorithm will not be changed in the future. + * + * @param timestamps a list of timestamps + * @return the timestamp for the tip of the tree. + */ + fun makeMerkleTree(timestamps: MutableList): Timestamp? { + var stamps = timestamps + var prevStamp: Timestamp? = null + var exit = false + + while (!exit) { + if (stamps.isNotEmpty()) { + prevStamp = stamps[0] + } + + val subStamps: MutableList = stamps.subList(1, stamps.size) + val nextStamps: MutableList = ArrayList() + + for (stamp in subStamps) { + if (prevStamp == null) { + prevStamp = stamp + } else { + nextStamps.add(catSha256(prevStamp, stamp)) + prevStamp = null + } + } + + if (nextStamps.size == 0) { + exit = true + } else { + if (prevStamp != null) { + nextStamps.add(prevStamp) + } + + stamps = nextStamps + } + } + + return prevStamp + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/OpenTimestamps.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/OpenTimestamps.kt new file mode 100644 index 0000000000..c99f5c6fa0 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/OpenTimestamps.kt @@ -0,0 +1,457 @@ +/** + * 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.nip03Timestamp.ots + +import android.util.Log +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.arrayReverse +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.randBytes +import com.vitorpamplona.quartz.nip03Timestamp.ots.VerifyResult.Chains +import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation +import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.PendingAttestation +import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException +import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Calendar +import com.vitorpamplona.quartz.nip03Timestamp.ots.http.CalendarAsyncSubmit +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256 +import com.vitorpamplona.quartz.utils.Hex +import java.io.IOException +import java.nio.charset.StandardCharsets +import java.security.NoSuchAlgorithmException +import java.util.Optional +import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.Executors + +/** + * The main class for timestamp operations. + */ +class OpenTimestamps( + var explorer: BitcoinExplorer, + var calBuilder: CalendarBuilder?, +) { + /** + * Show information on a detached timestamp with verbose option. + * + * @param detachedTimestampFile The DetachedTimestampFile ots. + * @param verbose Show verbose output. + * @return the string representation of the timestamp. + */ + fun info( + detachedTimestampFile: DetachedTimestampFile?, + verbose: Boolean = false, + ): String { + if (detachedTimestampFile == null) { + return "No ots file" + } + + val fileHash = + detachedTimestampFile.timestamp.digest + .toHexKey() + .lowercase() + val hashOp = (detachedTimestampFile.fileHashOp as OpCrypto).tagName() + + val firstLine = "File $hashOp hash: $fileHash\n" + + return firstLine + "Timestamp:\n" + detachedTimestampFile.timestamp.strTree(0, verbose) + } + + /** + * Show information on a timestamp. + * + * @param timestamp The timestamp buffered. + * @return the string representation of the timestamp. + */ + fun info(timestamp: Timestamp?): String { + if (timestamp == null) { + return "No timestamp" + } + + val fileHash = timestamp.digest.toHexKey().lowercase() + val firstLine = "Hash: $fileHash\n" + + return firstLine + "Timestamp:\n" + timestamp.strTree(0) + } + + /** + * Create timestamp with the aid of a remote calendar. May be specified multiple times. + * + * @param fileTimestamp The timestamp to stamp. + * @param calendarsUrl The list of calendar urls. + * @param m The number of calendar to use. + * @return The plain array buffer of stamped. + * @throws IOException if fileTimestamp is not valid, or the stamp procedure fails. + */ + @Throws(IOException::class) + fun stamp( + fileTimestamp: DetachedTimestampFile?, + calendarsUrl: MutableList = mutableListOf(), + m: Int? = 0, + ): Timestamp { + val fileTimestamps: MutableList = ArrayList() + fileTimestamps.add(fileTimestamp!!) + + return stamp(fileTimestamps, calendarsUrl, m) + } + + /** + * Create timestamp with the aid of a remote calendar. May be specified multiple times. + * + * @param fileTimestamps The list of timestamp to stamp. + * @param calendarsUrl The list of calendar urls. + * @param m The number of calendar to use. + * @return The plain array buffer of stamped. + * @throws IOException if fileTimestamp is not valid, or the stamp procedure fails. + */ + @Throws(IOException::class) + fun stamp( + fileTimestamps: MutableList, + calendarsUrl: MutableList, + m: Int?, + ): Timestamp { + // Parse parameters + var calendarsUrl = calendarsUrl + var m = m + if (fileTimestamps.isEmpty()) { + throw IOException() + } + + if (calendarsUrl.isEmpty()) { + calendarsUrl = ArrayList() + calendarsUrl.add("https://alice.btc.calendar.opentimestamps.org") + calendarsUrl.add("https://bob.btc.calendar.opentimestamps.org") + calendarsUrl.add("https://finney.calendar.eternitywall.com") + } + + if (m == null || m <= 0) { + if (calendarsUrl.size == 0) { + m = 2 + } else if (calendarsUrl.size == 1) { + m = 1 + } else { + m = calendarsUrl.size + } + } + + if (m < 0 || m > calendarsUrl.size) { + Log.e( + "OpenTimestamp", + "m cannot be greater than available calendar neither less or equal 0", + ) + throw IOException() + } + + // Build markle tree + val merkleTip = makeMerkleTree(fileTimestamps) + + // Stamping + val resultTimestamp = create(merkleTip, calendarsUrl, m) + + // Result of timestamp serialization + if (fileTimestamps.size == 1) { + return fileTimestamps.get(0).timestamp + } else { + return merkleTip + } + } + + /** + * Create a timestamp + * + * @param timestamp The timestamp. + * @param calendarUrls List of calendar's to use. + * @param m Number of calendars to use. + * @return The created timestamp. + */ + private fun create( + timestamp: Timestamp, + calendarUrls: MutableList, + m: Int, + ): Timestamp { + val capacity = calendarUrls.size + val executor = Executors.newFixedThreadPool(4) + val queue = ArrayBlockingQueue>(capacity) + + // Submit to all public calendars + for (calendarUrl in calendarUrls) { + Log.i("OpenTimestamps", "Submitting to remote calendar $calendarUrl") + + try { + val task = CalendarAsyncSubmit(calendarUrl, timestamp.digest) + task.setQueue(queue) + executor.submit>(task) + } catch (e: Exception) { + e.printStackTrace() + } + } + + var count = 0 + + count = 0 + while (count < capacity && count < m) { + try { + val optionalStamp = queue.take() + + if (optionalStamp.isPresent()) { + try { + val time = optionalStamp.get() + timestamp.merge(time) + Log.i("Open", "" + timestamp.attestations.size) + } catch (e: Exception) { + e.printStackTrace() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + count++ + } + + if (count < m) { + Log.e( + "OpenTimestamp", + "Failed to create timestamp: requested " + m.toString() + " attestation" + (if (m > 1) "s" else "") + " but received only " + count.toString(), + ) + } + + // shut down the executor service now + executor.shutdown() + + return timestamp + } + + /** + * Make Merkle Tree of detached timestamps. + * + * @param fileTimestamps The list of DetachedTimestampFile. + * @return merkle tip timestamp. + */ + fun makeMerkleTree(fileTimestamps: MutableList): Timestamp { + val merkleRoots: MutableList = ArrayList() + + for (fileTimestamp in fileTimestamps) { + var bytesRandom16 = ByteArray(16) + + try { + bytesRandom16 = randBytes(16) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } + + val nonceAppendedStamp = fileTimestamp.timestamp.add(OpAppend(bytesRandom16)) + val merkleRoot = nonceAppendedStamp.add(OpSHA256()) + merkleRoots.add(merkleRoot) + } + + val merkleTip: Timestamp = Merkle.makeMerkleTree(merkleRoots)!! + return merkleTip + } + + /** + * Compare and verify a detached timestamp. + * + * @param ots The DetachedTimestampFile containing the proof to verify. + * @param diggest The hash of the stamped file, in bytes + * @return Hashmap of block heights and timestamps indexed by chain: timestamp in seconds from 1 January 1970. + * @throws Exception if the verification procedure fails. + */ + @Throws(Exception::class) + fun verify( + ots: DetachedTimestampFile, + diggest: ByteArray, + ): HashMap { + if (!ots.fileDigest().contentEquals(diggest)) { + Log.e("OpenTimestamp", "Expected digest " + Hex.encode(ots.fileDigest()!!).lowercase()) + Log.e("OpenTimestamp", "File does not match original!") + throw Exception("File does not match original!") + } + + return verify(ots.timestamp) + } + + /** + * Verify a timestamp. + * + * @param timestamp The timestamp. + * @return HashMap of block heights and timestamps indexed by chain: timestamp in seconds from 1 January 1970. + * @throws Exception if the verification procedure fails. + */ + @Throws(Exception::class) + fun verify(timestamp: Timestamp): HashMap { + val verifyResults = HashMap() + + for (item in timestamp.allAttestations().entries) { + val msg = item.key + val attestation: TimeAttestation? = item.value + var verifyResult: VerifyResult? = null + + try { + if (attestation is BitcoinBlockHeaderAttestation) { + val time = verify(attestation, msg) + val height = attestation.height + verifyResult = VerifyResult(time, height) + + val currentResults = verifyResults.get(Chains.BITCOIN) + + if (currentResults != null) { + if (verifyResult.height < currentResults.height) { + verifyResults.put(Chains.BITCOIN, verifyResult) + } + } else { + verifyResults.put(Chains.BITCOIN, verifyResult) + } + } + } catch (e: VerificationException) { + throw e + } catch (e: Exception) { + Log.e("OpenTimestamp", "Verification failed: " + e.message) + throw e + } + } + return verifyResults + } + + /** + * Verify an Bitcoin Block Header Attestation. + * if the node is not reachable or it fails, uses Lite-client verification. + * + * @param attestation The BitcoinBlockHeaderAttestation attestation. + * @param msg The digest to verify. + * @return The unix timestamp in seconds from 1 January 1970. + * @throws VerificationException if it doesn't check the merkle root of the block. + * @throws Exception if the verification procedure fails. + */ + @Throws(VerificationException::class, Exception::class) + fun verify( + attestation: BitcoinBlockHeaderAttestation, + msg: ByteArray, + ): Long? { + val height = attestation.height + val blockInfo: BlockHeader? + + try { + val blockHash = explorer.blockHash(height) + blockInfo = explorer.block(blockHash) + Log.i( + "OpenTimestamps", + "Lite-client verification, assuming block $blockHash is valid", + ) + } catch (e2: Exception) { + e2.printStackTrace() + throw e2 + } + + return attestation.verifyAgainstBlockheader(arrayReverse(msg), blockInfo) + } + + /** + * Upgrade a timestamp. + * + * @param detachedTimestamp The DetachedTimestampFile containing the proof to verify. + * @return a boolean representing if the timestamp has changed. + * @throws Exception if the upgrading procedure fails. + */ + @Throws(Exception::class) + fun upgrade(detachedTimestamp: DetachedTimestampFile): Boolean { + // Upgrade timestamp + val changed = upgrade(detachedTimestamp.timestamp) + return changed + } + + /** + * Attempt to upgrade an incomplete timestamp to make it verifiable. + * Note that this means if the timestamp that is already complete, False will be returned as nothing has changed. + * + * @param timestamp The timestamp to upgrade. + * @return a boolean representing if the timestamp has changed. + * @throws Exception if the upgrading procedure fails. + */ + @Throws(Exception::class) + fun upgrade(timestamp: Timestamp): Boolean { + // Check remote calendars for upgrades. + // This time we only check PendingAttestations - we can't be as agressive. + + var upgraded = false + val existingAttestations: MutableSet = timestamp.getAttestations() + + for (subStamp in timestamp.directlyVerified()) { + for (attestation in subStamp.attestations) { + if (attestation is PendingAttestation && !subStamp.isTimestampComplete) { + val calendarUrl = String(attestation.uri, StandardCharsets.UTF_8) + // var calendarUrl = calendarUrls[0]; + val commitment = subStamp.digest + + try { + val calendar = Calendar(calendarUrl) + val upgradedStamp = upgrade(subStamp, calendar, commitment, existingAttestations) + + try { + subStamp.merge(upgradedStamp) + } catch (e: Exception) { + e.printStackTrace() + } + + upgraded = true + } catch (e: Exception) { + Log.i("OpenTimestamps", e.message ?: "${e.javaClass.simpleName}") + } + } + } + } + + return upgraded + } + + @Throws(Exception::class) + private fun upgrade( + subStamp: Timestamp, + calendar: Calendar, + commitment: ByteArray, + existingAttestations: MutableSet, + ): Timestamp { + val upgradedStamp: Timestamp = + try { + calendar.getTimestamp(commitment) + } catch (e: Exception) { + Log.i("OpenTimestamps", "Calendar " + calendar.url + ": " + e.message) + throw e + } + + val attsFromRemote: MutableSet = upgradedStamp.getAttestations() + + if (attsFromRemote.isNotEmpty()) { + Log.i("OpenTimestamps", "Got 1 attestation(s) from " + calendar.url) + } + + // Set difference from remote attestations & existing attestations + val newAttestations = attsFromRemote.minus(existingAttestations) + + // changed & found_new_attestations + // foundNewAttestations = true; + // Log.i("OpenTimestamps", attsFromRemote.size + ' attestation(s) from ' + calendar.url); + + // Set union of existingAttestations & newAttestations + existingAttestations.addAll(newAttestations) + + return upgradedStamp + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/README.md b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/README.md similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/README.md rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/README.md diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamDeserializationContext.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamDeserializationContext.kt new file mode 100644 index 0000000000..35a56451db --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamDeserializationContext.kt @@ -0,0 +1,112 @@ +/** + * 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.nip03Timestamp.ots + +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException +import java.util.Arrays + +class StreamDeserializationContext( + var output: ByteArray, +) { + var counter: Int = 0 + + fun read(l: Int): ByteArray { + var l = l + if (this.counter == this.output.size) { + return byteArrayOf() + } + + if (l + this.counter > this.output.size) { + l = this.output.size - this.counter + } + + // const uint8Array = new Uint8Array(this.buffer,this.counter,l); + val uint8Array = Arrays.copyOfRange(this.output, this.counter, this.counter + l) + this.counter += l + + return uint8Array + } + + fun readBool(): Boolean { + val b = this.read(1)[0] + + if (b.toInt() == 0xff) { + return true + } else if (b.toInt() == 0x00) { + return false + } + + return false + } + + fun readVaruint(): Int { + var value = 0 + var shift: Byte = 0 + var b: Byte + + do { + b = this.read(1)[0] + value = value or ((b.toInt() and 127) shl shift.toInt()) + shift = (shift + 7).toByte() + } while ((b.toInt() and 128) != 0) + + return value + } + + @Throws(DeserializationException::class) + fun readBytes(expectedLength: Int): ByteArray { + if (expectedLength == 0) { + return this.readVarbytes(1024, 0) + } + + return this.read(expectedLength) + } + + @JvmOverloads + @Throws(DeserializationException::class) + fun readVarbytes( + maxLen: Int, + minLen: Int = 0, + ): ByteArray { + val l = this.readVaruint() + + if (l > maxLen) { + throw DeserializationException("varbytes max length exceeded;") + } else if (l < minLen) { + throw DeserializationException("varbytes min length not met;") + } + + return this.read(l) + } + + fun assertMagic(expectedMagic: ByteArray): Boolean { + val actualMagic = this.read(expectedMagic.size) + + return expectedMagic.contentEquals(actualMagic) + } + + fun assertEof(): Boolean { + val excess = this.read(1) + return excess.isNotEmpty() + } + + override fun toString(): String = this.output.contentToString() +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamSerializationContext.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamSerializationContext.kt new file mode 100644 index 0000000000..f6e3daca0c --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamSerializationContext.kt @@ -0,0 +1,84 @@ +/** + * 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.nip03Timestamp.ots + +class StreamSerializationContext { + val buffer: MutableList = ArrayList() + + val output: ByteArray + get() { + val bytes = ByteArray(this.buffer.size) + + for (i in this.buffer.indices) { + bytes[i] = this.buffer.get(i) + } + + return bytes + } + + fun writeBool(value: Boolean) { + if (value) { + this.writeByte(0xff.toByte()) + } else { + this.writeByte(0x00.toByte()) + } + } + + fun writeVaruint(value: Int) { + var value = value + if ((value) == 0) { + this.writeByte(0x00.toByte()) + } else { + while (value != 0) { + var b = ((value and 0xff) and 127).toByte() + + if ((value) > 127) { + b = (b.toInt() or 128).toByte() + } + + this.writeByte(b) + + if ((value) <= 127) { + break + } + + value = value shr 7 + } + } + } + + fun writeByte(value: Byte) { + this.buffer.add(value) + } + + fun writeBytes(value: ByteArray) { + for (b in value) { + this.writeByte(b) + } + } + + fun writeVarbytes(value: ByteArray) { + this.writeVaruint(value.size) + this.writeBytes(value) + } + + override fun toString(): String = this.output.contentToString() +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Timestamp.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Timestamp.kt new file mode 100644 index 0000000000..dd72fc21d5 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/Timestamp.kt @@ -0,0 +1,662 @@ +/** + * 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.nip03Timestamp.ots + +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.arrayReverse +import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation +import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op +import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpBinary +import com.vitorpamplona.quartz.utils.Hex.encode +import java.util.Collections +import java.util.TreeMap + +/** + * Proof that one or more attestations commit to a message. + * The proof is in the form of a tree, with each node being a message, and the + * edges being operations acting on those messages. The leafs of the tree are + * attestations that attest to the time that messages in the tree existed prior. + */ +class Timestamp( + val digest: ByteArray, +) { + val attestations: MutableList = ArrayList() + val ops: HashMap = HashMap() + + /** + * Create a Serialize object. + * + * @return The byte array of the serialized timestamp + */ + fun serialize(): ByteArray { + val ctx = StreamSerializationContext() + serialize(ctx) + + return ctx.output + } + + /** + * Create a Serialize object. + * + * @param ctx - The stream serialization context. + */ + fun serialize(ctx: StreamSerializationContext) { + val sortedAttestations = this.attestations // TODO: Hm, this is just a reference copy... + Collections.sort(sortedAttestations) + + if (sortedAttestations.size > 1) { + for (i in 0..1 attestations : + // Search first BitcoinBlockHeaderAttestation + var minAttestation: TimeAttestation? = null + + for (entry in this.ops.entries) { + val timestamp: Timestamp = entry.value + + // Op op = entry.getKey(); + for (attestation in timestamp.getAttestations()) { + if (attestation is BitcoinBlockHeaderAttestation) { + if (minAttestation == null) { + minAttestation = attestation + } else { + if (minAttestation is BitcoinBlockHeaderAttestation && (minAttestation.height > attestation.height)) { + minAttestation = attestation + } + } + } + } + } + + // Only pending attestations : return the first + if (minAttestation == null) { + return allAttestations.values.iterator().next() + } + + // Remove attestation if not min attestation + var shrinked = false + + val it = this.ops.entries.iterator() + while (it.hasNext()) { + val entry = it.next() + val timestamp = entry.value + val op = entry.key + val attestations = timestamp.getAttestations() + + if (attestations.isNotEmpty() && attestations.contains(minAttestation) && !shrinked) { + timestamp.shrink() + shrinked = true + } else { + it.remove() + } + } + + return minAttestation + } + + /** + * Return as memory hierarchical object. + * + * @param indent - Initial hierarchical indention. + * @return The output string. + */ + fun toString(indent: Int): String { + val builder = StringBuilder() + builder.append(indention(indent) + "msg: " + encode(this.digest) + "\n") + builder.append(indention(indent) + this.attestations.size + " attestations: \n") + var i = 0 + + for (attestation in this.attestations) { + builder.append(indention(indent) + "[" + i + "] " + attestation.toString() + "\n") + i++ + } + + i = 0 + builder.append(indention(indent) + this.ops.size + " ops: \n") + + for (entry in this.ops.entries) { + val stamp: Timestamp = entry.value + val op: Op = entry.key + + builder.append(indention(indent) + "[" + i + "] op: " + op.toString() + "\n") + builder.append(indention(indent) + "[" + i + "] timestamp: \n") + builder.append(stamp.toString(indent + 1)) + i++ + } + + builder.append('\n') + + return builder.toString() + } + + private fun strResult( + verbosity: Boolean, + parameter: ByteArray?, + result: ByteArray?, + ): String { + var rr = "" + + if (verbosity == true && result != null) { + rr += " == " + val resultHex = result.toHexKey() + + if (parameter == null) { + rr += resultHex + } else { + val parameterHex = parameter.toHexKey() + + try { + val index = resultHex.indexOf(parameterHex) + val parameterHexHighlight = ANSI_BOLD + parameterHex + ANSI_ENDC + + if (index == 0) { + rr += parameterHexHighlight + + resultHex.substring( + index + parameterHex.length, + resultHex.length, + ) + } else { + rr += resultHex.substring(0, index) + parameterHexHighlight + } + } catch (err: Exception) { + rr += resultHex + } + } + } + + return rr + } + + /** + * Return as tree hierarchical object. + * + * @param indent - Initial hierarchical indention. + * @param verbosity - Verbose option. + * @return The output string. + */ + @JvmOverloads + fun strTree( + indent: Int, + verbosity: Boolean = false, + ): String { + val builder = StringBuilder() + + if (!this.attestations.isEmpty()) { + for (attestation in this.attestations) { + builder.append(indention(indent)) + builder.append( + "verify " + attestation.toString() + + strResult( + verbosity, + this.digest, + null, + ) + "\n", + ) + + if (attestation is BitcoinBlockHeaderAttestation) { + val tx = arrayReverse(this.digest).toHexKey() + builder.append(indention(indent) + "# Bitcoin block merkle root " + tx.lowercase() + "\n") + } + } + } + + if (this.ops.size > 1) { + val ordered = TreeMap(this.ops) + + for (entry in ordered.entries) { + val timestamp: Timestamp = entry.value!! + val op: Op = entry.key!! + + val curRes = op.call(this.digest) + var curPar: ByteArray? = null + + if (op is OpBinary) { + curPar = op.arg + } + + builder.append( + indention(indent) + " -> " + op.toString().lowercase() + + strResult( + verbosity, + curPar, + curRes, + ).lowercase() + "\n", + ) + builder.append(timestamp.strTree(indent + 1, verbosity)) + } + } else if (this.ops.size > 0) { + // output += com.eternitywall.ots.Timestamp.indention(indent); + for (entry in this.ops.entries) { + val timestamp: Timestamp = entry.value + val op: Op = entry.key + + val curRes = op.call(this.digest) + var curPar: ByteArray? = null + + if (op is OpBinary) { + curPar = op.arg + } + + builder.append( + indention(indent) + op.toString().lowercase() + + strResult( + verbosity, + curPar, + curRes, + ).lowercase() + "\n", + ) + builder.append(timestamp.strTree(indent, verbosity)) + } + } + + return builder.toString() + } + + /** + * Returns a list of all sub timestamps with attestations. + * + * @return List of all sub timestamps with attestations. + */ + fun directlyVerified(): MutableList { + if (!this.attestations.isEmpty()) { + val list: MutableList = ArrayList() + list.add(this) + return list + } + + val list: MutableList = ArrayList() + + for (entry in this.ops.entries) { + val ts: Timestamp = entry.value + + // Op op = entry.getKey(); + val result = ts.directlyVerified() + list.addAll(result) + } + + return list + } + + /** + * Returns a set of all Attestations. + * + * @return Set of all timestamp attestations. + */ + fun getAttestations(): MutableSet { + val set: MutableSet = HashSet() + + for (item in this.allAttestations().entries) { + // byte[] msg = item.getKey(); + val attestation = item.value + set.add(attestation) + } + + return set + } + + val isTimestampComplete: Boolean + /** + * Determine if timestamp is complete and can be verified. + * + * @return True if the timestamp is complete, False otherwise. + */ + get() { + for (item in this.allAttestations().entries) { + // byte[] msg = item.getKey(); + val attestation = item.value + + if (attestation is BitcoinBlockHeaderAttestation) { + return true + } + } + + return false + } + + /** + * Iterate over all attestations recursively + * + * @return Returns iterable of (msg, attestation) + */ + fun allAttestations(): HashMap { + val map = HashMap() + + for (attestation in this.attestations) { + map.put(this.digest, attestation) + } + + for (entry in this.ops.entries) { + val ts: Timestamp = entry.value + + // Op op = entry.getKey(); + val subMap = ts.allAttestations() + + for (item in subMap.entries) { + val msg = item.key + val attestation = item.value + map.put(msg, attestation) + } + } + + return map + } + + /** + * Iterate over all tips recursively + * + * @return Returns iterable of (msg, attestation) + */ + fun allTips(): MutableSet { + val set: MutableSet = HashSet() + + if (this.ops.size == 0) { + set.add(this.digest) + } + + for (entry in this.ops.entries) { + val ts: Timestamp = entry.value!! + + // Op op = entry.getKey(); + val subSet = ts.allTips() + + for (msg in subSet) { + set.add(msg) + } + } + + return set + } + + /** + * Compare timestamps. + * + * @param timestamp the timestamp to compare with + * @return Returns true if timestamps are equals + */ + fun equals(timestamp: Timestamp): Boolean { + if (this.digest.contentEquals(timestamp.digest) == false) { + return false + } + + // Check attestations + if (this.attestations.size != timestamp.attestations.size) { + return false + } + + for (i in this.attestations.indices) { + val ta1 = this.attestations.get(i) + val ta2 = timestamp.attestations.get(i) + + if (!(ta1 == ta2)) { + return false + } + } + + // Check operations + if (this.ops.size != timestamp.ops.size) { + return false + } + + // Order list of operations + val list1 = sortToList(this.ops.entries) + val list2 = sortToList(timestamp.ops.entries) + + for (i in list1.indices) { + val op1 = list1.get(i).key + val op2: Op? = list2.get(i).key + + if (op1 != op2) { + return false + } + + val t1 = list1.get(i).value + val t2 = list2.get(i).value + + if (!t1.equals(t2)) { + return false + } + } + + return true + } + + /** + * Add Op to current timestamp and return the sub stamp + * + * @param op - The operation to insert + * @return Returns the sub timestamp + */ + fun add(op: Op): Timestamp { + // nonce_appended_stamp = timestamp.ops.add(com.vitorpamplona.quartz.ots.op.OpAppend(os.urandom(16))) + // Op opAppend = new OpAppend(bytes); + + val existing = this.ops.get(op) + + if (existing != null) { + return existing + } + + val stamp = Timestamp(op.call(this.digest)) + this.ops.put(op, stamp) + + return stamp + } + + /** + * Retrieve a sorted list of all map entries. + * + * @param setEntries - The entries set of ops hashmap + * @return Returns the sorted list of map entries + */ + fun sortToList(setEntries: MutableSet>): MutableList> { + val entries: MutableList> = ArrayList(setEntries) + Collections.sort( + entries, + Comparator> { a, b -> a.key.compareTo(b.key) }, + ) + + return entries + } + + companion object { + val ANSI_HEADER = "\u001B[95m" + val ANSI_OKBLUE = "\u001B[94m" + val ANSI_OKGREEN = "\u001B[92m" + val ANSI_WARNING = "\u001B[93m" + val ANSI_FAIL = "\u001B[91m" + val ANSI_ENDC = "\u001B[0m" + val ANSI_BOLD = "\u001B[1m" + val ANSI_UNDERLINE = "\u001B[4m" + + /** + * Deserialize a Timestamp. + * + * @param ots - The serialized byte array. + * @param initialMsg - The initial message. + * @return The deserialized Timestamp. + */ + @Throws(DeserializationException::class) + fun deserialize( + ots: ByteArray, + initialMsg: ByteArray, + ): Timestamp { + val ctx = StreamDeserializationContext(ots) + + return deserialize(ctx, initialMsg) + } + + /** + * Deserialize a Timestamp. + * Because the serialization format doesn't include the message that the + * timestamp operates on, you have to provide it so that the correct + * operation results can be calculated. + * The message you provide is assumed to be correct; if it causes a op to + * raise MsgValueError when the results are being calculated (done + * immediately, not lazily) DeserializationError is raised instead. + * + * @param ctx - The stream deserialization context. + * @param initialMsg - The initial message. + * @return The deserialized Timestamp. + */ + @JvmStatic + @Throws(DeserializationException::class) + fun deserialize( + ctx: StreamDeserializationContext, + initialMsg: ByteArray, + ): Timestamp { + val self = Timestamp(initialMsg) + var tag = ctx.readBytes(1)[0] + + while ((tag.toInt() and 0xff) == 0xff) { + val current = ctx.readBytes(1)[0] + doTagOrAttestation(self, ctx, current, initialMsg) + tag = ctx.readBytes(1)[0] + } + + doTagOrAttestation(self, ctx, tag, initialMsg) + + return self + } + + @Throws(DeserializationException::class) + private fun doTagOrAttestation( + self: Timestamp, + ctx: StreamDeserializationContext, + tag: Byte, + initialMsg: ByteArray, + ) { + if ((tag.toInt() and 0xff) == 0x00) { + val attestation = TimeAttestation.deserialize(ctx) + self.attestations.add(attestation) + } else { + val op = Op.deserializeFromTag(ctx, tag) + val result = op!!.call(initialMsg) + + val stamp: Timestamp = deserialize(ctx, result) + self.ops.put(op, stamp) + } + } + + /** + * Indention function for printing tree. + * + * @param pos - Initial hierarchical indention. + * @return The output space string. + */ + fun indention(pos: Int): String { + val builder = StringBuilder() + + for (i in 0.. This describes my type parameter + * @return the first value that is not null. If all objects are null, then it returns null. + */ + @Deprecated("Not used by Java OpenTimestamps itself, and doesn't offer much useful functionality.") + fun coalesce(vararg items: T?): T? { + for (i in items) { + if (i != null) { + return i + } + } + + return null + } + + /** + * Returns a copy of the byte array argument, or null if the byte array argument is null. + * + * @param data the array of bytes to copy + * @return the copied byte array + */ + @JvmStatic + fun arraysCopy(data: ByteArray): ByteArray { + val copy = ByteArray(data.size) + System.arraycopy(data, 0, copy, 0, data.size) + + return copy + } + + /** + * Returns a byte array which is the result of concatenating the two passed in byte arrays. + * None of the passed in arrays may be null. + * + * @param array1 the first array of bytes + * @param array2 the second array of bytes + * @return a copy of array1 and array2 concatenated together + */ + @JvmStatic + fun arraysConcat( + array1: ByteArray, + array2: ByteArray, + ): ByteArray { + val array1and2 = ByteArray(array1.size + array2.size) + System.arraycopy(array1, 0, array1and2, 0, array1.size) + System.arraycopy(array2, 0, array1and2, array1.size, array2.size) + + return array1and2 + } + + /** + * Returns a given length array of random bytes. + * + * @param length the requested length of the byte array + * @return a given length array of random bytes + * @throws NoSuchAlgorithmException for Java 8 implementations + */ + @JvmStatic + @Throws(NoSuchAlgorithmException::class) + fun randBytes(length: Int): ByteArray { + // Java 6 & 7: + val random = SecureRandom() + val bytes = ByteArray(length) + random.nextBytes(bytes) + + // Java 8 (even more secure): + // SecureRandom.getInstanceStrong().nextBytes(bytes); + return bytes + } + + /** + * Returns a reversed copy of the passed in byte array. + * + * @param array the byte array to reverse + * @return a copy of the byte array, reversed + */ + @JvmStatic + fun arrayReverse(array: ByteArray): ByteArray { + val reversedArray = ByteArray(array.size) + + var i = array.size - 1 + var j = 0 + while (i >= 0) { + reversedArray[j] = array[i] + i-- + j++ + } + + return reversedArray + } + + /** + * Compares two byte arrays. + * + * @param left the left byte array to compare with + * @param right the right byte array to compare with + * @return 0 if the arrays are identical, negative if left < right, positive if left > right + */ + @JvmStatic + fun compare( + left: ByteArray, + right: ByteArray, + ): Int { + var i = 0 + var j = 0 + while (i < left.size && j < right.size) { + val a = (left[i].toInt() and 0xff) + val b = (right[j].toInt() and 0xff) + + if (a != b) { + return a - b + } + i++ + j++ + } + + return left.size - right.size + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/VerifyResult.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/VerifyResult.kt new file mode 100644 index 0000000000..bdd5cb1a38 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/VerifyResult.kt @@ -0,0 +1,66 @@ +/** + * 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.nip03Timestamp.ots + +import android.annotation.SuppressLint +import java.text.DateFormatSymbols +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + +/** + * Class that lets us compare, sort, store and print timestamps. + */ +class VerifyResult( + val timestamp: Long?, + val height: Int, +) : Comparable { + enum class Chains { BITCOIN } + + /** + * Returns, if existing, a string representation describing the existence of a block attest + */ + override fun toString(): String { + if (height == 0 || timestamp == null) { + return "" + } + + val pattern = "yyyy-MM-dd z" + val locale = Locale("en", "UK") + val dateFormatSymbols = DateFormatSymbols(locale) + + @SuppressLint("SimpleDateFormat") + val simpleDateFormat = + SimpleDateFormat(pattern, dateFormatSymbols) + val string = simpleDateFormat.format(Date(timestamp!! * 1000)) + + return "block $height attests data existed as of $string" + } + + override fun compareTo(other: VerifyResult): Int = this.height - other.height + + override fun equals(other: Any?): Boolean { + val vr = other as VerifyResult + return this.timestamp == vr.timestamp && this.height == vr.height + } + + override fun hashCode(): Int = (((this.timestamp) as Long).toInt()) xor this.height +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/BitcoinBlockHeaderAttestation.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/BitcoinBlockHeaderAttestation.kt new file mode 100644 index 0000000000..354418cd90 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/BitcoinBlockHeaderAttestation.kt @@ -0,0 +1,128 @@ +/** + * 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.nip03Timestamp.ots.attestation + +import com.vitorpamplona.quartz.nip01Core.core.hexToByteArray +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException + +/** + * Bitcoin Block Header Attestation. + * The commitment digest will be the merkleroot of the blockheader. + * The block height is recorded so that looking up the correct block header in + * an external block header database doesn't require every header to be stored + * locally (33MB and counting). (remember that a memory-constrained local + * client can save an MMR that commits to all blocks, and use an external service to fill + * in pruned details). + * Otherwise no additional redundant data about the block header is recorded. + * This is very intentional: since the attestation contains (nearly) the + * absolute bare minimum amount of data, we encourage implementations to do + * the correct thing and get the block header from a by-height index, check + * that the merkleroots match, and then calculate the time from the header + * information. Providing more data would encourage implementations to cheat. + * Remember that the only thing that would invalidate the block height is a + * reorg, but in the event of a reorg the merkleroot will be invalid anyway, + * so there's no point to recording data in the attestation like the header + * itself. At best that would just give us extra confirmation that a reorg + * made the attestation invalid; reorgs deep enough to invalidate timestamps are + * exceptionally rare events anyway, so better to just tell the user the timestamp + * can't be verified rather than add almost-never tested code to handle that case + * more gracefully. + * + * @see TimeAttestation + */ +class BitcoinBlockHeaderAttestation( + val height: Int, +) : TimeAttestation() { + override fun tag(): ByteArray = TAG + + override fun serializePayload(ctx: StreamSerializationContext) { + ctx.writeVaruint(this.height) + } + + override fun toString(): String = "BitcoinBlockHeaderAttestation(" + this.height + ")" + + override fun compareTo(other: TimeAttestation): Int { + val ob = other as BitcoinBlockHeaderAttestation + + return this.height - ob.height + } + + override fun equals(other: Any?): Boolean { + if (other !is BitcoinBlockHeaderAttestation) { + return false + } + + if (!this.tag().contentEquals(other.tag())) { + return false + } + + if (this.height != other.height) { + return false + } + + return true + } + + override fun hashCode(): Int = this.tag().contentHashCode() xor this.height + + /** + * Verify attestation against a Bitcoin block header. + * @param digest the digest + * @param block the Bitcoin block header + * @return the block time on success; raises VerificationError on failure. + * @throws VerificationException verification exception + */ + @Throws(VerificationException::class) + fun verifyAgainstBlockheader( + digest: ByteArray, + block: BlockHeader, + ): Long? { + if (digest.size != 32) { + throw VerificationException("Expected digest with length 32 bytes; got " + digest.size + " bytes") + } else if (!digest.contentEquals(block.merkleRoot.hexToByteArray())) { + throw VerificationException("Digest ${digest.toHexKey()} does not match merkleroot ${block.merkleRoot}") + } + + return block.getTime() + } + + companion object { + val TAG: ByteArray = + byteArrayOf( + 0x05.toByte(), + 0x88.toByte(), + 0x96.toByte(), + 0x0d.toByte(), + 0x73.toByte(), + 0xd7.toByte(), + 0x19.toByte(), + 0x01.toByte(), + ) + + val chain: String = "bitcoin" + + fun deserialize(ctxPayload: StreamDeserializationContext): BitcoinBlockHeaderAttestation = BitcoinBlockHeaderAttestation(ctxPayload.readVaruint()) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/PendingAttestation.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/PendingAttestation.kt new file mode 100644 index 0000000000..0644380fdb --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/PendingAttestation.kt @@ -0,0 +1,141 @@ +/** + * 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.nip03Timestamp.ots.attestation + +import android.util.Log +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.compare +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException +import java.nio.charset.StandardCharsets + +/** + * Pending attestations. + * Commitment has been recorded in a remote calendar for future attestation, + * and we have a URI to find a more complete timestamp in the future. + * Nothing other than the URI is recorded, nor is there provision made to add + * extra metadata (other than the URI) in future upgrades. The rational here + * is that remote calendars promise to keep commitments indefinitely, so from + * the moment they are created it should be possible to find the commitment in + * the calendar. Thus if you're not satisfied with the local verifiability of + * a timestamp, the correct thing to do is just ask the remote calendar if + * additional attestations are available and/or when they'll be available. + * While we could additional metadata like what types of attestations the + * remote calendar expects to be able to provide in the future, that metadata + * can easily change in the future too. Given that we don't expect timestamps + * to normally have more than a small number of remote calendar attestations, + * it'd be better to have verifiers get the most recent status of such + * information (possibly with appropriate negative response caching). + * + * @see TimeAttestation + */ +class PendingAttestation( + val uri: ByteArray, +) : TimeAttestation() { + override fun tag(): ByteArray = TAG + + override fun serializePayload(ctx: StreamSerializationContext) { + ctx.writeVarbytes(this.uri) + } + + override fun toString(): String = "PendingAttestation(\'" + String(this.uri, StandardCharsets.UTF_8) + "\')" + + override fun compareTo(other: TimeAttestation): Int { + val opa = other as PendingAttestation + + return compare(this.uri, opa.uri) + } + + override fun equals(other: Any?): Boolean { + if (other !is PendingAttestation) { + return false + } + + if (!this.tag().contentEquals(other.tag())) { + return false + } + + if (!this.uri.contentEquals(other.uri)) { + return false + } + + return true + } + + override fun hashCode(): Int = this.tag().contentHashCode() xor this.uri.contentHashCode() + + companion object { + val TAG: ByteArray = + byteArrayOf( + 0x83.toByte(), + 0xdf.toByte(), + 0xe3.toByte(), + 0x0d.toByte(), + 0x2e.toByte(), + 0xf9.toByte(), + 0x0c.toByte(), + 0x8e.toByte(), + ) + + const val MAX_URI_LENGTH: Int = 1000 + + const val ALLOWED_URI_CHARS: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._/:" + + fun checkUri(uri: ByteArray): Boolean { + if (uri.size > MAX_URI_LENGTH) { + System.err.print("URI exceeds maximum length") + + return false + } + + for (i in uri.indices) { + val c = String.format("%c", uri[i])[0] + + if (ALLOWED_URI_CHARS.indexOf(c) < 0) { + Log.e("OpenTimestamp", "URI contains invalid character ") + + return false + } + } + + return true + } + + @JvmStatic + @Throws(DeserializationException::class) + fun deserialize(ctxPayload: StreamDeserializationContext): PendingAttestation { + val utf8Uri: ByteArray + try { + utf8Uri = ctxPayload.readVarbytes(MAX_URI_LENGTH) + } catch (_: DeserializationException) { + Log.e("OpenTimestamp", "URI too long and thus invalid: ") + throw DeserializationException("Invalid URI: ") + } + + if (!checkUri(utf8Uri)) { + Log.e("OpenTimestamp", "Invalid URI: ") + throw DeserializationException("Invalid URI: ") + } + + return PendingAttestation(utf8Uri) + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/TimeAttestation.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/TimeAttestation.kt new file mode 100644 index 0000000000..f3a40286fd --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/TimeAttestation.kt @@ -0,0 +1,93 @@ +/** + * 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.nip03Timestamp.ots.attestation + +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException + +/** + * Class representing Timestamp signature verification + */ +abstract class TimeAttestation : Comparable { + open fun tag(): ByteArray = byteArrayOf() + + /** + * Serialize a a general Time Attestation to the specific subclass Attestation. + * + * @param ctx The output stream serialization context. + */ + fun serialize(ctx: StreamSerializationContext) { + ctx.writeBytes(this.tag()) + val ctxPayload = StreamSerializationContext() + serializePayload(ctxPayload) + ctx.writeVarbytes(ctxPayload.output) + } + + open fun serializePayload(ctx: StreamSerializationContext) { + // TODO: Is this intentional? + } + + override fun compareTo(other: TimeAttestation): Int { + val deltaTag = Utils.compare(this.tag(), other.tag()) + + return if (deltaTag == 0) { + this.compareTo(other) + } else { + deltaTag + } + } + + companion object { + const val TAG_SIZE: Int = 8 + + const val MAX_PAYLOAD_SIZE: Int = 8192 + + /** + * Deserialize a general Time Attestation to the specific subclass Attestation. + * + * @param ctx The stream deserialization context. + * @return The specific subclass Attestation. + */ + @Throws(DeserializationException::class) + fun deserialize(ctx: StreamDeserializationContext): TimeAttestation { + // console.log('attestation deserialize'); + + val tag = ctx.readBytes(TAG_SIZE) + + // console.log('tag: ', com.vitorpamplona.quartz.ots.Utils.bytesToHex(tag)); + val serializedAttestation = ctx.readVarbytes(MAX_PAYLOAD_SIZE) + + // console.log('serializedAttestation: ', com.vitorpamplona.quartz.ots.Utils.bytesToHex(serializedAttestation)); + val ctxPayload = StreamDeserializationContext(serializedAttestation) + + // eslint no-use-before-define: ["error", { "classes": false }] + if (tag.contentEquals(PendingAttestation.TAG)) { + return PendingAttestation.deserialize(ctxPayload) + } else if (tag.contentEquals(BitcoinBlockHeaderAttestation.TAG)) { + return BitcoinBlockHeaderAttestation.deserialize(ctxPayload) + } + + return UnknownAttestation(tag, serializedAttestation) + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/UnknownAttestation.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/UnknownAttestation.kt new file mode 100644 index 0000000000..a128070d2c --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/UnknownAttestation.kt @@ -0,0 +1,82 @@ +/** + * 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.nip03Timestamp.ots.attestation + +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException + +/** + * Placeholder for attestations that don't support + * + * @see TimeAttestation + */ +class UnknownAttestation internal constructor( + val tag: ByteArray, + val payload: ByteArray, +) : TimeAttestation() { + override fun tag(): ByteArray = tag + + override fun serializePayload(ctx: StreamSerializationContext) { + ctx.writeBytes(this.payload) + } + + override fun toString(): String = "UnknownAttestation " + this.tag().toHexKey() + ' ' + this.payload.toHexKey() + + override fun compareTo(other: TimeAttestation): Int { + val ota = other as UnknownAttestation + + return Utils.compare(this.payload, ota.payload) + } + + override fun equals(other: Any?): Boolean { + if (other !is UnknownAttestation) { + return false + } + + if (!this.tag().contentEquals(other.tag())) { + return false + } + + if (!this.payload.contentEquals(other.payload)) { + return false + } + + return true + } + + override fun hashCode(): Int = this.tag().contentHashCode() xor this.payload.contentHashCode() + + companion object { + val TAG: ByteArray = byteArrayOf() + + @Throws(DeserializationException::class) + fun deserialize( + ctxPayload: StreamDeserializationContext, + tag: ByteArray, + ): UnknownAttestation { + val payload = ctxPayload.readVarbytes(MAX_PAYLOAD_SIZE) + return UnknownAttestation(tag, payload) + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Digest.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Digest.kt new file mode 100644 index 0000000000..59536c1da2 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Digest.kt @@ -0,0 +1,79 @@ +/** + * 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.nip03Timestamp.ots.crypto + +/** + * Message digest interface + */ +interface Digest { + /** + * Return the algorithm name + * + * @return the algorithm name + */ + fun getAlgorithmName(): String + + /** + * Return the size, in bytes, of the digest produced by this message digest. + * + * @return the size, in bytes, of the digest produced by this message digest. + */ + fun getDigestSize(): Int + + /** + * Update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + fun update(`in`: Byte) + + /** + * Update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param inOff the offset into the byte array where the data starts. + * @param len the length of the data. + */ + fun update( + `in`: ByteArray, + inOff: Int, + len: Int, + ) + + /** + * Close the digest, producing the final digest value. The doFinal + * call also resets the digest. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + * @return something + * @see .reset + */ + fun doFinal( + out: ByteArray, + outOff: Int, + ): Int + + /** + * Reset the digest back to it's initial state. + */ + fun reset() +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/ExtendedDigest.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/ExtendedDigest.kt new file mode 100644 index 0000000000..74b030980b --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/ExtendedDigest.kt @@ -0,0 +1,31 @@ +/** + * 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.nip03Timestamp.ots.crypto + +interface ExtendedDigest : Digest { + /** + * Return the size in bytes of the internal buffer the digest applies it's compression + * function to. + * + * @return byte length of the digests internal buffer. + */ + fun getByteLength(): Int +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/GeneralDigest.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/GeneralDigest.kt new file mode 100644 index 0000000000..4f52a34340 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/GeneralDigest.kt @@ -0,0 +1,174 @@ +/** + * 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.nip03Timestamp.ots.crypto + +import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.Pack.bigEndianToInt +import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.Pack.bigEndianToLong +import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.Pack.intToBigEndian +import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.Pack.longToBigEndian +import kotlin.math.max + +/** + * Base implementation of MD4 family style digest as outlined in + * "Handbook of Applied Cryptography", pages 344 - 347. + */ +abstract class GeneralDigest : + ExtendedDigest, + Memoable { + private val xBuf = ByteArray(4) + private var xBufOff = 0 + + private var byteCount: Long = 0 + + /** + * Standard constructor + */ + protected constructor() { + xBufOff = 0 + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + * + * @param t the GeneralDigest + */ + protected constructor(t: GeneralDigest) { + copyIn(t) + } + + protected constructor(encodedState: ByteArray) { + System.arraycopy(encodedState, 0, xBuf, 0, xBuf.size) + xBufOff = bigEndianToInt(encodedState, 4) + byteCount = bigEndianToLong(encodedState, 8) + } + + protected fun copyIn(t: GeneralDigest) { + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.size) + + xBufOff = t.xBufOff + byteCount = t.byteCount + } + + override fun update(`in`: Byte) { + xBuf[xBufOff++] = `in` + + if (xBufOff == xBuf.size) { + processWord(xBuf, 0) + xBufOff = 0 + } + + byteCount++ + } + + override fun update( + `in`: ByteArray, + inOff: Int, + len: Int, + ) { + var len = len + len = max(0, len) + + // + // fill the current word + // + var i = 0 + + if (xBufOff != 0) { + while (i < len) { + xBuf[xBufOff++] = `in`[inOff + i++] + + if (xBufOff == 4) { + processWord(xBuf, 0) + xBufOff = 0 + break + } + } + } + + // + // process whole words. + // + val limit = ((len - i) and 3.inv()) + i + + while (i < limit) { + processWord(`in`, inOff + i) + i += 4 + } + + // + // load in the remainder. + // + while (i < len) { + xBuf[xBufOff++] = `in`[inOff + i++] + } + + byteCount += len.toLong() + } + + fun finish() { + val bitLength = (byteCount shl 3) + + // + // add the pad bytes. + // + update(128.toByte()) + + while (xBufOff != 0) { + update(0.toByte()) + } + + processLength(bitLength) + processBlock() + } + + override fun reset() { + byteCount = 0 + + xBufOff = 0 + + for (i in xBuf.indices) { + xBuf[i] = 0 + } + } + + protected fun populateState(state: ByteArray) { + System.arraycopy(xBuf, 0, state, 0, xBufOff) + intToBigEndian(xBufOff, state, 4) + longToBigEndian(byteCount, state, 8) + } + + override fun getByteLength(): Int = BYTE_LENGTH + + protected abstract fun processWord( + `in`: ByteArray, + inOff: Int, + ) + + protected abstract fun processLength(bitLength: Long) + + protected abstract fun processBlock() + + companion object { + private const val BYTE_LENGTH = 64 + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/KeccakDigest.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/KeccakDigest.kt new file mode 100644 index 0000000000..99b52fdc51 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/KeccakDigest.kt @@ -0,0 +1,551 @@ +/** + * 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.nip03Timestamp.ots.crypto + +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.arrayFill +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.arraysCopy + +/** + * Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ + * + * + * Following the naming conventions used in the C source code to enable easy review of the implementation. + */ +class KeccakDigest : ExtendedDigest { + var state: ByteArray = ByteArray((1600 / 8)) + var dataQueue: ByteArray = ByteArray((1536 / 8)) + var rate: Int = 0 + var bitsInQueue: Int = 0 + var fixedOutputLength: Int = 0 + var squeezing: Boolean = false + var bitsAvailableForSqueezing: Int = 0 + var chunk: ByteArray = byteArrayOf() + var oneByte: ByteArray = byteArrayOf() + + private fun clearDataQueueSection( + off: Int, + len: Int, + ) { + for (i in off.. 0) { + oneByte[0] = partialByte + absorb(oneByte, 0, partialBits.toLong()) + } + + squeeze(out, outOff, fixedOutputLength.toLong()) + reset() + + return getDigestSize() + } + + override fun reset() { + init(fixedOutputLength) + } + + /** + * Return the size of block that the compression function is applied to in bytes. + * + * @return internal byte length of a block. + */ + override fun getByteLength(): Int = rate / 8 + + private fun init(bitLength: Int) { + when (bitLength) { + 288 -> initSponge(1024, 576) + 128 -> initSponge(1344, 256) + 224 -> initSponge(1152, 448) + 256 -> initSponge(1088, 512) + 384 -> initSponge(832, 768) + 512 -> initSponge(576, 1024) + else -> throw IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512.") + } + } + + private fun initSponge( + rate: Int, + capacity: Int, + ) { + check(rate + capacity == 1600) { "rate + capacity != 1600" } + + check(!((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0))) { "invalid rate value" } + + this.rate = rate + // this is never read, need to check to see why we want to save it + // this.capacity = capacity; + arrayFill(this.state, 0.toByte()) + arrayFill(this.dataQueue, 0.toByte()) + this.bitsInQueue = 0 + this.squeezing = false + this.bitsAvailableForSqueezing = 0 + this.fixedOutputLength = capacity / 2 + this.chunk = ByteArray(rate / 8) + this.oneByte = ByteArray(1) + } + + private fun absorbQueue() { + keccakAbsorb(state, dataQueue, rate / 8) + bitsInQueue = 0 + } + + fun absorb( + data: ByteArray, + off: Int, + databitlen: Long, + ) { + var i: Long + var j: Long + var wholeBlocks: Long + + check((bitsInQueue % 8) == 0) { "attempt to absorb with odd length queue" } + + check(!squeezing) { "attempt to absorb while squeezing" } + + i = 0 + + while (i < databitlen) { + if ((bitsInQueue == 0) && (databitlen >= rate) && (i <= (databitlen - rate))) { + wholeBlocks = (databitlen - i) / rate + + j = 0 + while (j < wholeBlocks) { + System.arraycopy( + data, + (off + (i / 8) + (j * chunk.size)).toInt(), + chunk, + 0, + chunk.size, + ) + + // displayIntermediateValues.displayBytes(1, "Block to be absorbed", curData, rate / 8); + keccakAbsorb(state, chunk, chunk.size) + j++ + } + + i += wholeBlocks * rate + } else { + var partialBlock = (databitlen - i).toInt() + + if (partialBlock + bitsInQueue > rate) { + partialBlock = rate - bitsInQueue + } + + val partialByte = partialBlock % 8 + partialBlock -= partialByte + System.arraycopy( + data, + off + (i / 8).toInt(), + dataQueue, + bitsInQueue / 8, + partialBlock / 8, + ) + + bitsInQueue += partialBlock + i += partialBlock.toLong() + + if (bitsInQueue == rate) { + absorbQueue() + } + + if (partialByte > 0) { + val mask = (1 shl partialByte) - 1 + dataQueue[bitsInQueue / 8] = + (data[off + ((i / 8).toInt())].toInt() and mask).toByte() + bitsInQueue += partialByte + i += partialByte.toLong() + } + } + } + } + + private fun padAndSwitchToSqueezingPhase() { + if (bitsInQueue + 1 == rate) { + dataQueue[bitsInQueue / 8] = + (dataQueue[bitsInQueue / 8].toInt() or (1 shl (bitsInQueue % 8))).toByte() + absorbQueue() + clearDataQueueSection(0, rate / 8) + } else { + clearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8) + dataQueue[bitsInQueue / 8] = + (dataQueue[bitsInQueue / 8].toInt() or (1 shl (bitsInQueue % 8))).toByte() + } + + dataQueue[(rate - 1) / 8] = (dataQueue[(rate - 1) / 8].toInt() or (1 shl ((rate - 1) % 8))).toByte() + absorbQueue() + + // displayIntermediateValues.displayText(1, "--- Switching to squeezing phase ---"); + if (rate == 1024) { + keccakExtract1024bits(state, dataQueue) + bitsAvailableForSqueezing = 1024 + } else { + keccakExtract(state, dataQueue, rate / 64) + bitsAvailableForSqueezing = rate + } + + // displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8); + squeezing = true + } + + fun squeeze( + output: ByteArray, + offset: Int, + outputLength: Long, + ) { + var i: Long + var partialBlock: Int + + if (!squeezing) { + padAndSwitchToSqueezingPhase() + } + + check((outputLength % 8) == 0L) { "outputLength not a multiple of 8" } + + i = 0 + + while (i < outputLength) { + if (bitsAvailableForSqueezing == 0) { + keccakPermutation(state) + + if (rate == 1024) { + keccakExtract1024bits(state, dataQueue) + bitsAvailableForSqueezing = 1024 + } else { + keccakExtract(state, dataQueue, rate / 64) + bitsAvailableForSqueezing = rate + } + + // displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8); + } + + partialBlock = bitsAvailableForSqueezing + + if (partialBlock.toLong() > outputLength - i) { + partialBlock = (outputLength - i).toInt() + } + + System.arraycopy( + dataQueue, + (rate - bitsAvailableForSqueezing) / 8, + output, + offset + (i / 8).toInt(), + partialBlock / 8, + ) + bitsAvailableForSqueezing -= partialBlock + i += partialBlock.toLong() + } + } + + private fun fromBytesToWords( + stateAsWords: LongArray, + state: ByteArray, + ) { + for (i in 0..<(1600 / 64)) { + stateAsWords[i] = 0 + val index = i * (64 / 8) + + for (j in 0..<(64 / 8)) { + stateAsWords[i] = + stateAsWords[i] or ((state[index + j].toLong() and 0xffL) shl ((8 * j))) + } + } + } + + private fun fromWordsToBytes( + state: ByteArray, + stateAsWords: LongArray, + ) { + for (i in 0..<(1600 / 64)) { + val index = i * (64 / 8) + + for (j in 0..<(64 / 8)) { + state[index + j] = ((stateAsWords[i] ushr ((8 * j))) and 0xFFL).toByte() + } + } + } + + private fun keccakPermutation(state: ByteArray) { + val longState = LongArray(state.size / 8) + + fromBytesToWords(longState, state) + + // displayIntermediateValues.displayStateAsBytes(1, "Input of permutation", longState); + keccakPermutationOnWords(longState) + + // displayIntermediateValues.displayStateAsBytes(1, "State after permutation", longState); + fromWordsToBytes(state, longState) + } + + private fun keccakPermutationAfterXor( + state: ByteArray, + data: ByteArray, + dataLengthInBytes: Int, + ) { + var i: Int + + i = 0 + while (i < dataLengthInBytes) { + state[i] = (state[i].toInt() xor data[i].toInt()).toByte() + i++ + } + + keccakPermutation(state) + } + + private fun keccakPermutationOnWords(state: LongArray) { + var i: Int + + // displayIntermediateValues.displayStateAs64bitWords(3, "Same, with lanes as 64-bit words", state); + i = 0 + while (i < 24) { + // displayIntermediateValues.displayRoundNumber(3, i); + theta(state) + + // displayIntermediateValues.displayStateAs64bitWords(3, "After theta", state); + rho(state) + + // displayIntermediateValues.displayStateAs64bitWords(3, "After rho", state); + pi(state) + + // displayIntermediateValues.displayStateAs64bitWords(3, "After pi", state); + chi(state) + + // displayIntermediateValues.displayStateAs64bitWords(3, "After chi", state); + iota(state, i) + i++ + } + } + + var cArray: LongArray = LongArray(5) + + private fun theta(array: LongArray) { + for (x in 0..4) { + cArray[x] = 0 + + for (y in 0..4) { + cArray[x] = cArray[x] xor array[x + 5 * y] + } + } + for (x in 0..4) { + val dX = + ((((cArray[(x + 1) % 5]) shl 1) xor ((cArray[(x + 1) % 5]) ushr (64 - 1)))) xor cArray[(x + 4) % 5] + + for (y in 0..4) { + array[x + 5 * y] = array[x + 5 * y] xor dX + } + } + } + + private fun rho(array: LongArray) { + for (x in 0..4) { + for (y in 0..4) { + val index = x + 5 * y + array[index] = + (if (KeccakRhoOffsets[index] != 0) (((array[index]) shl KeccakRhoOffsets[index]) xor ((array[index]) ushr (64 - KeccakRhoOffsets[index]))) else array[index]) + } + } + } + + var tempA: LongArray = LongArray(25) + + private fun pi(array: LongArray) { + System.arraycopy(array, 0, tempA, 0, tempA.size) + + for (x in 0..4) { + for (y in 0..4) { + array[y + 5 * ((2 * x + 3 * y) % 5)] = tempA[x + 5 * y] + } + } + } + + var chiC: LongArray = LongArray(5) + + private fun chi(array: LongArray) { + for (y in 0..4) { + for (x in 0..4) { + chiC[x] = + array[x + 5 * y] xor ((array[(((x + 1) % 5) + 5 * y)].inv()) and array[(((x + 2) % 5) + 5 * y)]) + } + + for (x in 0..4) { + array[x + 5 * y] = chiC[x] + } + } + } + + private fun iota( + array: LongArray, + indexRound: Int, + ) { + array[(((0) % 5) + 5 * ((0) % 5))] = + array[(((0) % 5) + 5 * ((0) % 5))] xor KeccakRoundConstants[indexRound] + } + + private fun keccakAbsorb( + byteState: ByteArray, + data: ByteArray, + dataInBytes: Int, + ) { + keccakPermutationAfterXor(byteState, data, dataInBytes) + } + + private fun keccakExtract1024bits( + byteState: ByteArray, + data: ByteArray, + ) { + System.arraycopy(byteState, 0, data, 0, 128) + } + + private fun keccakExtract( + byteState: ByteArray, + data: ByteArray, + laneCount: Int, + ) { + System.arraycopy(byteState, 0, data, 0, laneCount * 8) + } + + companion object { + private val KeccakRoundConstants: LongArray = keccakInitializeRoundConstants() + + private val KeccakRhoOffsets: IntArray = keccakInitializeRhoOffsets() + + private fun keccakInitializeRoundConstants(): LongArray { + val keccakRoundConstants = LongArray(24) + val lfsrState = ByteArray(1) + + lfsrState[0] = 0x01 + var i: Int + var j: Int + var bitPosition: Int + + i = 0 + while (i < 24) { + keccakRoundConstants[i] = 0 + + j = 0 + while (j < 7) { + bitPosition = (1 shl j) - 1 + + if (lfsr86540(lfsrState)) { + keccakRoundConstants[i] = keccakRoundConstants[i] xor (1L shl bitPosition) + } + j++ + } + i++ + } + + return keccakRoundConstants + } + + private fun lfsr86540(lfsr: ByteArray): Boolean { + val result = (((lfsr[0]).toInt() and 0x01) != 0) + + if (((lfsr[0]).toInt() and 0x80) != 0) { + lfsr[0] = (((lfsr[0]).toInt() shl 1) xor 0x71).toByte() + } else { + lfsr[0] = (lfsr[0].toInt() shl 1).toByte() + } + + return result + } + + private fun keccakInitializeRhoOffsets(): IntArray { + val keccakRhoOffsets = IntArray(25) + var x: Int + var y: Int + var t: Int + var newX: Int + var newY: Int + + keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = 0 + x = 1 + y = 0 + + t = 0 + while (t < 24) { + keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = ((t + 1) * (t + 2) / 2) % 64 + newX = (0 * x + 1 * y) % 5 + newY = (2 * x + 3 * y) % 5 + x = newX + y = newY + t++ + } + + return keccakRhoOffsets + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Memoable.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Memoable.kt new file mode 100644 index 0000000000..f7b873829a --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Memoable.kt @@ -0,0 +1,50 @@ +/** + * 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.nip03Timestamp.ots.crypto + +/** + * Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state + * via the [copy()][.copy] method and then resetting the object back to that state later using the + * [reset()][.reset] method. + */ +interface Memoable { + /** + * Produce a copy of this object with its configuration and in its current state. + * + * + * The returned object may be used simply to store the state, or may be used as a similar object + * starting from the copied state. + * + * @return Memoable object + */ + fun copy(): Memoable + + /** + * Restore a copied object state into this object. + * + * + * Implementations of this method *should* try to avoid or minimise memory allocation to perform the reset. + * + * @param other an object originally [copied][.copy] from an object of the same type as this instance. + * @throws ClassCastException if the provided object is not of the correct type. + */ + fun reset(other: Memoable) +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Pack.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Pack.kt new file mode 100644 index 0000000000..77ac393621 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Pack.kt @@ -0,0 +1,322 @@ +/** + * 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.nip03Timestamp.ots.crypto + +/** + * Utility methods for converting byte arrays into ints and longs, and back again. + */ +object Pack { + fun bigEndianToShort( + bs: ByteArray, + off: Int, + ): Short { + var off = off + var n = (bs[off].toInt() and 0xff) shl 8 + n = n or (bs[++off].toInt() and 0xff) + return n.toShort() + } + + @JvmStatic + fun bigEndianToInt( + bs: ByteArray, + off: Int, + ): Int { + var off = off + var n = bs[off].toInt() shl 24 + n = n or ((bs[++off].toInt() and 0xff) shl 16) + n = n or ((bs[++off].toInt() and 0xff) shl 8) + n = n or (bs[++off].toInt() and 0xff) + return n + } + + fun bigEndianToInt( + bs: ByteArray, + off: Int, + ns: IntArray, + ) { + var off = off + for (i in ns.indices) { + ns[i] = bigEndianToInt(bs, off) + off += 4 + } + } + + fun intToBigEndian(n: Int): ByteArray { + val bs = ByteArray(4) + intToBigEndian(n, bs, 0) + return bs + } + + @JvmStatic + fun intToBigEndian( + n: Int, + bs: ByteArray, + off: Int, + ) { + var off = off + bs[off] = (n ushr 24).toByte() + bs[++off] = (n ushr 16).toByte() + bs[++off] = (n ushr 8).toByte() + bs[++off] = (n).toByte() + } + + fun intToBigEndian(ns: IntArray): ByteArray { + val bs = ByteArray(4 * ns.size) + intToBigEndian(ns, bs, 0) + return bs + } + + fun intToBigEndian( + ns: IntArray, + bs: ByteArray, + off: Int, + ) { + var off = off + for (i in ns.indices) { + intToBigEndian(ns[i], bs, off) + off += 4 + } + } + + @JvmStatic + fun bigEndianToLong( + bs: ByteArray, + off: Int, + ): Long { + val hi = bigEndianToInt(bs, off) + val lo = bigEndianToInt(bs, off + 4) + return ((hi.toLong() and 0xffffffffL) shl 32) or (lo.toLong() and 0xffffffffL) + } + + fun bigEndianToLong( + bs: ByteArray, + off: Int, + ns: LongArray, + ) { + var off = off + for (i in ns.indices) { + ns[i] = bigEndianToLong(bs, off) + off += 8 + } + } + + fun longToBigEndian(n: Long): ByteArray { + val bs = ByteArray(8) + longToBigEndian(n, bs, 0) + return bs + } + + @JvmStatic + fun longToBigEndian( + n: Long, + bs: ByteArray, + off: Int, + ) { + intToBigEndian((n ushr 32).toInt(), bs, off) + intToBigEndian((n and 0xffffffffL).toInt(), bs, off + 4) + } + + fun longToBigEndian(ns: LongArray): ByteArray { + val bs = ByteArray(8 * ns.size) + longToBigEndian(ns, bs, 0) + return bs + } + + fun longToBigEndian( + ns: LongArray, + bs: ByteArray, + off: Int, + ) { + var off = off + for (i in ns.indices) { + longToBigEndian(ns[i], bs, off) + off += 8 + } + } + + fun littleEndianToShort( + bs: ByteArray, + off: Int, + ): Short { + var off = off + var n = bs[off].toInt() and 0xff + n = n or ((bs[++off].toInt() and 0xff) shl 8) + return n.toShort() + } + + fun littleEndianToInt( + bs: ByteArray, + off: Int, + ): Int { + var off = off + var n = bs[off].toInt() and 0xff + n = n or ((bs[++off].toInt() and 0xff) shl 8) + n = n or ((bs[++off].toInt() and 0xff) shl 16) + n = n or (bs[++off].toInt() shl 24) + return n + } + + fun littleEndianToInt( + bs: ByteArray, + off: Int, + ns: IntArray, + ) { + var off = off + for (i in ns.indices) { + ns[i] = littleEndianToInt(bs, off) + off += 4 + } + } + + fun littleEndianToInt( + bs: ByteArray, + bOff: Int, + ns: IntArray, + nOff: Int, + count: Int, + ) { + var bOff = bOff + for (i in 0.. 14) { + processBlock() + } + + xArray[14] = (bitLength and 0xffffffffL).toInt() + xArray[15] = (bitLength ushr 32).toInt() + } + + private fun unpackWord( + word: Int, + out: ByteArray, + outOff: Int, + ) { + out[outOff] = word.toByte() + out[outOff + 1] = (word ushr 8).toByte() + out[outOff + 2] = (word ushr 16).toByte() + out[outOff + 3] = (word ushr 24).toByte() + } + + override fun doFinal( + out: ByteArray, + outOff: Int, + ): Int { + finish() + + unpackWord(h0, out, outOff) + unpackWord(h1, out, outOff + 4) + unpackWord(h2, out, outOff + 8) + unpackWord(h3, out, outOff + 12) + unpackWord(h4, out, outOff + 16) + + reset() + + return DIGEST_LENGTH + } + + /** + * reset the chaining variables to the IV values. + */ + override fun reset() { + super.reset() + + h0 = 0x67452301 + h1 = -0x10325477 + h2 = -0x67452302 + h3 = 0x10325476 + h4 = -0x3c2d1e10 + + xOff = 0 + + for (i in xArray.indices) { + xArray[i] = 0 + } + } + + /* + * rotate int x left n bits. + */ + private fun rotateLeft( + x: Int, + n: Int, + ): Int = (x shl n) or (x ushr (32 - n)) + + /* + * f1,f2,f3,f4,f5 are the basic RIPEMD160 functions. + * rounds 0-15 + */ + private fun f1( + x: Int, + y: Int, + z: Int, + ): Int = x xor y xor z + + /* + * rounds 16-31 + */ + private fun f2( + x: Int, + y: Int, + z: Int, + ): Int = (x and y) or (x.inv() and z) + + /* + * rounds 32-47 + */ + private fun f3( + x: Int, + y: Int, + z: Int, + ): Int = (x or y.inv()) xor z + + /* + * rounds 48-63 + */ + private fun f4( + x: Int, + y: Int, + z: Int, + ): Int = (x and z) or (y and z.inv()) + + /* + * rounds 64-79 + */ + private fun f5( + x: Int, + y: Int, + z: Int, + ): Int = x xor (y or z.inv()) + + override fun processBlock() { + var a: Int + var aa: Int + var b: Int + var bb: Int + var c: Int + var cc: Int + var d: Int + var dd: Int + var e: Int + var ee: Int + + aa = h0 + a = aa + bb = h1 + b = bb + cc = h2 + c = cc + dd = h3 + d = dd + ee = h4 + e = ee + + // + // Rounds 1 - 16 + // + // left + a = rotateLeft(a + f1(b, c, d) + xArray[0], 11) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f1(a, b, c) + xArray[1], 14) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f1(e, a, b) + xArray[2], 15) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f1(d, e, a) + xArray[3], 12) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f1(c, d, e) + xArray[4], 5) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f1(b, c, d) + xArray[5], 8) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f1(a, b, c) + xArray[6], 7) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f1(e, a, b) + xArray[7], 9) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f1(d, e, a) + xArray[8], 11) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f1(c, d, e) + xArray[9], 13) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f1(b, c, d) + xArray[10], 14) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f1(a, b, c) + xArray[11], 15) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f1(e, a, b) + xArray[12], 6) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f1(d, e, a) + xArray[13], 7) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f1(c, d, e) + xArray[14], 9) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f1(b, c, d) + xArray[15], 8) + e + c = rotateLeft(c, 10) + + // right + aa = rotateLeft(aa + f5(bb, cc, dd) + xArray[5] + 0x50a28be6, 8) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f5(aa, bb, cc) + xArray[14] + 0x50a28be6, 9) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f5(ee, aa, bb) + xArray[7] + 0x50a28be6, 9) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f5(dd, ee, aa) + xArray[0] + 0x50a28be6, 11) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f5(cc, dd, ee) + xArray[9] + 0x50a28be6, 13) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f5(bb, cc, dd) + xArray[2] + 0x50a28be6, 15) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f5(aa, bb, cc) + xArray[11] + 0x50a28be6, 15) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f5(ee, aa, bb) + xArray[4] + 0x50a28be6, 5) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f5(dd, ee, aa) + xArray[13] + 0x50a28be6, 7) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f5(cc, dd, ee) + xArray[6] + 0x50a28be6, 7) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f5(bb, cc, dd) + xArray[15] + 0x50a28be6, 8) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f5(aa, bb, cc) + xArray[8] + 0x50a28be6, 11) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f5(ee, aa, bb) + xArray[1] + 0x50a28be6, 14) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f5(dd, ee, aa) + xArray[10] + 0x50a28be6, 14) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f5(cc, dd, ee) + xArray[3] + 0x50a28be6, 12) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f5(bb, cc, dd) + xArray[12] + 0x50a28be6, 6) + ee + cc = rotateLeft(cc, 10) + + // + // Rounds 16-31 + // + // left + e = rotateLeft(e + f2(a, b, c) + xArray[7] + 0x5a827999, 7) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f2(e, a, b) + xArray[4] + 0x5a827999, 6) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f2(d, e, a) + xArray[13] + 0x5a827999, 8) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f2(c, d, e) + xArray[1] + 0x5a827999, 13) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f2(b, c, d) + xArray[10] + 0x5a827999, 11) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f2(a, b, c) + xArray[6] + 0x5a827999, 9) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f2(e, a, b) + xArray[15] + 0x5a827999, 7) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f2(d, e, a) + xArray[3] + 0x5a827999, 15) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f2(c, d, e) + xArray[12] + 0x5a827999, 7) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f2(b, c, d) + xArray[0] + 0x5a827999, 12) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f2(a, b, c) + xArray[9] + 0x5a827999, 15) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f2(e, a, b) + xArray[5] + 0x5a827999, 9) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f2(d, e, a) + xArray[2] + 0x5a827999, 11) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f2(c, d, e) + xArray[14] + 0x5a827999, 7) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f2(b, c, d) + xArray[11] + 0x5a827999, 13) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f2(a, b, c) + xArray[8] + 0x5a827999, 12) + d + b = rotateLeft(b, 10) + + // right + ee = rotateLeft(ee + f4(aa, bb, cc) + xArray[6] + 0x5c4dd124, 9) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f4(ee, aa, bb) + xArray[11] + 0x5c4dd124, 13) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f4(dd, ee, aa) + xArray[3] + 0x5c4dd124, 15) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f4(cc, dd, ee) + xArray[7] + 0x5c4dd124, 7) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f4(bb, cc, dd) + xArray[0] + 0x5c4dd124, 12) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f4(aa, bb, cc) + xArray[13] + 0x5c4dd124, 8) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f4(ee, aa, bb) + xArray[5] + 0x5c4dd124, 9) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f4(dd, ee, aa) + xArray[10] + 0x5c4dd124, 11) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f4(cc, dd, ee) + xArray[14] + 0x5c4dd124, 7) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f4(bb, cc, dd) + xArray[15] + 0x5c4dd124, 7) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f4(aa, bb, cc) + xArray[8] + 0x5c4dd124, 12) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f4(ee, aa, bb) + xArray[12] + 0x5c4dd124, 7) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f4(dd, ee, aa) + xArray[4] + 0x5c4dd124, 6) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f4(cc, dd, ee) + xArray[9] + 0x5c4dd124, 15) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f4(bb, cc, dd) + xArray[1] + 0x5c4dd124, 13) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f4(aa, bb, cc) + xArray[2] + 0x5c4dd124, 11) + dd + bb = rotateLeft(bb, 10) + + // + // Rounds 32-47 + // + // left + d = rotateLeft(d + f3(e, a, b) + xArray[3] + 0x6ed9eba1, 11) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f3(d, e, a) + xArray[10] + 0x6ed9eba1, 13) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f3(c, d, e) + xArray[14] + 0x6ed9eba1, 6) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f3(b, c, d) + xArray[4] + 0x6ed9eba1, 7) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f3(a, b, c) + xArray[9] + 0x6ed9eba1, 14) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f3(e, a, b) + xArray[15] + 0x6ed9eba1, 9) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f3(d, e, a) + xArray[8] + 0x6ed9eba1, 13) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f3(c, d, e) + xArray[1] + 0x6ed9eba1, 15) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f3(b, c, d) + xArray[2] + 0x6ed9eba1, 14) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f3(a, b, c) + xArray[7] + 0x6ed9eba1, 8) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f3(e, a, b) + xArray[0] + 0x6ed9eba1, 13) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f3(d, e, a) + xArray[6] + 0x6ed9eba1, 6) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f3(c, d, e) + xArray[13] + 0x6ed9eba1, 5) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f3(b, c, d) + xArray[11] + 0x6ed9eba1, 12) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f3(a, b, c) + xArray[5] + 0x6ed9eba1, 7) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f3(e, a, b) + xArray[12] + 0x6ed9eba1, 5) + c + a = rotateLeft(a, 10) + + // right + dd = rotateLeft(dd + f3(ee, aa, bb) + xArray[15] + 0x6d703ef3, 9) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f3(dd, ee, aa) + xArray[5] + 0x6d703ef3, 7) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f3(cc, dd, ee) + xArray[1] + 0x6d703ef3, 15) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f3(bb, cc, dd) + xArray[3] + 0x6d703ef3, 11) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f3(aa, bb, cc) + xArray[7] + 0x6d703ef3, 8) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f3(ee, aa, bb) + xArray[14] + 0x6d703ef3, 6) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f3(dd, ee, aa) + xArray[6] + 0x6d703ef3, 6) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f3(cc, dd, ee) + xArray[9] + 0x6d703ef3, 14) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f3(bb, cc, dd) + xArray[11] + 0x6d703ef3, 12) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f3(aa, bb, cc) + xArray[8] + 0x6d703ef3, 13) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f3(ee, aa, bb) + xArray[12] + 0x6d703ef3, 5) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f3(dd, ee, aa) + xArray[2] + 0x6d703ef3, 14) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f3(cc, dd, ee) + xArray[10] + 0x6d703ef3, 13) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f3(bb, cc, dd) + xArray[0] + 0x6d703ef3, 13) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f3(aa, bb, cc) + xArray[4] + 0x6d703ef3, 7) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f3(ee, aa, bb) + xArray[13] + 0x6d703ef3, 5) + cc + aa = rotateLeft(aa, 10) + + // + // Rounds 48-63 + // + // left + c = rotateLeft(c + f4(d, e, a) + xArray[1] + -0x70e44324, 11) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f4(c, d, e) + xArray[9] + -0x70e44324, 12) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f4(b, c, d) + xArray[11] + -0x70e44324, 14) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f4(a, b, c) + xArray[10] + -0x70e44324, 15) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f4(e, a, b) + xArray[0] + -0x70e44324, 14) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f4(d, e, a) + xArray[8] + -0x70e44324, 15) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f4(c, d, e) + xArray[12] + -0x70e44324, 9) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f4(b, c, d) + xArray[4] + -0x70e44324, 8) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f4(a, b, c) + xArray[13] + -0x70e44324, 9) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f4(e, a, b) + xArray[3] + -0x70e44324, 14) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f4(d, e, a) + xArray[7] + -0x70e44324, 5) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f4(c, d, e) + xArray[15] + -0x70e44324, 6) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f4(b, c, d) + xArray[14] + -0x70e44324, 8) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f4(a, b, c) + xArray[5] + -0x70e44324, 6) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f4(e, a, b) + xArray[6] + -0x70e44324, 5) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f4(d, e, a) + xArray[2] + -0x70e44324, 12) + b + e = rotateLeft(e, 10) + + // right + cc = rotateLeft(cc + f2(dd, ee, aa) + xArray[8] + 0x7a6d76e9, 15) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f2(cc, dd, ee) + xArray[6] + 0x7a6d76e9, 5) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f2(bb, cc, dd) + xArray[4] + 0x7a6d76e9, 8) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f2(aa, bb, cc) + xArray[1] + 0x7a6d76e9, 11) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f2(ee, aa, bb) + xArray[3] + 0x7a6d76e9, 14) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f2(dd, ee, aa) + xArray[11] + 0x7a6d76e9, 14) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f2(cc, dd, ee) + xArray[15] + 0x7a6d76e9, 6) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f2(bb, cc, dd) + xArray[0] + 0x7a6d76e9, 14) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f2(aa, bb, cc) + xArray[5] + 0x7a6d76e9, 6) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f2(ee, aa, bb) + xArray[12] + 0x7a6d76e9, 9) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f2(dd, ee, aa) + xArray[2] + 0x7a6d76e9, 12) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f2(cc, dd, ee) + xArray[13] + 0x7a6d76e9, 9) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f2(bb, cc, dd) + xArray[9] + 0x7a6d76e9, 12) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f2(aa, bb, cc) + xArray[7] + 0x7a6d76e9, 5) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f2(ee, aa, bb) + xArray[10] + 0x7a6d76e9, 15) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f2(dd, ee, aa) + xArray[14] + 0x7a6d76e9, 8) + bb + ee = rotateLeft(ee, 10) + + // + // Rounds 64-79 + // + // left + b = rotateLeft(b + f5(c, d, e) + xArray[4] + -0x56ac02b2, 9) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f5(b, c, d) + xArray[0] + -0x56ac02b2, 15) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f5(a, b, c) + xArray[5] + -0x56ac02b2, 5) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f5(e, a, b) + xArray[9] + -0x56ac02b2, 11) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f5(d, e, a) + xArray[7] + -0x56ac02b2, 6) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f5(c, d, e) + xArray[12] + -0x56ac02b2, 8) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f5(b, c, d) + xArray[2] + -0x56ac02b2, 13) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f5(a, b, c) + xArray[10] + -0x56ac02b2, 12) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f5(e, a, b) + xArray[14] + -0x56ac02b2, 5) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f5(d, e, a) + xArray[1] + -0x56ac02b2, 12) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f5(c, d, e) + xArray[3] + -0x56ac02b2, 13) + a + d = rotateLeft(d, 10) + a = rotateLeft(a + f5(b, c, d) + xArray[8] + -0x56ac02b2, 14) + e + c = rotateLeft(c, 10) + e = rotateLeft(e + f5(a, b, c) + xArray[11] + -0x56ac02b2, 11) + d + b = rotateLeft(b, 10) + d = rotateLeft(d + f5(e, a, b) + xArray[6] + -0x56ac02b2, 8) + c + a = rotateLeft(a, 10) + c = rotateLeft(c + f5(d, e, a) + xArray[15] + -0x56ac02b2, 5) + b + e = rotateLeft(e, 10) + b = rotateLeft(b + f5(c, d, e) + xArray[13] + -0x56ac02b2, 6) + a + d = rotateLeft(d, 10) + + // right + bb = rotateLeft(bb + f1(cc, dd, ee) + xArray[12], 8) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f1(bb, cc, dd) + xArray[15], 5) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f1(aa, bb, cc) + xArray[10], 12) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f1(ee, aa, bb) + xArray[4], 9) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f1(dd, ee, aa) + xArray[1], 12) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f1(cc, dd, ee) + xArray[5], 5) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f1(bb, cc, dd) + xArray[8], 14) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f1(aa, bb, cc) + xArray[7], 6) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f1(ee, aa, bb) + xArray[6], 8) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f1(dd, ee, aa) + xArray[2], 13) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f1(cc, dd, ee) + xArray[13], 6) + aa + dd = rotateLeft(dd, 10) + aa = rotateLeft(aa + f1(bb, cc, dd) + xArray[14], 5) + ee + cc = rotateLeft(cc, 10) + ee = rotateLeft(ee + f1(aa, bb, cc) + xArray[0], 15) + dd + bb = rotateLeft(bb, 10) + dd = rotateLeft(dd + f1(ee, aa, bb) + xArray[3], 13) + cc + aa = rotateLeft(aa, 10) + cc = rotateLeft(cc + f1(dd, ee, aa) + xArray[9], 11) + bb + ee = rotateLeft(ee, 10) + bb = rotateLeft(bb + f1(cc, dd, ee) + xArray[11], 11) + aa + dd = rotateLeft(dd, 10) + + dd += c + h1 + h1 = h2 + d + ee + h2 = h3 + e + aa + h3 = h4 + a + bb + h4 = h0 + b + cc + h0 = dd + + // + // reset the offset and clean out the word buffer. + // + xOff = 0 + for (i in xArray.indices) { + xArray[i] = 0 + } + } + + override fun copy(): Memoable = RIPEMD160Digest(this) + + override fun reset(other: Memoable) { + val d = other as RIPEMD160Digest + + copyIn(d) + } + + companion object { + private const val DIGEST_LENGTH = 20 + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/CommitmentNotFoundException.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/CommitmentNotFoundException.kt new file mode 100644 index 0000000000..f688cb8a34 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/CommitmentNotFoundException.kt @@ -0,0 +1,25 @@ +/** + * 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.nip03Timestamp.ots.exceptions + +class CommitmentNotFoundException( + message: String?, +) : Exception(message) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/DeserializationException.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/DeserializationException.kt new file mode 100644 index 0000000000..9cd75aadb3 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/DeserializationException.kt @@ -0,0 +1,25 @@ +/** + * 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.nip03Timestamp.ots.exceptions + +class DeserializationException( + message: String?, +) : Exception(message) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/ExceededSizeException.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/ExceededSizeException.kt new file mode 100644 index 0000000000..adb704dc56 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/ExceededSizeException.kt @@ -0,0 +1,25 @@ +/** + * 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.nip03Timestamp.ots.exceptions + +class ExceededSizeException( + message: String?, +) : Exception(message) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/UrlException.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/UrlException.kt new file mode 100644 index 0000000000..fbc73f64cf --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/UrlException.kt @@ -0,0 +1,25 @@ +/** + * 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.nip03Timestamp.ots.exceptions + +class UrlException( + message: String?, +) : Exception(message) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/VerificationException.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/VerificationException.kt new file mode 100644 index 0000000000..f50aabbfde --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/VerificationException.kt @@ -0,0 +1,25 @@ +/** + * 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.nip03Timestamp.ots.exceptions + +class VerificationException( + message: String?, +) : Exception(message) diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/BlockstreamExplorer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/BlockstreamExplorer.kt new file mode 100644 index 0000000000..e024986bb4 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/BlockstreamExplorer.kt @@ -0,0 +1,100 @@ +/** + * 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.nip03Timestamp.ots.http + +import android.util.Log +import com.vitorpamplona.quartz.nip03Timestamp.ots.BitcoinExplorer +import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader +import java.net.URL +import java.util.concurrent.Executors + +/** + * 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. + */ +class BlockstreamExplorer : BitcoinExplorer { + /** + * Retrieve the block information from the block hash. + * + * @param hash Hash of the block. + * @return the blockheader of the hash + * @throws Exception desc + */ + @Throws(Exception::class) + override fun block(hash: String): BlockHeader { + val url = URL("$EXPLORA_URL/block/$hash") + val task = Request(url) + val executor = Executors.newSingleThreadExecutor() + val future = executor.submit(task) + val take: Response = future.get() + executor.shutdown() + if (!take.isOk) throw Exception() + + val jsonObject = take.json + val merkleRoot = jsonObject!!.get("merkle_root").asText() + val time = jsonObject.get("timestamp").asInt().toString() + val blockHeader = BlockHeader(merkleRoot, hash, time) + Log.i("BlockstreamExplorer", take.fromUrl + " " + blockHeader) + return blockHeader + // log.warning("Cannot parse merkleroot from body: " + jsonObject + ": " + e.getMessage()); + } + + /** + * Retrieve the block hash from the block height. + * + * @param height Height of the block. + * @return the hash of the block at height height + * @throws Exception desc + */ + @Throws(Exception::class) + override fun blockHash(height: Int): String { + val url = URL("$EXPLORA_URL/block-height/$height") + val task = Request(url) + val executor = Executors.newSingleThreadExecutor() + val future = executor.submit(task) + val take = future.get() + executor.shutdown() + if (!take.isOk) throw Exception() + val blockHash = take.string + Log.i("BlockstreamExplorer", take.fromUrl + " " + blockHash) + return blockHash + } + + companion object { + private const val EXPLORA_URL = "https://blockstream.info/api" + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Calendar.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Calendar.kt new file mode 100644 index 0000000000..7af8862993 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Calendar.kt @@ -0,0 +1,127 @@ +/** + * 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.nip03Timestamp.ots.http + +import com.vitorpamplona.quartz.nip01Core.core.toHexKey +import com.vitorpamplona.quartz.nip03Timestamp.ots.ICalendar +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp +import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp.Companion.deserialize +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException +import java.net.URL + +/** + * Class representing remote calendar server interface. + */ +class Calendar( + val url: String, +) : ICalendar { + /** + * Submitting a digest to remote calendar. Returns a com.eternitywall.ots.Timestamp committing to that digest. + * + * @param digest The digest hash to send. + * @return the Timestamp received from the calendar. + * @throws ExceededSizeException if response is too big. + * @throws UrlException if url is not reachable. + * @throws DeserializationException if the data is corrupt + */ + @Throws(ExceededSizeException::class, UrlException::class, DeserializationException::class) + override fun submit(digest: ByteArray): Timestamp { + try { + val headers: MutableMap = HashMap() + headers.put("Accept", "application/vnd.opentimestamps.v1") + headers.put("User-Agent", "java-opentimestamps") + headers.put("Content-Type", "application/x-www-form-urlencoded") + + val obj = URL("$url/digest") + val task = Request(obj) + task.setData(digest) + task.setHeaders(headers) + val response = task.call() + val body = response.bytes + if (body.size > 10000) { + throw ExceededSizeException("Calendar response exceeded size limit") + } + + val ctx = StreamDeserializationContext(body) + return deserialize(ctx, digest) + } catch (e: ExceededSizeException) { + throw e + } catch (e: DeserializationException) { + throw e + } catch (e: Exception) { + throw UrlException(e.message) + } + } + + /** + * Get a timestamp for a given commitment. + * + * @param commitment The digest hash to send. + * @return the Timestamp from the calendar server (with blockchain information if already written). + * @throws ExceededSizeException if response is too big. + * @throws UrlException if url is not reachable. + * @throws CommitmentNotFoundException if commit is not found. + * @throws DeserializationException if the data is corrupt + */ + @Throws( + DeserializationException::class, + ExceededSizeException::class, + CommitmentNotFoundException::class, + UrlException::class, + ) + override fun getTimestamp(commitment: ByteArray): Timestamp { + try { + val headers: MutableMap = HashMap() + headers.put("Accept", "application/vnd.opentimestamps.v1") + headers.put("User-Agent", "java-opentimestamps") + headers.put("Content-Type", "application/x-www-form-urlencoded") + + val obj = URL(url + "/timestamp/" + commitment.toHexKey().lowercase()) + val task = Request(obj) + task.setHeaders(headers) + val response = task.call() + val body = response.bytes + if (body.size > 10000) { + throw ExceededSizeException("Calendar response exceeded size limit") + } + + if (!response.isOk) { + throw CommitmentNotFoundException("com.eternitywall.ots.Calendar response a status code != 200 which is: " + response.status) + } + + val ctx = StreamDeserializationContext(body) + + return deserialize(ctx, commitment) + } catch (e: DeserializationException) { + throw e + } catch (e: ExceededSizeException) { + throw e + } catch (e: CommitmentNotFoundException) { + throw e + } catch (e: Exception) { + throw UrlException(e.message) + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/CalendarAsyncSubmit.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/CalendarAsyncSubmit.kt new file mode 100644 index 0000000000..b03ac65231 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/CalendarAsyncSubmit.kt @@ -0,0 +1,71 @@ +/** + * 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.nip03Timestamp.ots.http + +import com.vitorpamplona.quartz.nip03Timestamp.ots.ICalendarAsyncSubmit +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp +import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp.Companion.deserialize +import java.net.URL +import java.util.Optional +import java.util.concurrent.ArrayBlockingQueue +import java.util.concurrent.BlockingQueue + +/** + * For making async calls to a calendar server + */ +class CalendarAsyncSubmit( + private val url: String, + private val digest: ByteArray, +) : ICalendarAsyncSubmit { + private var queue: BlockingQueue> = ArrayBlockingQueue(1) + + fun setQueue(queue: BlockingQueue>) { + this.queue = queue + } + + @Throws(Exception::class) + override fun call(): Optional { + val headers: MutableMap = HashMap() + headers.put("Accept", "application/vnd.opentimestamps.v1") + headers.put("User-Agent", "java-opentimestamps") + headers.put("Content-Type", "application/x-www-form-urlencoded") + + val obj = URL("$url/digest") + val task = Request(obj) + task.setData(digest) + task.setHeaders(headers) + val response = task.call() + + if (response.isOk) { + val body = response.bytes + val ctx = StreamDeserializationContext(body) + val timestamp = deserialize(ctx, digest) + val of: Optional = Optional.of(timestamp) + queue.add(of) + return of + } + + queue.add(Optional.empty()) + + return Optional.empty() + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/CalendarPureJavaBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/CalendarPureJavaBuilder.kt new file mode 100644 index 0000000000..a98bcd23d6 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/CalendarPureJavaBuilder.kt @@ -0,0 +1,54 @@ +/** + * 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.nip03Timestamp.ots.http + +import com.vitorpamplona.quartz.nip03Timestamp.ots.CalendarBuilder +import com.vitorpamplona.quartz.nip03Timestamp.ots.ICalendar +import com.vitorpamplona.quartz.nip03Timestamp.ots.ICalendarAsyncSubmit + +/** + * 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. + */ +class CalendarPureJavaBuilder : CalendarBuilder { + override fun newSyncCalendar(url: String): ICalendar = Calendar(url) + + override fun newAsyncCalendar( + url: String, + digest: ByteArray, + ): ICalendarAsyncSubmit = CalendarAsyncSubmit(url, digest) +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt new file mode 100644 index 0000000000..341b5a0d48 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.kt @@ -0,0 +1,97 @@ +/** + * 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.nip03Timestamp.ots.http + +import android.util.Log +import java.io.DataOutputStream +import java.net.HttpURLConnection +import java.net.URL +import java.util.concurrent.Callable +import java.util.zip.GZIPInputStream + +/** + * For making an HTTP request. + */ +class Request( + private val url: URL, +) : Callable { + private var data: ByteArray? = null + private var headers: MutableMap = mutableMapOf() + + fun setData(data: ByteArray?) { + this.data = data + } + + fun setHeaders(headers: MutableMap) { + this.headers = headers + } + + @Throws(Exception::class) + override fun call(): Response { + val response = Response() + + try { + val httpURLConnection = url.openConnection() as HttpURLConnection + httpURLConnection.setReadTimeout(10000) + httpURLConnection.setConnectTimeout(10000) + httpURLConnection.setRequestProperty("User-Agent", "OpenTimestamps Java") + httpURLConnection.setRequestProperty("Accept", "application/json") + httpURLConnection.setRequestProperty("Accept-Encoding", "gzip") + + for (entry in headers.entries) { + httpURLConnection.setRequestProperty(entry.key, entry.value) + } + + if (data != null) { + httpURLConnection.setDoOutput(true) + httpURLConnection.setRequestMethod("POST") + httpURLConnection.setRequestProperty( + "Content-Length", + "" + this.data!!.size.toString(), + ) + val wr = DataOutputStream(httpURLConnection.getOutputStream()) + wr.write(this.data, 0, this.data!!.size) + wr.flush() + wr.close() + } else { + httpURLConnection.setRequestMethod("GET") + } + + httpURLConnection.connect() + + val responseCode = httpURLConnection.getResponseCode() + + Log.i("OpenTimestamp", "$responseCode responseCode ") + + response.status = responseCode + response.fromUrl = url.toString() + var `is` = httpURLConnection.getInputStream() + if ("gzip" == httpURLConnection.getContentEncoding()) { + `is` = GZIPInputStream(`is`) + } + response.setStream(`is`) + } catch (e: Exception) { + Log.w("OpenTimestamp", "$url exception $e") + } + + return response + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt new file mode 100644 index 0000000000..dc44a417f2 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.kt @@ -0,0 +1,82 @@ +/** + * 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.nip03Timestamp.ots.http + +import com.fasterxml.jackson.databind.JsonNode +import com.fasterxml.jackson.databind.json.JsonMapper +import java.io.ByteArrayOutputStream +import java.io.IOException +import java.io.InputStream +import java.nio.charset.StandardCharsets + +/** + * Holds the response from an HTTP request. + */ +class Response { + private var stream: InputStream? = null + + var fromUrl: String? = null + var status: Int? = null + + constructor() + + constructor(stream: InputStream) { + this.stream = stream + } + + fun setStream(stream: InputStream) { + this.stream = stream + } + + val isOk: Boolean + get() = this.status != null && 200 == this.status + + fun getStream(): InputStream = this.stream!! + + @get:Throws(IOException::class) + val string: String + get() = kotlin.text.String(this.bytes, StandardCharsets.UTF_8) + + @get:Throws(IOException::class) + val bytes: ByteArray + get() { + val buffer = ByteArrayOutputStream() + var nRead: Int + val data = ByteArray(16384) + + while ((this.stream!!.read(data, 0, data.size).also { nRead = it }) != -1) { + buffer.write(data, 0, nRead) + } + + buffer.flush() + + return buffer.toByteArray() + } + + @get:Throws(IOException::class) + val json: JsonNode? + get() { + val jsonString = this.string + val builder = + JsonMapper.builder().build() + return builder.readTree(jsonString) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpBitcoinExplorer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpBitcoinExplorer.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpBitcoinExplorer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpBitcoinExplorer.kt index 7ec04b0e9b..5df44323ae 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpBitcoinExplorer.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpBitcoinExplorer.kt @@ -18,7 +18,7 @@ * 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.amethyst.service.ots +package com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp import android.util.Log import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper @@ -61,10 +61,9 @@ class OkHttpBitcoinExplorer( val jsonObject = jacksonObjectMapper().readTree(it.body.string()) - val blockHeader = BlockHeader() - blockHeader.merkleroot = jsonObject["merkle_root"].asText() - blockHeader.setTime(jsonObject["timestamp"].asInt().toString()) - blockHeader.blockHash = hash + val merkleRoot = jsonObject["merkle_root"].asText() + val time = jsonObject["timestamp"].asInt().toString() + val blockHeader = BlockHeader(merkleRoot, hash, time) cache.cacheHeaders.put(hash, blockHeader) return blockHeader } else { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendar.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendar.kt index b8af7b7efc..20e8d3bf8e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendar.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendar.kt @@ -18,7 +18,7 @@ * 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.amethyst.service.ots +package com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp import com.vitorpamplona.quartz.nip03Timestamp.ots.ICalendar import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext @@ -30,6 +30,7 @@ import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException import com.vitorpamplona.quartz.utils.Hex import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient +import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody /** @@ -57,7 +58,7 @@ class OkHttpCalendar( val requestBody = digest.toRequestBody(mediaType) val request = - okhttp3.Request + Request .Builder() .header("Accept", "application/vnd.opentimestamps.v1") .header("Content-Type", "application/x-www-form-urlencoded") @@ -108,7 +109,7 @@ class OkHttpCalendar( val url = url + "/timestamp/" + Hex.encode(commitment) val request = - okhttp3.Request + Request .Builder() .header("Accept", "application/vnd.opentimestamps.v1") .header("Content-Type", "application/x-www-form-urlencoded") @@ -118,15 +119,10 @@ class OkHttpCalendar( client.newCall(request).execute().use { if (it.isSuccessful) { - val ctx = - StreamDeserializationContext( - it.body.bytes(), - ) + val ctx = StreamDeserializationContext(it.body.bytes()) return Timestamp.deserialize(ctx, commitment) } else { - throw CommitmentNotFoundException( - "Calendar response a status code != 200: " + it.code, - ) + throw CommitmentNotFoundException("Calendar response a status code != 200: " + it.code) } } } catch (e: DeserializationException) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarAsyncSubmit.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendarAsyncSubmit.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarAsyncSubmit.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendarAsyncSubmit.kt index c412fc0c00..4a8ca91009 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarAsyncSubmit.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendarAsyncSubmit.kt @@ -18,13 +18,14 @@ * 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.amethyst.service.ots +package com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp import com.vitorpamplona.quartz.nip03Timestamp.ots.ICalendarAsyncSubmit import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp import okhttp3.MediaType.Companion.toMediaType import okhttp3.OkHttpClient +import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import java.util.Optional @@ -42,7 +43,7 @@ class OkHttpCalendarAsyncSubmit( val requestBody = digest.toRequestBody(mediaType) val request = - okhttp3.Request + Request .Builder() .header("Accept", "application/vnd.opentimestamps.v1") .header("Content-Type", "application/x-www-form-urlencoded") diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendarBuilder.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendarBuilder.kt index 7b079f1db7..691056b874 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpCalendarBuilder.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpCalendarBuilder.kt @@ -18,7 +18,7 @@ * 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.amethyst.service.ots +package com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp import com.vitorpamplona.quartz.nip03Timestamp.ots.CalendarBuilder import com.vitorpamplona.quartz.nip03Timestamp.ots.ICalendar diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpOtsResolverBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpOtsResolverBuilder.kt similarity index 71% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpOtsResolverBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpOtsResolverBuilder.kt index d858cdd6b6..39af2cc8ff 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OkHttpOtsResolverBuilder.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OkHttpOtsResolverBuilder.kt @@ -18,15 +18,15 @@ * 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.amethyst.service.ots +package com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp -import com.vitorpamplona.amethyst.service.okhttp.DualHttpClientManager import com.vitorpamplona.quartz.nip03Timestamp.OtsResolver import com.vitorpamplona.quartz.nip03Timestamp.OtsResolverBuilder +import okhttp3.OkHttpClient class OkHttpOtsResolverBuilder( - val okHttpClients: DualHttpClientManager, - val shouldUseTorForUrl: (String) -> Boolean, + val okHttpClient: (url: String) -> OkHttpClient, + val isTorActive: (url: String) -> Boolean, val cache: OtsBlockHeightCache, ) : OtsResolverBuilder { fun getAPI(usingTor: Boolean) = @@ -36,20 +36,14 @@ class OkHttpOtsResolverBuilder( OkHttpBitcoinExplorer.BLOCKSTREAM_API_URL } - override fun build(): OtsResolver { - val shouldUseTor = shouldUseTorForUrl(OkHttpBitcoinExplorer.MEMPOOL_API_URL) - - return OtsResolver( + override fun build(): OtsResolver = + OtsResolver( explorer = OkHttpBitcoinExplorer( - baseAPI = getAPI(usingTor = shouldUseTor), - client = okHttpClients.getHttpClient(shouldUseTor), + baseAPI = getAPI(usingTor = isTorActive(OkHttpBitcoinExplorer.MEMPOOL_API_URL)), + client = okHttpClient(OkHttpBitcoinExplorer.MEMPOOL_API_URL), cache = cache, ), - calendarBuilder = - OkHttpCalendarBuilder { - okHttpClients.getHttpClient(shouldUseTorForUrl(it)) - }, + calendarBuilder = OkHttpCalendarBuilder { okHttpClient(it) }, ) - } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OtsBlockHeightCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OtsBlockHeightCache.kt similarity index 95% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OtsBlockHeightCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OtsBlockHeightCache.kt index b618fdb287..602f617110 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/ots/OtsBlockHeightCache.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/okhttp/OtsBlockHeightCache.kt @@ -18,7 +18,7 @@ * 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.amethyst.service.ots +package com.vitorpamplona.quartz.nip03Timestamp.ots.okhttp import android.util.LruCache import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/Op.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/Op.kt new file mode 100644 index 0000000000..7ec89956f5 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/Op.kt @@ -0,0 +1,162 @@ +/** + * 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.nip03Timestamp.ots.op + +import android.util.Log +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException + +/** + * Operations are the edges in the timestamp tree, with each operation taking a message and zero or more arguments to produce a result. + */ +abstract class Op : Comparable { + open fun tagName(): String = "" + + open fun tag(): Byte = TAG + + /** + * Serialize operation. + * + * @param ctx The stream serialization context. + */ + open fun serialize(ctx: StreamSerializationContext) { + if (this.tag().toInt() == 0x00) { + Log.e("OpenTimestamp", "No valid serialized Op") + // TODO: Is it OK to just log and carry on? Won't it blow up later? Better to throw? + } + + ctx.writeByte(this.tag()) + } + + /** + * Apply the operation to a message. + * Raises MsgValueError if the message value is invalid, such as it being + * too long, or it causing the result to be too long. + * + * @param msg The message. + * @return the msg after the operation has been applied + */ + open fun call(msg: ByteArray): ByteArray { + if (msg.size > MAX_MSG_LENGTH) { + Log.e("OpenTimestamp", "Error : Message too long;") + return byteArrayOf() // TODO: Is this OK? Won't it blow up later? Better to throw? + } + + val r = this.call(msg) + + if (r.size > MAX_RESULT_LENGTH) { + Log.e("OpenTimestamp", "Error : Result too long;") + // TODO: Is it OK to just log and carry on? Won't it blow up later? Better to throw? + } + + return r + } + + override fun compareTo(other: Op): Int = this.tag() - other.tag() + + companion object { + /** + * Maximum length of an com.vitorpamplona.quartz.ots.op.Op result + * + * + * For a verifier, this limit is what limits the maximum amount of memory you + * need at any one time to verify a particular timestamp path; while verifying + * a particular commitment operation path previously calculated results can be + * discarded. + * + * + * Of course, if everything was a merkle tree you never need to append/prepend + * anything near 4KiB of data; 64 bytes would be plenty even with SHA512. The + * main need for this is compatibility with existing systems like Bitcoin + * timestamps and Certificate Transparency servers. While the pathological + * limits required by both are quite large - 1MB and 16MiB respectively - 4KiB + * is perfectly adequate in both cases for more reasonable usage. + * + * + * @see Op subclasses should set this limit even lower if doing so is appropriate + * for them. + */ + val MAX_RESULT_LENGTH: Int = 4096 + + /** + * Maximum length of the message an com.vitorpamplona.quartz.ots.op.Op can be applied too. + * + * + * Similar to the result length limit, this limit gives implementations a sane + * constraint to work with; the maximum result-length limit implicitly + * constrains maximum message length anyway. + * + * + * com.vitorpamplona.quartz.ots.op.Op subclasses should set this limit even lower if doing so is appropriate + * for them. + */ + val MAX_MSG_LENGTH: Int = 4096 + + val TAG: Byte = 0x00.toByte() + + /** + * Deserialize operation from a buffer. + * + * @param ctx The stream deserialization context. + * @return The subclass Operation. + */ + @Throws(DeserializationException::class) + fun deserialize(ctx: StreamDeserializationContext): Op? { + val tag = ctx.readBytes(1)[0] + + return deserializeFromTag(ctx, tag) + } + + /** + * Deserialize operation from a buffer. + * + * @param ctx The stream deserialization context. + * @param tag The tag of the operation. + * @return The subclass Operation. + */ + @Throws(DeserializationException::class) + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? { + if (tag == OpAppend.TAG) { + return OpAppend.deserializeFromTag(ctx, tag) + } else if (tag == OpPrepend.TAG) { + return OpPrepend.deserializeFromTag(ctx, tag) + } else if (tag == OpSHA1.TAG) { + return OpSHA1.deserializeFromTag(ctx, tag) + } else if (tag == OpSHA256.TAG) { + return OpSHA256.deserializeFromTag(ctx, tag) + } else if (tag == OpRIPEMD160.TAG) { + return OpRIPEMD160.deserializeFromTag(ctx, tag) + } else if (tag == OpKECCAK256.TAG) { + return OpKECCAK256.deserializeFromTag(ctx, tag) + } else { + Log.e( + "OpenTimestamp", + "Unknown operation tag: " + tag + " 0x" + String.format("%02x", tag), + ) + return null // TODO: Is this OK? Won't it blow up later? Better to throw? + } + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpAppend.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpAppend.kt new file mode 100644 index 0000000000..2d5608b8ad --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpAppend.kt @@ -0,0 +1,60 @@ +/** + * 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.nip03Timestamp.ots.op + +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.arraysConcat +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException + +/** + * Append a suffix to a message. + * + * @see OpBinary + */ +class OpAppend( + arg: ByteArray = byteArrayOf(), +) : OpBinary(arg) { + public override fun tag(): Byte = TAG + + override fun tagName(): String = "append" + + public override fun call(msg: ByteArray): ByteArray = arraysConcat(msg, this.arg) + + override fun equals(other: Any?): Boolean { + if (other !is OpAppend) { + return false + } + + return this.arg.contentEquals(other.arg) + } + + override fun hashCode(): Int = TAG.toInt() xor this.arg.contentHashCode() + + companion object { + val TAG: Byte = 0xf0.toByte() + + @Throws(DeserializationException::class) + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = OpBinary.deserializeFromTag(ctx, tag) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpBinary.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpBinary.kt new file mode 100644 index 0000000000..323c24c1d6 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpBinary.kt @@ -0,0 +1,80 @@ +/** + * 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.nip03Timestamp.ots.op + +import android.util.Log +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.compare +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException +import com.vitorpamplona.quartz.utils.Hex.encode + +/** + * Operations that act on a message and a single argument. + * + * @see OpUnary + */ +abstract class OpBinary( + val arg: ByteArray = byteArrayOf(), +) : Op(), + Comparable { + public override fun tagName(): String = "" + + public override fun serialize(ctx: StreamSerializationContext) { + super.serialize(ctx) + ctx.writeVarbytes(this.arg) + } + + override fun toString(): String = this.tagName() + ' ' + encode(this.arg).lowercase() + + override fun compareTo(other: Op): Int { + if (other is OpBinary && this.tag() == other.tag()) { + return compare(this.arg, other.arg) + } + + return this.tag() - other.tag() + } + + override fun hashCode(): Int = TAG.toInt() xor this.arg.contentHashCode() + + companion object { + @JvmStatic + @Throws(DeserializationException::class) + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? { + val arg = ctx.readVarbytes(MAX_RESULT_LENGTH, 1) + + if (tag == OpAppend.TAG) { + return OpAppend(arg) + } else if (tag == OpPrepend.TAG) { + return OpPrepend(arg) + } else { + Log.e( + "OpenTimestamp", + "Unknown operation tag: " + tag + " 0x" + String.format("%02x", tag), + ) + return null // TODO: Is this OK? Won't it blow up later? Better to throw? + } + } + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt new file mode 100644 index 0000000000..a54907c44d --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.kt @@ -0,0 +1,109 @@ +/** + * 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.nip03Timestamp.ots.op + +import android.util.Log +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException + +/** + * Cryptographic transformations. + * These transformations have the unique property that for any length message, + * the size of the result they return is fixed. Additionally, they're the only + * type of operation that can be applied directly to a stream. + * + * @see OpUnary + */ +abstract class OpCrypto internal constructor() : OpUnary() { + abstract fun hashLibName(): String + + abstract fun digestLength(): Int + + override fun call(msg: ByteArray): ByteArray { + // For Sha1 & Sha256 use java.security.MessageDigest library + try { + val digest = MessageDigest.getInstance(this.hashLibName()) + val hash = digest.digest(msg) + + return hash + } catch (e: NoSuchAlgorithmException) { + Log.e("OpenTimestamp", "NoSuchAlgorithmException") + e.printStackTrace() + + return byteArrayOf() // TODO: Is this OK? Won't it blow up later? Better to throw? + } + } + + @Throws(NoSuchAlgorithmException::class) + fun hashFd(ctx: StreamDeserializationContext): ByteArray { + val digest = MessageDigest.getInstance(this.hashLibName()) + var chunk = ctx.read(1048576) + + while (chunk != null && chunk.size > 0) { + digest.update(chunk) + chunk = ctx.read(1048576) + } + + val hash = digest.digest() + + return hash + } + + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun hashFd(file: File?): ByteArray = hashFd(FileInputStream(file)) + + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun hashFd(bytes: ByteArray): ByteArray { + val ctx = StreamDeserializationContext(bytes) + + return hashFd(ctx) + } + + @Throws(IOException::class, NoSuchAlgorithmException::class) + fun hashFd(inputStream: InputStream): ByteArray { + val digest = MessageDigest.getInstance(this.hashLibName()) + val chunk = ByteArray(1048576) + var count = inputStream.read(chunk, 0, 1048576) + + while (count >= 0) { + digest.update(chunk, 0, count) + count = inputStream.read(chunk, 0, 1048576) + } + + inputStream.close() + val hash = digest.digest() + + return hash + } + + companion object { + @JvmStatic + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = OpUnary.deserializeFromTag(ctx, tag) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpKECCAK256.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpKECCAK256.kt new file mode 100644 index 0000000000..78a65dfb59 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpKECCAK256.kt @@ -0,0 +1,62 @@ +/** + * 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.nip03Timestamp.ots.op + +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.KeccakDigest + +/** + * Cryptographic Keccak256 operation. + * Cryptographic operation tag numbers taken from RFC4880, although it's not + * guaranteed that they'll continue to match that RFC in the future. + * + * @see OpCrypto + */ +class OpKECCAK256 : OpCrypto() { + private val digest = KeccakDigest(256) + + override fun tag(): Byte = TAG + + override fun tagName(): String = "keccak256" + + override fun hashLibName(): String = "keccak256" + + override fun digestLength(): Int = digest.getDigestSize() + + override fun call(msg: ByteArray): ByteArray { + digest.update(msg, 0, msg.size) + val hash = ByteArray(digest.getDigestSize()) + digest.doFinal(hash, 0) + + return hash + } + + override fun equals(other: Any?): Boolean = (other is OpKECCAK256) + + companion object { + val TAG: Byte = 103.toByte() + + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = OpCrypto.deserializeFromTag(ctx, tag) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpPrepend.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpPrepend.kt new file mode 100644 index 0000000000..e88d1c6b37 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpPrepend.kt @@ -0,0 +1,60 @@ +/** + * 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.nip03Timestamp.ots.op + +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils.arraysConcat +import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException + +/** + * Prepend a prefix to a message. + * + * @see OpBinary + */ +class OpPrepend( + arg: ByteArray = byteArrayOf(), +) : OpBinary(arg) { + public override fun tag(): Byte = TAG + + override fun tagName(): String = "prepend" + + public override fun call(msg: ByteArray): ByteArray = arraysConcat(this.arg, msg) + + override fun equals(other: Any?): Boolean { + if (other !is OpPrepend) { + return false + } + + return this.arg.contentEquals(other.arg) + } + + public override fun hashCode(): Int = TAG.toInt() xor this.arg.contentHashCode() + + companion object { + val TAG: Byte = 0xf1.toByte() + + @Throws(DeserializationException::class) + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = OpBinary.deserializeFromTag(ctx, tag) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpRIPEMD160.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpRIPEMD160.kt new file mode 100644 index 0000000000..6eee1675de --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpRIPEMD160.kt @@ -0,0 +1,63 @@ +/** + * 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.nip03Timestamp.ots.op + +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext +import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.RIPEMD160Digest + +/** + * Cryptographic RIPEMD160 operation. + * Cryptographic operation tag numbers taken from RFC4880, although it's not + * guaranteed that they'll continue to match that RFC in the future. + * + * @see OpCrypto + */ +class OpRIPEMD160 : OpCrypto() { + override fun tag(): Byte = TAG + + override fun tagName(): String = "ripemd160" + + override fun hashLibName(): String = "ripemd160" + + override fun digestLength(): Int = 20 + + override fun call(msg: ByteArray): ByteArray { + val digest = RIPEMD160Digest() + digest.update(msg, 0, msg.size) + val hash = ByteArray(digest.getDigestSize()) + digest.doFinal(hash, 0) + + return hash + } + + override fun equals(other: Any?): Boolean = (other is OpRIPEMD160) + + override fun hashCode(): Int = TAG.toInt() + + companion object { + val TAG: Byte = 0x03 + + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = OpCrypto.deserializeFromTag(ctx, tag) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA1.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA1.kt new file mode 100644 index 0000000000..078a737550 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA1.kt @@ -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.nip03Timestamp.ots.op + +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext + +/** + * Cryptographic SHA1 operation. + * Cryptographic operation tag numbers taken from RFC4880, although it's not + * guaranteed that they'll continue to match that RFC in the future. + * Remember that for timestamping, hash algorithms with collision attacks + * *are* secure! We've still proven that both messages existed prior to some + * point in time - the fact that they both have the same hash digest doesn't + * change that. + * Heck, even md5 is still secure enough for timestamping... but that's + * pushing our luck... + * + * @see OpCrypto + */ +class OpSHA1 : OpCrypto() { + public override fun tag(): Byte = TAG + + override fun tagName(): String = "sha1" + + public override fun hashLibName(): String = "SHA-1" + + public override fun digestLength(): Int = 20 + + override fun call(msg: ByteArray): ByteArray = super.call(msg) + + override fun equals(other: Any?): Boolean = (other is OpSHA1) + + override fun hashCode(): Int = TAG.toInt() + + companion object { + val TAG: Byte = 0x02 + + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = OpCrypto.deserializeFromTag(ctx, tag) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA256.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA256.kt new file mode 100644 index 0000000000..6fe98a6c9a --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA256.kt @@ -0,0 +1,55 @@ +/** + * 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.nip03Timestamp.ots.op + +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext + +/** + * Cryptographic SHA256 operation. + * Cryptographic operation tag numbers taken from RFC4880, although it's not + * guaranteed that they'll continue to match that RFC in the future. + * + * @see OpCrypto + */ +class OpSHA256 : OpCrypto() { + override fun tag(): Byte = TAG + + override fun tagName(): String = "sha256" + + override fun hashLibName(): String = "SHA-256" + + override fun digestLength(): Int = 32 + + override fun call(msg: ByteArray): ByteArray = super.call(msg) + + override fun equals(other: Any?): Boolean = (other is OpSHA256) + + override fun hashCode(): Int = TAG.toInt() + + companion object { + val TAG: Byte = 0x08 + + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = OpCrypto.deserializeFromTag(ctx, tag) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpUnary.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpUnary.kt new file mode 100644 index 0000000000..bdd23920b8 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpUnary.kt @@ -0,0 +1,53 @@ +/** + * 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.nip03Timestamp.ots.op + +import android.util.Log +import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext + +/** + * Operations that act on a single message. + * + * @see Op + */ +abstract class OpUnary : Op() { + override fun tagName(): String = "" + + override fun toString(): String = tagName() + + companion object { + fun deserializeFromTag( + ctx: StreamDeserializationContext, + tag: Byte, + ): Op? = + when (tag) { + OpSHA1.TAG -> OpSHA1() + OpSHA256.TAG -> OpSHA256() + OpRIPEMD160.TAG -> OpRIPEMD160() + OpKECCAK256.TAG -> OpKECCAK256() + else -> { + Log.e("OpenTimestamp", "Unknown operation tag: $tag") + + null // TODO: Is this OK? Won't it blow up later? Better to throw? + } + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventKindTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventKindTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventKindTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventKindTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip03Timestamp/tags/TargetEventTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/PrivateDMCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/PrivateDMCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/PrivateDMCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/PrivateDMCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/AESCBC.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/AESCBC.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/AESCBC.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/AESCBC.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/EncryptedInfo.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/EncryptedInfo.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/EncryptedInfo.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/EncryptedInfo.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/Encryption.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/Encryption.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/Encryption.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/Encryption.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/Nip04.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/Nip04.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/crypto/Nip04.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/crypto/Nip04.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/messages/PrivateDmEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/messages/PrivateDmEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/messages/PrivateDmEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/messages/PrivateDmEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/messages/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/messages/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip04Dm/messages/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip04Dm/messages/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/Nip05.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivation.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivation.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivation.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip32SeedDerivation.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPath.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPath.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPath.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39KeyPath.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Bip39Mnemonics.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip06KeyDerivation/Nip06.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip09Deletions/DeletionEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip09Deletions/DeletionEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip09Deletions/DeletionEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip09Deletions/DeletionEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip09Deletions/DeletionIndex.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip09Deletions/DeletionIndex.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip09Deletions/DeletionIndex.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip09Deletions/DeletionIndex.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/BaseNoteEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/BaseNoteEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/BaseNoteEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/BaseNoteEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/BaseThreadedEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/BaseThreadedEvent.kt similarity index 93% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/BaseThreadedEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/BaseThreadedEvent.kt index 072cf6fbe4..92ec393ef7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/BaseThreadedEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/BaseThreadedEvent.kt @@ -72,16 +72,18 @@ open class BaseThreadedEvent( return newStyleReply ?: newStyleRoot ?: oldStylePositional } - fun tagsWithoutCitations(): List { + open fun tagsWithoutCitations(): List { val certainRepliesTo = markedReplyTos() val uncertainRepliesTo = unmarkedReplyTos() val tagAddresses = taggedATags() - .filter { - it.kind != CommunityDefinitionEvent.KIND && (kind != WikiNoteEvent.KIND || it.kind != WikiNoteEvent.KIND) + .filter { aTag -> + aTag.kind != CommunityDefinitionEvent.KIND && (kind != WikiNoteEvent.KIND || aTag.kind != WikiNoteEvent.KIND) // removes forks from itself. - }.map { it.toTag() } + }.map { + it.toTag() + } if (certainRepliesTo.isEmpty() && uncertainRepliesTo.isEmpty() && tagAddresses.isEmpty()) return emptyList() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/TextNoteEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/ContentHashTags.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/ContentHashTags.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/ContentHashTags.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/ContentHashTags.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/IndexedTags.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/IndexedTags.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/IndexedTags.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/IndexedTags.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/NostrUris.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/NostrUris.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/NostrUris.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/NostrUris.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/Urls.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/Urls.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/content/Urls.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/content/Urls.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/MarkedETag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/Positional.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/ReplyBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/ReplyBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/ReplyBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/ReplyBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip10Notes/tags/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip11RelayInfo/Nip11RelayInformation.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip11RelayInfo/Nip11RelayInformation.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip11RelayInfo/Nip11RelayInformation.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip11RelayInfo/Nip11RelayInformation.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/ByteArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/ByteArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/ByteArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/ByteArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/MiningBuffer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/MiningBuffer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/MiningBuffer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/MiningBuffer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/PoWMiner.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/PoWMiner.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/PoWMiner.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/PoWMiner.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/PoWRankEvaluator.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/PoWRankEvaluator.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/miner/PoWRankEvaluator.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/miner/PoWRankEvaluator.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip13Pow/tags/PoWTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/SubjectTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip14Subject/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip14Subject/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/NIP17Factory.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/NIP17Factory.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/NIP17Factory.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/NIP17Factory.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/BaseDMGroupEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/BaseDMGroupEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/BaseDMGroupEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/BaseDMGroupEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKey.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKey.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKey.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKey.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKeyable.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKeyable.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKeyable.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/ChatroomKeyable.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/NIP17Group.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/NIP17Group.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/base/NIP17Group.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/base/NIP17Group.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/ChatMessageEncryptedFileHeaderEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/ChatMessageEncryptedFileHeaderEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/ChatMessageEncryptedFileHeaderEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/ChatMessageEncryptedFileHeaderEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/encryption/AESGCM.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/encryption/AESGCM.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/encryption/AESGCM.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/encryption/AESGCM.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/encryption/NostrCipher.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/encryption/NostrCipher.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/encryption/NostrCipher.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/encryption/NostrCipher.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionAlgo.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionKey.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/EncryptionNonce.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/files/tags/FileTypeTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/messages/ChatMessageEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/messages/ChatMessageEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/messages/ChatMessageEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/messages/ChatMessageEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/messages/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/messages/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/messages/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/messages/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/settings/ChatMessageRelayListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip17Dm/settings/tags/RelayTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/GenericRepostEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/GenericRepostEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/GenericRepostEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/GenericRepostEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/RepostEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/RepostEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/RepostEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/RepostEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/EntityExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/EntityExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/EntityExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/EntityExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/QAddressableTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/QEventTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/QTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip18Reposts/quotes/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/ATagExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/ATagExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/ATagExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/ATagExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/ByteArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/ByteArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/ByteArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/ByteArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/ListEntityExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/ListEntityExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/ListEntityExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/ListEntityExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/Nip19Parser.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/Nip19Parser.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/Nip19Parser.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/Nip19Parser.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/TlvBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/TlvBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/TlvBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/TlvBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/TlvTypes.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/TlvTypes.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/TlvTypes.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/TlvTypes.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/bech32/Bech32Util.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/bech32/Bech32Util.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/bech32/Bech32Util.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/bech32/Bech32Util.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/Entity.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/Entity.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/Entity.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/Entity.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NAddress.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NAddress.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NAddress.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NAddress.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NEmbed.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NEmbed.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NEmbed.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NEmbed.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NNote.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NNote.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NNote.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NNote.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NProfile.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NProfile.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NProfile.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NProfile.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NPub.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NPub.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NPub.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NPub.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NRelay.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NRelay.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NRelay.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NRelay.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NSec.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NSec.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/entities/NSec.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/entities/NSec.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/tlv/Tlv.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/tlv/Tlv.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/tlv/Tlv.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/tlv/Tlv.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/tlv/TlvBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/tlv/TlvBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip19Bech32/tlv/TlvBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip19Bech32/tlv/TlvBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip21UriScheme/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip21UriScheme/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip21UriScheme/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip21UriScheme/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt similarity index 85% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt index b68eb4a34f..64a2dd0bd4 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/CommentEvent.kt @@ -25,6 +25,7 @@ import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent import com.vitorpamplona.quartz.nip01Core.core.Event import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.core.any import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider import com.vitorpamplona.quartz.nip01Core.hints.EventHintBundle import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider @@ -33,6 +34,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint import com.vitorpamplona.quartz.nip01Core.hints.types.PubKeyHint import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address import com.vitorpamplona.quartz.nip01Core.tags.geohash.GeoHashTag import com.vitorpamplona.quartz.nip10Notes.BaseThreadedEvent import com.vitorpamplona.quartz.nip18Reposts.quotes.QTag @@ -55,10 +57,12 @@ import com.vitorpamplona.quartz.nip22Comments.tags.RootIdentifierTag import com.vitorpamplona.quartz.nip22Comments.tags.RootKindTag import com.vitorpamplona.quartz.nip31Alts.alt import com.vitorpamplona.quartz.nip50Search.SearchableEvent +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.nip73ExternalIds.ExternalId import com.vitorpamplona.quartz.nip73ExternalIds.location.GeohashId import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.lastNotNullOfOrNull +import kotlin.collections.mapNotNull @Immutable class CommentEvent( @@ -126,6 +130,28 @@ class CommentEvent( return aHints + qHints + nip19Hints } + fun rootEventIds() = tags.mapNotNull(RootEventTag::parseKey) + + fun replyEventIds() = tags.mapNotNull(ReplyEventTag::parseKey) + + fun rootAddressIds() = tags.mapNotNull(RootAddressTag::parseAddressId) + + fun hasRootAddress(addressId: String) = tags.any(RootAddressTag::isTagged, addressId) + + fun hasReplyAddress(addressId: String) = tags.any(ReplyAddressTag::isTagged, addressId) + + fun replyAddressIds() = tags.mapNotNull(ReplyAddressTag::parseAddressId) + + fun replyAddress() = tags.mapNotNull(ReplyAddressTag::parseAddress) + + fun rootScopes() = tags.filter { RootIdentifierTag.match(it) || RootAddressTag.match(it) || RootEventTag.match(it) } + + fun rootKinds() = tags.filter(RootKindTag::match) + + fun directReplies() = tags.filter { ReplyIdentifierTag.match(it) || ReplyAddressTag.match(it) || ReplyEventTag.match(it) } + + fun directKinds() = tags.filter(ReplyKindTag::match) + fun rootAuthor() = tags.firstNotNullOfOrNull(RootAuthorTag::parse) fun replyAuthor() = tags.firstNotNullOfOrNull(ReplyAuthorTag::parse) @@ -142,14 +168,6 @@ class CommentEvent( fun replyAuthorHints() = tags.mapNotNull(ReplyAuthorTag::parseAsHint) - fun rootScopes() = tags.filter { RootIdentifierTag.match(it) || RootAddressTag.match(it) || RootEventTag.match(it) } - - fun rootKinds() = tags.filter(RootKindTag::match) - - fun directReplies() = tags.filter { ReplyIdentifierTag.match(it) || ReplyAddressTag.match(it) || ReplyEventTag.match(it) } - - fun directKinds() = tags.filter(ReplyKindTag::match) - /** root and reply scope search */ fun isTaggedScope(scopeId: String) = tags.any { RootIdentifierTag.isTagged(it, scopeId) || ReplyIdentifierTag.isTagged(it, scopeId) } @@ -164,9 +182,9 @@ class CommentEvent( fun isScoped(scopeTest: (String) -> Boolean) = tags.any { RootIdentifierTag.isTagged(it, scopeTest) || ReplyIdentifierTag.isTagged(it, scopeTest) } - fun hasRootScopeKind(kind: String) = tags.any { RootKindTag.isKind(it, kind) } + fun hasRootScopeKind(kind: String) = tags.any(RootKindTag::isKind, kind) - fun hasReplyScopeKind(kind: String) = tags.any { ReplyKindTag.isKind(it, kind) } + fun hasReplyScopeKind(kind: String) = tags.any(ReplyKindTag::isKind, kind) fun hasScopeKind(kind: String) = tags.any { RootKindTag.isKind(it, kind) || ReplyKindTag.isKind(it, kind) } @@ -184,12 +202,31 @@ class CommentEvent( tags.lastNotNullOfOrNull(ReplyEventTag::parseKey) ?: tags.lastNotNullOfOrNull(RootEventTag::parseKey) + fun rootAddress() = tags.mapNotNull(RootAddressTag::parseAddress) + + fun rootAddressId() = tags.mapNotNull(RootAddressTag::parseAddressId) + fun replyingToAddressId(): String? = - tags.lastNotNullOfOrNull(RootAddressTag::parseAddressId) - ?: tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddressId) + tags.lastNotNullOfOrNull(ReplyAddressTag::parseAddressId) + ?: tags.lastNotNullOfOrNull(RootAddressTag::parseAddressId) override fun replyingToAddressOrEvent(): HexKey? = replyingToAddressId() ?: replyingTo() + override fun tagsWithoutCitations(): List { + val rootAddress = rootAddressIds() + val replyAddress = replyAddressIds() + + if ( + rootAddress.any { Address.isOfKind(it, CommunityDefinitionEvent.KIND_STR) } && + replyAddress.any { Address.isOfKind(it, CommunityDefinitionEvent.KIND_STR) } + ) { + // this is the root of the community post + return emptyList() + } + + return rootAddress + replyAddress + rootEventIds() + replyEventIds() + } + companion object { const val KIND = 1111 const val ALT = "Reply to " diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/RootScope.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/RootScope.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/RootScope.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/RootScope.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt similarity index 88% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt index 4582ce1733..4035d3ce6f 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAddressTag.kt @@ -44,6 +44,12 @@ class ReplyAddressTag( @JvmStatic fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() + @JvmStatic + fun isTagged( + tag: Array, + addressId: String, + ) = tag.has(1) && tag[0] == RootAddressTag.Companion.TAG_NAME && tag[1] == addressId + @JvmStatic fun parse(tag: Array): ReplyAddressTag? { ensure(tag.has(1)) { return null } @@ -55,6 +61,15 @@ class ReplyAddressTag( return ReplyAddressTag(tag[1], relayHint) } + @JvmStatic + fun parseAddress(tag: Array): Address? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + + return Address.parse(tag[1]) + } + @JvmStatic fun parseAddressId(tag: Array): String? { ensure(tag.has(1)) { return null } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyAuthorTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyEventTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyIdentifierTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/ReplyKindTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt similarity index 93% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt index 9278c3e0cd..04688cb9eb 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootAddressTag.kt @@ -67,6 +67,15 @@ class RootAddressTag( return RootAddressTag(tag[1], relayHint) } + @JvmStatic + fun parseAddress(tag: Array): Address? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + + return Address.parse(tag[1]) + } + @JvmStatic fun parseValidAddress(tag: Array): String? { ensure(tag.has(1)) { return null } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootAuthorTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootEventTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootIdentifierTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip22Comments/tags/RootKindTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/LongTextNoteEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/LongTextNoteEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/LongTextNoteEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/LongTextNoteEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/ImageTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/PublishedAtTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/SummaryTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip23LongContent/tags/TitleTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip25Reactions/ReactionEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip25Reactions/ReactionEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip25Reactions/ReactionEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip25Reactions/ReactionEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelCreateEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelCreateEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelCreateEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelCreateEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelHideMessageEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelHideMessageEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelHideMessageEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelHideMessageEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMetadataEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMetadataEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMetadataEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMetadataEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMuteUserEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMuteUserEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMuteUserEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/admin/ChannelMuteUserEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/BasePublicChatEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/BasePublicChatEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/BasePublicChatEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/BasePublicChatEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/ChannelData.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/ChannelData.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/ChannelData.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/ChannelData.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/IsInPublicChatChannel.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/IsInPublicChatChannel.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/IsInPublicChatChannel.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/IsInPublicChatChannel.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/base/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/base/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/ChannelListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/tags/ChannelTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/tags/ChannelTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/list/tags/ChannelTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/list/tags/ChannelTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/message/ChannelMessageEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/message/ChannelMessageEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip28PublicChat/message/ChannelMessageEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip28PublicChat/message/ChannelMessageEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/CustomEmoji.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/CustomEmoji.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/CustomEmoji.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/CustomEmoji.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/EmojiUrlTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/EmojiUrlTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/EmojiUrlTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/EmojiUrlTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/pack/EmojiPackEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/selection/EmojiPackSelectionEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/selection/EmojiPackSelectionEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/selection/EmojiPackSelectionEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/selection/EmojiPackSelectionEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/selection/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/selection/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip30CustomEmoji/selection/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip30CustomEmoji/selection/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/AltTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/AltTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/AltTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip31Alts/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip31Alts/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/issue/GitIssueEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/issue/GitIssueEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/issue/GitIssueEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/issue/GitIssueEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/issue/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/issue/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/issue/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/issue/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/patch/GitPatchEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/patch/GitPatchEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/patch/GitPatchEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/patch/GitPatchEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/reply/GitReplyEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/reply/GitReplyEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/reply/GitReplyEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/reply/GitReplyEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/reply/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/reply/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/reply/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/reply/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/GitRepositoryEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/GitRepositoryEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/GitRepositoryEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/GitRepositoryEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/CloneTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/DescriptionTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/NameTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip34Git/repository/tags/WebTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/DefaultTrackers.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/DefaultTrackers.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/DefaultTrackers.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/DefaultTrackers.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/TorrentCommentEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/TorrentCommentEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/TorrentCommentEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/TorrentCommentEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/TorrentEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/TorrentEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/TorrentEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/TorrentEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/BtihTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/FileTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/InfoHashTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip35Torrents/tags/TrackerTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/ContentWarningTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/ContentWarningTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/ContentWarningTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/ContentWarningTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip36SensitiveContent/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/DraftWrapCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/DraftWrapCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/DraftWrapEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/ExposeInDraft.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/ExposeInDraft.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/ExposeInDraft.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/ExposeInDraft.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/privateOutbox/PrivateOutboxRelayListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/privateOutbox/PrivateOutboxRelayListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip37Drafts/privateOutbox/PrivateOutboxRelayListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip37Drafts/privateOutbox/PrivateOutboxRelayListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip38UserStatus/StatusEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip38UserStatus/StatusEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip38UserStatus/StatusEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip38UserStatus/StatusEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/GitHubIdentity.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/GitHubIdentity.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/GitHubIdentity.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/GitHubIdentity.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/IdentityClaimTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/IdentityClaimTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/IdentityClaimTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/IdentityClaimTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/MastodonIdentity.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/MastodonIdentity.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/MastodonIdentity.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/MastodonIdentity.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/TelegramIdentity.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/TelegramIdentity.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/TelegramIdentity.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/TelegramIdentity.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/TwitterIdentity.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/TwitterIdentity.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/TwitterIdentity.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/TwitterIdentity.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/UnsupportedIdentity.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/UnsupportedIdentity.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip39ExtIdentities/UnsupportedIdentity.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip39ExtIdentities/UnsupportedIdentity.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/ExpirationTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/ExpirationTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/ExpirationTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/ExpirationTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip40Expiration/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip40Expiration/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/RelayAuthEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip42RelayAuth/RelayAuthEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/RelayAuthEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip42RelayAuth/RelayAuthEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/ChallengeTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip42RelayAuth/tags/ChallengeTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/ChallengeTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip42RelayAuth/tags/ChallengeTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip42RelayAuth/tags/RelayTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/EncryptedInfoString.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/EncryptedInfoString.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/EncryptedInfoString.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/EncryptedInfoString.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v1.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v2.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v2.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/Nip44v2.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/Nip44v2.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/SharedKeyCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/SharedKeyCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/SharedKeyCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/SharedKeyCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/crypto/Hkdf.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/crypto/Hkdf.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip44Encryption/crypto/Hkdf.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip44Encryption/crypto/Hkdf.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerMessage.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerMessage.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerMessage.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerMessage.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequest.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequest.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequest.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequest.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestConnect.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestConnect.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestConnect.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestConnect.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetPublicKey.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetPublicKey.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetPublicKey.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetPublicKey.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetRelays.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetRelays.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetRelays.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestGetRelays.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Decrypt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Decrypt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Decrypt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Decrypt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Encrypt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Encrypt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Encrypt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip04Encrypt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Decrypt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Decrypt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Decrypt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Decrypt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Encrypt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Encrypt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Encrypt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestNip44Encrypt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestPing.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestPing.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestPing.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestPing.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestSign.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestSign.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestSign.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerRequestSign.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponse.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponse.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponse.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponse.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseAck.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseAck.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseAck.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseAck.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseDecrypt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseDecrypt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseDecrypt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseDecrypt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEncrypt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEncrypt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEncrypt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEncrypt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseError.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseError.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseError.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseError.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseGetRelays.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseGetRelays.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseGetRelays.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponseGetRelays.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePong.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePong.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePong.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePong.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePublicKey.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePublicKey.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePublicKey.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/BunkerResponsePublicKey.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/NostrConnectEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/NostrConnectEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip46RemoteSigner/NostrConnectEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip46RemoteSigner/NostrConnectEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentRequestEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentRequestEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentRequestEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentRequestEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentResponseEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentResponseEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentResponseEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/LnZapPaymentResponseEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Nip47WalletConnect.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/Nip47WalletConnect.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Nip47WalletConnect.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/Nip47WalletConnect.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectRequestCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectRequestCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectRequestCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectRequestCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectResponseCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectResponseCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectResponseCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/NostrWalletConnectResponseCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Request.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/Request.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Request.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/Request.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/RequestDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/RequestDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/RequestDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/RequestDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/Response.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/ResponseDeserializer.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/ResponseDeserializer.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip47WalletConnect/ResponseDeserializer.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip47WalletConnect/ResponseDeserializer.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/ProxyTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/ProxyTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/ProxyTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/ProxyTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip48ProxyTags/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/ByteArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/ByteArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/ByteArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/ByteArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/Nip49.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/Nip49.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/Nip49.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/Nip49.kt diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.kt new file mode 100644 index 0000000000..bcfbcfc6e3 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.kt @@ -0,0 +1,121 @@ +/** + * 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.nip49PrivKeyEnc + +import java.security.GeneralSecurityException +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import kotlin.math.ceil +import kotlin.math.pow + +/** + * An implementation of the Password-Based Key Derivation Function as specified + * in RFC 2898. + * + * @author Will Glozer + */ +object PBKDF { + /** + * Implementation of PBKDF2 (RFC2898). + * + * @param alg HMAC algorithm to use. + * @param password Password. + * @param salt Salt. + * @param iterationCount Iteration count. + * @param dkLen Intended length, in octets, of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException + */ + @Throws(GeneralSecurityException::class) + fun pbkdf2( + alg: String, + password: ByteArray, + salt: ByteArray, + iterationCount: Int, + dkLen: Int, + ): ByteArray { + val mac = Mac.getInstance(alg) + mac.init(SecretKeySpec(password, alg)) + val destKey = ByteArray(dkLen) + pbkdf2(mac, salt, iterationCount, destKey, dkLen) + return destKey + } + + /** + * Implementation of PBKDF2 (RFC2898). + * + * @param mac Pre-initialized [Mac] instance to use. + * @param salt Salt. + * @param c Iteration count. + * @param destKey Byte array that derived key will be placed in. + * @param dkLen Intended length, in octets, of the derived key. + * + * @throws GeneralSecurityException + */ + @JvmStatic + @Throws(GeneralSecurityException::class) + fun pbkdf2( + mac: Mac, + salt: ByteArray, + c: Int, + destKey: ByteArray, + dkLen: Int, + ) { + val hLen = mac.getMacLength() + + if (dkLen > (2.0.pow(32.0) - 1) * hLen) { + throw GeneralSecurityException("Requested key length too long") + } + + val uArray = ByteArray(hLen) + val rArray = ByteArray(hLen) + val block1 = ByteArray(salt.size + 4) + + val l = ceil(dkLen.toDouble() / hLen).toInt() + val r = dkLen - (l - 1) * hLen + + System.arraycopy(salt, 0, block1, 0, salt.size) + + for (i in 1..l) { + block1[salt.size + 0] = (i shr 24 and 0xff).toByte() + block1[salt.size + 1] = (i shr 16 and 0xff).toByte() + block1[salt.size + 2] = (i shr 8 and 0xff).toByte() + block1[salt.size + 3] = (i shr 0 and 0xff).toByte() + + mac.update(block1) + mac.doFinal(uArray, 0) + System.arraycopy(uArray, 0, rArray, 0, hLen) + + for (j in 1.. and + * fall back to the pure Java version if that fails. + * + * @author Will Glozer +](http://www.tarsnap.com/scrypt.html) */ +object SCrypt { + /** + * Implementation of the [](http://www.tarsnap.com/scrypt/scrypt.pdf)scrypt KDF. + * + * @param passwd Password. + * @param salt Salt. + * @param n CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * @param dkLen Intended length of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + @Throws(GeneralSecurityException::class) + fun scrypt( + passwd: ByteArray, + salt: ByteArray, + n: Int, + r: Int, + p: Int, + dkLen: Int, + ): ByteArray = scryptJ(passwd, salt, n, r, p, dkLen) + + /** + * Pure Java implementation of the [](http://www.tarsnap.com/scrypt/scrypt.pdf)scrypt KDF. + * + * @param passwd Password. + * @param salt Salt. + * @param n CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * @param dkLen Intended length of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + @Throws(GeneralSecurityException::class) + fun scryptJ( + passwd: ByteArray, + salt: ByteArray, + n: Int, + r: Int, + p: Int, + dkLen: Int, + ): ByteArray { + require(!(n < 2 || (n and (n - 1)) != 0)) { "N must be a power of 2 greater than 1" } + + require(n <= Int.Companion.MAX_VALUE / 128 / r) { "Parameter N is too large" } + require(r <= Int.Companion.MAX_VALUE / 128 / p) { "Parameter r is too large" } + + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeyOrEmptySpec(passwd, "HmacSHA256")) + + val deskKey = ByteArray(dkLen) + + val bArray = ByteArray(128 * r * p) + val xyArray = ByteArray(256 * r) + val vArray = ByteArray(128 * r * n) + var i: Int + + pbkdf2(mac, salt, 1, bArray, p * 128 * r) + + i = 0 + while (i < p) { + smix(bArray, i * 128 * r, r, n, vArray, xyArray) + i++ + } + + pbkdf2(mac, bArray, 1, deskKey, dkLen) + + return deskKey + } + + fun smix( + bArray: ByteArray, + biArray: Int, + r: Int, + n: Int, + vArray: ByteArray, + xyArray: ByteArray, + ) { + val xIndex = 0 + val yIndex = 128 * r + var i: Int + + System.arraycopy(bArray, biArray, xyArray, xIndex, 128 * r) + + i = 0 + while (i < n) { + System.arraycopy(xyArray, xIndex, vArray, i * (128 * r), 128 * r) + blockmixSalsa8(xyArray, xIndex, yIndex, r) + i++ + } + + i = 0 + while (i < n) { + val j = integerify(xyArray, xIndex, r) and (n - 1) + blockxor(vArray, j * (128 * r), xyArray, xIndex, 128 * r) + blockmixSalsa8(xyArray, xIndex, yIndex, r) + i++ + } + + System.arraycopy(xyArray, xIndex, bArray, biArray, 128 * r) + } + + fun blockmixSalsa8( + byArray: ByteArray, + biArray: Int, + yi: Int, + r: Int, + ) { + val xArray = ByteArray(64) + var i: Int + + System.arraycopy(byArray, biArray + (2 * r - 1) * 64, xArray, 0, 64) + + i = 0 + while (i < 2 * r) { + blockxor(byArray, i * 64, xArray, 0, 64) + salsa20x8(xArray) + System.arraycopy(xArray, 0, byArray, yi + (i * 64), 64) + i++ + } + + i = 0 + while (i < r) { + System.arraycopy(byArray, yi + (i * 2) * 64, byArray, biArray + (i * 64), 64) + i++ + } + + i = 0 + while (i < r) { + System.arraycopy(byArray, yi + (i * 2 + 1) * 64, byArray, biArray + (i + r) * 64, 64) + i++ + } + } + + // NOT SURE IF THIS IS ACTUALLY A ROTATE. Ignore the name + fun rotate( + a: Int, + b: Int, + ): Int = (a shl b) or (a ushr (32 - b)) + + fun salsa20x8(bArray: ByteArray) { + val b32 = IntArray(16) + val x = IntArray(16) + var i: Int + + i = 0 + while (i < 16) { + b32[i] = (bArray[i * 4 + 0].toInt() and 0xff) shl 0 + b32[i] = b32[i] or ((bArray[i * 4 + 1].toInt() and 0xff) shl 8) + b32[i] = b32[i] or ((bArray[i * 4 + 2].toInt() and 0xff) shl 16) + b32[i] = b32[i] or ((bArray[i * 4 + 3].toInt() and 0xff) shl 24) + i++ + } + + System.arraycopy(b32, 0, x, 0, 16) + + i = 8 + while (i > 0) { + x[4] = x[4] xor rotate(x[0] + x[12], 7) + x[8] = x[8] xor rotate(x[4] + x[0], 9) + x[12] = x[12] xor rotate(x[8] + x[4], 13) + x[0] = x[0] xor rotate(x[12] + x[8], 18) + x[9] = x[9] xor rotate(x[5] + x[1], 7) + x[13] = x[13] xor rotate(x[9] + x[5], 9) + x[1] = x[1] xor rotate(x[13] + x[9], 13) + x[5] = x[5] xor rotate(x[1] + x[13], 18) + x[14] = x[14] xor rotate(x[10] + x[6], 7) + x[2] = x[2] xor rotate(x[14] + x[10], 9) + x[6] = x[6] xor rotate(x[2] + x[14], 13) + x[10] = x[10] xor rotate(x[6] + x[2], 18) + x[3] = x[3] xor rotate(x[15] + x[11], 7) + x[7] = x[7] xor rotate(x[3] + x[15], 9) + x[11] = x[11] xor rotate(x[7] + x[3], 13) + x[15] = x[15] xor rotate(x[11] + x[7], 18) + x[1] = x[1] xor rotate(x[0] + x[3], 7) + x[2] = x[2] xor rotate(x[1] + x[0], 9) + x[3] = x[3] xor rotate(x[2] + x[1], 13) + x[0] = x[0] xor rotate(x[3] + x[2], 18) + x[6] = x[6] xor rotate(x[5] + x[4], 7) + x[7] = x[7] xor rotate(x[6] + x[5], 9) + x[4] = x[4] xor rotate(x[7] + x[6], 13) + x[5] = x[5] xor rotate(x[4] + x[7], 18) + x[11] = x[11] xor rotate(x[10] + x[9], 7) + x[8] = x[8] xor rotate(x[11] + x[10], 9) + x[9] = x[9] xor rotate(x[8] + x[11], 13) + x[10] = x[10] xor rotate(x[9] + x[8], 18) + x[12] = x[12] xor rotate(x[15] + x[14], 7) + x[13] = x[13] xor rotate(x[12] + x[15], 9) + x[14] = x[14] xor rotate(x[13] + x[12], 13) + x[15] = x[15] xor rotate(x[14] + x[13], 18) + i -= 2 + } + + i = 0 + while (i < 16) { + b32[i] = x[i] + b32[i] + ++i + } + + i = 0 + while (i < 16) { + bArray[i * 4 + 0] = (b32[i] shr 0 and 0xff).toByte() + bArray[i * 4 + 1] = (b32[i] shr 8 and 0xff).toByte() + bArray[i * 4 + 2] = (b32[i] shr 16 and 0xff).toByte() + bArray[i * 4 + 3] = (b32[i] shr 24 and 0xff).toByte() + i++ + } + } + + fun blockxor( + sArray: ByteArray, + si: Int, + dArray: ByteArray, + di: Int, + len: Int, + ) { + for (i in 0.. = tags.mapNotNull(CommunityTag::parse) - fun approvedATags() = taggedATags().filter { it.kind != CommunityDefinitionEvent.KIND } + fun communityAddresses(): List
= tags.mapNotNull(CommunityTag::parseAddress) - fun approvedAddresses() = taggedAddresses().filter { it.kind != CommunityDefinitionEvent.KIND } + fun approvedEvents(): List = tags.mapNotNull(ApprovedEventTag::parseId) + + fun approvedAddresses(): List
= tags.mapNotNull(ApprovedAddressTag::parseAddress) companion object { const val KIND = 4550 diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/approval/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/approval/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/TagArrayBuilderExt.kt diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/tags/ApprovedAddressTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/tags/ApprovedAddressTag.kt new file mode 100644 index 0000000000..727993df66 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/tags/ApprovedAddressTag.kt @@ -0,0 +1,142 @@ +/** + * 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.nip72ModCommunities.approval.tags + +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer +import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.bytesUsedInMemory +import com.vitorpamplona.quartz.utils.ensure +import com.vitorpamplona.quartz.utils.pointerSizeInBytes + +class ApprovedAddressTag( + val address: Address, + val relayHint: NormalizedRelayUrl? = null, +) { + fun countMemory(): Long = 2 * pointerSizeInBytes + address.countMemory() + (relayHint?.url?.bytesUsedInMemory() ?: 0) + + fun toTag() = Address.assemble(address.kind, address.pubKeyHex, address.dTag) + + fun toTagArray() = assemble(address, relayHint) + + fun toTagIdOnly() = assemble(address, null) + + companion object { + const val TAG_NAME = "a" + + @JvmStatic + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && !Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR) + + @JvmStatic + fun isTagged( + tag: Array, + addressId: String, + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == addressId + + @JvmStatic + fun isTagged( + tag: Array, + address: ApprovedAddressTag, + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag() + + @JvmStatic + fun isIn( + tag: Array, + addressIds: Set, + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds + + @JvmStatic + fun parse(tag: Array): ApprovedAddressTag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } + + val address = Address.parse(tag[1]) ?: return null + val relayHint = tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) } + return ApprovedAddressTag(address, relayHint) + } + + @JvmStatic + fun parseValidAddress(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } + return Address.parse(tag[1])?.toValue() + } + + @JvmStatic + fun parseAddress(tag: Array): Address? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + val address = Address.parse(tag[1]) ?: return null + ensure(address.kind != CommunityDefinitionEvent.KIND) { return null } + return address + } + + @JvmStatic + fun parseAddressId(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } + return tag[1] + } + + @JvmStatic + fun parseAsHint(tag: Array): AddressHint? { + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(!Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } + ensure(tag[1].contains(':')) { return null } + ensure(tag[2].isNotEmpty()) { return null } + + val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2]) + ensure(relayHint != null) { return null } + + return AddressHint(tag[1], relayHint) + } + + @JvmStatic + fun assemble( + aTagId: HexKey, + relay: NormalizedRelayUrl?, + ) = arrayOfNotNull(TAG_NAME, aTagId, relay?.url) + + @JvmStatic + fun assemble( + address: Address, + relay: NormalizedRelayUrl?, + ) = arrayOfNotNull(TAG_NAME, address.toValue(), relay?.url) + + @JvmStatic + fun assemble( + kind: Int, + pubKey: String, + dTag: String, + relay: NormalizedRelayUrl?, + ) = assemble(Address.assemble(kind, pubKey, dTag), relay) + } +} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/tags/ApprovedEventTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/tags/ApprovedEventTag.kt new file mode 100644 index 0000000000..94e8d04bfc --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/approval/tags/ApprovedEventTag.kt @@ -0,0 +1,109 @@ +/** + * 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.nip72ModCommunities.approval.tags + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.Tag +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.nip01Core.hints.types.EventIdHint +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer +import com.vitorpamplona.quartz.nip01Core.tags.events.EventReference +import com.vitorpamplona.quartz.utils.Hex +import com.vitorpamplona.quartz.utils.arrayOfNotNull +import com.vitorpamplona.quartz.utils.ensure + +@Immutable +class ApprovedEventTag( + val ref: EventReference, +) { + constructor(eventId: String, relayHint: NormalizedRelayUrl?, pubkey: String?) : this(EventReference(eventId, pubkey, relayHint)) + + fun toTagArray() = assemble(ref) + + companion object { + const val TAG_NAME = "e" + + @JvmStatic + fun match(tag: Tag) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() + + @JvmStatic + fun isTagged( + tag: Array, + eventId: String, + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == eventId + + @JvmStatic + fun isIn( + tag: Array, + eventIds: Set, + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in eventIds + + @JvmStatic + fun parse(tag: Array): ApprovedEventTag? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + return ApprovedEventTag(tag[1], tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) }, tag.getOrNull(3)) + } + + @JvmStatic + fun parseId(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + return tag[1] + } + + @JvmStatic + fun parseValidId(tag: Array): String? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(Hex.isHex(tag[1])) { return null } + return tag[1] + } + + @JvmStatic + fun parseAsHint(tag: Array): EventIdHint? { + ensure(tag.has(2)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].length == 64) { return null } + ensure(tag[2].isNotEmpty()) { return null } + + val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2]) + ensure(relayHint != null) { return null } + + return EventIdHint(tag[1], relayHint) + } + + @JvmStatic + fun assemble( + eventId: HexKey, + relay: NormalizedRelayUrl?, + pubkey: String?, + ) = arrayOfNotNull(TAG_NAME, eventId, relay?.url, pubkey) + + @JvmStatic + fun assemble(ref: EventReference) = assemble(ref.eventId, ref.relayHint, ref.author) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/CommunityDefinitionEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/CommunityDefinitionEvent.kt similarity index 98% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/CommunityDefinitionEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/CommunityDefinitionEvent.kt index 11ffee913f..b0cee2d875 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/CommunityDefinitionEvent.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/CommunityDefinitionEvent.kt @@ -80,7 +80,7 @@ class CommunityDefinitionEvent( fun moderators() = tags.mapNotNull(ModeratorTag::parse) - fun moderatorKeys() = tags.mapNotNull(ModeratorTag::parseKey) + fun moderatorKeys() = tags.mapNotNull(ModeratorTag::parseKey).plus(pubKey) fun relays() = tags.mapNotNull(RelayTag::parse) @@ -88,6 +88,7 @@ class CommunityDefinitionEvent( companion object { const val KIND = 34550 + const val KIND_STR = "34550" const val ALT_DESCRIPTION = "Community definition" fun build( diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/DescriptionTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ImageTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/ModeratorTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/NameTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RelayTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/definition/tags/RulesTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/CommunityListEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/tags/CommunityTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/tags/CommunityTag.kt similarity index 84% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/tags/CommunityTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/tags/CommunityTag.kt index 7b6bcc6d35..1534823516 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip72ModCommunities/follow/tags/CommunityTag.kt +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip72ModCommunities/follow/tags/CommunityTag.kt @@ -26,6 +26,7 @@ import com.vitorpamplona.quartz.nip01Core.hints.types.AddressHint import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer import com.vitorpamplona.quartz.nip01Core.tags.addressables.Address +import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent import com.vitorpamplona.quartz.utils.arrayOfNotNull import com.vitorpamplona.quartz.utils.bytesUsedInMemory import com.vitorpamplona.quartz.utils.ensure @@ -47,7 +48,7 @@ class CommunityTag( const val TAG_NAME = "a" @JvmStatic - fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && tag[1].isNotEmpty() + fun isTagged(tag: Array) = tag.has(1) && tag[0] == TAG_NAME && Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR) @JvmStatic fun isTagged( @@ -61,12 +62,6 @@ class CommunityTag( address: CommunityTag, ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] == address.toTag() - @JvmStatic - fun isIn( - tag: Array, - addressIds: Set, - ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds - @JvmStatic fun isTaggedWithKind( tag: Array, @@ -74,26 +69,27 @@ class CommunityTag( ) = tag.has(1) && tag[0] == TAG_NAME && Address.isOfKind(tag[1], kind) @JvmStatic - fun parse( - aTagId: String, - relay: String?, - ) = Address.parse(aTagId)?.let { - CommunityTag(it, relay?.let { RelayUrlNormalizer.normalizeOrNull(it) }) - } + fun isIn( + tag: Array, + addressIds: Set, + ) = tag.has(1) && tag[0] == TAG_NAME && tag[1] in addressIds @JvmStatic fun parse(tag: Array): CommunityTag? { ensure(tag.has(1)) { return null } ensure(tag[0] == TAG_NAME) { return null } - ensure(tag[1].isNotEmpty()) { return null } - return parse(tag[1], tag.getOrNull(2)) + ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } + + val address = Address.parse(tag[1]) ?: return null + val relayHint = tag.getOrNull(2)?.let { RelayUrlNormalizer.normalizeOrNull(it) } + return CommunityTag(address, relayHint) } @JvmStatic fun parseValidAddress(tag: Array): String? { ensure(tag.has(1)) { return null } ensure(tag[0] == TAG_NAME) { return null } - ensure(tag[1].isNotEmpty()) { return null } + ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } return Address.parse(tag[1])?.toValue() } @@ -102,14 +98,16 @@ class CommunityTag( ensure(tag.has(1)) { return null } ensure(tag[0] == TAG_NAME) { return null } ensure(tag[1].isNotEmpty()) { return null } - return Address.parse(tag[1]) + val address = Address.parse(tag[1]) ?: return null + ensure(address.kind == CommunityDefinitionEvent.KIND) { return null } + return address } @JvmStatic fun parseAddressId(tag: Array): String? { ensure(tag.has(1)) { return null } ensure(tag[0] == TAG_NAME) { return null } - ensure(tag[1].isNotEmpty()) { return null } + ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } return tag[1] } @@ -117,8 +115,7 @@ class CommunityTag( fun parseAsHint(tag: Array): AddressHint? { ensure(tag.has(2)) { return null } ensure(tag[0] == TAG_NAME) { return null } - ensure(tag[1].isNotEmpty()) { return null } - ensure(tag[1].contains(':')) { return null } + ensure(Address.isOfKind(tag[1], CommunityDefinitionEvent.KIND_STR)) { return null } ensure(tag[2].isNotEmpty()) { return null } val relayHint = RelayUrlNormalizer.normalizeOrNull(tag[2]) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/ExternalId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/ExternalId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/ExternalId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/ExternalId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/books/BookId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/books/BookId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/books/BookId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/books/BookId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/books/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/books/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/books/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/books/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/location/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/location/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/location/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/location/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/location/GeohashId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/location/GeohashId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/location/GeohashId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/location/GeohashId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/movies/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/movies/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/movies/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/movies/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/movies/MovieId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/movies/MovieId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/movies/MovieId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/movies/MovieId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/papers/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/papers/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/papers/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/papers/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/papers/PaperId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/papers/PaperId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/papers/PaperId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/papers/PaperId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastEpisodeId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastEpisodeId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastEpisodeId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastEpisodeId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastFeedId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastFeedId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastFeedId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastFeedId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastPublisherId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastPublisherId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastPublisherId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/podcasts/PodcastPublisherId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/topics/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/topics/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/topics/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/topics/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/topics/HashtagId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/topics/HashtagId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/topics/HashtagId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/topics/HashtagId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/urls/CommentEventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/urls/CommentEventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/urls/CommentEventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/urls/CommentEventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/urls/UrlId.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/urls/UrlId.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip73ExternalIds/urls/UrlId.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip73ExternalIds/urls/UrlId.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/GoalEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/GoalEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/GoalEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/GoalEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/tags/AmountTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/tags/ClosedAtTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip75ZapGoals/tags/RelayListTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip78AppData/AppSpecificDataEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip78AppData/AppSpecificDataEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip78AppData/AppSpecificDataEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip78AppData/AppSpecificDataEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/HighlightEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip84Highlights/HighlightEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/HighlightEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip84Highlights/HighlightEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/CommentTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip84Highlights/tags/CommentTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/CommentTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip84Highlights/tags/CommentTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip84Highlights/tags/ContextTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/PlatformType.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/PlatformType.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/PlatformType.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/PlatformType.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/ClientTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/ClientTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/ClientTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/ClientTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/clientTag/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppDefinitionEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppDefinitionEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppDefinitionEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppDefinitionEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppMetadata.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppMetadata.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppMetadata.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/AppMetadata.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/EntityType.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/EntityType.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/EntityType.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/EntityType.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/definition/tags/PlatformLinkTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/AppRecommendationEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip89AppHandlers/recommendation/tags/RecommendationTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryRequestEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryRequestEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryRequestEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryRequestEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryResponseEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryResponseEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryResponseEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90ContentDiscoveryResponseEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90StatusEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90StatusEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90StatusEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90StatusEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryRequestEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryRequestEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryRequestEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryRequestEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryResponseEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryResponseEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryResponseEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip90Dvms/NIP90UserDiscoveryResponseEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/EventExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/EventExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/EventExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/EventExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/IMetaTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTagBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/IMetaTagBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/IMetaTagBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/IMetaTagBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/TagArrayExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/TagArrayExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip92IMeta/TagArrayExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip92IMeta/TagArrayExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/FileHeaderEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/FileHeaderEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/FileHeaderEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/FileHeaderEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/IMetaTagBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/IMetaTagBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/IMetaTagBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/IMetaTagBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/BlurhashTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/DimensionTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/FallbackTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/HashSha256Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/ImageTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/MagnetTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/MimeTypeTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/OriginalHashTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/ServiceTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/SizeTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/SummaryTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/ThumbTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/TorrentInfoHash.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip94FileMetadata/tags/UrlTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/HttpUrlFormatter.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/HttpUrlFormatter.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/HttpUrlFormatter.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/HttpUrlFormatter.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/DeleteResult.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/actions/DeleteResult.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/DeleteResult.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/actions/DeleteResult.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/actions/UploadResult.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/FileServersEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/config/FileServersEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/FileServersEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/config/FileServersEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/config/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/config/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/config/tags/ServerTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfo.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfoParser.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfoParser.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfoParser.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip96FileStorage/info/ServerInfoParser.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/HTTPAuthorizationEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/HTTPAuthorizationEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/HTTPAuthorizationEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/HTTPAuthorizationEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/tags/MethodTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/tags/PayloadHashTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip98HttpAuth/tags/UrlTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/ClassifiedsEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/ClassifiedsEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/ClassifiedsEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/ClassifiedsEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/ProductImageMeta.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/ProductImageMeta.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/ProductImageMeta.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/ProductImageMeta.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/ConditionTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/LocationTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/PriceTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip99Classifieds/tags/StatusTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/BaseVoiceEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/BaseVoiceEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/BaseVoiceEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/BaseVoiceEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/DurationTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/DurationTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/DurationTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/DurationTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/HashSha256Tag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/HashSha256Tag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/HashSha256Tag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/HashSha256Tag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/MimeTypeTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/MimeTypeTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/MimeTypeTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/MimeTypeTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyAuthorTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyAuthorTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyAuthorTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyAuthorTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyEventTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyEventTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyEventTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyEventTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyKindTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyKindTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyKindTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/ReplyKindTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipB7Blossom/BlossomAuthorizationEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomAuthorizationEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipB7Blossom/BlossomAuthorizationEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomAuthorizationEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipB7Blossom/BlossomServersEvent.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomServersEvent.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/nipB7Blossom/BlossomServersEvent.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nipB7Blossom/BlossomServersEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/ArrayUtils.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/ArrayUtils.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/ArrayUtils.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/ArrayUtils.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheCollectors.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/CacheCollectors.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheCollectors.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/CacheCollectors.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheOperations.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/CacheOperations.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/CacheOperations.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/CacheOperations.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/Ensure.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Ensure.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/Ensure.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Ensure.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/GZip.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/GZip.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/GZip.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/GZip.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/Hex.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Hex.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/Hex.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Hex.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/Hmac512.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Hmac512.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/Hmac512.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Hmac512.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/IterableExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/IterableExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/IterableExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/IterableExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/LargeCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/LargeCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/LargeSoftCache.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/LargeSoftCache.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/LargeSoftCache.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/LibSodiumInstance.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/LibSodiumInstance.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/LibSodiumInstance.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/LibSodiumInstance.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/MapOfSetBuilder.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/MapOfSetBuilder.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/MapOfSetBuilder.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/MapOfSetBuilder.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/ParallelUtils.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/ParallelUtils.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/ParallelUtils.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/ParallelUtils.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/RandomInstance.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/RandomInstance.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/RandomInstance.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/RandomInstance.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/Secp256k1Instance.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1Instance.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/Secp256k1Instance.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/Secp256k1Instance.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/StringUtils.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/StringUtils.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/StringUtils.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/StringUtils.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/TimeUtils.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/TimeUtils.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/TimeUtils.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/UriReferenceExt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/UriReferenceExt.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/UriReferenceExt.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/UriReferenceExt.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/sha256/Sha256.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/sha256/Sha256.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256Hasher.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/sha256/Sha256Hasher.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256Hasher.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/sha256/Sha256Hasher.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256Pool.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/sha256/Sha256Pool.kt similarity index 100% rename from quartz/src/main/java/com/vitorpamplona/quartz/utils/sha256/Sha256Pool.kt rename to quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/utils/sha256/Sha256Pool.kt diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/Platform.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/Platform.kt new file mode 100644 index 0000000000..efd8e787c1 --- /dev/null +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/Platform.kt @@ -0,0 +1,23 @@ +/** + * 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 + +expect fun platform(): String diff --git a/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/Platform.ios.kt b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/Platform.ios.kt new file mode 100644 index 0000000000..60e069e46a --- /dev/null +++ b/quartz/src/iosMain/kotlin/com/vitorpamplona/quartz/Platform.ios.kt @@ -0,0 +1,23 @@ +/** + * 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 + +actual fun platform() = "iOS" diff --git a/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/Platform.jvm.kt b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/Platform.jvm.kt new file mode 100644 index 0000000000..3bcb460242 --- /dev/null +++ b/quartz/src/jvmMain/kotlin/com/vitorpamplona/quartz/Platform.jvm.kt @@ -0,0 +1,23 @@ +/** + * 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 + +actual fun platform() = "JVM" diff --git a/quartz/src/main/AndroidManifest.xml b/quartz/src/main/AndroidManifest.xml deleted file mode 100644 index 44008a4332..0000000000 --- a/quartz/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BitcoinExplorer.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BitcoinExplorer.java deleted file mode 100644 index 37a7de152d..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BitcoinExplorer.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -public interface BitcoinExplorer { - /** - * Retrieve the block information from the block hash. - * - * @param hash Hash of the block. - * @return the blockheader of the hash - * @throws Exception desc - */ - public BlockHeader block(final String hash) throws Exception; - - /** - * Retrieve the block hash from the block height. - * - * @param height Height of the block. - * @return the hash of the block at height height - * @throws Exception desc - */ - public String blockHash(final Integer height) throws Exception; -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockHeader.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockHeader.java deleted file mode 100644 index c13deba43b..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockHeader.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -public class BlockHeader { - - private String merkleroot; - private String blockHash; - private String time; - - public void setTime(String time) { - this.time = time; - } - - public Long getTime() { - return Long.valueOf(time); - } - - public String getMerkleroot() { - return merkleroot; - } - - public void setMerkleroot(String merkleroot) { - this.merkleroot = merkleroot; - } - - public String getBlockHash() { - return blockHash; - } - - public void setBlockHash(String blockHash) { - this.blockHash = blockHash; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - if (o == null || getClass() != o.getClass()) { - return false; - } - - BlockHeader that = (BlockHeader) o; - - if (merkleroot != null ? !merkleroot.equals(that.merkleroot) : that.merkleroot != null) { - return false; - } - - if (blockHash != null ? !blockHash.equals(that.blockHash) : that.blockHash != null) { - return false; - } - - return time != null ? time.equals(that.time) : that.time == null; - } - - @Override - public int hashCode() { - int result = merkleroot != null ? merkleroot.hashCode() : 0; - result = 31 * result + (blockHash != null ? blockHash.hashCode() : 0); - result = 31 * result + (time != null ? time.hashCode() : 0); - - return result; - } - - @Override - public String toString() { - return "BlockHeader{" + - "merkleroot='" + merkleroot + '\'' + - ", blockHash='" + blockHash + '\'' + - ", time='" + time + '\'' + - '}'; - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockstreamExplorer.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockstreamExplorer.java deleted file mode 100644 index d088214469..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/BlockstreamExplorer.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import android.util.Log; - -import com.fasterxml.jackson.databind.JsonNode; -import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request; -import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response; - -import java.net.URL; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -public class BlockstreamExplorer implements BitcoinExplorer { - private static final String esploraUrl = "https://blockstream.info/api"; - - /** - * Retrieve the block information from the block hash. - * - * @param hash Hash of the block. - * @return the blockheader of the hash - * @throws Exception desc - */ - public BlockHeader block(final String hash) throws Exception { - final URL url = new URL(esploraUrl + "/block/" + hash); - final Request task = new Request(url); - final ExecutorService executor = Executors.newSingleThreadExecutor(); - final Future future = executor.submit(task); - final Response take = future.get(); - executor.shutdown(); - if (!take.isOk()) - throw new Exception(); - - final JsonNode jsonObject = take.getJson(); - final String merkleroot = jsonObject.get("merkle_root").asText(); - final String time = String.valueOf(jsonObject.get("timestamp").asInt()); - final BlockHeader blockHeader = new BlockHeader(); - blockHeader.setMerkleroot(merkleroot); - blockHeader.setTime(time); - blockHeader.setBlockHash(hash); - Log.i("BlockstreamExplorer", take.getFromUrl() + " " + blockHeader); - return blockHeader; - //log.warning("Cannot parse merkleroot from body: " + jsonObject + ": " + e.getMessage()); - } - - /** - * Retrieve the block hash from the block height. - * - * @param height Height of the block. - * @return the hash of the block at height height - * @throws Exception desc - */ - public String blockHash(final Integer height) throws Exception { - final URL url = new URL(esploraUrl + "/block-height/" + height); - final Request task = new Request(url); - final ExecutorService executor = Executors.newSingleThreadExecutor(); - final Future future = executor.submit(task); - final Response take = future.get(); - executor.shutdown(); - if (!take.isOk()) - throw new Exception(); - final String blockHash = take.getString(); - Log.i("BlockstreamExplorer", take.getFromUrl() + " " + blockHash); - return blockHash; - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Calendar.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Calendar.java deleted file mode 100644 index 49fc52f8be..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Calendar.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request; -import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response; - -import java.net.URL; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; - -/** - * Class representing remote calendar server interface. - */ -public class Calendar implements ICalendar { - - private String url; - - - /** - * Create a RemoteCalendar. - * - * @param url The server url. - */ - public Calendar(String url) { - this.url = url; - } - - /** - * Get calendar url. - * - * @return The calendar url. - */ - public String getUrl() { - return this.url; - } - - /** - * Submitting a digest to remote calendar. Returns a com.eternitywall.ots.Timestamp committing to that digest. - * - * @param digest The digest hash to send. - * @return the Timestamp received from the calendar. - * @throws ExceededSizeException if response is too big. - * @throws UrlException if url is not reachable. - * @throws DeserializationException if the data is corrupt - */ - @Override - public Timestamp submit(byte[] digest) - throws ExceededSizeException, UrlException, DeserializationException { - try { - Map headers = new HashMap<>(); - headers.put("Accept", "application/vnd.opentimestamps.v1"); - headers.put("User-Agent", "java-opentimestamps"); - headers.put("Content-Type", "application/x-www-form-urlencoded"); - - URL obj = new URL(url + "/digest"); - Request task = new Request(obj); - task.setData(digest); - task.setHeaders(headers); - Response response = task.call(); - byte[] body = response.getBytes(); - if (body.length > 10000) { - throw new ExceededSizeException("Calendar response exceeded size limit"); - } - - StreamDeserializationContext ctx = new StreamDeserializationContext(body); - return Timestamp.deserialize(ctx, digest); - } catch (ExceededSizeException | DeserializationException e) - { - throw e; - } - catch (Exception e) { - throw new UrlException(e.getMessage()); - } - } - - /** - * Get a timestamp for a given commitment. - * - * @param commitment The digest hash to send. - * @return the Timestamp from the calendar server (with blockchain information if already written). - * @throws ExceededSizeException if response is too big. - * @throws UrlException if url is not reachable. - * @throws CommitmentNotFoundException if commit is not found. - * @throws DeserializationException if the data is corrupt - */ - @Override - public Timestamp getTimestamp(byte[] commitment) throws DeserializationException, ExceededSizeException, CommitmentNotFoundException, UrlException { - try { - Map headers = new HashMap<>(); - headers.put("Accept", "application/vnd.opentimestamps.v1"); - headers.put("User-Agent", "java-opentimestamps"); - headers.put("Content-Type", "application/x-www-form-urlencoded"); - - URL obj = new URL(url + "/timestamp/" + Utils.bytesToHex(commitment).toLowerCase(Locale.ROOT)); - Request task = new Request(obj); - task.setHeaders(headers); - Response response = task.call(); - byte[] body = response.getBytes(); - if (body.length > 10000) { - throw new ExceededSizeException("Calendar response exceeded size limit"); - } - - if (!response.isOk()) { - throw new CommitmentNotFoundException("com.eternitywall.ots.Calendar response a status code != 200 which is: " + response.getStatus()); - } - - StreamDeserializationContext ctx = new StreamDeserializationContext(body); - - return Timestamp.deserialize(ctx, commitment); - } - catch (DeserializationException | ExceededSizeException | CommitmentNotFoundException e) - { - throw e; - } - catch (Exception e) { - throw new UrlException(e.getMessage()); - } - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarAsyncSubmit.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarAsyncSubmit.java deleted file mode 100644 index e6b84a00f7..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarAsyncSubmit.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request; -import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response; - -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.BlockingQueue; - -/** - * For making async calls to a calendar server - */ -public class CalendarAsyncSubmit implements ICalendarAsyncSubmit { - - private String url; - private byte[] digest; - private BlockingQueue> queue; - - public CalendarAsyncSubmit(String url, byte[] digest) { - this.url = url; - this.digest = digest; - } - - public void setQueue(BlockingQueue> queue) { - this.queue = queue; - } - - @Override - public Optional call() throws Exception { - Map headers = new HashMap<>(); - headers.put("Accept", "application/vnd.opentimestamps.v1"); - headers.put("User-Agent", "java-opentimestamps"); - headers.put("Content-Type", "application/x-www-form-urlencoded"); - - URL obj = new URL(url + "/digest"); - Request task = new Request(obj); - task.setData(digest); - task.setHeaders(headers); - Response response = task.call(); - - if (response.isOk()) { - byte[] body = response.getBytes(); - StreamDeserializationContext ctx = new StreamDeserializationContext(body); - Timestamp timestamp = Timestamp.deserialize(ctx, digest); - Optional of = Optional.of(timestamp); - queue.add(of); - return of; - } - - queue.add(Optional.empty()); - - return Optional.empty(); - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarBuilder.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarBuilder.java deleted file mode 100644 index 1dd68ed8c5..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarBuilder.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -public interface CalendarBuilder { - public ICalendar newSyncCalendar(String url); - public ICalendarAsyncSubmit newAsyncCalendar(String url, byte[] digest); -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarPureJavaBuilder.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarPureJavaBuilder.java deleted file mode 100644 index 85ff8b2c52..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/CalendarPureJavaBuilder.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -public class CalendarPureJavaBuilder implements CalendarBuilder { - public ICalendar newSyncCalendar(String url) { - return new Calendar(url); - } - public ICalendarAsyncSubmit newAsyncCalendar(String url, byte[] digest) { - return new CalendarAsyncSubmit(url, digest); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/DetachedTimestampFile.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/DetachedTimestampFile.java deleted file mode 100644 index 2bc8d50e06..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/DetachedTimestampFile.java +++ /dev/null @@ -1,227 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.security.NoSuchAlgorithmException; - -/** - * Class representing Detached com.vitorpamplona.quartz.ots.Timestamp File. - * A file containing a timestamp for another file. - * Contains a timestamp, along with a header and the digest of the file. - */ -public class DetachedTimestampFile { - - /** - * Header magic bytes Designed to be give the user some information in a hexdump, while being - * identified as 'data' by the file utility. - * - * @default \x00OpenTimestamps\x00\x00Proof\x00\xbf\x89\xe2\xe8\x84\xe8\x92\x94 - */ - static byte[] HEADER_MAGIC = {(byte) 0x00, (byte) 0x4f, (byte) 0x70, (byte) 0x65, (byte) 0x6e, - (byte) 0x54, (byte) 0x69, (byte) 0x6d, (byte) 0x65, (byte) 0x73, - (byte) 0x74, (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x73, (byte) 0x00, (byte) 0x00, - (byte) 0x50, (byte) 0x72, (byte) 0x6f, (byte) 0x6f, (byte) 0x66, (byte) 0x00, - (byte) 0xbf, (byte) 0x89, (byte) 0xe2, (byte) 0xe8, (byte) 0x84, (byte) 0xe8, (byte) 0x92, - (byte) 0x94}; - - /** - * While the git commit timestamps have a minor version, probably better to - * leave it out here: unlike Git commits round-tripping is an issue when - * timestamps are upgraded, and we could end up with bugs related to not - * saving/updating minor version numbers correctly. - * - * @default 1 - */ - static byte MAJOR_VERSION = 1; - - Op fileHashOp; - Timestamp timestamp; - - public DetachedTimestampFile(Op fileHashOp, Timestamp timestamp) { - this.fileHashOp = fileHashOp; - this.timestamp = timestamp; - } - - /** - * The digest of the file that was timestamped. - * - * @return The message inside the timestamp. - */ - public byte[] fileDigest() { - return this.timestamp.msg; - } - - /** - * Retrieve the internal timestamp. - * - * @return the timestamp. - */ - public Timestamp getTimestamp() { - return this.timestamp; - } - - /** - * Serialize a com.vitorpamplona.quartz.ots.Timestamp File. - * - * @param ctx The stream serialization context. - */ - public void serialize(StreamSerializationContext ctx) { - ctx.writeBytes(HEADER_MAGIC); - ctx.writeVaruint(MAJOR_VERSION); - this.fileHashOp.serialize(ctx); - ctx.writeBytes(this.timestamp.msg); - this.timestamp.serialize(ctx); - } - - /** - * Serialize a com.vitorpamplona.quartz.ots.Timestamp File. - * - * @return The byte array of serialized data. - */ - public byte[] serialize() { - StreamSerializationContext ctx = new StreamSerializationContext(); - this.serialize(ctx); - - return ctx.getOutput(); - } - - /** - * Deserialize a com.vitorpamplona.quartz.ots.Timestamp File. - * - * @param ctx The stream deserialization context. - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - */ - public static DetachedTimestampFile deserialize(StreamDeserializationContext ctx) throws DeserializationException { - ctx.assertMagic(HEADER_MAGIC); - ctx.readVaruint(); - - OpCrypto fileHashOp = (OpCrypto) OpCrypto.deserialize(ctx); - byte[] fileHash = ctx.readBytes(fileHashOp._DIGEST_LENGTH()); - Timestamp timestamp = Timestamp.deserialize(ctx, fileHash); - - ctx.assertEof(); - - return new DetachedTimestampFile(fileHashOp, timestamp); - } - - /** - * Deserialize a com.vitorpamplona.quartz.ots.Timestamp File. - * - * @param ots The byte array of deserialization DetachedFileTimestamped. - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - */ - public static DetachedTimestampFile deserialize(byte[] ots) throws DeserializationException { - StreamDeserializationContext ctx = new StreamDeserializationContext(ots); - - return DetachedTimestampFile.deserialize(ctx); - } - - /** - * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from bytes. - * - * @param fileHashOp The file hash operation. - * @param ctx The stream deserialization context. - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - * @throws NoSuchAlgorithmException desc - */ - public static DetachedTimestampFile from(OpCrypto fileHashOp, StreamDeserializationContext ctx) throws NoSuchAlgorithmException { - byte[] fdHash = fileHashOp.hashFd(ctx); - - return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash)); - } - - /** - * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from bytes. - * - * @param fileHashOp The file hash operation. - * @param bytes The byte array of data to hash - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - * @throws NoSuchAlgorithmException desc - */ - public static DetachedTimestampFile from(OpCrypto fileHashOp, byte[] bytes) throws Exception { - byte[] fdHash = fileHashOp.hashFd(bytes); - - return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash)); - } - - /** - * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from hash. - * - * @param inputStream The InputStream of the file to hash - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - * @throws Exception if the input stream is null - */ - public static DetachedTimestampFile from(InputStream inputStream) throws Exception { - if (inputStream == null) { - throw new Exception(); // TODO: Add exception string later on - } - - try { - DetachedTimestampFile fileTimestamp = DetachedTimestampFile.from(new OpSHA256(), inputStream); // Read from file reader stream - return fileTimestamp; - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - throw new Exception(); - } - } - - /** - * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from InputStream. - * - * @param fileHashOp The file hash operation. - * @param inputStream The input stream file. - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - * @throws IOException desc - * @throws NoSuchAlgorithmException desc - */ - public static DetachedTimestampFile from(OpCrypto fileHashOp, InputStream inputStream) throws IOException, NoSuchAlgorithmException { - byte[] fdHash = fileHashOp.hashFd(inputStream); - - return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash)); - } - - /** - * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from hash. - * - * @param hash The hash of the file. - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - */ - public static DetachedTimestampFile from(Hash hash) { - return new DetachedTimestampFile(hash.getOp(), new Timestamp(hash.getValue())); - } - - /** - * Read the Detached com.vitorpamplona.quartz.ots.Timestamp File from File. - * - * @param fileHashOp The file hash operation. - * @param file The hash file. - * @return The generated com.vitorpamplona.quartz.ots.DetachedTimestampFile object. - * @throws IOException desc - * @throws NoSuchAlgorithmException desc - */ - public static DetachedTimestampFile from(OpCrypto fileHashOp, File file) throws IOException, NoSuchAlgorithmException { - byte[] fdHash = fileHashOp.hashFd(file); - - return new DetachedTimestampFile(fileHashOp, new Timestamp(fdHash)); - } - - /** - * Print the object. - * - * @return The output. - */ - @Override - public String toString() { - String output = "com.vitorpamplona.quartz.ots.DetachedTimestampFile\n"; - output += "fileHashOp: " + this.fileHashOp.toString() + '\n'; - output += "timestamp: " + this.timestamp.toString() + '\n'; - - return output; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Hash.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Hash.java deleted file mode 100644 index 80d9f42237..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Hash.java +++ /dev/null @@ -1,202 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import androidx.annotation.NonNull; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpKECCAK256; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpRIPEMD160; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA1; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256; -import com.vitorpamplona.quartz.utils.Hex; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.security.NoSuchAlgorithmException; -import java.util.Locale; - -public class Hash { - - private byte[] value; - private byte algorithm; - - /** - * Create a Hash object. - * - * @param value - The byte array of the hash - * @param algorithm - The hashlib tag of crypto operation - */ - public Hash(byte[] value, byte algorithm) { - this.value = value; - this.algorithm = algorithm; - } - - /** - * Create a Hash object. - * - * @param value - The byte array of the hash - * @param label - The hashlib name of crypto operation - */ - public Hash(byte[] value, String label) { - this.value = value; - this.algorithm = getOp(label)._TAG(); - } - - /** - * Get Value. - * - * @return value - The hash in byte array. - */ - public byte[] getValue() { - return value; - } - - /** - * Set Value tag. - * - * @param value - The hash in byte array. - */ - public void setValue(byte[] value) { - this.value = value; - } - - /** - * Get Algorithm tag. - * - * @return algorithm - The algorithm tag of crypto operation. - */ - public byte getAlgorithm() { - return algorithm; - } - - /** - * Set Algorithm tag. - * - * @param algorithm - The algorithm tag of crypto operation. - */ - public void setAlgorithm(byte algorithm) { - this.algorithm = algorithm; - } - - /** - * Get the current Crypto operation. - * - * @return The generated com.vitorpamplona.quartz.ots.OpCrypto object. - */ - public OpCrypto getOp() { - if (this.algorithm == OpSHA1._TAG) { - return new OpSHA1(); - } else if (this.algorithm == OpSHA256._TAG) { - return new OpSHA256(); - } else if (this.algorithm == OpRIPEMD160._TAG) { - return new OpRIPEMD160(); - } else if (this.algorithm == OpKECCAK256._TAG) { - return new OpKECCAK256(); - } - - return new OpSHA256(); - } - - /** - * Get Crypto operation from hashlib tag. - * - * @param algorithm The hashlib tag. - * @return The generated com.vitorpamplona.quartz.ots.OpCrypto object. - */ - public static OpCrypto getOp(byte algorithm) { - if (algorithm == OpSHA1._TAG) { - return new OpSHA1(); - } else if (algorithm == OpSHA256._TAG) { - return new OpSHA256(); - } else if (algorithm == OpRIPEMD160._TAG) { - return new OpRIPEMD160(); - } else if (algorithm == OpKECCAK256._TAG) { - return new OpKECCAK256(); - } - - return new OpSHA256(); - } - - /** - * Get Crypto operation from hashlib name. - * - * @param label The hashlib name. - * @return The generated com.vitorpamplona.quartz.ots.OpCrypto object. - */ - public static OpCrypto getOp(String label) { - if (label.toLowerCase(Locale.ROOT).equals(new OpSHA1()._TAG_NAME())) { - return new OpSHA1(); - } else if (label.toLowerCase(Locale.ROOT).equals(new OpSHA256()._TAG_NAME())) { - return new OpSHA256(); - } else if (label.toLowerCase(Locale.ROOT).equals(new OpRIPEMD160()._TAG_NAME())) { - return new OpRIPEMD160(); - } else if (label.toLowerCase(Locale.ROOT).equals(new OpKECCAK256()._TAG_NAME())) { - return new OpKECCAK256(); - } - - return new OpSHA256(); - } - - /** - * Build hash from data. - * - * @param bytes The byte array of data to hash. - * @param algorithm The hash file. - * @return The generated com.vitorpamplona.quartz.ots.Hash object. - * @throws IOException desc - * @throws NoSuchAlgorithmException desc - */ - public static Hash from(byte[] bytes, byte algorithm) throws IOException, NoSuchAlgorithmException { - OpCrypto opCrypto = getOp(algorithm); - byte[] value = opCrypto.hashFd(bytes); - - return new Hash(value, algorithm); - } - - /** - * Build hash from File. - * - * @param file The File of data to hash. - * @param algorithm The hash file. - * @return The generated com.vitorpamplona.quartz.ots.Hash object. - * @throws IOException desc - * @throws NoSuchAlgorithmException desc - */ - public static Hash from(File file, byte algorithm) throws IOException, NoSuchAlgorithmException { - OpCrypto opCrypto = getOp(algorithm); - byte[] value = opCrypto.hashFd(file); - - return new Hash(value, algorithm); - } - - /** - * Build hash from InputStream. - * - * @param inputStream The InputStream of data to hash. - * @param algorithm The hash file. - * @return The generated com.vitorpamplona.quartz.ots.Hash object. - * @throws IOException desc - * @throws NoSuchAlgorithmException desc - */ - public static Hash from(InputStream inputStream, byte algorithm) throws IOException, NoSuchAlgorithmException { - OpCrypto opCrypto = getOp(algorithm); - byte[] value = opCrypto.hashFd(inputStream); - - return new Hash(value, algorithm); - } - - /** - * Print the object. - * - * @return The output. - */ - @NonNull - @Override - public String toString() { - String output = "com.vitorpamplona.quartz.ots.Hash\n"; - output += "algorithm: " + this.getOp()._HASHLIB_NAME() + '\n'; - output += "value: " + Hex.encode(this.value) + '\n'; - - return output; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendar.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendar.java deleted file mode 100644 index 75a546583b..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendar.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException; - -public interface ICalendar { - Timestamp submit(byte[] digest) - throws ExceededSizeException, UrlException, DeserializationException; - - Timestamp getTimestamp(byte[] commitment) throws DeserializationException, ExceededSizeException, CommitmentNotFoundException, UrlException; -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendarAsyncSubmit.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendarAsyncSubmit.java deleted file mode 100644 index 22e785c2bc..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/ICalendarAsyncSubmit.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import java.util.Optional; -import java.util.concurrent.Callable; - -public interface ICalendarAsyncSubmit extends Callable> { - @Override - Optional call() throws Exception; -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Merkle.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Merkle.java deleted file mode 100644 index df129fbab0..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Merkle.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpPrepend; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256; - -import java.util.ArrayList; -import java.util.List; - -/** - * Utility functions for merkle trees - */ -public class Merkle { - - /** - * Concatenate left and right, then perform a unary operation on them left and right can be either timestamps or bytes. - * Appropriate intermediary append/prepend operations will be created as needed for left and right. - * - * @param left the left timestamp parameter - * @param right the right timestamp parameter - * @return the concatenation of left and right - */ - public static Timestamp catThenUnaryOp(Timestamp left, Timestamp right) { - // rightPrependStamp = right.ops.add(OpPrepend(left.msg)) - Timestamp rightPrependStamp = right.add(new OpPrepend(left.msg)); - - // Left and right should produce the same thing, so we can set the timestamp of the left to the right. - // left.ops[OpAppend(right.msg)] = right_prepend_stamp - // leftAppendStamp = left.ops.add(OpAppend(right.msg)) - //Timestamp leftPrependStamp = left.add(new OpAppend(right.msg)); - left.ops.put(new OpAppend(right.msg), rightPrependStamp); - - // return rightPrependStamp.ops.add(unaryOpCls()) - Timestamp res = rightPrependStamp.add(new OpSHA256()); - return res; - } - - public static Timestamp catSha256(Timestamp left, Timestamp right) { - return Merkle.catThenUnaryOp(left, right); - } - - public static Timestamp catSha256d(Timestamp left, Timestamp right) { - Timestamp sha256Timestamp = Merkle.catSha256(left, right); - // res = sha256Timestamp.ops.add(OpSHA256()); - Timestamp res = sha256Timestamp.add(new OpSHA256()); - return res; - } - - /** - * Merkelize a set of timestamps. - * A merkle tree of all the timestamps is built in-place using binop() to - * timestamp each pair of timestamps. The exact algorithm used is structurally - * identical to a merkle-mountain-range, although leaf sums aren't committed. - * As this function is under the consensus-critical core, it's guaranteed that - * the algorithm will not be changed in the future. - * - * @param timestamps a list of timestamps - * @return the timestamp for the tip of the tree. - */ - public static Timestamp makeMerkleTree(List timestamps) { - List stamps = timestamps; - Timestamp prevStamp = null; - boolean exit = false; - - while (!exit) { - if (stamps.size() > 0) { - prevStamp = stamps.get(0); - } - - List subStamps = stamps.subList(1, stamps.size()); - List nextStamps = new ArrayList<>(); - - for (Timestamp stamp : subStamps) { - if (prevStamp == null) { - prevStamp = stamp; - } else { - nextStamps.add(Merkle.catSha256(prevStamp, stamp)); - prevStamp = null; - } - } - - if (nextStamps.size() == 0) { - exit = true; - } else { - if (prevStamp != null) { - nextStamps.add(prevStamp); - } - - stamps = nextStamps; - } - } - - return prevStamp; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/OpenTimestamps.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/OpenTimestamps.java deleted file mode 100644 index 809d2f19b6..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/OpenTimestamps.java +++ /dev/null @@ -1,485 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import android.util.Log; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation; -import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.EthereumBlockHeaderAttestation; -import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.LitecoinBlockHeaderAttestation; -import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.PendingAttestation; -import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256; -import com.vitorpamplona.quartz.utils.Hex; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * The main class for timestamp operations. - */ -public class OpenTimestamps { - - BitcoinExplorer explorer; - CalendarBuilder calBuilder; - - - public OpenTimestamps(BitcoinExplorer explorer, CalendarBuilder builder) { - this.explorer = explorer; - this.calBuilder = builder; - } - - /** - * Show information on a detached timestamp. - * - * @param detachedTimestampFile The DetachedTimestampFile ots. - * @return the string representation of the timestamp. - */ - public String info(DetachedTimestampFile detachedTimestampFile) { - return info(detachedTimestampFile, false); - } - - /** - * Show information on a detached timestamp with verbose option. - * - * @param detachedTimestampFile The DetachedTimestampFile ots. - * @param verbose Show verbose output. - * @return the string representation of the timestamp. - */ - public String info(DetachedTimestampFile detachedTimestampFile, boolean verbose) { - if (detachedTimestampFile == null) { - return "No ots file"; - } - - String fileHash = Utils.bytesToHex(detachedTimestampFile.timestamp.msg).toLowerCase(Locale.ROOT); - String hashOp = ((OpCrypto) detachedTimestampFile.fileHashOp)._TAG_NAME(); - - String firstLine = "File " + hashOp + " hash: " + fileHash + '\n'; - - return firstLine + "Timestamp:\n" + detachedTimestampFile.timestamp.strTree(0, verbose); - } - - /** - * Show information on a timestamp. - * - * @param timestamp The timestamp buffered. - * @return the string representation of the timestamp. - */ - public String info(Timestamp timestamp) { - if (timestamp == null) { - return "No timestamp"; - } - - String fileHash = Utils.bytesToHex(timestamp.msg).toLowerCase(Locale.ROOT); - String firstLine = "Hash: " + fileHash + '\n'; - - return firstLine + "Timestamp:\n" + timestamp.strTree(0); - } - - /** - * Create timestamp with the aid of a remote calendar. May be specified multiple times. - * - * @param fileTimestamp The Detached Timestamp File. - * @return The plain array buffer of stamped. - * @throws IOException if fileTimestamp is not valid, or the stamp procedure fails. - */ - public Timestamp stamp(DetachedTimestampFile fileTimestamp) throws IOException { - return stamp(fileTimestamp, null, 0); - } - - /** - * Create timestamp with the aid of a remote calendar. May be specified multiple times. - * - * @param fileTimestamp The timestamp to stamp. - * @param calendarsUrl The list of calendar urls. - * @param m The number of calendar to use. - * @return The plain array buffer of stamped. - * @throws IOException if fileTimestamp is not valid, or the stamp procedure fails. - */ - public Timestamp stamp(DetachedTimestampFile fileTimestamp, List calendarsUrl, Integer m) throws IOException { - List fileTimestamps = new ArrayList(); - fileTimestamps.add(fileTimestamp); - - return stamp(fileTimestamps, calendarsUrl, m); - } - - /** - * Create timestamp with the aid of a remote calendar. May be specified multiple times. - * - * @param fileTimestamps The list of timestamp to stamp. - * @param calendarsUrl The list of calendar urls. - * @param m The number of calendar to use. - * @return The plain array buffer of stamped. - * @throws IOException if fileTimestamp is not valid, or the stamp procedure fails. - */ - public Timestamp stamp(List fileTimestamps, List calendarsUrl, Integer m) throws IOException { - // Parse parameters - if (fileTimestamps == null || fileTimestamps.size() == 0) { - throw new IOException(); - } - - if (calendarsUrl == null || calendarsUrl.size() == 0) { - calendarsUrl = new ArrayList(); - calendarsUrl.add("https://alice.btc.calendar.opentimestamps.org"); - calendarsUrl.add("https://bob.btc.calendar.opentimestamps.org"); - calendarsUrl.add("https://finney.calendar.eternitywall.com"); - } - - if (m == null || m <= 0) { - if (calendarsUrl.size() == 0) { - m = 2; - } else if (calendarsUrl.size() == 1) { - m = 1; - } else { - m = calendarsUrl.size(); - } - } - - if (m < 0 || m > calendarsUrl.size()) { - Log.e("OpenTimestamp", "m cannot be greater than available calendar neither less or equal 0"); - throw new IOException(); - } - - // Build markle tree - Timestamp merkleTip = makeMerkleTree(fileTimestamps); - - if (merkleTip == null) { - throw new IOException(); - } - - // Stamping - Timestamp resultTimestamp = create(merkleTip, calendarsUrl, m); - - if (resultTimestamp == null) { - throw new IOException(); - } - - // Result of timestamp serialization - if (fileTimestamps.size() == 1) { - return fileTimestamps.get(0).timestamp; - } else { - return merkleTip; - } - } - - /** - * Create a timestamp - * - * @param timestamp The timestamp. - * @param calendarUrls List of calendar's to use. - * @param m Number of calendars to use. - * @return The created timestamp. - */ - private Timestamp create(Timestamp timestamp, List calendarUrls, Integer m) { - int capacity = calendarUrls.size(); - ExecutorService executor = Executors.newFixedThreadPool(4); - ArrayBlockingQueue> queue = new ArrayBlockingQueue<>(capacity); - - // Submit to all public calendars - for (final String calendarUrl : calendarUrls) { - Log.i("OpenTimestamps", "Submitting to remote calendar " + calendarUrl); - - try { - CalendarAsyncSubmit task = new CalendarAsyncSubmit(calendarUrl, timestamp.msg); - task.setQueue(queue); - executor.submit(task); - } catch (Exception e) { - e.printStackTrace(); - } - } - - int count = 0; - - for (count = 0; count < capacity && count < m; count++) { - try { - Optional optionalStamp = queue.take(); - - if (optionalStamp.isPresent()) { - try { - Timestamp time = optionalStamp.get(); - timestamp.merge(time); - Log.i("Open", ""+ timestamp.attestations.size()); - } catch (Exception e) { - e.printStackTrace(); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - if (count < m) { - Log.e("OpenTimestamp", "Failed to create timestamp: requested " + String.valueOf(m) + " attestation" + ((m > 1) ? "s" : "") + " but received only " + String.valueOf(count)); - } - - //shut down the executor service now - executor.shutdown(); - - return timestamp; - } - - /** - * Make Merkle Tree of detached timestamps. - * - * @param fileTimestamps The list of DetachedTimestampFile. - * @return merkle tip timestamp. - */ - public Timestamp makeMerkleTree(List fileTimestamps) { - List merkleRoots = new ArrayList<>(); - - for (DetachedTimestampFile fileTimestamp : fileTimestamps) { - byte[] bytesRandom16 = new byte[16]; - - try { - bytesRandom16 = Utils.randBytes(16); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - - // nonce_appended_stamp = file_timestamp.timestamp.ops.add(com.vitorpamplona.quartz.ots.op.OpAppend(os.urandom(16))) - Timestamp nonceAppendedStamp = fileTimestamp.timestamp.add(new OpAppend(bytesRandom16)); - // merkle_root = nonce_appended_stamp.ops.add(com.vitorpamplona.quartz.ots.op.OpSHA256()) - Timestamp merkleRoot = nonceAppendedStamp.add(new OpSHA256()); - merkleRoots.add(merkleRoot); - } - - Timestamp merkleTip = Merkle.makeMerkleTree(merkleRoots); - return merkleTip; - } - - /** - * Compare and verify a detached timestamp. - * - * @param ots The DetachedTimestampFile containing the proof to verify. - * @param diggest The hash of the stamped file, in bytes - * @return Hashmap of block heights and timestamps indexed by chain: timestamp in seconds from 1 January 1970. - * @throws Exception if the verification procedure fails. - */ - - public HashMap verify(DetachedTimestampFile ots, byte[] diggest) throws Exception { - if (!Arrays.equals(ots.fileDigest(), diggest)) { - Log.e("OpenTimestamp", "Expected digest " + Hex.encode(ots.fileDigest()).toLowerCase(Locale.ROOT)); - Log.e("OpenTimestamp", "File does not match original!"); - throw new Exception("File does not match original!"); - } - - return verify(ots.timestamp); - } - - /** - * Verify a timestamp. - * - * @param timestamp The timestamp. - * @return HashMap of block heights and timestamps indexed by chain: timestamp in seconds from 1 January 1970. - * @throws Exception if the verification procedure fails. - */ - public HashMap verify(Timestamp timestamp) throws Exception { - HashMap verifyResults = new HashMap<>(); - - for (Map.Entry item : timestamp.allAttestations().entrySet()) { - byte[] msg = item.getKey(); - TimeAttestation attestation = item.getValue(); - VerifyResult verifyResult = null; - VerifyResult.Chains chain = null; - - try { - if (attestation instanceof BitcoinBlockHeaderAttestation) { - chain = VerifyResult.Chains.BITCOIN; - Long time = verify((BitcoinBlockHeaderAttestation) attestation, msg); - int height = ((BitcoinBlockHeaderAttestation) attestation).getHeight(); - verifyResult = new VerifyResult(time, height); - } else if (attestation instanceof LitecoinBlockHeaderAttestation) { - chain = VerifyResult.Chains.LITECOIN; - Long time = verify((LitecoinBlockHeaderAttestation) attestation, msg); - int height = ((LitecoinBlockHeaderAttestation) attestation).getHeight(); - verifyResult = new VerifyResult(time, height); - } - - if (verifyResult != null && verifyResults.containsKey(chain)) { - if (verifyResult.height < verifyResults.get(chain).height) { - verifyResults.put(chain, verifyResult); - } - } - - if (verifyResult != null && !verifyResults.containsKey(chain)) { - verifyResults.put(chain, verifyResult); - } - } catch (VerificationException e) { - throw e; - } catch (Exception e) { - String text = ""; - - if (chain == VerifyResult.Chains.BITCOIN) { - text = BitcoinBlockHeaderAttestation.chain; - } else if (chain == VerifyResult.Chains.LITECOIN) { - text = LitecoinBlockHeaderAttestation.chain; - } else if (chain == VerifyResult.Chains.ETHEREUM) { - text = EthereumBlockHeaderAttestation.chain; - } else { - throw e; - } - - Log.e("OpenTimestamp", Utils.toUpperFirstLetter(text) + " verification failed: " + e.getMessage()); - throw e; - } - } - return verifyResults; - } - - /** - * Verify an Bitcoin Block Header Attestation. - * if the node is not reachable or it fails, uses Lite-client verification. - * - * @param attestation The BitcoinBlockHeaderAttestation attestation. - * @param msg The digest to verify. - * @return The unix timestamp in seconds from 1 January 1970. - * @throws VerificationException if it doesn't check the merkle root of the block. - * @throws Exception if the verification procedure fails. - */ - public Long verify(BitcoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception { - Integer height = attestation.getHeight(); - BlockHeader blockInfo; - - try { - String blockHash = explorer.blockHash(height); - blockInfo = explorer.block(blockHash); - Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid"); - } catch (Exception e2) { - e2.printStackTrace(); - throw e2; - } - - return attestation.verifyAgainstBlockheader(Utils.arrayReverse(msg), blockInfo); - } - - /** - * Verify an Litecoin Block Header Attestation. Litecoin verification uses only lite-client verification. - * - * @param attestation The LitecoinBlockHeaderAttestation attestation. - * @param msg The digest to verify. - * @return The unix timestamp in seconds from 1 January 1970. - * @throws VerificationException if it doesn't check the merkle root of the block. - * @throws Exception if the verification procedure fails. - */ - public Long verify(LitecoinBlockHeaderAttestation attestation, byte[] msg) throws VerificationException, Exception { - Integer height = attestation.getHeight(); - BlockHeader blockInfo; - - try { - String blockHash = explorer.blockHash(height); - Log.i("OpenTimestamps", "Lite-client verification, assuming block " + blockHash + " is valid"); - blockInfo = explorer.block(blockHash); - } catch (Exception e2) { - e2.printStackTrace(); - throw e2; - } - - return attestation.verifyAgainstBlockheader(Utils.arrayReverse(msg), blockInfo); - } - - /** - * Upgrade a timestamp. - * - * @param detachedTimestamp The DetachedTimestampFile containing the proof to verify. - * @return a boolean representing if the timestamp has changed. - * @throws Exception if the upgrading procedure fails. - */ - public boolean upgrade(DetachedTimestampFile detachedTimestamp) throws Exception { - // Upgrade timestamp - boolean changed = upgrade(detachedTimestamp.timestamp); - return changed; - } - - /** - * Attempt to upgrade an incomplete timestamp to make it verifiable. - * Note that this means if the timestamp that is already complete, False will be returned as nothing has changed. - * - * @param timestamp The timestamp to upgrade. - * @return a boolean representing if the timestamp has changed. - * @throws Exception if the upgrading procedure fails. - */ - public boolean upgrade(Timestamp timestamp) throws Exception { - // Check remote calendars for upgrades. - // This time we only check PendingAttestations - we can't be as agressive. - - boolean upgraded = false; - Set existingAttestations = timestamp.getAttestations(); - - for (Timestamp subStamp : timestamp.directlyVerified()) { - for (TimeAttestation attestation : subStamp.attestations) { - if (attestation instanceof PendingAttestation && !subStamp.isTimestampComplete()) { - String calendarUrl = new String(((PendingAttestation) attestation).getUri(), StandardCharsets.UTF_8); - // var calendarUrl = calendarUrls[0]; - byte[] commitment = subStamp.msg; - - try { - Calendar calendar = new Calendar(calendarUrl); - Timestamp upgradedStamp = upgrade(subStamp, calendar, commitment, existingAttestations); - - try { - subStamp.merge(upgradedStamp); - } catch (Exception e) { - e.printStackTrace(); - } - - upgraded = true; - } catch (Exception e) { - Log.i("OpenTimestamps", e.getMessage()); - } - } - } - } - - return upgraded; - } - - private Timestamp upgrade(Timestamp subStamp, Calendar calendar, byte[] commitment, Set existingAttestations) throws Exception { - Timestamp upgradedStamp; - - try { - upgradedStamp = calendar.getTimestamp(commitment); - - if (upgradedStamp == null) { - throw new Exception("Invalid stamp"); - } - } catch (Exception e) { - Log.i("OpenTimestamps", "Calendar " + calendar.getUrl() + ": " + e.getMessage()); - throw e; - } - - Set attsFromRemote = upgradedStamp.getAttestations(); - - if (attsFromRemote.size() > 0) { - Log.i("OpenTimestamps", "Got 1 attestation(s) from " + calendar.getUrl()); - } - - // Set difference from remote attestations & existing attestations - Set newAttestations = attsFromRemote; - newAttestations.removeAll(existingAttestations); - - // changed & found_new_attestations - // foundNewAttestations = true; - // Log.i("OpenTimestamps", attsFromRemote.size + ' attestation(s) from ' + calendar.url); - - // Set union of existingAttestations & newAttestations - existingAttestations.addAll(newAttestations); - - return upgradedStamp; - // subStamp.merge(upgradedStamp); - // args.cache.merge(upgraded_stamp) - // sub_stamp.merge(upgraded_stamp) - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamDeserializationContext.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamDeserializationContext.java deleted file mode 100644 index 96e1b0679c..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamDeserializationContext.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; - -import java.util.Arrays; - -public class StreamDeserializationContext { - - byte[] buffer; - int counter = 0; - - public StreamDeserializationContext(byte[] stream) { - this.buffer = stream; - this.counter = 0; - } - - public byte[] getOutput() { - return this.buffer; - } - - public int getCounter() { - return this.counter; - } - - public byte[] read(int l) { - if (this.counter == this.buffer.length) { - return null; - } - - if (l+this.counter > this.buffer.length) { - l = this.buffer.length-this.counter; - } - - // const uint8Array = new Uint8Array(this.buffer,this.counter,l); - byte[] uint8Array = Arrays.copyOfRange(this.buffer, this.counter, this.counter + l); - this.counter += l; - - return uint8Array; - } - - public boolean readBool() { - byte b = this.read(1)[0]; - - if (b == 0xff) { - return true; - } else if (b == 0x00) { - return false; - } - - return false; - } - - public int readVaruint() { - int value = 0; - byte shift = 0; - byte b; - - do { - b = this.read(1)[0]; - value |= (b & 0b01111111) << shift; - shift += 7; - } while ((b & 0b10000000) != 0b00000000); - - return value; - } - - public byte[] readBytes(int expectedLength) throws DeserializationException { - - - if (expectedLength == 0) { - return this.readVarbytes(1024, 0); - } - - return this.read(expectedLength); - } - - public byte[] readVarbytes(int maxLen) throws DeserializationException { - return readVarbytes(maxLen, 0); - } - - public byte[] readVarbytes(int maxLen, int minLen) throws DeserializationException { - int l = this.readVaruint(); - - if (l > maxLen) { - throw new DeserializationException("varbytes max length exceeded;"); - } else if (l < minLen) { - throw new DeserializationException("varbytes min length not met;"); - } - - return this.read(l); - } - - public boolean assertMagic(byte[] expectedMagic) { - byte[] actualMagic = this.read(expectedMagic.length); - - return Arrays.equals(expectedMagic, actualMagic); - } - - public boolean assertEof() { - byte[] excess = this.read(1); - return excess != null; - } - - public String toString() { - return Arrays.toString(this.buffer); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamSerializationContext.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamSerializationContext.java deleted file mode 100644 index a95b4229a3..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/StreamSerializationContext.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -public class StreamSerializationContext { - - List buffer = new ArrayList<>(); - - public StreamSerializationContext() { - this.buffer = new ArrayList<>(); - } - - public byte[] getOutput() { - byte[] bytes = new byte[this.buffer.size()]; - - for (int i = 0; i < this.buffer.size(); i++) { - bytes[i] = this.buffer.get(i); - } - - return bytes; - } - - public int getLength() { - return this.buffer.size(); - } - - public void writeBool(boolean value) { - if (value) { - this.writeByte((byte) 0xff); - } else { - this.writeByte((byte) 0x00); - } - } - - public void writeVaruint(int value) { - if ((value) == 0b00000000) { - this.writeByte((byte) 0x00); - } else { - while (value != 0) { - byte b = (byte) ((value & 0xff) & 0b01111111); - - if ((value) > 0b01111111) { - b |= 0b10000000; - } - - this.writeByte(b); - - if ((value) <= 0b01111111) { - break; - } - - value = value >> 7; - } - } - } - - public void writeByte(byte value) { - this.buffer.add(Byte.valueOf(value)); - } - - public void writeByte(Byte value) { - this.buffer.add(value); - } - - public void writeBytes(byte[] value) { - for (byte b : value) { - this.writeByte(b); - } - } - - public void writeVarbytes(byte[] value) { - this.writeVaruint(value.length); - this.writeBytes(value); - } - - public String toString() { - return Arrays.toString(this.getOutput()); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Timestamp.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Timestamp.java deleted file mode 100644 index eff52a3c28..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Timestamp.java +++ /dev/null @@ -1,627 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation; -import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op; -import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpBinary; -import com.vitorpamplona.quartz.utils.Hex; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeMap; - -/** - * Proof that one or more attestations commit to a message. - * The proof is in the form of a tree, with each node being a message, and the - * edges being operations acting on those messages. The leafs of the tree are - * attestations that attest to the time that messages in the tree existed prior. - */ -public class Timestamp { - - public byte[] msg; - public List attestations; - public HashMap ops; - - /** - * Create a com.vitorpamplona.quartz.ots.Timestamp object. - * - * @param msg - Desc - */ - public Timestamp(byte[] msg) { - this.msg = msg; - this.attestations = new ArrayList<>(); - this.ops = new HashMap<>(); - } - - /** - * Deserialize a Timestamp. - * - * @param ots - The serialized byte array. - * @param initialMsg - The initial message. - * @return The deserialized Timestamp. - */ - public static Timestamp deserialize(byte[] ots, byte[] initialMsg) throws DeserializationException { - StreamDeserializationContext ctx = new StreamDeserializationContext(ots); - - return Timestamp.deserialize(ctx, initialMsg); - } - - /** - * Deserialize a Timestamp. - * Because the serialization format doesn't include the message that the - * timestamp operates on, you have to provide it so that the correct - * operation results can be calculated. - * The message you provide is assumed to be correct; if it causes a op to - * raise MsgValueError when the results are being calculated (done - * immediately, not lazily) DeserializationError is raised instead. - * - * @param ctx - The stream deserialization context. - * @param initialMsg - The initial message. - * @return The deserialized Timestamp. - */ - public static Timestamp deserialize(StreamDeserializationContext ctx, byte[] initialMsg) - throws DeserializationException { - Timestamp self = new Timestamp(initialMsg); - byte tag = ctx.readBytes(1)[0]; - - while ((tag & 0xff) == 0xff) { - byte current = ctx.readBytes(1)[0]; - doTagOrAttestation(self, ctx, current, initialMsg); - tag = ctx.readBytes(1)[0]; - } - - doTagOrAttestation(self, ctx, tag, initialMsg); - - return self; - } - - private static void doTagOrAttestation(Timestamp self, StreamDeserializationContext ctx, byte tag, byte[] initialMsg) - throws DeserializationException { - if ((tag & 0xff) == 0x00) { - TimeAttestation attestation = TimeAttestation.deserialize(ctx); - self.attestations.add(attestation); - } else { - Op op = Op.deserializeFromTag(ctx, tag); - byte[] result = op.call(initialMsg); - - Timestamp stamp = Timestamp.deserialize(ctx, result); - self.ops.put(op, stamp); - } - } - - /** - * Create a Serialize object. - * - * @return The byte array of the serialized timestamp - */ - public byte[] serialize() { - StreamSerializationContext ctx = new StreamSerializationContext(); - serialize(ctx); - - return ctx.getOutput(); - } - - /** - * Create a Serialize object. - * - * @param ctx - The stream serialization context. - */ - public void serialize(StreamSerializationContext ctx) { - List sortedAttestations = this.attestations; // TODO: Hm, this is just a reference copy... - Collections.sort(sortedAttestations); - - if (sortedAttestations.size() > 1) { - for (int i = 0; i < sortedAttestations.size() - 1; i++) { - ctx.writeBytes(new byte[]{(byte) 0xff, (byte) 0x00}); - sortedAttestations.get(i).serialize(ctx); - } - } - - if (this.ops.isEmpty()) { - ctx.writeByte((byte) 0x00); - - if (!sortedAttestations.isEmpty()) { - sortedAttestations.get(sortedAttestations.size() - 1).serialize(ctx); - } - } else if (!this.ops.isEmpty()) { - if (!sortedAttestations.isEmpty()) { - ctx.writeBytes(new byte[]{(byte) 0xff, (byte) 0x00}); - sortedAttestations.get(sortedAttestations.size() - 1).serialize(ctx); - } - - int counter = 0; - List> list = sortToList(this.ops.entrySet()); - - for (Map.Entry entry : list) { - Timestamp stamp = entry.getValue(); - Op op = entry.getKey(); - - if (counter < this.ops.size() - 1) { - ctx.writeBytes(new byte[]{(byte) 0xff}); - counter++; - } - - op.serialize(ctx); - stamp.serialize(ctx); - } - } - } - - /** - * Add all operations and attestations from another timestamp to this one. - * - * @param other - Initial other com.vitorpamplona.quartz.ots.Timestamp to merge. - * @throws Exception different timestamps messages - */ - public void merge(Timestamp other) throws Exception { - if (!Arrays.equals(this.msg, other.msg)) { - //Log.e("OpenTimestamp", "Can\'t merge timestamps for different messages together"); - throw new Exception("Can\'t merge timestamps for different messages together"); - } - - for (final TimeAttestation attestation : other.attestations) { - this.attestations.add(attestation); - } - - for (Map.Entry entry : other.ops.entrySet()) { - Timestamp otherOpStamp = entry.getValue(); - Op otherOp = entry.getKey(); - - Timestamp ourOpStamp = this.ops.get(otherOp); - - if (ourOpStamp == null) { - ourOpStamp = new Timestamp(otherOp.call(this.msg)); - this.ops.put(otherOp, ourOpStamp); - } - - ourOpStamp.merge(otherOpStamp); - } - } - - /** - * Shrink Timestamp. - * Remove useless pending attestions if exist a full bitcoin attestation. - * - * @return TimeAttestation - the minimal attestation. - * @throws Exception no attestion founds. - */ - public TimeAttestation shrink() throws Exception { - // Get all attestations - HashMap allAttestations = this.allAttestations(); - - if (allAttestations.size() == 0) { - throw new Exception(); - } else if (allAttestations.size() == 1) { - return allAttestations.values().iterator().next(); - } else if (this.ops.size() == 0) { - throw new Exception(); // TODO: Need a descriptive exception string here - } - - // Fore >1 attestations : - // Search first BitcoinBlockHeaderAttestation - TimeAttestation minAttestation = null; - - for (Map.Entry entry : this.ops.entrySet()) { - Timestamp timestamp = entry.getValue(); - //Op op = entry.getKey(); - - for (TimeAttestation attestation : timestamp.getAttestations()) { - if (attestation instanceof BitcoinBlockHeaderAttestation) { - if (minAttestation == null) { - minAttestation = attestation; - } else { - if (minAttestation instanceof BitcoinBlockHeaderAttestation - && attestation instanceof BitcoinBlockHeaderAttestation - && ((BitcoinBlockHeaderAttestation) minAttestation).getHeight() - > ((BitcoinBlockHeaderAttestation) attestation).getHeight()) { - minAttestation = attestation; - } - } - } - } - } - - // Only pending attestations : return the first - if (minAttestation == null) { - return allAttestations.values().iterator().next(); - } - - // Remove attestation if not min attestation - boolean shrinked = false; - - for (Iterator> it = this.ops.entrySet().iterator(); it.hasNext(); ) { - Map.Entry entry = it.next(); - Timestamp timestamp = entry.getValue(); - Op op = entry.getKey(); - Set attestations = timestamp.getAttestations(); - - if (attestations.size() > 0 && attestations.contains(minAttestation) && shrinked == false) { - timestamp.shrink(); - shrinked = true; - } else { - it.remove(); - } - } - - return minAttestation; - } - - /** - * Return the digest of the timestamp. - * - * @return The byte[] digest string. - */ - public byte[] getDigest() { - return this.msg; - } - - /** - * Return as memory hierarchical object. - * - * @param indent - Initial hierarchical indention. - * @return The output string. - */ - public String toString(int indent) { - StringBuilder builder = new StringBuilder(); - builder.append(Timestamp.indention(indent) + "msg: " + Hex.encode(this.msg) + "\n"); - builder.append(Timestamp.indention(indent) + this.attestations.size() + " attestations: \n"); - int i = 0; - - for (final TimeAttestation attestation : this.attestations) { - builder.append(Timestamp.indention(indent) + "[" + i + "] " + attestation.toString() + "\n"); - i++; - } - - i = 0; - builder.append(Timestamp.indention(indent) + this.ops.size() + " ops: \n"); - - for (Map.Entry entry : this.ops.entrySet()) { - Timestamp stamp = entry.getValue(); - Op op = entry.getKey(); - - builder.append(Timestamp.indention(indent) + "[" + i + "] op: " + op.toString() + "\n"); - builder.append(Timestamp.indention(indent) + "[" + i + "] timestamp: \n"); - builder.append(stamp.toString(indent + 1)); - i++; - } - - builder.append('\n'); - - return builder.toString(); - } - - /** - * Indention function for printing tree. - * - * @param pos - Initial hierarchical indention. - * @return The output space string. - */ - public static String indention(int pos) { - StringBuilder builder = new StringBuilder(); - - for (int i = 0; i < pos; i++) { - builder.append(" "); - } - - return builder.toString(); - } - - public String strTree(int indent) { - return strTree(indent, false); - } - - private String strResult(boolean verbosity, byte[] parameter, byte[] result) { - final String ANSI_HEADER = "\u001B[95m"; - final String ANSI_OKBLUE = "\u001B[94m"; - final String ANSI_OKGREEN = "\u001B[92m"; - final String ANSI_WARNING = "\u001B[93m"; - final String ANSI_FAIL = "\u001B[91m"; - final String ANSI_ENDC = "\u001B[0m"; - final String ANSI_BOLD = "\u001B[1m"; - final String ANSI_UNDERLINE = "\u001B[4m"; - - String rr = ""; - - if (verbosity == true && result != null) { - rr += " == "; - String resultHex = Utils.bytesToHex(result); - - if (parameter == null) { - rr += resultHex; - } else { - String parameterHex = Utils.bytesToHex(parameter); - - try { - int index = resultHex.indexOf(parameterHex); - String parameterHexHighlight = ANSI_BOLD + parameterHex + ANSI_ENDC; - - if (index == 0) { - rr += parameterHexHighlight + resultHex.substring(index + parameterHex.length(), resultHex.length()); - } else { - rr += resultHex.substring(0, index) + parameterHexHighlight; - } - } catch (Exception err) { - rr += resultHex; - } - } - } - - return rr; - } - - /** - * Return as tree hierarchical object. - * - * @param indent - Initial hierarchical indention. - * @param verbosity - Verbose option. - * @return The output string. - */ - public String strTree(int indent, boolean verbosity) { - StringBuilder builder = new StringBuilder(); - - if (!this.attestations.isEmpty()) { - for (final TimeAttestation attestation : this.attestations) { - builder.append(Timestamp.indention(indent)); - builder.append("verify " + attestation.toString() + strResult(verbosity, this.msg, null) + "\n"); - - if (attestation instanceof BitcoinBlockHeaderAttestation) { - String tx = Utils.bytesToHex(Utils.arrayReverse(this.msg)); - builder.append(Timestamp.indention(indent) + "# Bitcoin block merkle root " + tx.toLowerCase(Locale.ROOT) + "\n"); - } - } - } - - if (this.ops.size() > 1) { - TreeMap ordered = new TreeMap<>(this.ops); - - for (Map.Entry entry : ordered.entrySet()) { - Timestamp timestamp = entry.getValue(); - Op op = entry.getKey(); - - byte[] curRes = op.call(this.msg); - byte[] curPar = null; - - if (op instanceof OpBinary) { - curPar = ((OpBinary) op).arg; - } - - builder.append(Timestamp.indention(indent) + " -> " + op.toString().toLowerCase(Locale.ROOT) + strResult(verbosity, curPar, curRes).toLowerCase(Locale.ROOT) + "\n"); - builder.append(timestamp.strTree(indent + 1, verbosity)); - } - } else if (this.ops.size() > 0) { - // output += com.eternitywall.ots.Timestamp.indention(indent); - for (Map.Entry entry : this.ops.entrySet()) { - Timestamp timestamp = entry.getValue(); - Op op = entry.getKey(); - - byte[] curRes = op.call(this.msg); - byte[] curPar = null; - - if (op instanceof OpBinary) { - curPar = ((OpBinary) op).arg; - } - - builder.append(Timestamp.indention(indent) + op.toString().toLowerCase(Locale.ROOT) + strResult(verbosity, curPar, curRes).toLowerCase(Locale.ROOT) + "\n"); - builder.append(timestamp.strTree(indent, verbosity)); - } - } - - return builder.toString(); - } - - /** - * Returns a list of all sub timestamps with attestations. - * - * @return List of all sub timestamps with attestations. - */ - public List directlyVerified() { - if (!this.attestations.isEmpty()) { - List list = new ArrayList<>(); - list.add(this); - return list; - } - - List list = new ArrayList<>(); - - for (Map.Entry entry : this.ops.entrySet()) { - Timestamp ts = entry.getValue(); - //Op op = entry.getKey(); - - List result = ts.directlyVerified(); - list.addAll(result); - } - - return list; - } - - /** - * Returns a set of all Attestations. - * - * @return Set of all timestamp attestations. - */ - public Set getAttestations() { - Set set = new HashSet(); - - for (Map.Entry item : this.allAttestations().entrySet()) { - //byte[] msg = item.getKey(); - TimeAttestation attestation = item.getValue(); - set.add(attestation); - } - - return set; - } - - /** - * Determine if timestamp is complete and can be verified. - * - * @return True if the timestamp is complete, False otherwise. - */ - public Boolean isTimestampComplete() { - for (Map.Entry item : this.allAttestations().entrySet()) { - //byte[] msg = item.getKey(); - TimeAttestation attestation = item.getValue(); - - if (attestation instanceof BitcoinBlockHeaderAttestation) { - return true; - } - } - - return false; - } - - /** - * Iterate over all attestations recursively - * - * @return Returns iterable of (msg, attestation) - */ - public HashMap allAttestations() { - HashMap map = new HashMap<>(); - - for (TimeAttestation attestation : this.attestations) { - map.put(this.msg, attestation); - } - - for (Map.Entry entry : this.ops.entrySet()) { - Timestamp ts = entry.getValue(); - //Op op = entry.getKey(); - - HashMap subMap = ts.allAttestations(); - - for (Map.Entry item : subMap.entrySet()) { - byte[] msg = item.getKey(); - TimeAttestation attestation = item.getValue(); - map.put(msg, attestation); - } - } - - return map; - } - - /** - * Iterate over all tips recursively - * - * @return Returns iterable of (msg, attestation) - */ - public Set allTips() { - Set set = new HashSet<>(); - - if (this.ops.size() == 0) { - set.add(this.msg); - } - - for (Map.Entry entry : this.ops.entrySet()) { - Timestamp ts = entry.getValue(); - //Op op = entry.getKey(); - - Set subSet = ts.allTips(); - - for (byte[] msg : subSet) { - set.add(msg); - } - } - - return set; - } - - /** - * Compare timestamps. - * - * @param timestamp the timestamp to compare with - * @return Returns true if timestamps are equals - */ - public boolean equals(Timestamp timestamp) { - if (Arrays.equals(this.getDigest(), timestamp.getDigest()) == false) { - return false; - } - - // Check attestations - if (this.attestations.size() != timestamp.attestations.size()) { - return false; - } - - for (int i = 0; i < this.attestations.size(); i++) { - TimeAttestation ta1 = this.attestations.get(i); - TimeAttestation ta2 = timestamp.attestations.get(i); - - if (!(ta1.equals(ta2))) { - return false; - } - } - - // Check operations - if (this.ops.size() != timestamp.ops.size()) { - return false; - } - - // Order list of operations - List> list1 = sortToList(this.ops.entrySet()); - List> list2 = sortToList(timestamp.ops.entrySet()); - - for (int i = 0; i < list1.size(); i++) { - Op op1 = list1.get(i).getKey(); - Op op2 = list2.get(i).getKey(); - - if (!op1.equals(op2)) { - return false; - } - - Timestamp t1 = list1.get(i).getValue(); - Timestamp t2 = list2.get(i).getValue(); - - if (!t1.equals(t2)) { - return false; - } - } - - return true; - } - - /** - * Add Op to current timestamp and return the sub stamp - * - * @param op - The operation to insert - * @return Returns the sub timestamp - */ - public Timestamp add(Op op) { - // nonce_appended_stamp = timestamp.ops.add(com.vitorpamplona.quartz.ots.op.OpAppend(os.urandom(16))) - //Op opAppend = new OpAppend(bytes); - - if (this.ops.containsKey(op)) { - return this.ops.get(op); - } - - Timestamp stamp = new Timestamp(op.call(this.msg)); - this.ops.put(op, stamp); - - return stamp; - } - - /** - * Retrieve a sorted list of all map entries. - * - * @param setEntries - The entries set of ops hashmap - * @return Returns the sorted list of map entries - */ - public List> sortToList(Set> setEntries) { - List> entries = new ArrayList<>(setEntries); - Collections.sort(entries, new Comparator>() { - @Override - public int compare(Entry a, Entry b) { - return a.getKey().compareTo(b.getKey()); - } - }); - - return entries; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Utils.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Utils.java deleted file mode 100644 index fdc2895dd4..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/Utils.java +++ /dev/null @@ -1,205 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; -import java.util.Locale; -import java.util.logging.ConsoleHandler; -import java.util.logging.LogRecord; -import java.util.logging.Logger; -import java.util.logging.SimpleFormatter; - -/** - * Utility functions for (mostly) manipulating byte arrays. - */ -public class Utils { - /** - * Fills a byte array with the given byte value. - * - * @param array the array to fill - * @param value the value to fill the array with - */ - public static void arrayFill(byte[] array, byte value) { - for (int i = 0; i < array.length; i++) { - array[i] = value; - } - } - - /** - * Returns the first value that is not null. If all objects are null, then it returns null. - * - * @param items the array of Ts - * @param This describes my type parameter - * @return the first value that is not null. If all objects are null, then it returns null. - * @deprecated Not used by Java OpenTimestamps itself, and doesn't offer much useful functionality. - */ - @Deprecated - public static T coalesce(T... items) { - for (T i : items) { - if (i != null) { - return i; - } - } - - return null; - } - - /** - * Returns a copy of the byte array argument, or null if the byte array argument is null. - * - * @param data the array of bytes to copy - * @return the copied byte array - */ - public static byte[] arraysCopy(byte[] data) { - if (data == null) { - return null; - } - - byte[] copy = new byte[data.length]; - System.arraycopy(data, 0, copy, 0, data.length); - - return copy; - } - - /** - * Returns a byte array which is the result of concatenating the two passed in byte arrays. - * None of the passed in arrays may be null. - * - * @param array1 the first array of bytes - * @param array2 the second array of bytes - * @return a copy of array1 and array2 concatenated together - */ - public static byte[] arraysConcat(byte[] array1, byte[] array2) { - byte[] array1and2 = new byte[array1.length + array2.length]; - System.arraycopy(array1, 0, array1and2, 0, array1.length); - System.arraycopy(array2, 0, array1and2, array1.length, array2.length); - - return array1and2; - } - - /** - * Returns a given length array of random bytes. - * - * @param length the requested length of the byte array - * @return a given length array of random bytes - * @throws NoSuchAlgorithmException for Java 8 implementations - */ - public static byte[] randBytes(int length) throws NoSuchAlgorithmException { - //Java 6 & 7: - SecureRandom random = new SecureRandom(); - byte[] bytes = new byte[length]; - random.nextBytes(bytes); - - // Java 8 (even more secure): - // SecureRandom.getInstanceStrong().nextBytes(bytes); - - return bytes; - } - - /** - * Returns a reversed copy of the passed in byte array. - * - * @param array the byte array to reverse - * @return a copy of the byte array, reversed - */ - public static byte[] arrayReverse(byte[] array) { - byte[] reversedArray = new byte[array.length]; - - for (int i = array.length - 1, j = 0; i >= 0; i--, j++) { - reversedArray[j] = array[i]; - } - - return reversedArray; - } - - /** - * Compares two byte arrays. - * - * @param left the left byte array to compare with - * @param right the right byte array to compare with - * @return 0 if the arrays are identical, negative if left < right, positive if left > right - */ - public static int compare(byte[] left, byte[] right) { - for (int i = 0, j = 0; i < left.length && j < right.length; i++, j++) { - int a = (left[i] & 0xff); - int b = (right[j] & 0xff); - - if (a != b) { - return a - b; - } - } - - return left.length - right.length; - } - - /** - * Returns a HEX representation of the passed in byte array. - * - * @param bytes the byte array to convert into a string of HEX - * @return a string representation of the byte array - */ - public static String bytesToHex(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - - for (byte b : bytes) { - sb.append(String.format("%02X", b)); - } - - return sb.toString(); - } - - /** - * Converts a HEX representation to its byte array representation. The passed in string must not be null. - * - * @param s a string in HEX format to be converted to a corresponding byte array - * @return the byte array representation of the string in HEX format - * @throws IllegalArgumentException if the passed in HEX string can't be converted to a byte array - */ - public static byte[] hexToBytes(String s) throws IllegalArgumentException { - int len = s.length(); - - if (len % 2 == 1) { - throw new IllegalArgumentException(); - } - - byte[] data = new byte[len / 2]; - - for (int i = 0; i < len; i += 2) { - if ((Character.digit(s.charAt(i), 16) == -1) || (Character.digit(s.charAt(i + 1), 16) == -1)) { - throw new IllegalArgumentException(); - } - - data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) - + Character.digit(s.charAt(i + 1), 16)); - } - - return data; - } - - /** - * Returns a string with the first letter uppercase. - * - * @param string the string to get its first character converted to uppercase - * @return the string, with its first character converted to uppercase - */ - public static String toUpperFirstLetter(String string) { - return string.substring(0, 1).toUpperCase(Locale.getDefault()) + string.substring(1).toLowerCase(Locale.getDefault()); - } - - // TODO: This is not the way to do logging. Fix later, possibly with slf4j annotation. Need to read up on the subject. - public static Logger getLogger(String name) { - Logger log = Logger.getLogger(name); - ConsoleHandler handler = new ConsoleHandler(); - - handler.setFormatter(new SimpleFormatter() { - @Override - public synchronized String format(LogRecord lr) { - return lr.getMessage() + "\r\n"; - } - }); - - log.setUseParentHandlers(false); - log.addHandler(handler); - - return log; - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/VerifyResult.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/VerifyResult.java deleted file mode 100644 index 51b48ab0ba..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/VerifyResult.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots; - -import android.annotation.SuppressLint; - -import java.text.DateFormatSymbols; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - -/** - * Class that lets us compare, sort, store and print timestamps. - */ -public class VerifyResult implements Comparable { - public static enum Chains { - BITCOIN, LITECOIN, ETHEREUM - } - - public Long timestamp; - public int height; - - public VerifyResult(Long timestamp, int height) { - this.timestamp = timestamp; - this.height = height; - } - - /** - * Returns, if existing, a string representation describing the existence of a block attest - */ - public String toString() { - if (height == 0 || timestamp == null) { - return ""; - } - - String pattern = "yyyy-MM-dd z"; - Locale locale = new Locale("en", "UK"); - DateFormatSymbols dateFormatSymbols = new DateFormatSymbols(locale); - @SuppressLint("SimpleDateFormat") - SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, dateFormatSymbols); - String string = simpleDateFormat.format(new Date(timestamp * 1000)); - - return "block " + String.valueOf(height) + " attests data existed as of " + string; - } - - @Override - public int compareTo(VerifyResult vr) { - return this.height - vr.height; - } - - @Override - public boolean equals(Object obj) { - VerifyResult vr = (VerifyResult) obj; - return this.timestamp == vr.timestamp && this.height == vr.height; - } - - @Override - public int hashCode() { - return ((int) (long) (this.timestamp)) ^ this.height; - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/BitcoinBlockHeaderAttestation.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/BitcoinBlockHeaderAttestation.java deleted file mode 100644 index abb74bd9f3..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/BitcoinBlockHeaderAttestation.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException; - -import java.util.Arrays; - -/** - * Bitcoin Block Header Attestation. - * The commitment digest will be the merkleroot of the blockheader. - * The block height is recorded so that looking up the correct block header in - * an external block header database doesn't require every header to be stored - * locally (33MB and counting). (remember that a memory-constrained local - * client can save an MMR that commits to all blocks, and use an external service to fill - * in pruned details). - * Otherwise no additional redundant data about the block header is recorded. - * This is very intentional: since the attestation contains (nearly) the - * absolute bare minimum amount of data, we encourage implementations to do - * the correct thing and get the block header from a by-height index, check - * that the merkleroots match, and then calculate the time from the header - * information. Providing more data would encourage implementations to cheat. - * Remember that the only thing that would invalidate the block height is a - * reorg, but in the event of a reorg the merkleroot will be invalid anyway, - * so there's no point to recording data in the attestation like the header - * itself. At best that would just give us extra confirmation that a reorg - * made the attestation invalid; reorgs deep enough to invalidate timestamps are - * exceptionally rare events anyway, so better to just tell the user the timestamp - * can't be verified rather than add almost-never tested code to handle that case - * more gracefully. - * - * @see TimeAttestation - */ -public class BitcoinBlockHeaderAttestation extends TimeAttestation { - - public static byte[] _TAG = {(byte) 0x05, (byte) 0x88, (byte) 0x96, (byte) 0x0d, (byte) 0x73, (byte) 0xd7, (byte) 0x19, (byte) 0x01}; - public static String chain = "bitcoin"; - - @Override - public byte[] _TAG() { - return BitcoinBlockHeaderAttestation._TAG; - } - - private int height = 0; - - public int getHeight() { - return height; - } - - public BitcoinBlockHeaderAttestation(int height_) { - super(); - this.height = height_; - } - - public static BitcoinBlockHeaderAttestation deserialize(StreamDeserializationContext ctxPayload) { - int height = ctxPayload.readVaruint(); - - return new BitcoinBlockHeaderAttestation(height); - } - - @Override - public void serializePayload(StreamSerializationContext ctx) { - ctx.writeVaruint(this.height); - } - - public String toString() { - return "BitcoinBlockHeaderAttestation(" + this.height + ")"; - } - - @Override - public int compareTo(TimeAttestation o) { - BitcoinBlockHeaderAttestation ob = (BitcoinBlockHeaderAttestation) o; - - return this.height - ob.height; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof BitcoinBlockHeaderAttestation)) { - return false; - } - - if (!Arrays.equals(this._TAG(), ((BitcoinBlockHeaderAttestation) obj)._TAG())) { - return false; - } - - if (this.height != ((BitcoinBlockHeaderAttestation) obj).height) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - return Arrays.hashCode(this._TAG()) ^ this.height; - } - - /** - * Verify attestation against a Bitcoin block header. - * @param digest the digest - * @param block the Bitcoin block header - * @return the block time on success; raises VerificationError on failure. - * @throws VerificationException verification exception - */ - public Long verifyAgainstBlockheader(byte[] digest, BlockHeader block) throws VerificationException { - if (digest.length != 32) { - throw new VerificationException("Expected digest with length 32 bytes; got " + digest.length + " bytes"); - } else if (!Arrays.equals(digest, Utils.hexToBytes(block.getMerkleroot()))) { - throw new VerificationException("Digest does not match merkleroot"); - } - - return block.getTime(); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/EthereumBlockHeaderAttestation.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/EthereumBlockHeaderAttestation.java deleted file mode 100644 index e0d5142753..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/EthereumBlockHeaderAttestation.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; - -import java.util.Arrays; - -/** - * Ethereum Block Header Attestation. - * - * @see TimeAttestation - */ -public class EthereumBlockHeaderAttestation extends TimeAttestation { - - public static byte[] _TAG = {(byte) 0x30, (byte) 0xfe, (byte) 0x80, (byte) 0x87, (byte) 0xb5, (byte) 0xc7, (byte) 0xea, (byte) 0xd7}; - public static String chain = "ethereum"; - - @Override - public byte[] _TAG() { - return EthereumBlockHeaderAttestation._TAG; - } - - private int height = 0; - - public int getHeight() { - return height; - } - - EthereumBlockHeaderAttestation(int height_) { - super(); - this.height = height_; - } - - public static EthereumBlockHeaderAttestation deserialize(StreamDeserializationContext ctxPayload) { - int height = ctxPayload.readVaruint(); - - return new EthereumBlockHeaderAttestation(height); - } - - @Override - public void serializePayload(StreamSerializationContext ctx) { - ctx.writeVaruint(this.height); - } - - public String toString() { - return "EthereumBlockHeaderAttestation(" + this.height + ")"; - } - - @Override - public int compareTo(TimeAttestation o) { - EthereumBlockHeaderAttestation ob = (EthereumBlockHeaderAttestation) o; - - return this.height - ob.height; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof EthereumBlockHeaderAttestation)) { - return false; - } - - if (!Arrays.equals(this._TAG(), ((EthereumBlockHeaderAttestation) obj)._TAG())) { - return false; - } - - if (this.height != ((EthereumBlockHeaderAttestation) obj).height) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - return Arrays.hashCode(this._TAG()) ^ this.height; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/LitecoinBlockHeaderAttestation.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/LitecoinBlockHeaderAttestation.java deleted file mode 100644 index dc7c22f286..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/LitecoinBlockHeaderAttestation.java +++ /dev/null @@ -1,98 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException; - -import java.util.Arrays; - -/** - * Litecoin Block Header Attestation. - * - * @see TimeAttestation - */ -public class LitecoinBlockHeaderAttestation extends TimeAttestation { - - public static byte[] _TAG = {(byte) 0x06, (byte) 0x86, (byte) 0x9a, (byte) 0x0d, (byte) 0x73, (byte) 0xd7, (byte) 0x1b, (byte) 0x45}; - - public static String chain = "litecoin"; - - @Override - public byte[] _TAG() { - return LitecoinBlockHeaderAttestation._TAG; - } - - private int height = 0; - - public int getHeight() { - return height; - } - - public LitecoinBlockHeaderAttestation(int height_) { - super(); - this.height = height_; - } - - public static LitecoinBlockHeaderAttestation deserialize(StreamDeserializationContext ctxPayload) { - int height = ctxPayload.readVaruint(); - - return new LitecoinBlockHeaderAttestation(height); - } - - @Override - public void serializePayload(StreamSerializationContext ctx) { - ctx.writeVaruint(this.height); - } - - public String toString() { - return "LitecoinBlockHeaderAttestation(" + this.height + ")"; - } - - @Override - public int compareTo(TimeAttestation o) { - LitecoinBlockHeaderAttestation ob = (LitecoinBlockHeaderAttestation) o; - - return this.height - ob.height; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof LitecoinBlockHeaderAttestation)) { - return false; - } - - if (!Arrays.equals(this._TAG(), ((LitecoinBlockHeaderAttestation) obj)._TAG())) { - return false; - } - - if (this.height != ((LitecoinBlockHeaderAttestation) obj).height) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - return Arrays.hashCode(this._TAG()) ^ this.height; - } - - /** - * Verify attestation against a Litecoin block header. - * @param digest the digest - * @param block the Litecoin block header - * @return the block time on success; raises VerificationError on failure. - * @throws VerificationException verification exception - */ - public Long verifyAgainstBlockheader(byte[] digest, BlockHeader block) throws VerificationException { - if (digest.length != 32) { - throw new VerificationException("Expected digest with length 32 bytes; got " + digest.length + " bytes"); - } else if (!Arrays.equals(digest, Utils.hexToBytes(block.getMerkleroot()))) { - throw new VerificationException("Digest does not match merkleroot"); - } - - return block.getTime(); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/PendingAttestation.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/PendingAttestation.java deleted file mode 100644 index 93c0e26e6f..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/PendingAttestation.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation; - -import android.util.Log; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; - -/** - * Pending attestations. - * Commitment has been recorded in a remote calendar for future attestation, - * and we have a URI to find a more complete timestamp in the future. - * Nothing other than the URI is recorded, nor is there provision made to add - * extra metadata (other than the URI) in future upgrades. The rational here - * is that remote calendars promise to keep commitments indefinitely, so from - * the moment they are created it should be possible to find the commitment in - * the calendar. Thus if you're not satisfied with the local verifiability of - * a timestamp, the correct thing to do is just ask the remote calendar if - * additional attestations are available and/or when they'll be available. - * While we could additional metadata like what types of attestations the - * remote calendar expects to be able to provide in the future, that metadata - * can easily change in the future too. Given that we don't expect timestamps - * to normally have more than a small number of remote calendar attestations, - * it'd be better to have verifiers get the most recent status of such - * information (possibly with appropriate negative response caching). - * - * @see TimeAttestation - */ -public class PendingAttestation extends TimeAttestation { - - public static byte[] _TAG = {(byte) 0x83, (byte) 0xdf, (byte) 0xe3, (byte) 0x0d, (byte) 0x2e, (byte) 0xf9, (byte) 0x0c, (byte) 0x8e}; - - @Override - public byte[] _TAG() { - return PendingAttestation._TAG; - } - - public static int _MAX_URI_LENGTH = 1000; - - public static String _ALLOWED_URI_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._/:"; - - private byte[] uri; - - public byte[] getUri() { - return uri; - } - - public PendingAttestation(byte[] uri_) { - super(); - this.uri = uri_; - } - - public static boolean checkUri(byte[] uri) { - if (uri.length > PendingAttestation._MAX_URI_LENGTH) { - System.err.print("URI exceeds maximum length"); - - return false; - } - - for (int i = 0; i < uri.length; i++) { - Character c = String.format("%c", uri[i]).charAt(0); - - if (PendingAttestation._ALLOWED_URI_CHARS.indexOf(c) < 0) { - Log.e("OpenTimestamp","URI contains invalid character "); - - return false; - } - } - - return true; - } - - public static PendingAttestation deserialize(StreamDeserializationContext ctxPayload) - throws DeserializationException { - - byte[] utf8Uri; - try { - utf8Uri = ctxPayload.readVarbytes(PendingAttestation._MAX_URI_LENGTH); - } catch (DeserializationException e) { - Log.e("OpenTimestamp","URI too long and thus invalid: "); - throw new DeserializationException("Invalid URI: "); - } - - if (PendingAttestation.checkUri(utf8Uri) == false) { - Log.e("OpenTimestamp","Invalid URI: "); - throw new DeserializationException("Invalid URI: "); - } - - return new PendingAttestation(utf8Uri); - } - - @Override - public void serializePayload(StreamSerializationContext ctx) { - ctx.writeVarbytes(this.uri); - } - - public String toString() { - return "PendingAttestation(\'" + new String(this.uri, StandardCharsets.UTF_8) + "\')"; - } - - @Override - public int compareTo(TimeAttestation o) { - PendingAttestation opa = (PendingAttestation) o; - - return Utils.compare(this.uri, opa.uri); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof PendingAttestation)) { - return false; - } - - if (!Arrays.equals(this._TAG(), ((PendingAttestation) obj)._TAG())) { - return false; - } - - if (!Arrays.equals(this.uri, ((PendingAttestation) obj).uri)) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - return Arrays.hashCode(this._TAG()) ^ Arrays.hashCode(this.uri); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/TimeAttestation.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/TimeAttestation.java deleted file mode 100644 index 2f511f8b56..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/TimeAttestation.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; - -import java.util.Arrays; - -/** - * Class representing {@link Timestamp} signature verification - */ -public abstract class TimeAttestation implements Comparable { - - public static int _TAG_SIZE = 8; - - public static int _MAX_PAYLOAD_SIZE = 8192; - - public byte[] _TAG; - - public byte[] _TAG() { - return new byte[]{}; - } - - /** - * Deserialize a general Time Attestation to the specific subclass Attestation. - * - * @param ctx The stream deserialization context. - * @return The specific subclass Attestation. - */ - public static TimeAttestation deserialize(StreamDeserializationContext ctx) throws DeserializationException { - // console.log('attestation deserialize'); - - byte[] tag = ctx.readBytes(_TAG_SIZE); - // console.log('tag: ', com.vitorpamplona.quartz.ots.Utils.bytesToHex(tag)); - - byte[] serializedAttestation = ctx.readVarbytes(_MAX_PAYLOAD_SIZE); - // console.log('serializedAttestation: ', com.vitorpamplona.quartz.ots.Utils.bytesToHex(serializedAttestation)); - - StreamDeserializationContext ctxPayload = new StreamDeserializationContext(serializedAttestation); - - /* eslint no-use-before-define: ["error", { "classes": false }] */ - if (Arrays.equals(tag, PendingAttestation._TAG) == true) { - return PendingAttestation.deserialize(ctxPayload); - } else if (Arrays.equals(tag, BitcoinBlockHeaderAttestation._TAG) == true) { - return BitcoinBlockHeaderAttestation.deserialize(ctxPayload); - } else if (Arrays.equals(tag, LitecoinBlockHeaderAttestation._TAG) == true) { - return LitecoinBlockHeaderAttestation.deserialize(ctxPayload); - } else if (Arrays.equals(tag, EthereumBlockHeaderAttestation._TAG) == true) { - return EthereumBlockHeaderAttestation.deserialize(ctxPayload); - } - - return new UnknownAttestation(tag, serializedAttestation); - } - - /** - * Serialize a a general Time Attestation to the specific subclass Attestation. - * - * @param ctx The output stream serialization context. - */ - public void serialize(StreamSerializationContext ctx) { - ctx.writeBytes(this._TAG()); - StreamSerializationContext ctxPayload = new StreamSerializationContext(); - serializePayload(ctxPayload); - ctx.writeVarbytes(ctxPayload.getOutput()); - } - - public void serializePayload(StreamSerializationContext ctxPayload) { - // TODO: Is this intentional? - } - - @Override - public int compareTo(TimeAttestation o) { - int deltaTag = Utils.compare(this._TAG(), o._TAG()); - - if (deltaTag == 0) { - return this.compareTo(o); - } else { - return deltaTag; - } - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/UnknownAttestation.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/UnknownAttestation.java deleted file mode 100644 index 2f726a2d1c..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/attestation/UnknownAttestation.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; - -import java.util.Arrays; - -/** - * Placeholder for attestations that don't support - * - * @see TimeAttestation - */ -public class UnknownAttestation extends TimeAttestation { - - byte[] payload; - - public static byte[] _TAG = new byte[]{}; - - @Override - public byte[] _TAG() { - return _TAG; - } - - UnknownAttestation(byte[] tag, byte[] payload) { - super(); - this._TAG = tag; - this.payload = payload; - } - - @Override - public void serializePayload(StreamSerializationContext ctx) { - ctx.writeBytes(this.payload); - } - - public static UnknownAttestation deserialize(StreamDeserializationContext ctxPayload, byte[] tag) throws DeserializationException { - byte[] payload = ctxPayload.readVarbytes(_MAX_PAYLOAD_SIZE); - - return new UnknownAttestation(tag, payload); - } - - public String toString() { - return "UnknownAttestation " + Utils.bytesToHex(this._TAG()) + ' ' + Utils.bytesToHex(this.payload); - } - - @Override - public int compareTo(TimeAttestation o) { - UnknownAttestation ota = (UnknownAttestation) o; - - return Utils.compare(this.payload, ota.payload); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof UnknownAttestation)) { - return false; - } - - if (!Arrays.equals(this._TAG(), ((UnknownAttestation) obj)._TAG())) { - return false; - } - - if (!Arrays.equals(this.payload, ((UnknownAttestation) obj).payload)) { - return false; - } - - return true; - } - - @Override - public int hashCode() { - return Arrays.hashCode(this._TAG()) ^ Arrays.hashCode(this.payload); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Digest.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Digest.java deleted file mode 100644 index 03f47a14a2..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Digest.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto; - -/** - * Message digest interface - */ -public interface Digest { - /** - * Return the algorithm name - * - * @return the algorithm name - */ - public String getAlgorithmName(); - - /** - * Return the size, in bytes, of the digest produced by this message digest. - * - * @return the size, in bytes, of the digest produced by this message digest. - */ - public int getDigestSize(); - - /** - * Update the message digest with a single byte. - * - * @param in the input byte to be entered. - */ - public void update(byte in); - - /** - * Update the message digest with a block of bytes. - * - * @param in the byte array containing the data. - * @param inOff the offset into the byte array where the data starts. - * @param len the length of the data. - */ - public void update(byte[] in, int inOff, int len); - - /** - * Close the digest, producing the final digest value. The doFinal - * call also resets the digest. - * - * @param out the array the digest is to be copied into. - * @param outOff the offset into the out array the digest is to start at. - * @return something - * @see #reset() - */ - public int doFinal(byte[] out, int outOff); - - /** - * Reset the digest back to it's initial state. - */ - public void reset(); -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/ExtendedDigest.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/ExtendedDigest.java deleted file mode 100644 index ed3b81563a..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/ExtendedDigest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto; - -public interface ExtendedDigest extends Digest { - /** - * Return the size in bytes of the internal buffer the digest applies it's compression - * function to. - * - * @return byte length of the digests internal buffer. - */ - public int getByteLength(); -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/GeneralDigest.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/GeneralDigest.java deleted file mode 100644 index 284d036534..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/GeneralDigest.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto; - -/** - * Base implementation of MD4 family style digest as outlined in - * "Handbook of Applied Cryptography", pages 344 - 347. - */ -public abstract class GeneralDigest implements ExtendedDigest, Memoable { - - private static final int BYTE_LENGTH = 64; - - private final byte[] xBuf = new byte[4]; - private int xBufOff; - - private long byteCount; - - /** - * Standard constructor - */ - protected GeneralDigest() { - xBufOff = 0; - } - - /** - * Copy constructor. We are using copy constructors in place - * of the Object.clone() interface as this interface is not - * supported by J2ME. - * - * @param t the GeneralDigest - */ - protected GeneralDigest(GeneralDigest t) { - copyIn(t); - } - - protected GeneralDigest(byte[] encodedState) { - System.arraycopy(encodedState, 0, xBuf, 0, xBuf.length); - xBufOff = Pack.bigEndianToInt(encodedState, 4); - byteCount = Pack.bigEndianToLong(encodedState, 8); - } - - protected void copyIn(GeneralDigest t) { - System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); - - xBufOff = t.xBufOff; - byteCount = t.byteCount; - } - - public void update(byte in) { - xBuf[xBufOff++] = in; - - if (xBufOff == xBuf.length) { - processWord(xBuf, 0); - xBufOff = 0; - } - - byteCount++; - } - - public void update(byte[] in, int inOff, int len) { - len = Math.max(0, len); - - // - // fill the current word - // - int i = 0; - - if (xBufOff != 0) { - while (i < len) { - xBuf[xBufOff++] = in[inOff + i++]; - - if (xBufOff == 4) { - processWord(xBuf, 0); - xBufOff = 0; - break; - } - } - } - - // - // process whole words. - // - int limit = ((len - i) & ~3) + i; - - for (; i < limit; i += 4) { - processWord(in, inOff + i); - } - - // - // load in the remainder. - // - while (i < len) { - xBuf[xBufOff++] = in[inOff + i++]; - } - - byteCount += len; - } - - public void finish() { - long bitLength = (byteCount << 3); - - // - // add the pad bytes. - // - update((byte) 128); - - while (xBufOff != 0) { - update((byte) 0); - } - - processLength(bitLength); - processBlock(); - } - - public void reset() { - byteCount = 0; - - xBufOff = 0; - - for (int i = 0; i < xBuf.length; i++) { - xBuf[i] = 0; - } - } - - protected void populateState(byte[] state) { - System.arraycopy(xBuf, 0, state, 0, xBufOff); - Pack.intToBigEndian(xBufOff, state, 4); - Pack.longToBigEndian(byteCount, state, 8); - } - - public int getByteLength() { - return BYTE_LENGTH; - } - - protected abstract void processWord(byte[] in, int inOff); - - protected abstract void processLength(long bitLength); - - protected abstract void processBlock(); -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/KeccakDigest.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/KeccakDigest.java deleted file mode 100644 index cd669e1ef5..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/KeccakDigest.java +++ /dev/null @@ -1,473 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; - -/** - * Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/ - *

- * Following the naming conventions used in the C source code to enable easy review of the implementation. - */ -public class KeccakDigest implements ExtendedDigest { - - private static long[] KeccakRoundConstants = keccakInitializeRoundConstants(); - - private static int[] KeccakRhoOffsets = keccakInitializeRhoOffsets(); - - private static long[] keccakInitializeRoundConstants() { - long[] keccakRoundConstants = new long[24]; - byte[] LFSRstate = new byte[1]; - - LFSRstate[0] = 0x01; - int i, j, bitPosition; - - for (i = 0; i < 24; i++) { - keccakRoundConstants[i] = 0; - - for (j = 0; j < 7; j++) { - bitPosition = (1 << j) - 1; - - if (LFSR86540(LFSRstate)) { - keccakRoundConstants[i] ^= 1L << bitPosition; - } - } - } - - return keccakRoundConstants; - } - - private static boolean LFSR86540(byte[] LFSR) { - boolean result = (((LFSR[0]) & 0x01) != 0); - - if (((LFSR[0]) & 0x80) != 0) { - LFSR[0] = (byte) (((LFSR[0]) << 1) ^ 0x71); - } else { - LFSR[0] <<= 1; - } - - return result; - } - - private static int[] keccakInitializeRhoOffsets() { - int[] keccakRhoOffsets = new int[25]; - int x, y, t, newX, newY; - - keccakRhoOffsets[(((0) % 5) + 5 * ((0) % 5))] = 0; - x = 1; - y = 0; - - for (t = 0; t < 24; t++) { - keccakRhoOffsets[(((x) % 5) + 5 * ((y) % 5))] = ((t + 1) * (t + 2) / 2) % 64; - newX = (0 * x + 1 * y) % 5; - newY = (2 * x + 3 * y) % 5; - x = newX; - y = newY; - } - - return keccakRhoOffsets; - } - - protected byte[] state = new byte[(1600 / 8)]; - protected byte[] dataQueue = new byte[(1536 / 8)]; - protected int rate; - protected int bitsInQueue; - protected int fixedOutputLength; - protected boolean squeezing; - protected int bitsAvailableForSqueezing; - protected byte[] chunk; - protected byte[] oneByte; - - private void clearDataQueueSection(int off, int len) { - for (int i = off; i != off + len; i++) { - dataQueue[i] = 0; - } - } - - public KeccakDigest() { - this(288); - } - - public KeccakDigest(int bitLength) { - init(bitLength); - } - - public KeccakDigest(KeccakDigest source) { - System.arraycopy(source.state, 0, this.state, 0, source.state.length); - System.arraycopy(source.dataQueue, 0, this.dataQueue, 0, source.dataQueue.length); - this.rate = source.rate; - this.bitsInQueue = source.bitsInQueue; - this.fixedOutputLength = source.fixedOutputLength; - this.squeezing = source.squeezing; - this.bitsAvailableForSqueezing = source.bitsAvailableForSqueezing; - this.chunk = Utils.arraysCopy(source.chunk); - this.oneByte = Utils.arraysCopy(source.oneByte); - } - - public String getAlgorithmName() { - return "Keccak-" + fixedOutputLength; - } - - public int getDigestSize() { - return fixedOutputLength / 8; - } - - public void update(byte in) { - oneByte[0] = in; - - absorb(oneByte, 0, 8L); - } - - public void update(byte[] in, int inOff, int len) { - absorb(in, inOff, len * 8L); - } - - public int doFinal(byte[] out, int outOff) { - squeeze(out, outOff, fixedOutputLength); - reset(); - - return getDigestSize(); - } - - /* - * TODO Possible API change to support partial-byte suffixes. - */ - protected int doFinal(byte[] out, int outOff, byte partialByte, int partialBits) { - if (partialBits > 0) { - oneByte[0] = partialByte; - absorb(oneByte, 0, partialBits); - } - - squeeze(out, outOff, fixedOutputLength); - reset(); - - return getDigestSize(); - } - - public void reset() { - init(fixedOutputLength); - } - - /** - * Return the size of block that the compression function is applied to in bytes. - * - * @return internal byte length of a block. - */ - public int getByteLength() { - return rate / 8; - } - - private void init(int bitLength) { - switch (bitLength) { - case 288: - initSponge(1024, 576); - break; - case 128: - initSponge(1344, 256); - break; - case 224: - initSponge(1152, 448); - break; - case 256: - initSponge(1088, 512); - break; - case 384: - initSponge(832, 768); - break; - case 512: - initSponge(576, 1024); - break; - default: - throw new IllegalArgumentException("bitLength must be one of 128, 224, 256, 288, 384, or 512."); - } - } - - private void initSponge(int rate, int capacity) { - if (rate + capacity != 1600) { - throw new IllegalStateException("rate + capacity != 1600"); - } - - if ((rate <= 0) || (rate >= 1600) || ((rate % 64) != 0)) { - throw new IllegalStateException("invalid rate value"); - } - - this.rate = rate; - // this is never read, need to check to see why we want to save it - // this.capacity = capacity; - Utils.arrayFill(this.state, (byte) 0); - Utils.arrayFill(this.dataQueue, (byte) 0); - this.bitsInQueue = 0; - this.squeezing = false; - this.bitsAvailableForSqueezing = 0; - this.fixedOutputLength = capacity / 2; - this.chunk = new byte[rate / 8]; - this.oneByte = new byte[1]; - } - - private void absorbQueue() { - KeccakAbsorb(state, dataQueue, rate / 8); - bitsInQueue = 0; - } - - protected void absorb(byte[] data, int off, long databitlen) { - long i, j, wholeBlocks; - - if ((bitsInQueue % 8) != 0) { - throw new IllegalStateException("attempt to absorb with odd length queue"); - } - - if (squeezing) { - throw new IllegalStateException("attempt to absorb while squeezing"); - } - - i = 0; - - while (i < databitlen) { - if ((bitsInQueue == 0) && (databitlen >= rate) && (i <= (databitlen - rate))) { - wholeBlocks = (databitlen - i) / rate; - - for (j = 0; j < wholeBlocks; j++) { - System.arraycopy(data, (int) (off + (i / 8) + (j * chunk.length)), chunk, 0, chunk.length); - -// displayIntermediateValues.displayBytes(1, "Block to be absorbed", curData, rate / 8); - - KeccakAbsorb(state, chunk, chunk.length); - } - - i += wholeBlocks * rate; - } else { - int partialBlock = (int) (databitlen - i); - - if (partialBlock + bitsInQueue > rate) { - partialBlock = rate - bitsInQueue; - } - - int partialByte = partialBlock % 8; - partialBlock -= partialByte; - System.arraycopy(data, off + (int) (i / 8), dataQueue, bitsInQueue / 8, partialBlock / 8); - - bitsInQueue += partialBlock; - i += partialBlock; - - if (bitsInQueue == rate) { - absorbQueue(); - } - - if (partialByte > 0) { - int mask = (1 << partialByte) - 1; - dataQueue[bitsInQueue / 8] = (byte) (data[off + ((int) (i / 8))] & mask); - bitsInQueue += partialByte; - i += partialByte; - } - } - } - } - - private void padAndSwitchToSqueezingPhase() { - if (bitsInQueue + 1 == rate) { - dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8); - absorbQueue(); - clearDataQueueSection(0, rate / 8); - } else { - clearDataQueueSection((bitsInQueue + 7) / 8, rate / 8 - (bitsInQueue + 7) / 8); - dataQueue[bitsInQueue / 8] |= 1 << (bitsInQueue % 8); - } - - dataQueue[(rate - 1) / 8] |= 1 << ((rate - 1) % 8); - absorbQueue(); - -// displayIntermediateValues.displayText(1, "--- Switching to squeezing phase ---"); - - if (rate == 1024) { - KeccakExtract1024bits(state, dataQueue); - bitsAvailableForSqueezing = 1024; - } else { - KeccakExtract(state, dataQueue, rate / 64); - bitsAvailableForSqueezing = rate; - } - -// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8); - - squeezing = true; - } - - protected void squeeze(byte[] output, int offset, long outputLength) { - long i; - int partialBlock; - - if (!squeezing) { - padAndSwitchToSqueezingPhase(); - } - - if ((outputLength % 8) != 0) { - throw new IllegalStateException("outputLength not a multiple of 8"); - } - - i = 0; - - while (i < outputLength) { - if (bitsAvailableForSqueezing == 0) { - keccakPermutation(state); - - if (rate == 1024) { - KeccakExtract1024bits(state, dataQueue); - bitsAvailableForSqueezing = 1024; - } else { - KeccakExtract(state, dataQueue, rate / 64); - bitsAvailableForSqueezing = rate; - } - -// displayIntermediateValues.displayBytes(1, "Block available for squeezing", dataQueue, bitsAvailableForSqueezing / 8); - - } - - partialBlock = bitsAvailableForSqueezing; - - if ((long) partialBlock > outputLength - i) { - partialBlock = (int) (outputLength - i); - } - - System.arraycopy(dataQueue, (rate - bitsAvailableForSqueezing) / 8, output, offset + (int) (i / 8), partialBlock / 8); - bitsAvailableForSqueezing -= partialBlock; - i += partialBlock; - } - } - - private void fromBytesToWords(long[] stateAsWords, byte[] state) { - for (int i = 0; i < (1600 / 64); i++) { - stateAsWords[i] = 0; - int index = i * (64 / 8); - - for (int j = 0; j < (64 / 8); j++) { - stateAsWords[i] |= ((long) state[index + j] & 0xff) << ((8 * j)); - } - } - } - - private void fromWordsToBytes(byte[] state, long[] stateAsWords) { - for (int i = 0; i < (1600 / 64); i++) { - int index = i * (64 / 8); - - for (int j = 0; j < (64 / 8); j++) { - state[index + j] = (byte) ((stateAsWords[i] >>> ((8 * j))) & 0xFF); - } - } - } - - private void keccakPermutation(byte[] state) { - long[] longState = new long[state.length / 8]; - - fromBytesToWords(longState, state); - -// displayIntermediateValues.displayStateAsBytes(1, "Input of permutation", longState); - - keccakPermutationOnWords(longState); - -// displayIntermediateValues.displayStateAsBytes(1, "State after permutation", longState); - - fromWordsToBytes(state, longState); - } - - private void keccakPermutationAfterXor(byte[] state, byte[] data, int dataLengthInBytes) { - int i; - - for (i = 0; i < dataLengthInBytes; i++) { - state[i] ^= data[i]; - } - - keccakPermutation(state); - } - - private void keccakPermutationOnWords(long[] state) { - int i; - -// displayIntermediateValues.displayStateAs64bitWords(3, "Same, with lanes as 64-bit words", state); - - for (i = 0; i < 24; i++) { -// displayIntermediateValues.displayRoundNumber(3, i); - - theta(state); -// displayIntermediateValues.displayStateAs64bitWords(3, "After theta", state); - - rho(state); -// displayIntermediateValues.displayStateAs64bitWords(3, "After rho", state); - - pi(state); -// displayIntermediateValues.displayStateAs64bitWords(3, "After pi", state); - - chi(state); -// displayIntermediateValues.displayStateAs64bitWords(3, "After chi", state); - - iota(state, i); -// displayIntermediateValues.displayStateAs64bitWords(3, "After iota", state); - } - } - - long[] C = new long[5]; - - private void theta(long[] A) { - for (int x = 0; x < 5; x++) { - C[x] = 0; - - for (int y = 0; y < 5; y++) { - C[x] ^= A[x + 5 * y]; - } - } - for (int x = 0; x < 5; x++) { - long dX = ((((C[(x + 1) % 5]) << 1) ^ ((C[(x + 1) % 5]) >>> (64 - 1)))) ^ C[(x + 4) % 5]; - - for (int y = 0; y < 5; y++) { - A[x + 5 * y] ^= dX; - } - } - } - - private void rho(long[] A) { - for (int x = 0; x < 5; x++) { - for (int y = 0; y < 5; y++) { - int index = x + 5 * y; - A[index] = ((KeccakRhoOffsets[index] != 0) ? (((A[index]) << KeccakRhoOffsets[index]) ^ ((A[index]) >>> (64 - KeccakRhoOffsets[index]))) : A[index]); - } - } - } - - long[] tempA = new long[25]; - - private void pi(long[] A) { - System.arraycopy(A, 0, tempA, 0, tempA.length); - - for (int x = 0; x < 5; x++) { - for (int y = 0; y < 5; y++) { - A[y + 5 * ((2 * x + 3 * y) % 5)] = tempA[x + 5 * y]; - } - } - } - - long[] chiC = new long[5]; - - private void chi(long[] A) { - for (int y = 0; y < 5; y++) { - for (int x = 0; x < 5; x++) { - chiC[x] = A[x + 5 * y] ^ ((~A[(((x + 1) % 5) + 5 * y)]) & A[(((x + 2) % 5) + 5 * y)]); - } - - for (int x = 0; x < 5; x++) { - A[x + 5 * y] = chiC[x]; - } - } - } - - private void iota(long[] A, int indexRound) { - A[(((0) % 5) + 5 * ((0) % 5))] ^= KeccakRoundConstants[indexRound]; - } - - private void KeccakAbsorb(byte[] byteState, byte[] data, int dataInBytes) { - keccakPermutationAfterXor(byteState, data, dataInBytes); - } - - private void KeccakExtract1024bits(byte[] byteState, byte[] data) { - System.arraycopy(byteState, 0, data, 0, 128); - } - - private void KeccakExtract(byte[] byteState, byte[] data, int laneCount) { - System.arraycopy(byteState, 0, data, 0, laneCount * 8); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Memoable.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Memoable.java deleted file mode 100644 index 59b8154a80..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Memoable.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto; - -/** - * Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state - * via the {@link #copy copy()} method and then resetting the object back to that state later using the - * {@link #reset reset()} method. - */ -public interface Memoable { - /** - * Produce a copy of this object with its configuration and in its current state. - *

- * The returned object may be used simply to store the state, or may be used as a similar object - * starting from the copied state. - * - * @return Memoable object - */ - Memoable copy(); - - /** - * Restore a copied object state into this object. - *

- * Implementations of this method should try to avoid or minimise memory allocation to perform the reset. - * - * @param other an object originally {@link #copy() copied} from an object of the same type as this instance. - * @throws ClassCastException if the provided object is not of the correct type. - */ - void reset(Memoable other); -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Pack.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Pack.java deleted file mode 100644 index 7fe7a0ce34..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/Pack.java +++ /dev/null @@ -1,242 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto; - -/** - * Utility methods for converting byte arrays into ints and longs, and back again. - */ -public abstract class Pack -{ - public static short bigEndianToShort(byte[] bs, int off) - { - int n = (bs[ off] & 0xff) << 8; - n |= (bs[++off] & 0xff); - return (short)n; - } - - public static int bigEndianToInt(byte[] bs, int off) - { - int n = bs[ off] << 24; - n |= (bs[++off] & 0xff) << 16; - n |= (bs[++off] & 0xff) << 8; - n |= (bs[++off] & 0xff); - return n; - } - - public static void bigEndianToInt(byte[] bs, int off, int[] ns) - { - for (int i = 0; i < ns.length; ++i) - { - ns[i] = bigEndianToInt(bs, off); - off += 4; - } - } - - public static byte[] intToBigEndian(int n) - { - byte[] bs = new byte[4]; - intToBigEndian(n, bs, 0); - return bs; - } - - public static void intToBigEndian(int n, byte[] bs, int off) - { - bs[ off] = (byte)(n >>> 24); - bs[++off] = (byte)(n >>> 16); - bs[++off] = (byte)(n >>> 8); - bs[++off] = (byte)(n ); - } - - public static byte[] intToBigEndian(int[] ns) - { - byte[] bs = new byte[4 * ns.length]; - intToBigEndian(ns, bs, 0); - return bs; - } - - public static void intToBigEndian(int[] ns, byte[] bs, int off) - { - for (int i = 0; i < ns.length; ++i) - { - intToBigEndian(ns[i], bs, off); - off += 4; - } - } - - public static long bigEndianToLong(byte[] bs, int off) - { - int hi = bigEndianToInt(bs, off); - int lo = bigEndianToInt(bs, off + 4); - return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); - } - - public static void bigEndianToLong(byte[] bs, int off, long[] ns) - { - for (int i = 0; i < ns.length; ++i) - { - ns[i] = bigEndianToLong(bs, off); - off += 8; - } - } - - public static byte[] longToBigEndian(long n) - { - byte[] bs = new byte[8]; - longToBigEndian(n, bs, 0); - return bs; - } - - public static void longToBigEndian(long n, byte[] bs, int off) - { - intToBigEndian((int)(n >>> 32), bs, off); - intToBigEndian((int)(n & 0xffffffffL), bs, off + 4); - } - - public static byte[] longToBigEndian(long[] ns) - { - byte[] bs = new byte[8 * ns.length]; - longToBigEndian(ns, bs, 0); - return bs; - } - - public static void longToBigEndian(long[] ns, byte[] bs, int off) - { - for (int i = 0; i < ns.length; ++i) - { - longToBigEndian(ns[i], bs, off); - off += 8; - } - } - - public static short littleEndianToShort(byte[] bs, int off) - { - int n = bs[ off] & 0xff; - n |= (bs[++off] & 0xff) << 8; - return (short)n; - } - - public static int littleEndianToInt(byte[] bs, int off) - { - int n = bs[ off] & 0xff; - n |= (bs[++off] & 0xff) << 8; - n |= (bs[++off] & 0xff) << 16; - n |= bs[++off] << 24; - return n; - } - - public static void littleEndianToInt(byte[] bs, int off, int[] ns) - { - for (int i = 0; i < ns.length; ++i) - { - ns[i] = littleEndianToInt(bs, off); - off += 4; - } - } - - public static void littleEndianToInt(byte[] bs, int bOff, int[] ns, int nOff, int count) - { - for (int i = 0; i < count; ++i) - { - ns[nOff + i] = littleEndianToInt(bs, bOff); - bOff += 4; - } - } - - public static int[] littleEndianToInt(byte[] bs, int off, int count) - { - int[] ns = new int[count]; - for (int i = 0; i < ns.length; ++i) - { - ns[i] = littleEndianToInt(bs, off); - off += 4; - } - return ns; - } - - public static byte[] shortToLittleEndian(short n) - { - byte[] bs = new byte[2]; - shortToLittleEndian(n, bs, 0); - return bs; - } - - public static void shortToLittleEndian(short n, byte[] bs, int off) - { - bs[ off] = (byte)(n ); - bs[++off] = (byte)(n >>> 8); - } - - public static byte[] intToLittleEndian(int n) - { - byte[] bs = new byte[4]; - intToLittleEndian(n, bs, 0); - return bs; - } - - public static void intToLittleEndian(int n, byte[] bs, int off) - { - bs[ off] = (byte)(n ); - bs[++off] = (byte)(n >>> 8); - bs[++off] = (byte)(n >>> 16); - bs[++off] = (byte)(n >>> 24); - } - - public static byte[] intToLittleEndian(int[] ns) - { - byte[] bs = new byte[4 * ns.length]; - intToLittleEndian(ns, bs, 0); - return bs; - } - - public static void intToLittleEndian(int[] ns, byte[] bs, int off) - { - for (int i = 0; i < ns.length; ++i) - { - intToLittleEndian(ns[i], bs, off); - off += 4; - } - } - - public static long littleEndianToLong(byte[] bs, int off) - { - int lo = littleEndianToInt(bs, off); - int hi = littleEndianToInt(bs, off + 4); - return ((long)(hi & 0xffffffffL) << 32) | (long)(lo & 0xffffffffL); - } - - public static void littleEndianToLong(byte[] bs, int off, long[] ns) - { - for (int i = 0; i < ns.length; ++i) - { - ns[i] = littleEndianToLong(bs, off); - off += 8; - } - } - - public static byte[] longToLittleEndian(long n) - { - byte[] bs = new byte[8]; - longToLittleEndian(n, bs, 0); - return bs; - } - - public static void longToLittleEndian(long n, byte[] bs, int off) - { - intToLittleEndian((int)(n & 0xffffffffL), bs, off); - intToLittleEndian((int)(n >>> 32), bs, off + 4); - } - - public static byte[] longToLittleEndian(long[] ns) - { - byte[] bs = new byte[8 * ns.length]; - longToLittleEndian(ns, bs, 0); - return bs; - } - - public static void longToLittleEndian(long[] ns, byte[] bs, int off) - { - for (int i = 0; i < ns.length; ++i) - { - longToLittleEndian(ns[i], bs, off); - off += 8; - } - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/RIPEMD160Digest.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/RIPEMD160Digest.java deleted file mode 100644 index d50e65ab63..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/crypto/RIPEMD160Digest.java +++ /dev/null @@ -1,442 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto; - -/** - * Implementation of RIPEMD - * - * @see ripemd160 - */ -public class RIPEMD160Digest - extends GeneralDigest -{ - private static final int DIGEST_LENGTH = 20; - - private int H0, H1, H2, H3, H4; // IV's - - private int[] X = new int[16]; - private int xOff; - - /** - * Standard constructor - */ - public RIPEMD160Digest() - { - reset(); - } - - /** - * Copy constructor. This will copy the state of the provided - * message digest. - * @param t RIPEMD160Digest - */ - public RIPEMD160Digest(RIPEMD160Digest t) - { - super(t); - - copyIn(t); - } - - private void copyIn(RIPEMD160Digest t) - { - super.copyIn(t); - - H0 = t.H0; - H1 = t.H1; - H2 = t.H2; - H3 = t.H3; - H4 = t.H4; - - System.arraycopy(t.X, 0, X, 0, t.X.length); - xOff = t.xOff; - } - - public String getAlgorithmName() - { - return "RIPEMD160"; - } - - public int getDigestSize() - { - return DIGEST_LENGTH; - } - - protected void processWord( - byte[] in, - int inOff) - { - X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) - | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); - - if (xOff == 16) - { - processBlock(); - } - } - - protected void processLength( - long bitLength) - { - if (xOff > 14) - { - processBlock(); - } - - X[14] = (int)(bitLength & 0xffffffff); - X[15] = (int)(bitLength >>> 32); - } - - private void unpackWord( - int word, - byte[] out, - int outOff) - { - out[outOff] = (byte)word; - out[outOff + 1] = (byte)(word >>> 8); - out[outOff + 2] = (byte)(word >>> 16); - out[outOff + 3] = (byte)(word >>> 24); - } - - public int doFinal( - byte[] out, - int outOff) - { - finish(); - - unpackWord(H0, out, outOff); - unpackWord(H1, out, outOff + 4); - unpackWord(H2, out, outOff + 8); - unpackWord(H3, out, outOff + 12); - unpackWord(H4, out, outOff + 16); - - reset(); - - return DIGEST_LENGTH; - } - - /** - * reset the chaining variables to the IV values. - */ - public void reset() - { - super.reset(); - - H0 = 0x67452301; - H1 = 0xefcdab89; - H2 = 0x98badcfe; - H3 = 0x10325476; - H4 = 0xc3d2e1f0; - - xOff = 0; - - for (int i = 0; i != X.length; i++) - { - X[i] = 0; - } - } - - /* - * rotate int x left n bits. - */ - private int RL( - int x, - int n) - { - return (x << n) | (x >>> (32 - n)); - } - - /* - * f1,f2,f3,f4,f5 are the basic RIPEMD160 functions. - */ - - /* - * rounds 0-15 - */ - private int f1( - int x, - int y, - int z) - { - return x ^ y ^ z; - } - - /* - * rounds 16-31 - */ - private int f2( - int x, - int y, - int z) - { - return (x & y) | (~x & z); - } - - /* - * rounds 32-47 - */ - private int f3( - int x, - int y, - int z) - { - return (x | ~y) ^ z; - } - - /* - * rounds 48-63 - */ - private int f4( - int x, - int y, - int z) - { - return (x & z) | (y & ~z); - } - - /* - * rounds 64-79 - */ - private int f5( - int x, - int y, - int z) - { - return x ^ (y | ~z); - } - - protected void processBlock() - { - int a, aa; - int b, bb; - int c, cc; - int d, dd; - int e, ee; - - a = aa = H0; - b = bb = H1; - c = cc = H2; - d = dd = H3; - e = ee = H4; - - // - // Rounds 1 - 16 - // - // left - a = RL(a + f1(b,c,d) + X[ 0], 11) + e; c = RL(c, 10); - e = RL(e + f1(a,b,c) + X[ 1], 14) + d; b = RL(b, 10); - d = RL(d + f1(e,a,b) + X[ 2], 15) + c; a = RL(a, 10); - c = RL(c + f1(d,e,a) + X[ 3], 12) + b; e = RL(e, 10); - b = RL(b + f1(c,d,e) + X[ 4], 5) + a; d = RL(d, 10); - a = RL(a + f1(b,c,d) + X[ 5], 8) + e; c = RL(c, 10); - e = RL(e + f1(a,b,c) + X[ 6], 7) + d; b = RL(b, 10); - d = RL(d + f1(e,a,b) + X[ 7], 9) + c; a = RL(a, 10); - c = RL(c + f1(d,e,a) + X[ 8], 11) + b; e = RL(e, 10); - b = RL(b + f1(c,d,e) + X[ 9], 13) + a; d = RL(d, 10); - a = RL(a + f1(b,c,d) + X[10], 14) + e; c = RL(c, 10); - e = RL(e + f1(a,b,c) + X[11], 15) + d; b = RL(b, 10); - d = RL(d + f1(e,a,b) + X[12], 6) + c; a = RL(a, 10); - c = RL(c + f1(d,e,a) + X[13], 7) + b; e = RL(e, 10); - b = RL(b + f1(c,d,e) + X[14], 9) + a; d = RL(d, 10); - a = RL(a + f1(b,c,d) + X[15], 8) + e; c = RL(c, 10); - - // right - aa = RL(aa + f5(bb,cc,dd) + X[ 5] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); - ee = RL(ee + f5(aa,bb,cc) + X[14] + 0x50a28be6, 9) + dd; bb = RL(bb, 10); - dd = RL(dd + f5(ee,aa,bb) + X[ 7] + 0x50a28be6, 9) + cc; aa = RL(aa, 10); - cc = RL(cc + f5(dd,ee,aa) + X[ 0] + 0x50a28be6, 11) + bb; ee = RL(ee, 10); - bb = RL(bb + f5(cc,dd,ee) + X[ 9] + 0x50a28be6, 13) + aa; dd = RL(dd, 10); - aa = RL(aa + f5(bb,cc,dd) + X[ 2] + 0x50a28be6, 15) + ee; cc = RL(cc, 10); - ee = RL(ee + f5(aa,bb,cc) + X[11] + 0x50a28be6, 15) + dd; bb = RL(bb, 10); - dd = RL(dd + f5(ee,aa,bb) + X[ 4] + 0x50a28be6, 5) + cc; aa = RL(aa, 10); - cc = RL(cc + f5(dd,ee,aa) + X[13] + 0x50a28be6, 7) + bb; ee = RL(ee, 10); - bb = RL(bb + f5(cc,dd,ee) + X[ 6] + 0x50a28be6, 7) + aa; dd = RL(dd, 10); - aa = RL(aa + f5(bb,cc,dd) + X[15] + 0x50a28be6, 8) + ee; cc = RL(cc, 10); - ee = RL(ee + f5(aa,bb,cc) + X[ 8] + 0x50a28be6, 11) + dd; bb = RL(bb, 10); - dd = RL(dd + f5(ee,aa,bb) + X[ 1] + 0x50a28be6, 14) + cc; aa = RL(aa, 10); - cc = RL(cc + f5(dd,ee,aa) + X[10] + 0x50a28be6, 14) + bb; ee = RL(ee, 10); - bb = RL(bb + f5(cc,dd,ee) + X[ 3] + 0x50a28be6, 12) + aa; dd = RL(dd, 10); - aa = RL(aa + f5(bb,cc,dd) + X[12] + 0x50a28be6, 6) + ee; cc = RL(cc, 10); - - // - // Rounds 16-31 - // - // left - e = RL(e + f2(a,b,c) + X[ 7] + 0x5a827999, 7) + d; b = RL(b, 10); - d = RL(d + f2(e,a,b) + X[ 4] + 0x5a827999, 6) + c; a = RL(a, 10); - c = RL(c + f2(d,e,a) + X[13] + 0x5a827999, 8) + b; e = RL(e, 10); - b = RL(b + f2(c,d,e) + X[ 1] + 0x5a827999, 13) + a; d = RL(d, 10); - a = RL(a + f2(b,c,d) + X[10] + 0x5a827999, 11) + e; c = RL(c, 10); - e = RL(e + f2(a,b,c) + X[ 6] + 0x5a827999, 9) + d; b = RL(b, 10); - d = RL(d + f2(e,a,b) + X[15] + 0x5a827999, 7) + c; a = RL(a, 10); - c = RL(c + f2(d,e,a) + X[ 3] + 0x5a827999, 15) + b; e = RL(e, 10); - b = RL(b + f2(c,d,e) + X[12] + 0x5a827999, 7) + a; d = RL(d, 10); - a = RL(a + f2(b,c,d) + X[ 0] + 0x5a827999, 12) + e; c = RL(c, 10); - e = RL(e + f2(a,b,c) + X[ 9] + 0x5a827999, 15) + d; b = RL(b, 10); - d = RL(d + f2(e,a,b) + X[ 5] + 0x5a827999, 9) + c; a = RL(a, 10); - c = RL(c + f2(d,e,a) + X[ 2] + 0x5a827999, 11) + b; e = RL(e, 10); - b = RL(b + f2(c,d,e) + X[14] + 0x5a827999, 7) + a; d = RL(d, 10); - a = RL(a + f2(b,c,d) + X[11] + 0x5a827999, 13) + e; c = RL(c, 10); - e = RL(e + f2(a,b,c) + X[ 8] + 0x5a827999, 12) + d; b = RL(b, 10); - - // right - ee = RL(ee + f4(aa,bb,cc) + X[ 6] + 0x5c4dd124, 9) + dd; bb = RL(bb, 10); - dd = RL(dd + f4(ee,aa,bb) + X[11] + 0x5c4dd124, 13) + cc; aa = RL(aa, 10); - cc = RL(cc + f4(dd,ee,aa) + X[ 3] + 0x5c4dd124, 15) + bb; ee = RL(ee, 10); - bb = RL(bb + f4(cc,dd,ee) + X[ 7] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); - aa = RL(aa + f4(bb,cc,dd) + X[ 0] + 0x5c4dd124, 12) + ee; cc = RL(cc, 10); - ee = RL(ee + f4(aa,bb,cc) + X[13] + 0x5c4dd124, 8) + dd; bb = RL(bb, 10); - dd = RL(dd + f4(ee,aa,bb) + X[ 5] + 0x5c4dd124, 9) + cc; aa = RL(aa, 10); - cc = RL(cc + f4(dd,ee,aa) + X[10] + 0x5c4dd124, 11) + bb; ee = RL(ee, 10); - bb = RL(bb + f4(cc,dd,ee) + X[14] + 0x5c4dd124, 7) + aa; dd = RL(dd, 10); - aa = RL(aa + f4(bb,cc,dd) + X[15] + 0x5c4dd124, 7) + ee; cc = RL(cc, 10); - ee = RL(ee + f4(aa,bb,cc) + X[ 8] + 0x5c4dd124, 12) + dd; bb = RL(bb, 10); - dd = RL(dd + f4(ee,aa,bb) + X[12] + 0x5c4dd124, 7) + cc; aa = RL(aa, 10); - cc = RL(cc + f4(dd,ee,aa) + X[ 4] + 0x5c4dd124, 6) + bb; ee = RL(ee, 10); - bb = RL(bb + f4(cc,dd,ee) + X[ 9] + 0x5c4dd124, 15) + aa; dd = RL(dd, 10); - aa = RL(aa + f4(bb,cc,dd) + X[ 1] + 0x5c4dd124, 13) + ee; cc = RL(cc, 10); - ee = RL(ee + f4(aa,bb,cc) + X[ 2] + 0x5c4dd124, 11) + dd; bb = RL(bb, 10); - - // - // Rounds 32-47 - // - // left - d = RL(d + f3(e,a,b) + X[ 3] + 0x6ed9eba1, 11) + c; a = RL(a, 10); - c = RL(c + f3(d,e,a) + X[10] + 0x6ed9eba1, 13) + b; e = RL(e, 10); - b = RL(b + f3(c,d,e) + X[14] + 0x6ed9eba1, 6) + a; d = RL(d, 10); - a = RL(a + f3(b,c,d) + X[ 4] + 0x6ed9eba1, 7) + e; c = RL(c, 10); - e = RL(e + f3(a,b,c) + X[ 9] + 0x6ed9eba1, 14) + d; b = RL(b, 10); - d = RL(d + f3(e,a,b) + X[15] + 0x6ed9eba1, 9) + c; a = RL(a, 10); - c = RL(c + f3(d,e,a) + X[ 8] + 0x6ed9eba1, 13) + b; e = RL(e, 10); - b = RL(b + f3(c,d,e) + X[ 1] + 0x6ed9eba1, 15) + a; d = RL(d, 10); - a = RL(a + f3(b,c,d) + X[ 2] + 0x6ed9eba1, 14) + e; c = RL(c, 10); - e = RL(e + f3(a,b,c) + X[ 7] + 0x6ed9eba1, 8) + d; b = RL(b, 10); - d = RL(d + f3(e,a,b) + X[ 0] + 0x6ed9eba1, 13) + c; a = RL(a, 10); - c = RL(c + f3(d,e,a) + X[ 6] + 0x6ed9eba1, 6) + b; e = RL(e, 10); - b = RL(b + f3(c,d,e) + X[13] + 0x6ed9eba1, 5) + a; d = RL(d, 10); - a = RL(a + f3(b,c,d) + X[11] + 0x6ed9eba1, 12) + e; c = RL(c, 10); - e = RL(e + f3(a,b,c) + X[ 5] + 0x6ed9eba1, 7) + d; b = RL(b, 10); - d = RL(d + f3(e,a,b) + X[12] + 0x6ed9eba1, 5) + c; a = RL(a, 10); - - // right - dd = RL(dd + f3(ee,aa,bb) + X[15] + 0x6d703ef3, 9) + cc; aa = RL(aa, 10); - cc = RL(cc + f3(dd,ee,aa) + X[ 5] + 0x6d703ef3, 7) + bb; ee = RL(ee, 10); - bb = RL(bb + f3(cc,dd,ee) + X[ 1] + 0x6d703ef3, 15) + aa; dd = RL(dd, 10); - aa = RL(aa + f3(bb,cc,dd) + X[ 3] + 0x6d703ef3, 11) + ee; cc = RL(cc, 10); - ee = RL(ee + f3(aa,bb,cc) + X[ 7] + 0x6d703ef3, 8) + dd; bb = RL(bb, 10); - dd = RL(dd + f3(ee,aa,bb) + X[14] + 0x6d703ef3, 6) + cc; aa = RL(aa, 10); - cc = RL(cc + f3(dd,ee,aa) + X[ 6] + 0x6d703ef3, 6) + bb; ee = RL(ee, 10); - bb = RL(bb + f3(cc,dd,ee) + X[ 9] + 0x6d703ef3, 14) + aa; dd = RL(dd, 10); - aa = RL(aa + f3(bb,cc,dd) + X[11] + 0x6d703ef3, 12) + ee; cc = RL(cc, 10); - ee = RL(ee + f3(aa,bb,cc) + X[ 8] + 0x6d703ef3, 13) + dd; bb = RL(bb, 10); - dd = RL(dd + f3(ee,aa,bb) + X[12] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); - cc = RL(cc + f3(dd,ee,aa) + X[ 2] + 0x6d703ef3, 14) + bb; ee = RL(ee, 10); - bb = RL(bb + f3(cc,dd,ee) + X[10] + 0x6d703ef3, 13) + aa; dd = RL(dd, 10); - aa = RL(aa + f3(bb,cc,dd) + X[ 0] + 0x6d703ef3, 13) + ee; cc = RL(cc, 10); - ee = RL(ee + f3(aa,bb,cc) + X[ 4] + 0x6d703ef3, 7) + dd; bb = RL(bb, 10); - dd = RL(dd + f3(ee,aa,bb) + X[13] + 0x6d703ef3, 5) + cc; aa = RL(aa, 10); - - // - // Rounds 48-63 - // - // left - c = RL(c + f4(d,e,a) + X[ 1] + 0x8f1bbcdc, 11) + b; e = RL(e, 10); - b = RL(b + f4(c,d,e) + X[ 9] + 0x8f1bbcdc, 12) + a; d = RL(d, 10); - a = RL(a + f4(b,c,d) + X[11] + 0x8f1bbcdc, 14) + e; c = RL(c, 10); - e = RL(e + f4(a,b,c) + X[10] + 0x8f1bbcdc, 15) + d; b = RL(b, 10); - d = RL(d + f4(e,a,b) + X[ 0] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); - c = RL(c + f4(d,e,a) + X[ 8] + 0x8f1bbcdc, 15) + b; e = RL(e, 10); - b = RL(b + f4(c,d,e) + X[12] + 0x8f1bbcdc, 9) + a; d = RL(d, 10); - a = RL(a + f4(b,c,d) + X[ 4] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); - e = RL(e + f4(a,b,c) + X[13] + 0x8f1bbcdc, 9) + d; b = RL(b, 10); - d = RL(d + f4(e,a,b) + X[ 3] + 0x8f1bbcdc, 14) + c; a = RL(a, 10); - c = RL(c + f4(d,e,a) + X[ 7] + 0x8f1bbcdc, 5) + b; e = RL(e, 10); - b = RL(b + f4(c,d,e) + X[15] + 0x8f1bbcdc, 6) + a; d = RL(d, 10); - a = RL(a + f4(b,c,d) + X[14] + 0x8f1bbcdc, 8) + e; c = RL(c, 10); - e = RL(e + f4(a,b,c) + X[ 5] + 0x8f1bbcdc, 6) + d; b = RL(b, 10); - d = RL(d + f4(e,a,b) + X[ 6] + 0x8f1bbcdc, 5) + c; a = RL(a, 10); - c = RL(c + f4(d,e,a) + X[ 2] + 0x8f1bbcdc, 12) + b; e = RL(e, 10); - - // right - cc = RL(cc + f2(dd,ee,aa) + X[ 8] + 0x7a6d76e9, 15) + bb; ee = RL(ee, 10); - bb = RL(bb + f2(cc,dd,ee) + X[ 6] + 0x7a6d76e9, 5) + aa; dd = RL(dd, 10); - aa = RL(aa + f2(bb,cc,dd) + X[ 4] + 0x7a6d76e9, 8) + ee; cc = RL(cc, 10); - ee = RL(ee + f2(aa,bb,cc) + X[ 1] + 0x7a6d76e9, 11) + dd; bb = RL(bb, 10); - dd = RL(dd + f2(ee,aa,bb) + X[ 3] + 0x7a6d76e9, 14) + cc; aa = RL(aa, 10); - cc = RL(cc + f2(dd,ee,aa) + X[11] + 0x7a6d76e9, 14) + bb; ee = RL(ee, 10); - bb = RL(bb + f2(cc,dd,ee) + X[15] + 0x7a6d76e9, 6) + aa; dd = RL(dd, 10); - aa = RL(aa + f2(bb,cc,dd) + X[ 0] + 0x7a6d76e9, 14) + ee; cc = RL(cc, 10); - ee = RL(ee + f2(aa,bb,cc) + X[ 5] + 0x7a6d76e9, 6) + dd; bb = RL(bb, 10); - dd = RL(dd + f2(ee,aa,bb) + X[12] + 0x7a6d76e9, 9) + cc; aa = RL(aa, 10); - cc = RL(cc + f2(dd,ee,aa) + X[ 2] + 0x7a6d76e9, 12) + bb; ee = RL(ee, 10); - bb = RL(bb + f2(cc,dd,ee) + X[13] + 0x7a6d76e9, 9) + aa; dd = RL(dd, 10); - aa = RL(aa + f2(bb,cc,dd) + X[ 9] + 0x7a6d76e9, 12) + ee; cc = RL(cc, 10); - ee = RL(ee + f2(aa,bb,cc) + X[ 7] + 0x7a6d76e9, 5) + dd; bb = RL(bb, 10); - dd = RL(dd + f2(ee,aa,bb) + X[10] + 0x7a6d76e9, 15) + cc; aa = RL(aa, 10); - cc = RL(cc + f2(dd,ee,aa) + X[14] + 0x7a6d76e9, 8) + bb; ee = RL(ee, 10); - - // - // Rounds 64-79 - // - // left - b = RL(b + f5(c,d,e) + X[ 4] + 0xa953fd4e, 9) + a; d = RL(d, 10); - a = RL(a + f5(b,c,d) + X[ 0] + 0xa953fd4e, 15) + e; c = RL(c, 10); - e = RL(e + f5(a,b,c) + X[ 5] + 0xa953fd4e, 5) + d; b = RL(b, 10); - d = RL(d + f5(e,a,b) + X[ 9] + 0xa953fd4e, 11) + c; a = RL(a, 10); - c = RL(c + f5(d,e,a) + X[ 7] + 0xa953fd4e, 6) + b; e = RL(e, 10); - b = RL(b + f5(c,d,e) + X[12] + 0xa953fd4e, 8) + a; d = RL(d, 10); - a = RL(a + f5(b,c,d) + X[ 2] + 0xa953fd4e, 13) + e; c = RL(c, 10); - e = RL(e + f5(a,b,c) + X[10] + 0xa953fd4e, 12) + d; b = RL(b, 10); - d = RL(d + f5(e,a,b) + X[14] + 0xa953fd4e, 5) + c; a = RL(a, 10); - c = RL(c + f5(d,e,a) + X[ 1] + 0xa953fd4e, 12) + b; e = RL(e, 10); - b = RL(b + f5(c,d,e) + X[ 3] + 0xa953fd4e, 13) + a; d = RL(d, 10); - a = RL(a + f5(b,c,d) + X[ 8] + 0xa953fd4e, 14) + e; c = RL(c, 10); - e = RL(e + f5(a,b,c) + X[11] + 0xa953fd4e, 11) + d; b = RL(b, 10); - d = RL(d + f5(e,a,b) + X[ 6] + 0xa953fd4e, 8) + c; a = RL(a, 10); - c = RL(c + f5(d,e,a) + X[15] + 0xa953fd4e, 5) + b; e = RL(e, 10); - b = RL(b + f5(c,d,e) + X[13] + 0xa953fd4e, 6) + a; d = RL(d, 10); - - // right - bb = RL(bb + f1(cc,dd,ee) + X[12], 8) + aa; dd = RL(dd, 10); - aa = RL(aa + f1(bb,cc,dd) + X[15], 5) + ee; cc = RL(cc, 10); - ee = RL(ee + f1(aa,bb,cc) + X[10], 12) + dd; bb = RL(bb, 10); - dd = RL(dd + f1(ee,aa,bb) + X[ 4], 9) + cc; aa = RL(aa, 10); - cc = RL(cc + f1(dd,ee,aa) + X[ 1], 12) + bb; ee = RL(ee, 10); - bb = RL(bb + f1(cc,dd,ee) + X[ 5], 5) + aa; dd = RL(dd, 10); - aa = RL(aa + f1(bb,cc,dd) + X[ 8], 14) + ee; cc = RL(cc, 10); - ee = RL(ee + f1(aa,bb,cc) + X[ 7], 6) + dd; bb = RL(bb, 10); - dd = RL(dd + f1(ee,aa,bb) + X[ 6], 8) + cc; aa = RL(aa, 10); - cc = RL(cc + f1(dd,ee,aa) + X[ 2], 13) + bb; ee = RL(ee, 10); - bb = RL(bb + f1(cc,dd,ee) + X[13], 6) + aa; dd = RL(dd, 10); - aa = RL(aa + f1(bb,cc,dd) + X[14], 5) + ee; cc = RL(cc, 10); - ee = RL(ee + f1(aa,bb,cc) + X[ 0], 15) + dd; bb = RL(bb, 10); - dd = RL(dd + f1(ee,aa,bb) + X[ 3], 13) + cc; aa = RL(aa, 10); - cc = RL(cc + f1(dd,ee,aa) + X[ 9], 11) + bb; ee = RL(ee, 10); - bb = RL(bb + f1(cc,dd,ee) + X[11], 11) + aa; dd = RL(dd, 10); - - dd += c + H1; - H1 = H2 + d + ee; - H2 = H3 + e + aa; - H3 = H4 + a + bb; - H4 = H0 + b + cc; - H0 = dd; - - // - // reset the offset and clean out the word buffer. - // - xOff = 0; - for (int i = 0; i != X.length; i++) - { - X[i] = 0; - } - } - - public Memoable copy() - { - return new RIPEMD160Digest(this); - } - - public void reset(Memoable other) - { - RIPEMD160Digest d = (RIPEMD160Digest)other; - - copyIn(d); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/CommitmentNotFoundException.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/CommitmentNotFoundException.java deleted file mode 100644 index 47af42bf22..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/CommitmentNotFoundException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions; - -public class CommitmentNotFoundException extends Exception { - public CommitmentNotFoundException(String message) { - super(message); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/DeserializationException.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/DeserializationException.java deleted file mode 100644 index 2b5d462653..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/DeserializationException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions; - -public class DeserializationException extends Exception { - public DeserializationException(String message) { - super(message); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/ExceededSizeException.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/ExceededSizeException.java deleted file mode 100644 index 7c2269a458..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/ExceededSizeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions; - -public class ExceededSizeException extends Exception { - public ExceededSizeException(String message) { - super(message); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/UrlException.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/UrlException.java deleted file mode 100644 index d5a1955056..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/UrlException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions; - -public class UrlException extends Exception { - public UrlException(String message) { - super(message); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/VerificationException.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/VerificationException.java deleted file mode 100644 index 39e24656a9..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/exceptions/VerificationException.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions; - -public class VerificationException extends Exception { - public VerificationException(String message) { - super(message); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.java deleted file mode 100644 index c2dd66c76b..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Request.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.http; - -import android.util.Log; - -import java.io.DataOutputStream; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Map; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.Callable; -import java.util.zip.GZIPInputStream; - -/** - * For making an HTTP request. - */ -public class Request implements Callable { - private URL url; - private byte[] data; - private Map headers; - private BlockingQueue queue; - - public Request(URL url) { - this.url = url; - } - - public void setData(byte[] data) { - this.data = data; - } - - public void setHeaders(Map headers) { - this.headers = headers; - } - - public void setQueue(BlockingQueue queue) { - this.queue = queue; - } - - @Override - public Response call() throws Exception { - Response response = new Response(); - - try { - HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection(); - httpURLConnection.setReadTimeout(10000); - httpURLConnection.setConnectTimeout(10000); - httpURLConnection.setRequestProperty("User-Agent", "OpenTimestamps Java"); - httpURLConnection.setRequestProperty("Accept", "application/json"); - httpURLConnection.setRequestProperty("Accept-Encoding", "gzip"); - - if (headers != null) { - for (Map.Entry entry : headers.entrySet()) { - httpURLConnection.setRequestProperty(entry.getKey(), entry.getValue()); - } - } - - if (data != null) { - httpURLConnection.setDoOutput(true); - httpURLConnection.setRequestMethod("POST"); - httpURLConnection.setRequestProperty("Content-Length", "" + Integer.toString(this.data.length)); - DataOutputStream wr = new DataOutputStream(httpURLConnection.getOutputStream()); - wr.write(this.data, 0, this.data.length); - wr.flush(); - wr.close(); - } else { - httpURLConnection.setRequestMethod("GET"); - } - - httpURLConnection.connect(); - - int responseCode = httpURLConnection.getResponseCode(); - - Log.i("OpenTimestamp", responseCode + " responseCode "); - - response.setStatus(responseCode); - response.setFromUrl(url.toString()); - InputStream is = httpURLConnection.getInputStream(); - if ("gzip".equals(httpURLConnection.getContentEncoding())) { - is = new GZIPInputStream(is); - } - response.setStream(is); - } catch (Exception e) { - Log.w("OpenTimestamp", url.toString() + " exception " + e); - } finally { - if (queue != null) { - queue.offer(response); - } - } - - return response; - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.java deleted file mode 100644 index d03a08c037..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/http/Response.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.http; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.json.JsonMapper; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; - -/** - * Holds the response from an HTTP request. - */ -public class Response { - private InputStream stream; - private String fromUrl; - private Integer status; - - public Response() { - } - - public Response(InputStream stream) { - this.stream = stream; - } - - public void setStream(InputStream stream) { - this.stream = stream; - } - - public Integer getStatus() { - return status; - } - - public void setStatus(Integer status) { - this.status = status; - } - - public boolean isOk() { - return getStatus() != null && 200 == getStatus(); - } - - public String getFromUrl() { - return fromUrl; - } - - public void setFromUrl(String fromUrl) { - this.fromUrl = fromUrl; - } - - public InputStream getStream() { - return this.stream; - } - - public String getString() throws IOException { - return new String(getBytes(), StandardCharsets.UTF_8); - } - - public byte[] getBytes() throws IOException { - ByteArrayOutputStream buffer = new ByteArrayOutputStream(); - int nRead; - byte[] data = new byte[16384]; - - while ((nRead = this.stream.read(data, 0, data.length)) != -1) { - buffer.write(data, 0, nRead); - } - - buffer.flush(); - - return buffer.toByteArray(); - } - - public JsonNode getJson() throws IOException { - String jsonString = getString(); - JsonMapper builder = JsonMapper.builder().build(); - return builder.readTree(jsonString); - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/Op.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/Op.java deleted file mode 100644 index eccb53ec6e..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/Op.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import android.util.Log; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; - -/** - * Operations are the edges in the timestamp tree, with each operation taking a message and zero or more arguments to produce a result. - */ -public abstract class Op implements Comparable { - - /** - * Maximum length of an com.vitorpamplona.quartz.ots.op.Op result - *

- * For a verifier, this limit is what limits the maximum amount of memory you - * need at any one time to verify a particular timestamp path; while verifying - * a particular commitment operation path previously calculated results can be - * discarded. - *

- * Of course, if everything was a merkle tree you never need to append/prepend - * anything near 4KiB of data; 64 bytes would be plenty even with SHA512. The - * main need for this is compatibility with existing systems like Bitcoin - * timestamps and Certificate Transparency servers. While the pathological - * limits required by both are quite large - 1MB and 16MiB respectively - 4KiB - * is perfectly adequate in both cases for more reasonable usage. - *

- * @see Op subclasses should set this limit even lower if doing so is appropriate - * for them. - */ - public static int _MAX_RESULT_LENGTH = 4096; - - /** - * Maximum length of the message an com.vitorpamplona.quartz.ots.op.Op can be applied too. - *

- * Similar to the result length limit, this limit gives implementations a sane - * constraint to work with; the maximum result-length limit implicitly - * constrains maximum message length anyway. - *

- * com.vitorpamplona.quartz.ots.op.Op subclasses should set this limit even lower if doing so is appropriate - * for them. - */ - public static int _MAX_MSG_LENGTH = 4096; - - public static byte _TAG = (byte) 0x00; - - public String _TAG_NAME() { - return ""; - } - - public byte _TAG() { - return Op._TAG; - } - - /** - * Deserialize operation from a buffer. - * - * @param ctx The stream deserialization context. - * @return The subclass Operation. - */ - public static Op deserialize(StreamDeserializationContext ctx) throws DeserializationException { - byte tag = ctx.readBytes(1)[0]; - - return Op.deserializeFromTag(ctx, tag); - } - - /** - * Deserialize operation from a buffer. - * - * @param ctx The stream deserialization context. - * @param tag The tag of the operation. - * @return The subclass Operation. - */ - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) - throws DeserializationException { - if (tag == OpAppend._TAG) { - return OpAppend.deserializeFromTag(ctx, tag); - } else if (tag == OpPrepend._TAG) { - return OpPrepend.deserializeFromTag(ctx, tag); - } else if (tag == OpSHA1._TAG) { - return OpSHA1.deserializeFromTag(ctx, tag); - } else if (tag == OpSHA256._TAG) { - return OpSHA256.deserializeFromTag(ctx, tag); - } else if (tag == OpRIPEMD160._TAG) { - return OpRIPEMD160.deserializeFromTag(ctx, tag); - } else if (tag == OpKECCAK256._TAG) { - return OpKECCAK256.deserializeFromTag(ctx, tag); - } else { - Log.e("OpenTimestamp", "Unknown operation tag: " + tag + " 0x" + String.format("%02x", tag)); - return null; // TODO: Is this OK? Won't it blow up later? Better to throw? - } - } - - /** - * Serialize operation. - * - * @param ctx The stream serialization context. - */ - public void serialize(StreamSerializationContext ctx) { - if (this._TAG() == 0x00) { - Log.e("OpenTimestamp", "No valid serialized Op"); - // TODO: Is it OK to just log and carry on? Won't it blow up later? Better to throw? - } - - ctx.writeByte(this._TAG()); - } - - /** - * Apply the operation to a message. - * Raises MsgValueError if the message value is invalid, such as it being - * too long, or it causing the result to be too long. - * - * @param msg The message. - * @return the msg after the operation has been applied - */ - public byte[] call(byte[] msg) { - if (msg.length > _MAX_MSG_LENGTH) { - Log.e("OpenTimestamp", "Error : Message too long;"); - return new byte[]{}; // TODO: Is this OK? Won't it blow up later? Better to throw? - } - - byte[] r = this.call(msg); - - if (r.length > _MAX_RESULT_LENGTH) { - Log.e("OpenTimestamp", "Error : Result too long;"); - // TODO: Is it OK to just log and carry on? Won't it blow up later? Better to throw? - } - - return r; - } - - @Override - public int compareTo(Op o) { - return this._TAG() - o._TAG(); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpAppend.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpAppend.java deleted file mode 100644 index e1631278a6..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpAppend.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; - -import java.util.Arrays; - -/** - * Append a suffix to a message. - * - * @see OpBinary - */ -public class OpAppend extends OpBinary { - byte[] arg; - - public static byte _TAG = (byte) 0xf0; - - @Override - public byte _TAG() { - return OpAppend._TAG; - } - - @Override - public String _TAG_NAME() { - return "append"; - } - - public OpAppend() { - super(); - this.arg = new byte[]{}; - } - - public OpAppend(byte[] arg_) { - super(arg_); - this.arg = arg_; - } - - @Override - public byte[] call(byte[] msg) { - return Utils.arraysConcat(msg, this.arg); - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) - throws DeserializationException { - return OpBinary.deserializeFromTag(ctx, tag); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof OpAppend)) { - return false; - } - - return Arrays.equals(this.arg, ((OpAppend) obj).arg); - } - - @Override - public int hashCode() { - return _TAG ^ Arrays.hashCode(this.arg); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpBinary.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpBinary.java deleted file mode 100644 index 5a16b97321..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpBinary.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import android.util.Log; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; -import com.vitorpamplona.quartz.utils.Hex; - -import java.util.Arrays; -import java.util.Locale; - -/** - * Operations that act on a message and a single argument. - * - * @see OpUnary - */ -public abstract class OpBinary extends Op implements Comparable { - - public byte[] arg; - - @Override - public String _TAG_NAME() { - return ""; - } - - public OpBinary() { - super(); - this.arg = new byte[]{}; - } - - public OpBinary(byte[] arg_) { - super(); - this.arg = arg_; - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) - throws DeserializationException { - byte[] arg = ctx.readVarbytes(_MAX_RESULT_LENGTH, 1); - - if (tag == OpAppend._TAG) { - return new OpAppend(arg); - } else if (tag == OpPrepend._TAG) { - return new OpPrepend(arg); - } else { - Log.e("OpenTimestamp", "Unknown operation tag: " + tag + " 0x" + String.format("%02x", tag)); - return null; // TODO: Is this OK? Won't it blow up later? Better to throw? - } - } - - @Override - public void serialize(StreamSerializationContext ctx) { - super.serialize(ctx); - ctx.writeVarbytes(this.arg); - } - - @Override - public String toString() { - return this._TAG_NAME() + ' ' + Hex.encode(this.arg).toLowerCase(Locale.ROOT); - } - - @Override - public int compareTo(Op o) { - if (o instanceof OpBinary && this._TAG() == o._TAG()) { - return Utils.compare(this.arg, ((OpBinary) o).arg); - } - - return this._TAG() - o._TAG(); - } - - @Override - public int hashCode() { - return _TAG ^ Arrays.hashCode(this.arg); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.java deleted file mode 100644 index 4ba85d3454..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpCrypto.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import android.util.Log; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -/** - * Cryptographic transformations. - * These transformations have the unique property that for any length message, - * the size of the result they return is fixed. Additionally, they're the only - * type of operation that can be applied directly to a stream. - * - * @see OpUnary - */ -public class OpCrypto extends OpUnary { - - public String _TAG_NAME = ""; - - public String _HASHLIB_NAME() { - return ""; - } - - public int _DIGEST_LENGTH() { - return 0; - } - - OpCrypto() { - super(); - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) { - return OpUnary.deserializeFromTag(ctx, tag); - } - - @Override - public byte[] call(byte[] msg) { - // For Sha1 & Sha256 use java.security.MessageDigest library - try { - MessageDigest digest = MessageDigest.getInstance(this._HASHLIB_NAME()); - byte[] hash = digest.digest(msg); - - return hash; - } catch (NoSuchAlgorithmException e) { - Log.e("OpenTimestamp", "NoSuchAlgorithmException"); - e.printStackTrace(); - - return new byte[]{}; // TODO: Is this OK? Won't it blow up later? Better to throw? - } - } - - public byte[] hashFd(StreamDeserializationContext ctx) throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance(this._HASHLIB_NAME()); - byte[] chunk = ctx.read(1048576); - - while (chunk != null && chunk.length > 0) { - digest.update(chunk); - chunk = ctx.read(1048576); - } - - byte[] hash = digest.digest(); - - return hash; - } - - public byte[] hashFd(File file) throws IOException, NoSuchAlgorithmException { - return hashFd(new FileInputStream(file)); - } - - public byte[] hashFd(byte[] bytes) throws IOException, NoSuchAlgorithmException { - StreamDeserializationContext ctx = new StreamDeserializationContext(bytes); - - return hashFd(ctx); - } - - public byte[] hashFd(InputStream inputStream) throws IOException, NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance(this._HASHLIB_NAME()); - byte[] chunk = new byte[1048576]; - int count = inputStream.read(chunk, 0, 1048576); - - while (count >= 0) { - digest.update(chunk, 0, count); - count = inputStream.read(chunk, 0, 1048576); - } - - inputStream.close(); - byte[] hash = digest.digest(); - - return hash; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpKECCAK256.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpKECCAK256.java deleted file mode 100644 index 47e2185d41..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpKECCAK256.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.KeccakDigest; - -/** - * Cryptographic Keccak256 operation. - * Cryptographic operation tag numbers taken from RFC4880, although it's not - * guaranteed that they'll continue to match that RFC in the future. - * - * @see OpCrypto - */ -public class OpKECCAK256 extends OpCrypto { - private KeccakDigest digest = new KeccakDigest(256); - - public static byte _TAG = (byte) 103; - - @Override - public byte _TAG() { - return OpKECCAK256._TAG; - } - - @Override - public String _TAG_NAME() { - return "keccak256"; - } - - @Override - public String _HASHLIB_NAME() { - return "keccak256"; - } - - @Override - public int _DIGEST_LENGTH() { - return digest.getDigestSize(); - } - - public OpKECCAK256() { - super(); - } - - @Override - public byte[] call(byte[] msg) { - digest.update(msg, 0, msg.length); - byte[] hash = new byte[digest.getDigestSize()]; - digest.doFinal(hash, 0); - - return hash; - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) { - return OpCrypto.deserializeFromTag(ctx, tag); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof OpKECCAK256); - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpPrepend.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpPrepend.java deleted file mode 100644 index 510749f89e..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpPrepend.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils; -import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException; - -import java.util.Arrays; - -/** - * Prepend a prefix to a message. - * - * @see OpBinary - */ -public class OpPrepend extends OpBinary { - - byte[] arg; - - public static byte _TAG = (byte) 0xf1; - - @Override - public byte _TAG() { - return OpPrepend._TAG; - } - - @Override - public String _TAG_NAME() { - return "prepend"; - } - - public OpPrepend() { - super(); - this.arg = new byte[]{}; - } - - public OpPrepend(byte[] arg_) { - super(arg_); - this.arg = arg_; - } - - @Override - public byte[] call(byte[] msg) { - return Utils.arraysConcat(this.arg, msg); - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) - throws DeserializationException { - return OpBinary.deserializeFromTag(ctx, tag); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof OpPrepend)) { - return false; - } - - return Arrays.equals(this.arg, ((OpPrepend) obj).arg); - } - - @Override - public int hashCode() { - return _TAG ^ Arrays.hashCode(this.arg); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpRIPEMD160.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpRIPEMD160.java deleted file mode 100644 index 4ef69398c5..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpRIPEMD160.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; -import com.vitorpamplona.quartz.nip03Timestamp.ots.crypto.RIPEMD160Digest; - -/** - * Cryptographic RIPEMD160 operation. - * Cryptographic operation tag numbers taken from RFC4880, although it's not - * guaranteed that they'll continue to match that RFC in the future. - * - * @see OpCrypto - */ -public class OpRIPEMD160 extends OpCrypto { - - public static byte _TAG = 0x03; - - @Override - public byte _TAG() { - return OpRIPEMD160._TAG; - } - - @Override - public String _TAG_NAME() { - return "ripemd160"; - } - - @Override - public String _HASHLIB_NAME() { - return "ripemd160"; - } - - @Override - public int _DIGEST_LENGTH() { - return 20; - } - - public OpRIPEMD160() { - super(); - } - - @Override - public byte[] call(byte[] msg) { - RIPEMD160Digest digest = new RIPEMD160Digest(); - digest.update(msg, 0, msg.length); - byte[] hash = new byte[digest.getDigestSize()]; - digest.doFinal(hash, 0); - - return hash; - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) { - return OpCrypto.deserializeFromTag(ctx, tag); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof OpRIPEMD160); - } - - @Override - public int hashCode() { - return _TAG; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA1.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA1.java deleted file mode 100644 index 0557056770..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA1.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; - -/** - * Cryptographic SHA1 operation. - * Cryptographic operation tag numbers taken from RFC4880, although it's not - * guaranteed that they'll continue to match that RFC in the future. - * Remember that for timestamping, hash algorithms with collision attacks - * *are* secure! We've still proven that both messages existed prior to some - * point in time - the fact that they both have the same hash digest doesn't - * change that. - * Heck, even md5 is still secure enough for timestamping... but that's - * pushing our luck... - * - * @see OpCrypto - */ -public class OpSHA1 extends OpCrypto { - - - public static byte _TAG = 0x02; - - @Override - public byte _TAG() { - return OpSHA1._TAG; - } - - @Override - public String _TAG_NAME() { - return "sha1"; - } - - @Override - public String _HASHLIB_NAME() { - return "SHA-1"; - } - - @Override - public int _DIGEST_LENGTH() { - return 20; - } - - public OpSHA1() { - super(); - } - - @Override - public byte[] call(byte[] msg) { - return super.call(msg); - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) { - return OpCrypto.deserializeFromTag(ctx, tag); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof OpSHA1); - } - - @Override - public int hashCode() { - return _TAG; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA256.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA256.java deleted file mode 100644 index c5772c2e3e..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpSHA256.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; - -/** - * Cryptographic SHA256 operation. - * Cryptographic operation tag numbers taken from RFC4880, although it's not - * guaranteed that they'll continue to match that RFC in the future. - * - * @see OpCrypto - */ -public class OpSHA256 extends OpCrypto { - - - public static byte _TAG = 0x08; - - @Override - public byte _TAG() { - return OpSHA256._TAG; - } - - @Override - public String _TAG_NAME() { - return "sha256"; - } - - @Override - public String _HASHLIB_NAME() { - return "SHA-256"; - } - - @Override - public int _DIGEST_LENGTH() { - return 32; - } - - public OpSHA256() { - super(); - } - - @Override - public byte[] call(byte[] msg) { - return super.call(msg); - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) { - return OpCrypto.deserializeFromTag(ctx, tag); - } - - @Override - public boolean equals(Object obj) { - return (obj instanceof OpSHA256); - } - - @Override - public int hashCode() { - return _TAG; - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpUnary.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpUnary.java deleted file mode 100644 index 458a75b319..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip03Timestamp/ots/op/OpUnary.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.vitorpamplona.quartz.nip03Timestamp.ots.op; - -import android.util.Log; - -import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext; - -/** - * Operations that act on a single message. - * - * @see Op - */ -public abstract class OpUnary extends Op { - - @Override - public String _TAG_NAME() { - return ""; - } - - public OpUnary() { - super(); - } - - public static Op deserializeFromTag(StreamDeserializationContext ctx, byte tag) { - if (tag == OpSHA1._TAG) { - return new OpSHA1(); - } else if (tag == OpSHA256._TAG) { - return new OpSHA256(); - } else if (tag == OpRIPEMD160._TAG) { - return new OpRIPEMD160(); - } else if (tag == OpKECCAK256._TAG) { - return new OpKECCAK256(); - } else { - Log.e("OpenTimestamp", "Unknown operation tag: " + tag); - - return null; // TODO: Is this OK? Won't it blow up later? Better to throw? - } - } - - @Override - public String toString() { - return this._TAG_NAME(); - } -} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.java deleted file mode 100644 index 5c2300431c..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2011 - Will Glozer. All rights reserved. - -package com.vitorpamplona.quartz.nip49PrivKeyEnc; - -import static java.lang.System.arraycopy; - -import java.security.GeneralSecurityException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -/** - * An implementation of the Password-Based Key Derivation Function as specified - * in RFC 2898. - * - * @author Will Glozer - */ -public class PBKDF { - /** - * Implementation of PBKDF2 (RFC2898). - * - * @param alg HMAC algorithm to use. - * @param P Password. - * @param S Salt. - * @param c Iteration count. - * @param dkLen Intended length, in octets, of the derived key. - * - * @return The derived key. - * - * @throws GeneralSecurityException - */ - public static byte[] pbkdf2(String alg, byte[] P, byte[] S, int c, int dkLen) throws GeneralSecurityException { - Mac mac = Mac.getInstance(alg); - mac.init(new SecretKeySpec(P, alg)); - byte[] DK = new byte[dkLen]; - pbkdf2(mac, S, c, DK, dkLen); - return DK; - } - - /** - * Implementation of PBKDF2 (RFC2898). - * - * @param mac Pre-initialized {@link Mac} instance to use. - * @param S Salt. - * @param c Iteration count. - * @param DK Byte array that derived key will be placed in. - * @param dkLen Intended length, in octets, of the derived key. - * - * @throws GeneralSecurityException - */ - public static void pbkdf2(Mac mac, byte[] S, int c, byte[] DK, int dkLen) throws GeneralSecurityException { - int hLen = mac.getMacLength(); - - if (dkLen > (Math.pow(2, 32) - 1) * hLen) { - throw new GeneralSecurityException("Requested key length too long"); - } - - byte[] U = new byte[hLen]; - byte[] T = new byte[hLen]; - byte[] block1 = new byte[S.length + 4]; - - int l = (int) Math.ceil((double) dkLen / hLen); - int r = dkLen - (l - 1) * hLen; - - arraycopy(S, 0, block1, 0, S.length); - - for (int i = 1; i <= l; i++) { - block1[S.length + 0] = (byte) (i >> 24 & 0xff); - block1[S.length + 1] = (byte) (i >> 16 & 0xff); - block1[S.length + 2] = (byte) (i >> 8 & 0xff); - block1[S.length + 3] = (byte) (i >> 0 & 0xff); - - mac.update(block1); - mac.doFinal(U, 0); - arraycopy(U, 0, T, 0, hLen); - - for (int j = 1; j < c; j++) { - mac.update(U); - mac.doFinal(U, 0); - - for (int k = 0; k < hLen; k++) { - T[k] ^= U[k]; - } - } - - arraycopy(T, 0, DK, (i - 1) * hLen, (i == l ? r : hLen)); - } - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/SCrypt.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/SCrypt.java deleted file mode 100644 index c0b4ed2364..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/SCrypt.java +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright (C) 2011 - Will Glozer. All rights reserved. - -package com.vitorpamplona.quartz.nip49PrivKeyEnc; - -import static java.lang.Integer.MAX_VALUE; -import static java.lang.System.arraycopy; - -import java.security.GeneralSecurityException; - -import javax.crypto.Mac; - -/** - * An implementation of the scrypt - * key derivation function. This class will attempt to load a native library - * containing the optimized C implementation from - * http://www.tarsnap.com/scrypt.html and - * fall back to the pure Java version if that fails. - * - * @author Will Glozer - */ -public class SCrypt { - - /** - * Implementation of the scrypt KDF. - * - * @param passwd Password. - * @param salt Salt. - * @param N CPU cost parameter. - * @param r Memory cost parameter. - * @param p Parallelization parameter. - * @param dkLen Intended length of the derived key. - * - * @return The derived key. - * - * @throws GeneralSecurityException when HMAC_SHA256 is not available. - */ - public static byte[] scrypt(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException { - return scryptJ(passwd, salt, N, r, p, dkLen); - } - - /** - * Pure Java implementation of the scrypt KDF. - * - * @param passwd Password. - * @param salt Salt. - * @param N CPU cost parameter. - * @param r Memory cost parameter. - * @param p Parallelization parameter. - * @param dkLen Intended length of the derived key. - * - * @return The derived key. - * - * @throws GeneralSecurityException when HMAC_SHA256 is not available. - */ - public static byte[] scryptJ(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException { - if (N < 2 || (N & (N - 1)) != 0) throw new IllegalArgumentException("N must be a power of 2 greater than 1"); - - if (N > MAX_VALUE / 128 / r) throw new IllegalArgumentException("Parameter N is too large"); - if (r > MAX_VALUE / 128 / p) throw new IllegalArgumentException("Parameter r is too large"); - - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeyOrEmptySpec(passwd, "HmacSHA256")); - - byte[] DK = new byte[dkLen]; - - byte[] B = new byte[128 * r * p]; - byte[] XY = new byte[256 * r]; - byte[] V = new byte[128 * r * N]; - int i; - - PBKDF.pbkdf2(mac, salt, 1, B, p * 128 * r); - - for (i = 0; i < p; i++) { - smix(B, i * 128 * r, r, N, V, XY); - } - - PBKDF.pbkdf2(mac, B, 1, DK, dkLen); - - return DK; - } - - public static void smix(byte[] B, int Bi, int r, int N, byte[] V, byte[] XY) { - int Xi = 0; - int Yi = 128 * r; - int i; - - arraycopy(B, Bi, XY, Xi, 128 * r); - - for (i = 0; i < N; i++) { - arraycopy(XY, Xi, V, i * (128 * r), 128 * r); - blockmix_salsa8(XY, Xi, Yi, r); - } - - for (i = 0; i < N; i++) { - int j = integerify(XY, Xi, r) & (N - 1); - blockxor(V, j * (128 * r), XY, Xi, 128 * r); - blockmix_salsa8(XY, Xi, Yi, r); - } - - arraycopy(XY, Xi, B, Bi, 128 * r); - } - - public static void blockmix_salsa8(byte[] BY, int Bi, int Yi, int r) { - byte[] X = new byte[64]; - int i; - - arraycopy(BY, Bi + (2 * r - 1) * 64, X, 0, 64); - - for (i = 0; i < 2 * r; i++) { - blockxor(BY, i * 64, X, 0, 64); - salsa20_8(X); - arraycopy(X, 0, BY, Yi + (i * 64), 64); - } - - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64); - } - - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64); - } - } - - public static int R(int a, int b) { - return (a << b) | (a >>> (32 - b)); - } - - public static void salsa20_8(byte[] B) { - int[] B32 = new int[16]; - int[] x = new int[16]; - int i; - - for (i = 0; i < 16; i++) { - B32[i] = (B[i * 4 + 0] & 0xff) << 0; - B32[i] |= (B[i * 4 + 1] & 0xff) << 8; - B32[i] |= (B[i * 4 + 2] & 0xff) << 16; - B32[i] |= (B[i * 4 + 3] & 0xff) << 24; - } - - arraycopy(B32, 0, x, 0, 16); - - for (i = 8; i > 0; i -= 2) { - x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); - x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); - x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); - x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); - x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); - x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); - x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); - x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); - x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); - x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); - x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); - x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); - x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); - x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); - x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); - x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); - } - - for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i]; - - for (i = 0; i < 16; i++) { - B[i * 4 + 0] = (byte) (B32[i] >> 0 & 0xff); - B[i * 4 + 1] = (byte) (B32[i] >> 8 & 0xff); - B[i * 4 + 2] = (byte) (B32[i] >> 16 & 0xff); - B[i * 4 + 3] = (byte) (B32[i] >> 24 & 0xff); - } - } - - public static void blockxor(byte[] S, int Si, byte[] D, int Di, int len) { - for (int i = 0; i < len; i++) { - D[Di + i] ^= S[Si + i]; - } - } - - public static int integerify(byte[] B, int Bi, int r) { - int n; - - Bi += (2 * r - 1) * 64; - - n = (B[Bi + 0] & 0xff) << 0; - n |= (B[Bi + 1] & 0xff) << 8; - n |= (B[Bi + 2] & 0xff) << 16; - n |= (B[Bi + 3] & 0xff) << 24; - - return n; - } -} \ No newline at end of file diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/SecretKeyOrEmptySpec.java b/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/SecretKeyOrEmptySpec.java deleted file mode 100644 index 5b6e83a8e4..0000000000 --- a/quartz/src/main/java/com/vitorpamplona/quartz/nip49PrivKeyEnc/SecretKeyOrEmptySpec.java +++ /dev/null @@ -1,230 +0,0 @@ -/* - * Copyright (c) 1998, 2015, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.vitorpamplona.quartz.nip49PrivKeyEnc; - -import java.security.MessageDigest; -import java.security.spec.KeySpec; -import java.util.Locale; - -import javax.crypto.SecretKey; -import javax.crypto.spec.DESKeySpec; - -/** - * This class specifies a secret key in a provider-independent fashion. - * - *

It can be used to construct a SecretKey from a byte array, - * without having to go through a (provider-based) - * SecretKeyFactory. - * - *

This class is only useful for raw secret keys that can be represented as - * a byte array and have no key parameters associated with them, e.g., DES or - * Triple DES keys. - * - * @author Jan Luehe - * - * @see javax.crypto.SecretKey - * @see javax.crypto.SecretKeyFactory - * @since 1.4 - */ -public class SecretKeyOrEmptySpec implements KeySpec, SecretKey { - - private static final long serialVersionUID = 6577238317307289933L; - - /** - * The secret key. - * - * @serial - */ - private byte[] key; - - /** - * The name of the algorithm associated with this key. - * - * @serial - */ - private String algorithm; - - /** - * Constructs a secret key from the given byte array. - * - *

This constructor does not check if the given bytes indeed specify a - * secret key of the specified algorithm. For example, if the algorithm is - * DES, this constructor does not check if key is 8 bytes - * long, and also does not check for weak or semi-weak keys. - * In order for those checks to be performed, an algorithm-specific - * key specification class (in this case: - * {@link DESKeySpec DESKeySpec}) - * should be used. - * - * @param key the key material of the secret key. The contents of - * the array are copied to protect against subsequent modification. - * @param algorithm the name of the secret-key algorithm to be associated - * with the given key material. - * See Appendix A in the - * Java Cryptography Architecture Reference Guide - * for information about standard algorithm names. - * @exception IllegalArgumentException if algorithm - * is null or key is null or empty. - */ - public SecretKeyOrEmptySpec(byte[] key, String algorithm) { - if (key == null || algorithm == null) { - throw new IllegalArgumentException("Missing argument"); - } - this.key = key.clone(); - this.algorithm = algorithm; - } - - /** - * Constructs a secret key from the given byte array, using the first - * len bytes of key, starting at - * offset inclusive. - * - *

The bytes that constitute the secret key are - * those between key[offset] and - * key[offset+len-1] inclusive. - * - *

This constructor does not check if the given bytes indeed specify a - * secret key of the specified algorithm. For example, if the algorithm is - * DES, this constructor does not check if key is 8 bytes - * long, and also does not check for weak or semi-weak keys. - * In order for those checks to be performed, an algorithm-specific key - * specification class (in this case: - * {@link DESKeySpec DESKeySpec}) - * must be used. - * - * @param key the key material of the secret key. The first - * len bytes of the array beginning at - * offset inclusive are copied to protect - * against subsequent modification. - * @param offset the offset in key where the key material - * starts. - * @param len the length of the key material. - * @param algorithm the name of the secret-key algorithm to be associated - * with the given key material. - * See Appendix A in the - * Java Cryptography Architecture Reference Guide - * for information about standard algorithm names. - * @exception IllegalArgumentException if algorithm - * is null or key is null, empty, or too short, - * i.e. {@code key.length-offsetoffset or len index bytes outside the - * key. - */ - public SecretKeyOrEmptySpec(byte[] key, int offset, int len, String algorithm) { - if (key == null || algorithm == null) { - throw new IllegalArgumentException("Missing argument"); - } - if (key.length-offset < len) { - throw new IllegalArgumentException - ("Invalid offset/length combination"); - } - if (len < 0) { - throw new ArrayIndexOutOfBoundsException("len is negative"); - } - this.key = new byte[len]; - System.arraycopy(key, offset, this.key, 0, len); - this.algorithm = algorithm; - } - - /** - * Returns the name of the algorithm associated with this secret key. - * - * @return the secret key algorithm. - */ - public String getAlgorithm() { - return this.algorithm; - } - - /** - * Returns the name of the encoding format for this secret key. - * - * @return the string "RAW". - */ - public String getFormat() { - return "RAW"; - } - - /** - * Returns the key material of this secret key. - * - * @return the key material. Returns a new array - * each time this method is called. - */ - public byte[] getEncoded() { - return this.key.clone(); - } - - /** - * Calculates a hash code value for the object. - * Objects that are equal will also have the same hashcode. - */ - public int hashCode() { - int retval = 0; - for (int i = 1; i < this.key.length; i++) { - retval += this.key[i] * i; - } - if (this.algorithm.equalsIgnoreCase("TripleDES")) - return (retval ^= "desede".hashCode()); - else - return (retval ^= - this.algorithm.toLowerCase(Locale.ENGLISH).hashCode()); - } - - /** - * Tests for equality between the specified object and this - * object. Two SecretKeySpec objects are considered equal if - * they are both SecretKey instances which have the - * same case-insensitive algorithm name and key encoding. - * - * @param obj the object to test for equality with this object. - * - * @return true if the objects are considered equal, false if - * obj is null or otherwise. - */ - public boolean equals(Object obj) { - if (this == obj) - return true; - - if (!(obj instanceof SecretKey)) - return false; - - String thatAlg = ((SecretKey)obj).getAlgorithm(); - if (!(thatAlg.equalsIgnoreCase(this.algorithm))) { - if ((!(thatAlg.equalsIgnoreCase("DESede")) - || !(this.algorithm.equalsIgnoreCase("TripleDES"))) - && (!(thatAlg.equalsIgnoreCase("TripleDES")) - || !(this.algorithm.equalsIgnoreCase("DESede")))) - return false; - } - - byte[] thatKey = ((SecretKey)obj).getEncoded(); - - return MessageDigest.isEqual(this.key, thatKey); - } -} diff --git a/quartz/src/test/java/android/util/Log.java b/quartz/src/test/java/android/util/Log.java deleted file mode 100644 index af85d0b47e..0000000000 --- a/quartz/src/test/java/android/util/Log.java +++ /dev/null @@ -1,35 +0,0 @@ -package android.util; - -public class Log { - public static int d(String tag, String msg) { - System.out.println("DEBUG: " + tag + ": " + msg); - return 0; - } - - public static int i(String tag, String msg) { - System.out.println("INFO: " + tag + ": " + msg); - return 0; - } - - public static int w(String tag, String msg) { - System.out.println("WARN: " + tag + ": " + msg); - return 0; - } - - public static int w(String tag, String msg, Throwable e) { - System.out.println("WARN: " + tag + ": " + msg); - e.printStackTrace(); - return 0; - } - - public static int e(String tag, String msg) { - System.out.println("ERROR: " + tag + ": " + msg); - return 0; - } - - public static int e(String tag, String msg, Throwable e) { - System.out.println("ERROR: " + tag + ": " + msg); - e.printStackTrace(); - return 0; - } -} \ No newline at end of file