feat: add Marmot Protocol event definitions for MLS-over-Nostr
Implements Nostr event kinds for the Marmot Protocol (MIP-00 through MIP-05), which provides end-to-end encrypted group messaging using MLS (RFC 9420) with Nostr as the identity and transport layer. Event kinds added: - kind 30443: KeyPackage (addressable, MIP-00) — MLS KeyPackage publication - kind 10051: KeyPackage Relay List (replaceable, MIP-00) — KeyPackage discovery - kind 444: Welcome (MIP-02) — gift-wrapped group onboarding - kind 445: Group Event (MIP-03) — encrypted group messages and control - kind 446: Notification Request (MIP-05) — push notification trigger - kind 447: Token Request (MIP-05) — push token gossip - kind 448: Token List (MIP-05) — push token sync response - kind 449: Token Removal (MIP-05) — push token cleanup Also includes: - MarmotGroupData model (MIP-01) — 0xF2EE extension data structure - MlsCiphersuite enum — RFC 9420 ciphersuite registry - Full tag definitions following existing quartz patterns https://claude.ai/code/session_017jmJgCAXnaiVh1HsbC8CrW
This commit is contained in:
+113
@@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag
|
||||||
|
import com.vitorpamplona.quartz.nip31Alts.alt
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot KeyPackage Event (MIP-00) — kind 30443.
|
||||||
|
*
|
||||||
|
* Addressable event that publishes an MLS KeyPackage for asynchronous group invitations.
|
||||||
|
* Each KeyPackage is identified by its (kind, pubkey, d-tag) tuple, enabling native
|
||||||
|
* rotation without NIP-09 deletion events.
|
||||||
|
*
|
||||||
|
* Content: base64-encoded TLS-serialized KeyPackageBundle from MLS library.
|
||||||
|
*
|
||||||
|
* Required tags: d, mls_protocol_version, mls_ciphersuite, mls_extensions,
|
||||||
|
* mls_proposals, encoding, i, relays
|
||||||
|
* Optional tags: client
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class KeyPackageEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
/** Base64-encoded TLS-serialized KeyPackageBundle */
|
||||||
|
fun keyPackageBase64() = content
|
||||||
|
|
||||||
|
/** MLS protocol version (e.g., "1.0") */
|
||||||
|
fun mlsProtocolVersion() = tags.mlsProtocolVersion()
|
||||||
|
|
||||||
|
/** MLS ciphersuite ID (e.g., "0x0001") */
|
||||||
|
fun mlsCiphersuite() = tags.mlsCiphersuite()
|
||||||
|
|
||||||
|
/** Supported non-default MLS extension IDs */
|
||||||
|
fun mlsExtensions() = tags.mlsExtensions()
|
||||||
|
|
||||||
|
/** Supported non-default MLS proposal type IDs */
|
||||||
|
fun mlsProposals() = tags.mlsProposals()
|
||||||
|
|
||||||
|
/** Content encoding format (must be "base64") */
|
||||||
|
fun encoding() = tags.encoding()
|
||||||
|
|
||||||
|
/** Hex-encoded KeyPackageRef for efficient relay queries */
|
||||||
|
fun keyPackageRef() = tags.keyPackageRef()
|
||||||
|
|
||||||
|
/** Relays where this KeyPackage is published */
|
||||||
|
fun relays() = tags.keyPackageRelays()
|
||||||
|
|
||||||
|
/** Optional client name */
|
||||||
|
fun clientName() = tags.clientName()
|
||||||
|
|
||||||
|
/** Whether content encoding is valid (must be base64) */
|
||||||
|
fun hasValidEncoding() = encoding() == EncodingTag.BASE64
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 30443
|
||||||
|
const val ALT_DESCRIPTION = "MLS KeyPackage"
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
keyPackageBase64: String,
|
||||||
|
dTagSlot: String,
|
||||||
|
keyPackageRef: HexKey,
|
||||||
|
relays: List<NormalizedRelayUrl>,
|
||||||
|
ciphersuite: String = "0x0001",
|
||||||
|
clientName: String? = null,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<KeyPackageEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, keyPackageBase64, createdAt) {
|
||||||
|
alt(ALT_DESCRIPTION)
|
||||||
|
dTag(dTagSlot)
|
||||||
|
mlsProtocolVersion()
|
||||||
|
mlsCiphersuite(ciphersuite)
|
||||||
|
mlsExtensions(listOf("0xf2ee", "0x000a"))
|
||||||
|
mlsProposals(listOf("0x000a"))
|
||||||
|
encoding()
|
||||||
|
keyPackageRef(keyPackageRef)
|
||||||
|
keyPackageRelays(relays)
|
||||||
|
clientName?.let { client(it) }
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.isLocalHost
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
|
||||||
|
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot KeyPackage Relay List Event (MIP-00) — kind 10051.
|
||||||
|
*
|
||||||
|
* Replaceable event that advertises which relays hold this user's MLS KeyPackages.
|
||||||
|
* Other clients query this to discover where to fetch KeyPackages for group invitations.
|
||||||
|
*
|
||||||
|
* Uses the same "relay" tag format as NIP-17's ChatMessageRelayListEvent (kind 10050)
|
||||||
|
* but serves a different purpose: KeyPackage discovery vs DM inbox routing.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class KeyPackageRelayListEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : BaseReplaceableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
fun relays(): List<NormalizedRelayUrl> = tags.mapNotNull(RelayTag::parse)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 10051
|
||||||
|
const val ALT_DESCRIPTION = "MLS KeyPackage relay list"
|
||||||
|
|
||||||
|
fun createAddress(pubKey: HexKey): Address = Address(KIND, pubKey, FIXED_D_TAG)
|
||||||
|
|
||||||
|
fun createAddressTag(pubKey: HexKey): String = Address.assemble(KIND, pubKey, FIXED_D_TAG)
|
||||||
|
|
||||||
|
private fun createTagArray(relays: List<NormalizedRelayUrl>): Array<Array<String>> =
|
||||||
|
relays
|
||||||
|
.map { RelayTag.assemble(it) }
|
||||||
|
.plusElement(AltTag.assemble(ALT_DESCRIPTION))
|
||||||
|
.toTypedArray()
|
||||||
|
|
||||||
|
suspend fun create(
|
||||||
|
relays: List<NormalizedRelayUrl>,
|
||||||
|
signer: NostrSigner,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
): KeyPackageRelayListEvent = signer.sign(createdAt, KIND, createTagArray(relays), "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relay tag parser for KeyPackage relay list events.
|
||||||
|
* Uses the standard "relay" tag name.
|
||||||
|
*/
|
||||||
|
private object RelayTag {
|
||||||
|
const val TAG_NAME = "relay"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): NormalizedRelayUrl? {
|
||||||
|
if (tag.size < 2 || tag[0] != TAG_NAME || tag[1].isEmpty()) return null
|
||||||
|
val relay =
|
||||||
|
com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||||
|
.normalizeOrNull(tag[1])
|
||||||
|
return relay?.takeUnless { it.isLocalHost() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(relay: NormalizedRelayUrl) = arrayOf(TAG_NAME, relay.url)
|
||||||
|
}
|
||||||
+49
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.ClientTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.KeyPackageRefTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsCiphersuiteTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsExtensionsTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsProposalsTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsProtocolVersionTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.RelaysTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.mlsProtocolVersion(version: String = MlsProtocolVersionTag.CURRENT_VERSION) = addUnique(MlsProtocolVersionTag.assemble(version))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.mlsCiphersuite(ciphersuite: String = MlsCiphersuiteTag.DEFAULT_CIPHERSUITE) = addUnique(MlsCiphersuiteTag.assemble(ciphersuite))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.mlsExtensions(extensionIds: List<String>) = addUnique(MlsExtensionsTag.assemble(extensionIds))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.mlsProposals(proposalIds: List<String>) = addUnique(MlsProposalsTag.assemble(proposalIds))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.encoding() = addUnique(EncodingTag.assemble())
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.keyPackageRef(ref: HexKey) = addUnique(KeyPackageRefTag.assemble(ref))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.keyPackageRelays(relays: List<NormalizedRelayUrl>) = addUnique(RelaysTag.assemble(relays))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<KeyPackageEvent>.client(name: String) = addUnique(ClientTag.assemble(name))
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.ClientTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.KeyPackageRefTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsCiphersuiteTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsExtensionsTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsProposalsTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.MlsProtocolVersionTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.RelaysTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||||
|
|
||||||
|
fun TagArray.mlsProtocolVersion() = firstNotNullOfOrNull(MlsProtocolVersionTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.mlsCiphersuite() = firstNotNullOfOrNull(MlsCiphersuiteTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.mlsExtensions() = firstNotNullOfOrNull(MlsExtensionsTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.mlsProposals() = firstNotNullOfOrNull(MlsProposalsTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.encoding() = firstNotNullOfOrNull(EncodingTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.keyPackageRef() = firstNotNullOfOrNull(KeyPackageRefTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.keyPackageRelays() = firstNotNullOfOrNull(RelaysTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.clientName() = firstNotNullOfOrNull(ClientTag::parse)
|
||||||
+42
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional client name tag for KeyPackage events.
|
||||||
|
* Helps with UX when users can't access signing keys across devices.
|
||||||
|
*/
|
||||||
|
class ClientTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "client"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(clientName: String) = arrayOf(TAG_NAME, clientName)
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encoding format tag for MLS content fields.
|
||||||
|
* Only "base64" is supported; hex encoding is deprecated and MUST be rejected.
|
||||||
|
*/
|
||||||
|
class EncodingTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "encoding"
|
||||||
|
const val BASE64 = "base64"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1] == BASE64) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble() = arrayOf(TAG_NAME, BASE64)
|
||||||
|
}
|
||||||
|
}
|
||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KeyPackageRef tag ("i") for efficient relay queries of KeyPackages.
|
||||||
|
*
|
||||||
|
* Contains the hex-encoded KeyPackageRef computed per RFC 9420 Section 5.2:
|
||||||
|
* MakeKeyPackageRef(value) = RefHash("MLS 1.0 KeyPackage Reference", value)
|
||||||
|
*
|
||||||
|
* The hash function MUST be the one defined by the KeyPackage's ciphersuite
|
||||||
|
* (e.g., SHA-256 for ciphersuite 0x0001).
|
||||||
|
*/
|
||||||
|
class KeyPackageRefTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "i"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): HexKey? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(keyPackageRef: HexKey) = arrayOf(TAG_NAME, keyPackageRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MLS ciphersuite identifier tag per RFC 9420 Section 17.1.
|
||||||
|
* Default Marmot ciphersuite: "0x0001" (MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519).
|
||||||
|
*/
|
||||||
|
class MlsCiphersuiteTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "mls_ciphersuite"
|
||||||
|
const val DEFAULT_CIPHERSUITE = "0x0001"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(ciphersuite: String = DEFAULT_CIPHERSUITE) = arrayOf(TAG_NAME, ciphersuite)
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported non-default MLS extension type IDs per RFC 9420.
|
||||||
|
* Marmot requires: 0xf2ee (marmot_group_data) and 0x000a (last_resort).
|
||||||
|
*
|
||||||
|
* Example tag: ["mls_extensions", "0xf2ee", "0x000a"]
|
||||||
|
*/
|
||||||
|
class MlsExtensionsTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "mls_extensions"
|
||||||
|
|
||||||
|
/** Marmot Group Data extension (MIP-01) */
|
||||||
|
const val MARMOT_GROUP_DATA = "0xf2ee"
|
||||||
|
|
||||||
|
/** Last resort KeyPackage extension */
|
||||||
|
const val LAST_RESORT = "0x000a"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<String>? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
return tag.drop(1).filter { it.isNotEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(extensionIds: List<String>) = arrayOf(TAG_NAME, *extensionIds.toTypedArray())
|
||||||
|
|
||||||
|
fun assembleDefault() = assemble(listOf(MARMOT_GROUP_DATA, LAST_RESORT))
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported non-default MLS proposal type IDs.
|
||||||
|
* Marmot requires: 0x000a (self_remove) per MLS Extensions draft.
|
||||||
|
*
|
||||||
|
* Note: 0x000a is used for both self_remove (proposal type) and last_resort
|
||||||
|
* (extension type) but they belong to different IANA registries.
|
||||||
|
*
|
||||||
|
* Example tag: ["mls_proposals", "0x000a"]
|
||||||
|
*/
|
||||||
|
class MlsProposalsTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "mls_proposals"
|
||||||
|
|
||||||
|
/** SelfRemove proposal type (MIP-03) */
|
||||||
|
const val SELF_REMOVE = "0x000a"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<String>? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
return tag.drop(1).filter { it.isNotEmpty() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(proposalIds: List<String>) = arrayOf(TAG_NAME, *proposalIds.toTypedArray())
|
||||||
|
|
||||||
|
fun assembleDefault() = assemble(listOf(SELF_REMOVE))
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MLS protocol version tag for KeyPackage events.
|
||||||
|
* Currently "1.0" per RFC 9420.
|
||||||
|
*/
|
||||||
|
class MlsProtocolVersionTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "mls_protocol_version"
|
||||||
|
const val CURRENT_VERSION = "1.0"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(version: String = CURRENT_VERSION) = arrayOf(TAG_NAME, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip00KeyPackages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.isLocalHost
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multi-value relay tag for KeyPackage events.
|
||||||
|
* Lists relays where this KeyPackage is published.
|
||||||
|
* MUST contain at least one valid WebSocket URL.
|
||||||
|
*
|
||||||
|
* Example tag: ["relays", "wss://relay1.com", "wss://relay2.com"]
|
||||||
|
*/
|
||||||
|
class RelaysTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "relays"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<NormalizedRelayUrl>? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
val relays =
|
||||||
|
tag.drop(1).mapNotNull { url ->
|
||||||
|
RelayUrlNormalizer.normalizeOrNull(url)?.takeUnless { it.isLocalHost() }
|
||||||
|
}
|
||||||
|
ensure(relays.isNotEmpty()) { return null }
|
||||||
|
return relays
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(relays: List<NormalizedRelayUrl>) = arrayOf(TAG_NAME, *relays.map { it.url }.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip01Groups
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot Group Data Extension (MIP-01) — extension ID 0xF2EE.
|
||||||
|
*
|
||||||
|
* This data model represents the NostrGroupData structure carried inside
|
||||||
|
* the MLS GroupContext.extensions. It links MLS group encryption with
|
||||||
|
* Nostr's decentralized identity system.
|
||||||
|
*
|
||||||
|
* The actual TLS serialization/deserialization is handled by the MLS engine.
|
||||||
|
* This class provides a Kotlin-friendly representation for the application layer.
|
||||||
|
*
|
||||||
|
* Wire format (TLS presentation language):
|
||||||
|
* ```
|
||||||
|
* struct {
|
||||||
|
* uint16 version; // Current: 2
|
||||||
|
* opaque nostr_group_id[32]; // Nostr routing ID (distinct from MLS group ID)
|
||||||
|
* opaque name<0..2^16-1>;
|
||||||
|
* opaque description<0..2^16-1>;
|
||||||
|
* opaque admin_pubkeys<0..2^16-1>; // Concatenated raw 32-byte x-only pubkeys
|
||||||
|
* RelayUrl relays<0..2^16-1>;
|
||||||
|
* opaque image_hash<0..32>;
|
||||||
|
* opaque image_key<0..32>; // HKDF seed for encryption key derivation
|
||||||
|
* opaque image_nonce<0..12>;
|
||||||
|
* opaque image_upload_key<0..32>; // HKDF seed for upload keypair derivation
|
||||||
|
* } NostrGroupData;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
data class MarmotGroupData(
|
||||||
|
/** Extension format version. Current: 2 */
|
||||||
|
val version: Int = CURRENT_VERSION,
|
||||||
|
/**
|
||||||
|
* 32-byte Nostr routing ID (hex-encoded).
|
||||||
|
* Distinct from the private MLS group ID. Used in "h" tags of kind:445 events.
|
||||||
|
* MUST be cryptographically random when initially generated.
|
||||||
|
*/
|
||||||
|
val nostrGroupId: HexKey,
|
||||||
|
/** UTF-8 encoded group name. Empty string for unnamed groups. */
|
||||||
|
val name: String = "",
|
||||||
|
/** UTF-8 encoded group description. Empty string if not set. */
|
||||||
|
val description: String = "",
|
||||||
|
/**
|
||||||
|
* Admin public keys (hex-encoded, 32-byte x-only secp256k1 pubkeys).
|
||||||
|
* At least one admin key MUST be present for most group operations.
|
||||||
|
* MUST NOT contain duplicates.
|
||||||
|
*/
|
||||||
|
val adminPubkeys: List<HexKey> = emptyList(),
|
||||||
|
/** Relay URLs for group message distribution. SHOULD contain at least one. */
|
||||||
|
val relays: List<String> = emptyList(),
|
||||||
|
/** SHA-256 hash of the encrypted group image (hex). Empty if no image. */
|
||||||
|
val imageHash: HexKey? = null,
|
||||||
|
/** HKDF seed for deriving the image encryption key. Empty if no image. */
|
||||||
|
val imageKey: ByteArray? = null,
|
||||||
|
/** ChaCha20-Poly1305 nonce for image encryption. Empty if no image. */
|
||||||
|
val imageNonce: ByteArray? = null,
|
||||||
|
/** HKDF seed for deriving the Blossom upload keypair. Empty if no image. */
|
||||||
|
val imageUploadKey: ByteArray? = null,
|
||||||
|
) {
|
||||||
|
/** Whether the given pubkey is an admin of this group */
|
||||||
|
fun isAdmin(pubKey: HexKey): Boolean = adminPubkeys.contains(pubKey)
|
||||||
|
|
||||||
|
/** Whether this group has an encrypted image set */
|
||||||
|
fun hasImage(): Boolean = imageHash != null && imageKey != null && imageNonce != null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val CURRENT_VERSION = 2
|
||||||
|
|
||||||
|
/** MLS extension type identifier for marmot_group_data */
|
||||||
|
const val EXTENSION_ID: UShort = 0xF2EEu
|
||||||
|
}
|
||||||
|
}
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip01Groups
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MLS ciphersuites defined in RFC 9420 Section 17.1.
|
||||||
|
* Marmot default: MLS_128_DHKEMX25519_AES128GCM_SHA256_Ed25519 (0x0001).
|
||||||
|
*/
|
||||||
|
enum class MlsCiphersuite(
|
||||||
|
val code: String,
|
||||||
|
val hashAlgorithm: String,
|
||||||
|
val hashOutputBytes: Int,
|
||||||
|
) {
|
||||||
|
MLS_128_DHKEMX25519_AES128GCM_SHA256_ED25519("0x0001", "SHA-256", 32),
|
||||||
|
MLS_128_DHKEMP256_AES128GCM_SHA256_P256("0x0002", "SHA-256", 32),
|
||||||
|
MLS_128_DHKEMX25519_CHACHA20POLY1305_SHA256_ED25519("0x0003", "SHA-256", 32),
|
||||||
|
MLS_256_DHKEMX448_AES256GCM_SHA512_ED448("0x0004", "SHA-512", 64),
|
||||||
|
MLS_256_DHKEMP521_AES256GCM_SHA512_P521("0x0005", "SHA-512", 64),
|
||||||
|
MLS_256_DHKEMX448_CHACHA20POLY1305_SHA512_ED448("0x0006", "SHA-512", 64),
|
||||||
|
MLS_256_DHKEMP384_AES256GCM_SHA384_P384("0x0007", "SHA-384", 48),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = MLS_128_DHKEMX25519_AES128GCM_SHA256_ED25519
|
||||||
|
|
||||||
|
fun fromCode(code: String): MlsCiphersuite? = entries.find { it.code == code }
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip02Welcome
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip02Welcome.tags.KeyPackageEventTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip02Welcome.tags.WelcomeRelaysTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
|
||||||
|
fun TagArrayBuilder<WelcomeEvent>.keyPackageEventId(eventId: HexKey) = addUnique(KeyPackageEventTag.assemble(eventId))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<WelcomeEvent>.welcomeRelays(relays: List<NormalizedRelayUrl>) = addUnique(WelcomeRelaysTag.assemble(relays))
|
||||||
|
|
||||||
|
fun TagArrayBuilder<WelcomeEvent>.encoding() = addUnique(EncodingTag.assemble())
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip02Welcome
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip02Welcome.tags.KeyPackageEventTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip02Welcome.tags.WelcomeRelaysTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||||
|
|
||||||
|
fun TagArray.keyPackageEventId() = firstNotNullOfOrNull(KeyPackageEventTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.welcomeRelays() = firstNotNullOfOrNull(WelcomeRelaysTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.welcomeEncoding() = firstNotNullOfOrNull(EncodingTag::parse)
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip02Welcome
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
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.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot Welcome Event (MIP-02) — kind 444.
|
||||||
|
*
|
||||||
|
* An unsigned rumor event (no sig) that carries an MLS Welcome message for
|
||||||
|
* onboarding new members to a group. Delivered via NIP-59 gift wrap:
|
||||||
|
*
|
||||||
|
* GiftWrap(kind:1059) → Seal(kind:13) → WelcomeEvent(kind:444, unsigned)
|
||||||
|
*
|
||||||
|
* Content: base64-encoded MLSMessage with wire_format = mls_welcome.
|
||||||
|
*
|
||||||
|
* CRITICAL timing requirement: The Commit that adds this member MUST be
|
||||||
|
* confirmed by relays BEFORE the Welcome is sent, to prevent state forks.
|
||||||
|
*
|
||||||
|
* After processing a Welcome, the new member MUST:
|
||||||
|
* 1. Rotate their KeyPackage (publish new kind:30443 under same d-tag)
|
||||||
|
* 2. Securely delete the init_key private material
|
||||||
|
* 3. Perform a self-update within 24 hours for forward secrecy
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class WelcomeEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
/** Base64-encoded MLSMessage (wire_format = mls_welcome) */
|
||||||
|
fun welcomeBase64() = content
|
||||||
|
|
||||||
|
/** Event ID of the KeyPackage that was consumed for this invitation */
|
||||||
|
fun keyPackageEventId() = tags.keyPackageEventId()
|
||||||
|
|
||||||
|
/** Relays where the new member should look for Group Events */
|
||||||
|
fun relays() = tags.welcomeRelays()
|
||||||
|
|
||||||
|
/** Content encoding (must be "base64") */
|
||||||
|
fun encoding() = tags.welcomeEncoding()
|
||||||
|
|
||||||
|
override fun isContentEncoded() = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 444
|
||||||
|
const val ALT_DESCRIPTION = "MLS Welcome"
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
welcomeBase64: String,
|
||||||
|
keyPackageEventId: HexKey,
|
||||||
|
relays: List<NormalizedRelayUrl>,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<WelcomeEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, welcomeBase64, createdAt) {
|
||||||
|
keyPackageEventId(keyPackageEventId)
|
||||||
|
welcomeRelays(relays)
|
||||||
|
encoding()
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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.marmot.mip02Welcome.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event reference tag ("e") pointing to the KeyPackage event (kind:30443)
|
||||||
|
* that was consumed to add this member to the group.
|
||||||
|
*/
|
||||||
|
class KeyPackageEventTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "e"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): HexKey? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].length == 64) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(keyPackageEventId: HexKey) = arrayOf(TAG_NAME, keyPackageEventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip02Welcome.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.RelayUrlNormalizer
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.isLocalHost
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Relay list tag for Welcome events.
|
||||||
|
* Tells the new member where to find Group Events for this group.
|
||||||
|
*
|
||||||
|
* Example tag: ["relays", "wss://relay1.com", "wss://relay2.com"]
|
||||||
|
*/
|
||||||
|
class WelcomeRelaysTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "relays"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): List<NormalizedRelayUrl>? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
val relays =
|
||||||
|
tag.drop(1).mapNotNull { url ->
|
||||||
|
RelayUrlNormalizer.normalizeOrNull(url)?.takeUnless { it.isLocalHost() }
|
||||||
|
}
|
||||||
|
ensure(relays.isNotEmpty()) { return null }
|
||||||
|
return relays
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(relays: List<NormalizedRelayUrl>) = arrayOf(TAG_NAME, *relays.map { it.url }.toTypedArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
+106
@@ -0,0 +1,106 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip03GroupMessages
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
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.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot Group Event (MIP-03) — kind 445.
|
||||||
|
*
|
||||||
|
* Carries both control messages (Proposals, Commits) and application messages
|
||||||
|
* (chat, reactions) for encrypted group communication.
|
||||||
|
*
|
||||||
|
* Content: base64(nonce || ciphertext) where ciphertext is ChaCha20-Poly1305
|
||||||
|
* encrypted MLSMessage using key derived from MLS-Exporter("marmot", "group-event", 32).
|
||||||
|
*
|
||||||
|
* Privacy properties:
|
||||||
|
* - pubkey is an ephemeral key (different for each event, MUST NOT be reused)
|
||||||
|
* - "h" tag contains nostr_group_id for relay routing (not the private MLS group ID)
|
||||||
|
* - Content is double-encrypted: MLS framing + ChaCha20-Poly1305 outer layer
|
||||||
|
*
|
||||||
|
* Inner application messages are unsigned Nostr events:
|
||||||
|
* - kind:9 for chat messages
|
||||||
|
* - kind:7 for reactions
|
||||||
|
* - Other appropriate Nostr event kinds
|
||||||
|
*
|
||||||
|
* Commit ordering for race conditions:
|
||||||
|
* 1. Lowest created_at wins
|
||||||
|
* 2. If tied, lexicographically smallest event id wins
|
||||||
|
* 3. All other competing Commits are discarded
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class GroupEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
/**
|
||||||
|
* Base64-encoded encrypted content: nonce(12 bytes) || ciphertext.
|
||||||
|
* Decrypt with ChaCha20-Poly1305 using the MLS exporter-derived key.
|
||||||
|
*/
|
||||||
|
fun encryptedContent() = content
|
||||||
|
|
||||||
|
/** Nostr group ID from the Marmot Group Data Extension (for relay routing) */
|
||||||
|
fun groupId() = tags.marmotGroupId()
|
||||||
|
|
||||||
|
override fun isContentEncoded() = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 445
|
||||||
|
const val ALT_DESCRIPTION = "Encrypted group message"
|
||||||
|
|
||||||
|
/** MLS Exporter label for deriving the group event encryption key */
|
||||||
|
const val EXPORTER_LABEL = "marmot"
|
||||||
|
|
||||||
|
/** MLS Exporter context for deriving the group event encryption key */
|
||||||
|
const val EXPORTER_CONTEXT = "group-event"
|
||||||
|
|
||||||
|
/** Length of the MLS exporter-derived key in bytes */
|
||||||
|
const val EXPORTER_KEY_LENGTH = 32
|
||||||
|
|
||||||
|
/** ChaCha20-Poly1305 nonce length in bytes */
|
||||||
|
const val NONCE_LENGTH = 12
|
||||||
|
|
||||||
|
/** ChaCha20-Poly1305 auth tag length in bytes */
|
||||||
|
const val AUTH_TAG_LENGTH = 16
|
||||||
|
|
||||||
|
/** Minimum base64-decoded content length: nonce + auth tag */
|
||||||
|
const val MIN_CONTENT_LENGTH = NONCE_LENGTH + AUTH_TAG_LENGTH
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
encryptedContentBase64: String,
|
||||||
|
nostrGroupId: HexKey,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<GroupEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, encryptedContentBase64, createdAt) {
|
||||||
|
marmotGroupId(nostrGroupId)
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip03GroupMessages
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.tags.GroupIdTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
|
||||||
|
fun TagArrayBuilder<GroupEvent>.marmotGroupId(nostrGroupId: HexKey) = addUnique(GroupIdTag.assemble(nostrGroupId))
|
||||||
+26
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip03GroupMessages
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.tags.GroupIdTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||||
|
|
||||||
|
fun TagArray.marmotGroupId() = firstNotNullOfOrNull(GroupIdTag::parse)
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip03GroupMessages.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group ID tag ("h") for Marmot Group Events.
|
||||||
|
*
|
||||||
|
* Contains the nostr_group_id from the Marmot Group Data Extension (MIP-01).
|
||||||
|
* Used for relay routing and group message filtering.
|
||||||
|
* This is NOT the private MLS group ID — it's a separate routing identifier.
|
||||||
|
*/
|
||||||
|
class GroupIdTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "h"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): HexKey? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(nostrGroupId: HexKey) = arrayOf(TAG_NAME, nostrGroupId)
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip05PushNotifications
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.tags.VersionTag
|
||||||
|
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.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot Notification Request Event (MIP-05) — kind 446.
|
||||||
|
*
|
||||||
|
* An unsigned rumor event delivered via NIP-59 gift wrap to the notification server.
|
||||||
|
* Contains concatenated EncryptedTokens (each 280 bytes), base64-encoded.
|
||||||
|
*
|
||||||
|
* Flow: Rumor(kind:446) → Seal(kind:13) → GiftWrap(kind:1059) → notification server
|
||||||
|
*
|
||||||
|
* The pubkey MUST be a fresh ephemeral key (not the sender's identity)
|
||||||
|
* to prevent the notification server from linking events to users.
|
||||||
|
*
|
||||||
|
* Content includes real group tokens plus decoy tokens from other groups
|
||||||
|
* (shuffled) to obscure group size and prevent social graph inference.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class NotificationRequestEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
/**
|
||||||
|
* Base64-encoded concatenation of EncryptedTokens.
|
||||||
|
* Each token is exactly 280 bytes when decoded.
|
||||||
|
* Total decoded length MUST be a multiple of 280.
|
||||||
|
*/
|
||||||
|
fun tokensBase64() = content
|
||||||
|
|
||||||
|
/** Notification protocol version (must be "mip05-v1") */
|
||||||
|
fun version() = tags.notificationVersion()
|
||||||
|
|
||||||
|
/** Content encoding (must be "base64") */
|
||||||
|
fun encoding() = tags.notificationEncoding()
|
||||||
|
|
||||||
|
override fun isContentEncoded() = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 446
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
tokensBase64: String,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<NotificationRequestEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, tokensBase64, createdAt) {
|
||||||
|
addUnique(VersionTag.assemble())
|
||||||
|
addUnique(EncodingTag.assemble())
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+30
@@ -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.marmot.mip05PushNotifications
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.tags.TokenTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.tags.TokenTagData
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
|
||||||
|
fun <T : Event> TagArrayBuilder<T>.tokens(tokens: List<TokenTagData>) = addAll(TokenTag.assemble(tokens))
|
||||||
|
|
||||||
|
fun <T : Event> TagArrayBuilder<T>.token(data: TokenTagData) = add(TokenTag.assemble(data))
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip05PushNotifications
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.tags.EncodingTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.tags.TokenTag
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.tags.VersionTag
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||||
|
|
||||||
|
fun TagArray.notificationVersion() = firstNotNullOfOrNull(VersionTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.notificationEncoding() = firstNotNullOfOrNull(EncodingTag::parse)
|
||||||
|
|
||||||
|
fun TagArray.tokens() = mapNotNull(TokenTag::parse)
|
||||||
+74
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip05PushNotifications
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.tags.TokenTagData
|
||||||
|
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.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot Token List Response Event (MIP-05) — kind 448.
|
||||||
|
*
|
||||||
|
* Unsigned application message sent inside a GroupEvent (kind:445) in response
|
||||||
|
* to a TokenRequestEvent (kind:447). Contains the responder's complete view
|
||||||
|
* of all active encrypted device tokens in the group.
|
||||||
|
*
|
||||||
|
* Token tags include a leaf_index field (4th value) to identify which
|
||||||
|
* MLS leaf owns each token. An "e" tag references the kind:447 event
|
||||||
|
* this is responding to.
|
||||||
|
*
|
||||||
|
* Members SHOULD add random delay (0-2s) before responding.
|
||||||
|
* MUST remain unsigned (no sig field) per MIP-03 security requirements.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class TokenListEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
/** All known encrypted tokens with their leaf indices */
|
||||||
|
fun tokens() = tags.tokens()
|
||||||
|
|
||||||
|
/** Event ID of the kind:447 request this responds to */
|
||||||
|
fun requestEventId() = tags.firstOrNull { it.size >= 2 && it[0] == "e" }?.get(1)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 448
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
allTokens: List<TokenTagData>,
|
||||||
|
requestEventId: HexKey,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<TokenListEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, "", createdAt) {
|
||||||
|
tokens(allTokens)
|
||||||
|
add(arrayOf("e", requestEventId))
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+60
@@ -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.marmot.mip05PushNotifications
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
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.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot Token Removal Event (MIP-05) — kind 449.
|
||||||
|
*
|
||||||
|
* Unsigned application message sent inside a GroupEvent (kind:445) when a device
|
||||||
|
* leaves a group or wants to disable push notifications.
|
||||||
|
*
|
||||||
|
* Has no tags — the MLS leaf index is implicit from the MLS sender identity.
|
||||||
|
* Receiving clients MUST remove the token for the identified leaf.
|
||||||
|
*
|
||||||
|
* MUST remain unsigned (no sig field) per MIP-03 security requirements.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class TokenRemovalEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
companion object {
|
||||||
|
const val KIND = 449
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<TokenRemovalEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, "", createdAt) {
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+67
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.marmot.mip05PushNotifications
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.tags.TokenTagData
|
||||||
|
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.signers.eventTemplate
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marmot Token Request Event (MIP-05) — kind 447.
|
||||||
|
*
|
||||||
|
* Unsigned application message sent inside a GroupEvent (kind:445) when a device
|
||||||
|
* joins a group, needs to refresh its token view, or has a token change.
|
||||||
|
*
|
||||||
|
* Includes the sender's own encrypted token in "token" tags to bootstrap
|
||||||
|
* the device into the group's notification system immediately.
|
||||||
|
*
|
||||||
|
* The MLS leaf index is implicit from the MLS sender identity.
|
||||||
|
* MUST remain unsigned (no sig field) per MIP-03 security requirements.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
class TokenRequestEvent(
|
||||||
|
id: HexKey,
|
||||||
|
pubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
tags: Array<Array<String>>,
|
||||||
|
content: String,
|
||||||
|
sig: HexKey,
|
||||||
|
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
|
||||||
|
/** Encrypted tokens included with this request (sender's own tokens) */
|
||||||
|
fun tokens() = tags.tokens()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val KIND = 447
|
||||||
|
|
||||||
|
fun build(
|
||||||
|
ownTokens: List<TokenTagData>,
|
||||||
|
createdAt: Long = TimeUtils.now(),
|
||||||
|
initializer: TagArrayBuilder<TokenRequestEvent>.() -> Unit = {},
|
||||||
|
) = eventTemplate(KIND, "", createdAt) {
|
||||||
|
tokens(ownTokens)
|
||||||
|
initializer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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.marmot.mip05PushNotifications.tags
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encrypted token tag for push notification token distribution (kinds 447, 448).
|
||||||
|
*
|
||||||
|
* Each EncryptedToken is exactly 280 bytes:
|
||||||
|
* ephemeral_pubkey(32) || nonce(12) || ciphertext(236)
|
||||||
|
*
|
||||||
|
* The token payload inside is padded to 220 bytes:
|
||||||
|
* platform(1) || token_length(2 BE) || device_token(N) || random_padding(220-3-N)
|
||||||
|
*
|
||||||
|
* Platform values: 0x01 = APNs, 0x02 = FCM.
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
data class TokenTagData(
|
||||||
|
/** Base64-encoded EncryptedToken (280 bytes when decoded) */
|
||||||
|
val encryptedToken: String,
|
||||||
|
/** Hex-encoded notification server public key */
|
||||||
|
val serverPubKey: HexKey,
|
||||||
|
/** Relay hint URL for finding the server's kind:10050 event */
|
||||||
|
val relayHint: String,
|
||||||
|
/** MLS leaf index of the token owner (only present in kind:448 responses) */
|
||||||
|
val leafIndex: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class TokenTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "token"
|
||||||
|
|
||||||
|
/** Expected decoded size of an EncryptedToken */
|
||||||
|
const val ENCRYPTED_TOKEN_SIZE = 280
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): TokenTagData? {
|
||||||
|
ensure(tag.has(3) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1].isNotEmpty()) { return null }
|
||||||
|
ensure(tag[2].length == 64) { return null }
|
||||||
|
ensure(tag[3].isNotEmpty()) { return null }
|
||||||
|
|
||||||
|
val leafIndex = tag.getOrNull(4)?.toIntOrNull()
|
||||||
|
|
||||||
|
return TokenTagData(
|
||||||
|
encryptedToken = tag[1],
|
||||||
|
serverPubKey = tag[2],
|
||||||
|
relayHint = tag[3],
|
||||||
|
leafIndex = leafIndex,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(data: TokenTagData): Array<String> =
|
||||||
|
if (data.leafIndex != null) {
|
||||||
|
arrayOf(TAG_NAME, data.encryptedToken, data.serverPubKey, data.relayHint, data.leafIndex.toString())
|
||||||
|
} else {
|
||||||
|
arrayOf(TAG_NAME, data.encryptedToken, data.serverPubKey, data.relayHint)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble(tokens: List<TokenTagData>) = tokens.map { assemble(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
+43
@@ -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.marmot.mip05PushNotifications.tags
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.has
|
||||||
|
import com.vitorpamplona.quartz.utils.ensure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Version tag for Marmot push notification events (kind 446).
|
||||||
|
* Current version: "mip05-v1".
|
||||||
|
*/
|
||||||
|
class VersionTag {
|
||||||
|
companion object {
|
||||||
|
const val TAG_NAME = "v"
|
||||||
|
const val CURRENT_VERSION = "mip05-v1"
|
||||||
|
|
||||||
|
fun parse(tag: Array<String>): String? {
|
||||||
|
ensure(tag.has(1) && tag[0] == TAG_NAME) { return null }
|
||||||
|
ensure(tag[1] == CURRENT_VERSION) { return null }
|
||||||
|
return tag[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assemble() = arrayOf(TAG_NAME, CURRENT_VERSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -42,6 +42,14 @@ import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
|||||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||||
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
|
||||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip00KeyPackages.KeyPackageRelayListEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip03GroupMessages.GroupEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.NotificationRequestEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.TokenListEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.TokenRemovalEvent
|
||||||
|
import com.vitorpamplona.quartz.marmot.mip05PushNotifications.TokenRequestEvent
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
|
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
|
||||||
@@ -291,245 +299,502 @@ class EventFactory {
|
|||||||
): T =
|
): T =
|
||||||
when (kind) {
|
when (kind) {
|
||||||
AcceptedBadgeSetEvent.KIND -> AcceptedBadgeSetEvent(id, pubKey, createdAt, tags, content, sig)
|
AcceptedBadgeSetEvent.KIND -> AcceptedBadgeSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AdvertisedRelayListEvent.KIND -> AdvertisedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
AdvertisedRelayListEvent.KIND -> AdvertisedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AppCurationSetEvent.KIND -> AppCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
AppCurationSetEvent.KIND -> AppCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AppDefinitionEvent.KIND -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
AppDefinitionEvent.KIND -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AppRecommendationEvent.KIND -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
AppRecommendationEvent.KIND -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AppSpecificDataEvent.KIND -> AppSpecificDataEvent(id, pubKey, createdAt, tags, content, sig)
|
AppSpecificDataEvent.KIND -> AppSpecificDataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AttestationEvent.KIND -> AttestationEvent(id, pubKey, createdAt, tags, content, sig)
|
AttestationEvent.KIND -> AttestationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AttestationRequestEvent.KIND -> AttestationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
AttestationRequestEvent.KIND -> AttestationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AttestorRecommendationEvent.KIND -> AttestorRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
AttestorRecommendationEvent.KIND -> AttestorRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AttestorProficiencyEvent.KIND -> AttestorProficiencyEvent(id, pubKey, createdAt, tags, content, sig)
|
AttestorProficiencyEvent.KIND -> AttestorProficiencyEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ArticleCurationSetEvent.KIND -> ArticleCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
ArticleCurationSetEvent.KIND -> ArticleCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AudioHeaderEvent.KIND -> AudioHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
AudioHeaderEvent.KIND -> AudioHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AuctionEvent.KIND -> AuctionEvent(id, pubKey, createdAt, tags, content, sig)
|
AuctionEvent.KIND -> AuctionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AudioTrackEvent.KIND -> AudioTrackEvent(id, pubKey, createdAt, tags, content, sig)
|
AudioTrackEvent.KIND -> AudioTrackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BadgeAwardEvent.KIND -> BadgeAwardEvent(id, pubKey, createdAt, tags, content, sig)
|
BadgeAwardEvent.KIND -> BadgeAwardEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BadgeDefinitionEvent.KIND -> BadgeDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
BadgeDefinitionEvent.KIND -> BadgeDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BidEvent.KIND -> BidEvent(id, pubKey, createdAt, tags, content, sig)
|
BidEvent.KIND -> BidEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BidConfirmationEvent.KIND -> BidConfirmationEvent(id, pubKey, createdAt, tags, content, sig)
|
BidConfirmationEvent.KIND -> BidConfirmationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BlockedRelayListEvent.KIND -> BlockedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
BlockedRelayListEvent.KIND -> BlockedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BlossomServersEvent.KIND -> BlossomServersEvent(id, pubKey, createdAt, tags, content, sig)
|
BlossomServersEvent.KIND -> BlossomServersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BlossomAuthorizationEvent.KIND -> BlossomAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
BlossomAuthorizationEvent.KIND -> BlossomAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BroadcastRelayListEvent.KIND -> BroadcastRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
BroadcastRelayListEvent.KIND -> BroadcastRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
BookmarkListEvent.KIND -> BookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
BookmarkListEvent.KIND -> BookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
OldBookmarkListEvent.KIND -> OldBookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
OldBookmarkListEvent.KIND -> OldBookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CalendarDateSlotEvent.KIND -> CalendarDateSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
CalendarDateSlotEvent.KIND -> CalendarDateSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
|
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
|
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CallAnswerEvent.KIND -> CallAnswerEvent(id, pubKey, createdAt, tags, content, sig)
|
CallAnswerEvent.KIND -> CallAnswerEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CallHangupEvent.KIND -> CallHangupEvent(id, pubKey, createdAt, tags, content, sig)
|
CallHangupEvent.KIND -> CallHangupEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CallIceCandidateEvent.KIND -> CallIceCandidateEvent(id, pubKey, createdAt, tags, content, sig)
|
CallIceCandidateEvent.KIND -> CallIceCandidateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CallOfferEvent.KIND -> CallOfferEvent(id, pubKey, createdAt, tags, content, sig)
|
CallOfferEvent.KIND -> CallOfferEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CallRejectEvent.KIND -> CallRejectEvent(id, pubKey, createdAt, tags, content, sig)
|
CallRejectEvent.KIND -> CallRejectEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CallRenegotiateEvent.KIND -> CallRenegotiateEvent(id, pubKey, createdAt, tags, content, sig)
|
CallRenegotiateEvent.KIND -> CallRenegotiateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CashuMintEvent.KIND -> CashuMintEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuMintEvent.KIND -> CashuMintEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CashuMintQuoteEvent.KIND -> CashuMintQuoteEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuMintQuoteEvent.KIND -> CashuMintQuoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CashuTokenEvent.KIND -> CashuTokenEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuTokenEvent.KIND -> CashuTokenEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CashuSpendingHistoryEvent.KIND -> CashuSpendingHistoryEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuSpendingHistoryEvent.KIND -> CashuSpendingHistoryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CashuWalletEvent.KIND -> CashuWalletEvent(id, pubKey, createdAt, tags, content, sig)
|
CashuWalletEvent.KIND -> CashuWalletEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChatEvent.KIND -> ChatEvent(id, pubKey, createdAt, tags, content, sig)
|
ChatEvent.KIND -> ChatEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PutUserEvent.KIND -> PutUserEvent(id, pubKey, createdAt, tags, content, sig)
|
PutUserEvent.KIND -> PutUserEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RemoveUserEvent.KIND -> RemoveUserEvent(id, pubKey, createdAt, tags, content, sig)
|
RemoveUserEvent.KIND -> RemoveUserEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
EditMetadataEvent.KIND -> EditMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
EditMetadataEvent.KIND -> EditMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
DeleteEventEvent.KIND -> DeleteEventEvent(id, pubKey, createdAt, tags, content, sig)
|
DeleteEventEvent.KIND -> DeleteEventEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CreateGroupEvent.KIND -> CreateGroupEvent(id, pubKey, createdAt, tags, content, sig)
|
CreateGroupEvent.KIND -> CreateGroupEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
DeleteGroupEvent.KIND -> DeleteGroupEvent(id, pubKey, createdAt, tags, content, sig)
|
DeleteGroupEvent.KIND -> DeleteGroupEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CreateInviteEvent.KIND -> CreateInviteEvent(id, pubKey, createdAt, tags, content, sig)
|
CreateInviteEvent.KIND -> CreateInviteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
JoinRequestEvent.KIND -> JoinRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
JoinRequestEvent.KIND -> JoinRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LeaveRequestEvent.KIND -> LeaveRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
LeaveRequestEvent.KIND -> LeaveRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GroupMetadataEvent.KIND -> GroupMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
GroupMetadataEvent.KIND -> GroupMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GroupAdminsEvent.KIND -> GroupAdminsEvent(id, pubKey, createdAt, tags, content, sig)
|
GroupAdminsEvent.KIND -> GroupAdminsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GroupMembersEvent.KIND -> GroupMembersEvent(id, pubKey, createdAt, tags, content, sig)
|
GroupMembersEvent.KIND -> GroupMembersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
SupportedRolesEvent.KIND -> SupportedRolesEvent(id, pubKey, createdAt, tags, content, sig)
|
SupportedRolesEvent.KIND -> SupportedRolesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChessGameEvent.KIND -> ChessGameEvent(id, pubKey, createdAt, tags, content, sig)
|
ChessGameEvent.KIND -> ChessGameEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CodeSnippetEvent.KIND -> CodeSnippetEvent(id, pubKey, createdAt, tags, content, sig)
|
CodeSnippetEvent.KIND -> CodeSnippetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayFeedsListEvent.KIND -> RelayFeedsListEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayFeedsListEvent.KIND -> RelayFeedsListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
JesterEvent.KIND -> JesterEvent(id, pubKey, createdAt, tags, content, sig)
|
JesterEvent.KIND -> JesterEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LiveChessGameChallengeEvent.KIND -> LiveChessGameChallengeEvent(id, pubKey, createdAt, tags, content, sig)
|
LiveChessGameChallengeEvent.KIND -> LiveChessGameChallengeEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LiveChessGameAcceptEvent.KIND -> LiveChessGameAcceptEvent(id, pubKey, createdAt, tags, content, sig)
|
LiveChessGameAcceptEvent.KIND -> LiveChessGameAcceptEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LiveChessMoveEvent.KIND -> LiveChessMoveEvent(id, pubKey, createdAt, tags, content, sig)
|
LiveChessMoveEvent.KIND -> LiveChessMoveEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LiveChessGameEndEvent.KIND -> LiveChessGameEndEvent(id, pubKey, createdAt, tags, content, sig)
|
LiveChessGameEndEvent.KIND -> LiveChessGameEndEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LiveChessDrawOfferEvent.KIND -> LiveChessDrawOfferEvent(id, pubKey, createdAt, tags, content, sig)
|
LiveChessDrawOfferEvent.KIND -> LiveChessDrawOfferEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChannelCreateEvent.KIND -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
|
ChannelCreateEvent.KIND -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChannelHideMessageEvent.KIND -> ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
ChannelHideMessageEvent.KIND -> ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChannelListEvent.KIND -> ChannelListEvent(id, pubKey, createdAt, tags, content, sig)
|
ChannelListEvent.KIND -> ChannelListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChannelMessageEvent.KIND -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
ChannelMessageEvent.KIND -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChannelMetadataEvent.KIND -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
ChannelMetadataEvent.KIND -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChannelMuteUserEvent.KIND -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
|
ChannelMuteUserEvent.KIND -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChatMessageEncryptedFileHeaderEvent.KIND -> ChatMessageEncryptedFileHeaderEvent(id.ifBlank { EventHasher.hashId(pubKey, createdAt, kind, tags, content) }, pubKey, createdAt, tags, content, sig)
|
ChatMessageEncryptedFileHeaderEvent.KIND -> ChatMessageEncryptedFileHeaderEvent(id.ifBlank { EventHasher.hashId(pubKey, createdAt, kind, tags, content) }, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChatMessageEvent.KIND -> ChatMessageEvent(id.ifBlank { EventHasher.hashId(pubKey, createdAt, kind, tags, content) }, pubKey, createdAt, tags, content, sig)
|
ChatMessageEvent.KIND -> ChatMessageEvent(id.ifBlank { EventHasher.hashId(pubKey, createdAt, kind, tags, content) }, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ChatMessageRelayListEvent.KIND -> ChatMessageRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
ChatMessageRelayListEvent.KIND -> ChatMessageRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ClassifiedsEvent.KIND -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
|
ClassifiedsEvent.KIND -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CommentEvent.KIND -> CommentEvent(id, pubKey, createdAt, tags, content, sig)
|
CommentEvent.KIND -> CommentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CommunityDefinitionEvent.KIND -> CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
CommunityDefinitionEvent.KIND -> CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CommunityListEvent.KIND -> CommunityListEvent(id, pubKey, createdAt, tags, content, sig)
|
CommunityListEvent.KIND -> CommunityListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
CommunityPostApprovalEvent.KIND -> CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
|
CommunityPostApprovalEvent.KIND -> CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
|
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
DeletionEvent.KIND -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
|
DeletionEvent.KIND -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
DraftWrapEvent.KIND -> DraftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
DraftWrapEvent.KIND -> DraftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
|
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
|
EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
EphemeralChatEvent.KIND -> EphemeralChatEvent(id, pubKey, createdAt, tags, content, sig)
|
EphemeralChatEvent.KIND -> EphemeralChatEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
EphemeralChatListEvent.KIND -> EphemeralChatListEvent(id, pubKey, createdAt, tags, content, sig)
|
EphemeralChatListEvent.KIND -> EphemeralChatListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ExternalIdentitiesEvent.KIND -> ExternalIdentitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
ExternalIdentitiesEvent.KIND -> ExternalIdentitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
FedimintEvent.KIND -> FedimintEvent(id, pubKey, createdAt, tags, content, sig)
|
FedimintEvent.KIND -> FedimintEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
|
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
|
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
|
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
FollowListEvent.KIND -> FollowListEvent(id, pubKey, createdAt, tags, content, sig)
|
FollowListEvent.KIND -> FollowListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GeohashListEvent.KIND -> GeohashListEvent(id, pubKey, createdAt, tags, content, sig)
|
GeohashListEvent.KIND -> GeohashListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
EphemeralGiftWrapEvent.KIND -> EphemeralGiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
EphemeralGiftWrapEvent.KIND -> EphemeralGiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GitAuthorListEvent.KIND -> GitAuthorListEvent(id, pubKey, createdAt, tags, content, sig)
|
GitAuthorListEvent.KIND -> GitAuthorListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GitRepositoryListEvent.KIND -> GitRepositoryListEvent(id, pubKey, createdAt, tags, content, sig)
|
GitRepositoryListEvent.KIND -> GitRepositoryListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
|
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GitReplyEvent.KIND -> GitReplyEvent(id, pubKey, createdAt, tags, content, sig)
|
GitReplyEvent.KIND -> GitReplyEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
|
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
|
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GoodWikiAuthorListEvent.KIND -> GoodWikiAuthorListEvent(id, pubKey, createdAt, tags, content, sig)
|
GoodWikiAuthorListEvent.KIND -> GoodWikiAuthorListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GoodWikiRelayListEvent.KIND -> GoodWikiRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
GoodWikiRelayListEvent.KIND -> GoodWikiRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
|
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
HashtagListEvent.KIND -> HashtagListEvent(id, pubKey, createdAt, tags, content, sig)
|
HashtagListEvent.KIND -> HashtagListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
IndexerRelayListEvent.KIND -> IndexerRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
IndexerRelayListEvent.KIND -> IndexerRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
InterestSetEvent.KIND -> InterestSetEvent(id, pubKey, createdAt, tags, content, sig)
|
InterestSetEvent.KIND -> InterestSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
InteractiveStoryPrologueEvent.KIND -> InteractiveStoryPrologueEvent(id, pubKey, createdAt, tags, content, sig)
|
InteractiveStoryPrologueEvent.KIND -> InteractiveStoryPrologueEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
InteractiveStorySceneEvent.KIND -> InteractiveStorySceneEvent(id, pubKey, createdAt, tags, content, sig)
|
InteractiveStorySceneEvent.KIND -> InteractiveStorySceneEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
InteractiveStoryReadingStateEvent.KIND -> InteractiveStoryReadingStateEvent(id, pubKey, createdAt, tags, content, sig)
|
InteractiveStoryReadingStateEvent.KIND -> InteractiveStoryReadingStateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LabelEvent.KIND -> LabelEvent(id, pubKey, createdAt, tags, content, sig)
|
LabelEvent.KIND -> LabelEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
KindMuteSetEvent.KIND -> KindMuteSetEvent(id, pubKey, createdAt, tags, content, sig)
|
KindMuteSetEvent.KIND -> KindMuteSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LabeledBookmarkListEvent.KIND -> LabeledBookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
LabeledBookmarkListEvent.KIND -> LabeledBookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LiveActivitiesChatMessageEvent.KIND -> LiveActivitiesChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
LiveActivitiesChatMessageEvent.KIND -> LiveActivitiesChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LiveActivitiesEvent.KIND -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
LiveActivitiesEvent.KIND -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NwcInfoEvent.KIND -> NwcInfoEvent(id, pubKey, createdAt, tags, content, sig)
|
NwcInfoEvent.KIND -> NwcInfoEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NwcNotificationEvent.KIND -> NwcNotificationEvent(id, pubKey, createdAt, tags, content, sig)
|
NwcNotificationEvent.KIND -> NwcNotificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NwcNotificationEvent.LEGACY_KIND -> NwcNotificationEvent(id, pubKey, createdAt, tags, content, sig)
|
NwcNotificationEvent.LEGACY_KIND -> NwcNotificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MarketplaceEvent.KIND -> MarketplaceEvent(id, pubKey, createdAt, tags, content, sig)
|
MarketplaceEvent.KIND -> MarketplaceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MeetingRoomEvent.KIND -> MeetingRoomEvent(id, pubKey, createdAt, tags, content, sig)
|
MeetingRoomEvent.KIND -> MeetingRoomEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MeetingRoomPresenceEvent.KIND -> MeetingRoomPresenceEvent(id, pubKey, createdAt, tags, content, sig)
|
MeetingRoomPresenceEvent.KIND -> MeetingRoomPresenceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MeetingSpaceEvent.KIND -> MeetingSpaceEvent(id, pubKey, createdAt, tags, content, sig)
|
MeetingSpaceEvent.KIND -> MeetingSpaceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MintRecommendationEvent.KIND -> MintRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
MintRecommendationEvent.KIND -> MintRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MediaFollowListEvent.KIND -> MediaFollowListEvent(id, pubKey, createdAt, tags, content, sig)
|
MediaFollowListEvent.KIND -> MediaFollowListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MediaStarterPackEvent.KIND -> MediaStarterPackEvent(id, pubKey, createdAt, tags, content, sig)
|
MediaStarterPackEvent.KIND -> MediaStarterPackEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
// Marmot Protocol (MLS over Nostr)
|
||||||
|
KeyPackageEvent.KIND -> KeyPackageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
KeyPackageRelayListEvent.KIND -> KeyPackageRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
WelcomeEvent.KIND -> WelcomeEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
GroupEvent.KIND -> GroupEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
NotificationRequestEvent.KIND -> NotificationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
TokenRequestEvent.KIND -> TokenRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
TokenListEvent.KIND -> TokenListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
|
TokenRemovalEvent.KIND -> TokenRemovalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NamedSiteEvent.KIND -> NamedSiteEvent(id, pubKey, createdAt, tags, content, sig)
|
NamedSiteEvent.KIND -> NamedSiteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NipTextEvent.KIND -> NipTextEvent(id, pubKey, createdAt, tags, content, sig)
|
NipTextEvent.KIND -> NipTextEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NutzapEvent.KIND -> NutzapEvent(id, pubKey, createdAt, tags, content, sig)
|
NutzapEvent.KIND -> NutzapEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NutzapInfoEvent.KIND -> NutzapInfoEvent(id, pubKey, createdAt, tags, content, sig)
|
NutzapInfoEvent.KIND -> NutzapInfoEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NutzapRedemptionEvent.KIND -> NutzapRedemptionEvent(id, pubKey, createdAt, tags, content, sig)
|
NutzapRedemptionEvent.KIND -> NutzapRedemptionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NostrConnectEvent.KIND -> NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
|
NostrConnectEvent.KIND -> NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TextExtractionRequestEvent.KIND -> NIP90TextExtractionRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TextExtractionRequestEvent.KIND -> NIP90TextExtractionRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TextExtractionResponseEvent.KIND -> NIP90TextExtractionResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TextExtractionResponseEvent.KIND -> NIP90TextExtractionResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90SummarizationRequestEvent.KIND -> NIP90SummarizationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90SummarizationRequestEvent.KIND -> NIP90SummarizationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90SummarizationResponseEvent.KIND -> NIP90SummarizationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90SummarizationResponseEvent.KIND -> NIP90SummarizationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TranslationRequestEvent.KIND -> NIP90TranslationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TranslationRequestEvent.KIND -> NIP90TranslationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TranslationResponseEvent.KIND -> NIP90TranslationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TranslationResponseEvent.KIND -> NIP90TranslationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TextGenerationRequestEvent.KIND -> NIP90TextGenerationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TextGenerationRequestEvent.KIND -> NIP90TextGenerationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TextGenerationResponseEvent.KIND -> NIP90TextGenerationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TextGenerationResponseEvent.KIND -> NIP90TextGenerationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ImageGenerationRequestEvent.KIND -> NIP90ImageGenerationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ImageGenerationRequestEvent.KIND -> NIP90ImageGenerationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ImageGenerationResponseEvent.KIND -> NIP90ImageGenerationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ImageGenerationResponseEvent.KIND -> NIP90ImageGenerationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90VideoConversionRequestEvent.KIND -> NIP90VideoConversionRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90VideoConversionRequestEvent.KIND -> NIP90VideoConversionRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90VideoConversionResponseEvent.KIND -> NIP90VideoConversionResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90VideoConversionResponseEvent.KIND -> NIP90VideoConversionResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90VideoTranslationRequestEvent.KIND -> NIP90VideoTranslationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90VideoTranslationRequestEvent.KIND -> NIP90VideoTranslationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90VideoTranslationResponseEvent.KIND -> NIP90VideoTranslationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90VideoTranslationResponseEvent.KIND -> NIP90VideoTranslationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ImageToVideoRequestEvent.KIND -> NIP90ImageToVideoRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ImageToVideoRequestEvent.KIND -> NIP90ImageToVideoRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ImageToVideoResponseEvent.KIND -> NIP90ImageToVideoResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ImageToVideoResponseEvent.KIND -> NIP90ImageToVideoResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TextToSpeechRequestEvent.KIND -> NIP90TextToSpeechRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TextToSpeechRequestEvent.KIND -> NIP90TextToSpeechRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90TextToSpeechResponseEvent.KIND -> NIP90TextToSpeechResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90TextToSpeechResponseEvent.KIND -> NIP90TextToSpeechResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90UserDiscoveryRequestEvent.KIND -> NIP90UserDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90UserDiscoveryRequestEvent.KIND -> NIP90UserDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90UserDiscoveryResponseEvent.KIND -> NIP90UserDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90UserDiscoveryResponseEvent.KIND -> NIP90UserDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ContentSearchRequestEvent.KIND -> NIP90ContentSearchRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ContentSearchRequestEvent.KIND -> NIP90ContentSearchRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90ContentSearchResponseEvent.KIND -> NIP90ContentSearchResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90ContentSearchResponseEvent.KIND -> NIP90ContentSearchResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90PeopleSearchRequestEvent.KIND -> NIP90PeopleSearchRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90PeopleSearchRequestEvent.KIND -> NIP90PeopleSearchRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90PeopleSearchResponseEvent.KIND -> NIP90PeopleSearchResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90PeopleSearchResponseEvent.KIND -> NIP90PeopleSearchResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventCountRequestEvent.KIND -> NIP90EventCountRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventCountRequestEvent.KIND -> NIP90EventCountRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventCountResponseEvent.KIND -> NIP90EventCountResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventCountResponseEvent.KIND -> NIP90EventCountResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90MalwareScanRequestEvent.KIND -> NIP90MalwareScanRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90MalwareScanRequestEvent.KIND -> NIP90MalwareScanRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90MalwareScanResponseEvent.KIND -> NIP90MalwareScanResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90MalwareScanResponseEvent.KIND -> NIP90MalwareScanResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventTimestampingRequestEvent.KIND -> NIP90EventTimestampingRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventTimestampingRequestEvent.KIND -> NIP90EventTimestampingRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventTimestampingResponseEvent.KIND -> NIP90EventTimestampingResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventTimestampingResponseEvent.KIND -> NIP90EventTimestampingResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90OpReturnRequestEvent.KIND -> NIP90OpReturnRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90OpReturnRequestEvent.KIND -> NIP90OpReturnRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90OpReturnResponseEvent.KIND -> NIP90OpReturnResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90OpReturnResponseEvent.KIND -> NIP90OpReturnResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventPublishScheduleRequestEvent.KIND -> NIP90EventPublishScheduleRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventPublishScheduleRequestEvent.KIND -> NIP90EventPublishScheduleRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventPublishScheduleResponseEvent.KIND -> NIP90EventPublishScheduleResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventPublishScheduleResponseEvent.KIND -> NIP90EventPublishScheduleResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventPowDelegationRequestEvent.KIND -> NIP90EventPowDelegationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventPowDelegationRequestEvent.KIND -> NIP90EventPowDelegationRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
NIP90EventPowDelegationResponseEvent.KIND -> NIP90EventPowDelegationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
NIP90EventPowDelegationResponseEvent.KIND -> NIP90EventPowDelegationResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
|
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PaymentTargetsEvent.KIND -> PaymentTargetsEvent(id, pubKey, createdAt, tags, content, sig)
|
PaymentTargetsEvent.KIND -> PaymentTargetsEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
|
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PictureCurationSetEvent.KIND -> PictureCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
PictureCurationSetEvent.KIND -> PictureCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
P2POrderEvent.KIND -> P2POrderEvent(id, pubKey, createdAt, tags, content, sig)
|
P2POrderEvent.KIND -> P2POrderEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PictureEvent.KIND -> PictureEvent(id, pubKey, createdAt, tags, content, sig)
|
PictureEvent.KIND -> PictureEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
|
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ProfileBadgesEvent.KIND -> ProfileBadgesEvent(id, pubKey, createdAt, tags, content, sig)
|
ProfileBadgesEvent.KIND -> ProfileBadgesEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ZapPollEvent.KIND -> ZapPollEvent(id, pubKey, createdAt, tags, content, sig)
|
ZapPollEvent.KIND -> ZapPollEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PollEvent.KIND -> PollEvent(id, pubKey, createdAt, tags, content, sig)
|
PollEvent.KIND -> PollEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PollResponseEvent.KIND -> PollResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
PollResponseEvent.KIND -> PollResponseEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ProductEvent.KIND -> ProductEvent(id, pubKey, createdAt, tags, content, sig)
|
ProductEvent.KIND -> ProductEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PrivateDmEvent.KIND -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
PrivateDmEvent.KIND -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PrivateOutboxRelayListEvent.KIND -> PrivateOutboxRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
PrivateOutboxRelayListEvent.KIND -> PrivateOutboxRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ProxyRelayListEvent.KIND -> ProxyRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
ProxyRelayListEvent.KIND -> ProxyRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
PublicMessageEvent.KIND -> PublicMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
PublicMessageEvent.KIND -> PublicMessageEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ContactCardEvent.KIND -> ContactCardEvent(id, pubKey, createdAt, tags, content, sig)
|
ContactCardEvent.KIND -> ContactCardEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
EventAssertionEvent.KIND -> EventAssertionEvent(id, pubKey, createdAt, tags, content, sig)
|
EventAssertionEvent.KIND -> EventAssertionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
AddressableAssertionEvent.KIND -> AddressableAssertionEvent(id, pubKey, createdAt, tags, content, sig)
|
AddressableAssertionEvent.KIND -> AddressableAssertionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ExternalIdAssertionEvent.KIND -> ExternalIdAssertionEvent(id, pubKey, createdAt, tags, content, sig)
|
ExternalIdAssertionEvent.KIND -> ExternalIdAssertionEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayAddMemberEvent.KIND -> RelayAddMemberEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayAddMemberEvent.KIND -> RelayAddMemberEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayRemoveMemberEvent.KIND -> RelayRemoveMemberEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayRemoveMemberEvent.KIND -> RelayRemoveMemberEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayMembershipListEvent.KIND -> RelayMembershipListEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayMembershipListEvent.KIND -> RelayMembershipListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayJoinRequestEvent.KIND -> RelayJoinRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayJoinRequestEvent.KIND -> RelayJoinRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayInviteRequestEvent.KIND -> RelayInviteRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayInviteRequestEvent.KIND -> RelayInviteRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayLeaveRequestEvent.KIND -> RelayLeaveRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayLeaveRequestEvent.KIND -> RelayLeaveRequestEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayDiscoveryEvent.KIND -> RelayDiscoveryEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayDiscoveryEvent.KIND -> RelayDiscoveryEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelayMonitorEvent.KIND -> RelayMonitorEvent(id, pubKey, createdAt, tags, content, sig)
|
RelayMonitorEvent.KIND -> RelayMonitorEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ReleaseArtifactSetEvent.KIND -> ReleaseArtifactSetEvent(id, pubKey, createdAt, tags, content, sig)
|
ReleaseArtifactSetEvent.KIND -> ReleaseArtifactSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
|
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RootSiteEvent.KIND -> RootSiteEvent(id, pubKey, createdAt, tags, content, sig)
|
RootSiteEvent.KIND -> RootSiteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
RequestToVanishEvent.KIND -> RequestToVanishEvent(id, pubKey, createdAt, tags, content, sig)
|
RequestToVanishEvent.KIND -> RequestToVanishEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
SealedRumorEvent.KIND -> SealedRumorEvent(id, pubKey, createdAt, tags, content, sig)
|
SealedRumorEvent.KIND -> SealedRumorEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
SearchRelayListEvent.KIND -> SearchRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
SearchRelayListEvent.KIND -> SearchRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
SimpleGroupListEvent.KIND -> SimpleGroupListEvent(id, pubKey, createdAt, tags, content, sig)
|
SimpleGroupListEvent.KIND -> SimpleGroupListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
StallEvent.KIND -> StallEvent(id, pubKey, createdAt, tags, content, sig)
|
StallEvent.KIND -> StallEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
StatusEvent.KIND -> StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
StatusEvent.KIND -> StatusEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
ThreadEvent.KIND -> ThreadEvent(id, pubKey, createdAt, tags, content, sig)
|
ThreadEvent.KIND -> ThreadEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
TextNoteModificationEvent.KIND -> TextNoteModificationEvent(id, pubKey, createdAt, tags, content, sig)
|
TextNoteModificationEvent.KIND -> TextNoteModificationEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
TokenEvent.KIND -> TokenEvent(id, pubKey, createdAt, tags, content, sig)
|
TokenEvent.KIND -> TokenEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
TorrentEvent.KIND -> TorrentEvent(id, pubKey, createdAt, tags, content, sig)
|
TorrentEvent.KIND -> TorrentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
TorrentCommentEvent.KIND -> TorrentCommentEvent(id, pubKey, createdAt, tags, content, sig)
|
TorrentCommentEvent.KIND -> TorrentCommentEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
TrustedRelayListEvent.KIND -> TrustedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
TrustedRelayListEvent.KIND -> TrustedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
TrustProviderListEvent.KIND -> TrustProviderListEvent(id, pubKey, createdAt, tags, content, sig)
|
TrustProviderListEvent.KIND -> TrustProviderListEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
VideoCurationSetEvent.KIND -> VideoCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
VideoCurationSetEvent.KIND -> VideoCurationSetEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
|
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
|
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
VideoNormalEvent.KIND -> VideoNormalEvent(id, pubKey, createdAt, tags, content, sig)
|
VideoNormalEvent.KIND -> VideoNormalEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
VideoShortEvent.KIND -> VideoShortEvent(id, pubKey, createdAt, tags, content, sig)
|
VideoShortEvent.KIND -> VideoShortEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
VoiceEvent.KIND -> VoiceEvent(id, pubKey, createdAt, tags, content, sig)
|
VoiceEvent.KIND -> VoiceEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
VoiceReplyEvent.KIND -> VoiceReplyEvent(id, pubKey, createdAt, tags, content, sig)
|
VoiceReplyEvent.KIND -> VoiceReplyEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
WebBookmarkEvent.KIND -> WebBookmarkEvent(id, pubKey, createdAt, tags, content, sig)
|
WebBookmarkEvent.KIND -> WebBookmarkEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
|
||||||
|
|
||||||
else -> factories[kind]?.build(id, pubKey, createdAt, tags, content, sig) ?: Event(id, pubKey, createdAt, kind, tags, content, sig)
|
else -> factories[kind]?.build(id, pubKey, createdAt, tags, content, sig) ?: Event(id, pubKey, createdAt, kind, tags, content, sig)
|
||||||
} as T
|
} as T
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user