Massive refactoring of quartz to prepare for nip-based packages.

This commit is contained in:
Vitor Pamplona
2025-01-13 10:38:45 -05:00
parent 1430ba4745
commit d2b731e372
592 changed files with 7710 additions and 5334 deletions
@@ -18,11 +18,12 @@
* 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.events
package com.vitorpamplona.quartz.blossom
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,12 +18,13 @@
* 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.events
package com.vitorpamplona.quartz.blossom
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -20,13 +20,13 @@
*/
package com.vitorpamplona.quartz.crypto
import com.vitorpamplona.quartz.crypto.nip01.Nip01
import com.vitorpamplona.quartz.crypto.nip04.Nip04
import com.vitorpamplona.quartz.crypto.nip06.Nip06
import com.vitorpamplona.quartz.crypto.nip44.Nip44
import com.vitorpamplona.quartz.crypto.nip44.Nip44v2
import com.vitorpamplona.quartz.crypto.nip49.Nip49
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.Nip01
import com.vitorpamplona.quartz.nip04Dm.Nip04
import com.vitorpamplona.quartz.nip06KeyDerivation.Nip06
import com.vitorpamplona.quartz.nip44Encryption.Nip44
import com.vitorpamplona.quartz.nip44Encryption.Nip44v2
import com.vitorpamplona.quartz.nip49PrivKeyEnc.Nip49
import fr.acinq.secp256k1.Secp256k1
import java.security.SecureRandom
@@ -34,11 +34,11 @@ object CryptoUtils {
private val secp256k1 = Secp256k1.get()
private val random = SecureRandom()
public val nip01 = Nip01(secp256k1, random)
public val nip06 = Nip06(secp256k1)
public val nip04 = Nip04(secp256k1, random)
public val nip44 = Nip44(secp256k1, random, nip04)
public val nip49 = Nip49(secp256k1, random)
val nip01 = Nip01(secp256k1, random)
val nip06 = Nip06(secp256k1)
val nip04 = Nip04(secp256k1, random)
val nip44 = Nip44(secp256k1, random, nip04)
val nip49 = Nip49(secp256k1, random)
fun clearCache() {
nip04.clearCache()
@@ -18,31 +18,25 @@
* 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.encoders
package com.vitorpamplona.quartz.crypto
/** Makes the distinction between String and Hex * */
typealias HexKey = String
object Hex {
private val lowerCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
private val upperCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
fun ByteArray.toHexKey(): HexKey = Hex.encode(this)
private val hexToByte: IntArray =
IntArray(256) { -1 }.apply {
lowerCaseHex.forEachIndexed { index, char -> this[char.code] = index }
upperCaseHex.forEachIndexed { index, char -> this[char.code] = index }
}
fun HexKey.hexToByteArray(): ByteArray = Hex.decode(this)
// Encodes both chars in a single Int variable
private val byteToHex =
IntArray(256) {
(lowerCaseHex[(it shr 4)].code shl 8) or lowerCaseHex[(it and 0xF)].code
}
val lowerCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f')
val upperCaseHex = arrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
val hexToByte: IntArray =
IntArray(256) { -1 }.apply {
lowerCaseHex.forEachIndexed { index, char -> this[char.code] = index }
upperCaseHex.forEachIndexed { index, char -> this[char.code] = index }
}
// Encodes both chars in a single Int variable
val byteToHex =
IntArray(256) {
(lowerCaseHex[(it shr 4)].code shl 8) or lowerCaseHex[(it and 0xF)].code
}
object HexValidator {
@JvmStatic
fun isHex(hex: String?): Boolean {
if (hex == null) return false
if (hex.isEmpty()) return false
@@ -54,9 +48,7 @@ object HexValidator {
return true
}
}
object Hex {
@JvmStatic
fun decode(hex: String): ByteArray {
// faster version of hex decoder
@@ -20,7 +20,7 @@
*/
package com.vitorpamplona.quartz.crypto
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.nip01Core.toHexKey
class KeyPair(
privKey: ByteArray? = null,
@@ -1,133 +0,0 @@
/**
* Copyright (c) 2024 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.encoders
import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
import com.vitorpamplona.quartz.utils.removeTrailingNullsAndEmptyOthers
@Immutable
data class ATag(
val kind: Int,
val pubKeyHex: String,
val dTag: String,
) {
var relay: String? = null
constructor(
kind: Int,
pubKeyHex: String,
dTag: String,
relayHint: String?,
) : this(kind, pubKeyHex, dTag) {
this.relay = relayHint
}
fun countMemory(): Long =
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
8L + // kind
pubKeyHex.bytesUsedInMemory() +
dTag.bytesUsedInMemory() +
(relay?.bytesUsedInMemory() ?: 0)
fun toTag() = assembleATag(kind, pubKeyHex, dTag)
fun toATagArray() = removeTrailingNullsAndEmptyOthers("a", toTag(), relay)
fun toQTagArray() = removeTrailingNullsAndEmptyOthers("q", toTag(), relay)
fun toNAddr(overrideRelay: String? = relay): String =
TlvBuilder()
.apply {
addString(Nip19Bech32.TlvTypes.SPECIAL, dTag)
addStringIfNotNull(Nip19Bech32.TlvTypes.RELAY, overrideRelay ?: relay)
addHex(Nip19Bech32.TlvTypes.AUTHOR, pubKeyHex)
addInt(Nip19Bech32.TlvTypes.KIND, kind)
}.build()
.toNAddress()
companion object {
fun assembleATag(
kind: Int,
pubKeyHex: String,
dTag: String,
) = "$kind:$pubKeyHex:$dTag"
fun isATag(key: String): Boolean = key.startsWith("naddr1") || key.contains(":")
fun parse(
address: String,
relay: String?,
): ATag? =
if (address.startsWith("naddr") || address.startsWith("nostr:naddr")) {
parseNAddr(address)
} else {
parseAtag(address, relay)
}
fun parseAtag(
atag: String,
relay: String?,
): ATag? =
try {
val parts = atag.split(":", limit = 3)
Hex.decode(parts[1])
ATag(parts[0].toInt(), parts[1], parts[2], relay)
} catch (t: Throwable) {
Log.w("ATag", "Error parsing A Tag: $atag: ${t.message}")
null
}
fun parseAtagUnckecked(atag: String): ATag? =
try {
val parts = atag.split(":")
ATag(parts[0].toInt(), parts[1], parts[2], null)
} catch (t: Throwable) {
null
}
fun parseNAddr(naddr: String): ATag? {
try {
val key = naddr.removePrefix("nostr:")
if (key.startsWith("naddr")) {
val tlv = Tlv.parse(key.bechToBytes())
val d = tlv.firstAsString(Nip19Bech32.TlvTypes.SPECIAL) ?: ""
val relay = tlv.firstAsString(Nip19Bech32.TlvTypes.RELAY)
val author = tlv.firstAsHex(Nip19Bech32.TlvTypes.AUTHOR)
val kind = tlv.firstAsInt(Nip19Bech32.TlvTypes.KIND)
if (kind != null && author != null) {
return ATag(kind, author, d, relay)
}
}
} catch (e: Throwable) {
Log.w("ATag", "Issue trying to Decode NIP19 $this: ${e.message}")
// e.printStackTrace()
}
return null
}
}
}
@@ -1,418 +0,0 @@
/**
* Copyright (c) 2024 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.encoders
import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.events.Event
import kotlinx.coroutines.CancellationException
import java.io.ByteArrayOutputStream
import java.util.regex.Pattern
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
object Nip19Bech32 {
enum class Type {
USER,
NOTE,
EVENT,
RELAY,
ADDRESS,
}
enum class TlvTypes(
val id: Byte,
) {
SPECIAL(0),
RELAY(1),
AUTHOR(2),
KIND(3),
}
val nip19PlusNip46regex =
Pattern.compile(
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1|nembed1|ncryptsec1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)",
Pattern.CASE_INSENSITIVE,
)
val nip19regex =
Pattern.compile(
"(nostr:)?@?(nsec1|npub1|nevent1|naddr1|note1|nprofile1|nrelay1|nembed1)([qpzry9x8gf2tvdw0s3jn54khce6mua7l]+)([\\S]*)",
Pattern.CASE_INSENSITIVE,
)
@Immutable
data class ParseReturn(
val entity: Entity,
val nip19raw: String,
val additionalChars: String? = null,
)
interface Entity
@Immutable
data class NSec(
val hex: String,
) : Entity
@Immutable
data class NPub(
val hex: String,
) : Entity
@Immutable
data class Note(
val hex: String,
) : Entity
@Immutable
data class NProfile(
val hex: String,
val relay: List<String>,
) : Entity
@Immutable
data class NEvent(
val hex: String,
val relay: List<String>,
val author: String?,
val kind: Int?,
) : Entity
@Immutable
data class NAddress(
val atag: String,
val relay: List<String>,
val author: String,
val kind: Int,
) : Entity
@Immutable
data class NRelay(
val relay: List<String>,
) : Entity
@Immutable
data class NEmbed(
val event: Event,
) : Entity
fun tryParseAndClean(uri: String?): String? {
if (uri == null) return null
try {
val matcher = nip19PlusNip46regex.matcher(uri)
if (!matcher.find()) {
return null
}
val type = matcher.group(2) // npub1
val key = matcher.group(3) // bech32
return type + key
} catch (e: Throwable) {
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
}
return null
}
fun uriToRoute(uri: String?): ParseReturn? {
if (uri == null) return null
try {
val matcher = nip19regex.matcher(uri)
if (!matcher.find()) {
return null
}
val type = matcher.group(2) // npub1
val key = matcher.group(3) // bech32
val additionalChars = matcher.group(4) // additional chars
if (type == null) return null
return parseComponents(type, key, additionalChars.ifEmpty { null })
} catch (e: Throwable) {
Log.e("NIP19 Parser", "Issue trying to Decode NIP19 $uri: ${e.message}", e)
}
return null
}
fun parseComponents(
type: String,
key: String?,
additionalChars: String?,
): ParseReturn? =
try {
val nip19 = (type + key)
val bytes = nip19.bechToBytes()
when (type.lowercase()) {
"nsec1" -> nsec(bytes)
"npub1" -> npub(bytes)
"note1" -> note(bytes)
"nprofile1" -> nprofile(bytes)
"nevent1" -> nevent(bytes)
"nrelay1" -> nrelay(bytes)
"naddr1" -> naddr(bytes)
"nembed1" -> nembed(bytes)
else -> null
}?.let {
ParseReturn(it, nip19, additionalChars)
}
} catch (e: Throwable) {
Log.w("NIP19 Parser", "Issue trying to Decode NIP19 $key: ${e.message}", e)
null
}
private fun nembed(bytes: ByteArray): NEmbed? {
if (bytes.isEmpty()) return null
return NEmbed(Event.fromJson(ungzip(bytes)))
}
private fun nsec(bytes: ByteArray): NSec? {
if (bytes.isEmpty()) return null
return NSec(bytes.toHexKey())
}
private fun npub(bytes: ByteArray): NPub? {
if (bytes.isEmpty()) return null
return NPub(bytes.toHexKey())
}
private fun note(bytes: ByteArray): Note? {
if (bytes.isEmpty()) return null
return Note(bytes.toHexKey())
}
private fun nprofile(bytes: ByteArray): NProfile? {
if (bytes.isEmpty()) return null
val tlv = Tlv.parse(bytes)
val hex = tlv.firstAsHex(TlvTypes.SPECIAL) ?: return null
val relay = tlv.asStringList(TlvTypes.RELAY) ?: emptyList()
if (hex.isBlank()) return null
return NProfile(hex, relay)
}
private fun nevent(bytes: ByteArray): NEvent? {
if (bytes.isEmpty()) return null
val tlv = Tlv.parse(bytes)
val hex = tlv.firstAsHex(TlvTypes.SPECIAL) ?: return null
val relay = tlv.asStringList(TlvTypes.RELAY) ?: emptyList()
val author = tlv.firstAsHex(TlvTypes.AUTHOR)
val kind = tlv.firstAsInt(TlvTypes.KIND.id)
if (hex.isBlank()) return null
return NEvent(hex, relay, author, kind)
}
private fun nrelay(bytes: ByteArray): NRelay? {
if (bytes.isEmpty()) return null
val relayUrl = Tlv.parse(bytes).asStringList(TlvTypes.SPECIAL.id) ?: return null
return NRelay(relayUrl)
}
private fun naddr(bytes: ByteArray): NAddress? {
if (bytes.isEmpty()) return null
val tlv = Tlv.parse(bytes)
val d = tlv.firstAsString(TlvTypes.SPECIAL.id) ?: ""
val relay = tlv.asStringList(TlvTypes.RELAY.id) ?: emptyList()
val author = tlv.firstAsHex(TlvTypes.AUTHOR.id) ?: return null
val kind = tlv.firstAsInt(TlvTypes.KIND.id) ?: return null
return NAddress("$kind:$author:$d", relay, author, kind)
}
fun createNEvent(
idHex: String,
author: String?,
kind: Int?,
relay: String?,
): String =
TlvBuilder()
.apply {
addHex(TlvTypes.SPECIAL, idHex)
addStringIfNotNull(TlvTypes.RELAY, relay)
addHexIfNotNull(TlvTypes.AUTHOR, author)
addIntIfNotNull(TlvTypes.KIND, kind)
}.build()
.toNEvent()
@Deprecated("Use nevent instead")
fun createNote(eventId: HexKey): String = eventId.hexToByteArray().toNote()
fun createNPub(authorPubKeyHex: HexKey): String = authorPubKeyHex.hexToByteArray().toNpub()
fun createNProfile(
authorPubKeyHex: String,
relay: List<String>,
): String =
TlvBuilder()
.apply {
addHex(TlvTypes.SPECIAL, authorPubKeyHex)
relay.forEach {
addStringIfNotNull(TlvTypes.RELAY, it)
}
}.build()
.toNProfile()
fun createNEmbed(event: Event): String = gzip(event.toJson()).toNEmbed()
fun gzip(content: String): ByteArray {
val bos = ByteArrayOutputStream()
GZIPOutputStream(bos).bufferedWriter(Charsets.UTF_8).use { it.write(content) }
val array = bos.toByteArray()
return array
}
fun ungzip(content: ByteArray): String = GZIPInputStream(content.inputStream()).bufferedReader(Charsets.UTF_8).use { it.readText() }
}
fun ByteArray.toNsec() = Bech32.encodeBytes(hrp = "nsec", this, Bech32.Encoding.Bech32)
fun ByteArray.toNpub() = Bech32.encodeBytes(hrp = "npub", this, Bech32.Encoding.Bech32)
@Deprecated("Prefer nevent1 instead")
fun ByteArray.toNote() = Bech32.encodeBytes(hrp = "note", this, Bech32.Encoding.Bech32)
fun ByteArray.toNEvent() = Bech32.encodeBytes(hrp = "nevent", this, Bech32.Encoding.Bech32)
fun ByteArray.toNProfile() = Bech32.encodeBytes(hrp = "nprofile", this, Bech32.Encoding.Bech32)
fun ByteArray.toNAddress() = Bech32.encodeBytes(hrp = "naddr", this, Bech32.Encoding.Bech32)
fun ByteArray.toLnUrl() = Bech32.encodeBytes(hrp = "lnurl", this, Bech32.Encoding.Bech32)
fun ByteArray.toNEmbed() = Bech32.encodeBytes(hrp = "nembed", this, Bech32.Encoding.Bech32)
fun decodePublicKey(key: String): ByteArray =
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
is Nip19Bech32.NSec -> KeyPair(privKey = key.bechToBytes()).pubKey
is Nip19Bech32.NPub -> parsed.hex.hexToByteArray()
is Nip19Bech32.NProfile -> parsed.hex.hexToByteArray()
else -> Hex.decode(key) // crashes on purpose
}
fun decodePrivateKeyAsHexOrNull(key: String): HexKey? =
try {
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
is Nip19Bech32.NSec -> parsed.hex
is Nip19Bech32.NPub -> null
is Nip19Bech32.NProfile -> null
is Nip19Bech32.Note -> null
is Nip19Bech32.NEvent -> null
is Nip19Bech32.NEmbed -> null
is Nip19Bech32.NRelay -> null
is Nip19Bech32.NAddress -> null
else -> Hex.decode(key).toHexKey()
}
} catch (e: Exception) {
if (e is CancellationException) throw e
null
}
fun decodePublicKeyAsHexOrNull(key: String): HexKey? =
try {
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
is Nip19Bech32.NSec -> KeyPair(privKey = key.bechToBytes()).pubKey.toHexKey()
is Nip19Bech32.NPub -> parsed.hex
is Nip19Bech32.NProfile -> parsed.hex
is Nip19Bech32.Note -> null
is Nip19Bech32.NEvent -> null
is Nip19Bech32.NEmbed -> null
is Nip19Bech32.NRelay -> null
is Nip19Bech32.NAddress -> null
else -> Hex.decode(key).toHexKey()
}
} catch (e: Exception) {
if (e is CancellationException) throw e
null
}
fun decodeEventIdAsHexOrNull(key: String): HexKey? =
try {
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
is Nip19Bech32.NSec -> null
is Nip19Bech32.NPub -> null
is Nip19Bech32.NProfile -> null
is Nip19Bech32.Note -> parsed.hex
is Nip19Bech32.NEvent -> parsed.hex
is Nip19Bech32.NAddress -> parsed.atag
is Nip19Bech32.NEmbed -> null
is Nip19Bech32.NRelay -> null
else -> Hex.decode(key).toHexKey()
}
} catch (e: Exception) {
if (e is CancellationException) throw e
null
}
fun TlvBuilder.addString(
type: Nip19Bech32.TlvTypes,
string: String,
) = addString(type.id, string)
fun TlvBuilder.addHex(
type: Nip19Bech32.TlvTypes,
key: HexKey,
) = addHex(type.id, key)
fun TlvBuilder.addInt(
type: Nip19Bech32.TlvTypes,
data: Int,
) = addInt(type.id, data)
fun TlvBuilder.addStringIfNotNull(
type: Nip19Bech32.TlvTypes,
data: String?,
) = addStringIfNotNull(type.id, data)
fun TlvBuilder.addHexIfNotNull(
type: Nip19Bech32.TlvTypes,
data: HexKey?,
) = addHexIfNotNull(type.id, data)
fun TlvBuilder.addIntIfNotNull(
type: Nip19Bech32.TlvTypes,
data: Int?,
) = addIntIfNotNull(type.id, data)
fun Tlv.firstAsInt(type: Nip19Bech32.TlvTypes) = firstAsInt(type.id)
fun Tlv.firstAsHex(type: Nip19Bech32.TlvTypes) = firstAsHex(type.id)
fun Tlv.firstAsString(type: Nip19Bech32.TlvTypes) = firstAsString(type.id)
fun Tlv.asStringList(type: Nip19Bech32.TlvTypes) = asStringList(type.id)
@@ -1,559 +0,0 @@
/**
* Copyright (c) 2024 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.events
import android.util.Log
import androidx.compose.runtime.Immutable
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.nip01.EventHasher
import com.vitorpamplona.quartz.crypto.nip01.EventHasher.Companion.hashId
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.Nip19Bech32
import com.vitorpamplona.quartz.encoders.PoWRank
import com.vitorpamplona.quartz.events.nip46.BunkerMessage
import com.vitorpamplona.quartz.events.nip46.BunkerRequest
import com.vitorpamplona.quartz.events.nip46.BunkerResponse
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
import com.vitorpamplona.quartz.utils.remove
import java.math.BigDecimal
@Immutable
open class Event(
val id: HexKey,
@JsonProperty("pubkey") val pubKey: HexKey,
@JsonProperty("created_at") val createdAt: Long,
val kind: Int,
val tags: Array<Array<String>>,
val content: String,
val sig: HexKey,
) : EventInterface {
override fun isContentEncoded() = false
override fun countMemory(): Long =
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
12L + // createdAt + kind
id.bytesUsedInMemory() +
pubKey.bytesUsedInMemory() +
tags.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } +
content.bytesUsedInMemory() +
sig.bytesUsedInMemory()
override fun id(): HexKey = id
override fun pubKey(): HexKey = pubKey
override fun createdAt(): Long = createdAt
override fun kind(): Int = kind
override fun tags(): Array<Array<String>> = tags
override fun content(): String = content
override fun sig(): HexKey = sig
override fun toJson(): String = mapper.writeValueAsString(toJsonObject())
override fun hasAnyTaggedUser() = hasTagWithContent("p")
override fun hasTagWithContent(tagName: String) = tags.any { it.size > 1 && it[0] == tagName }
override fun forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) = forEachTagged("e", onEach)
override fun forEachHashTag(onEach: (eventId: HexKey) -> Unit) = forEachTagged("t", onEach)
private fun forEachTagged(
tagName: String,
onEach: (eventId: HexKey) -> Unit,
) = tags.forEach {
if (it.size > 1 && it[0] == tagName) {
onEach(it[1])
}
}
override fun anyHashTag(onEach: (str: String) -> Boolean) = anyTagged("t", onEach)
private fun anyTagged(
tagName: String,
onEach: (str: String) -> Boolean,
) = tags.any {
if (it.size > 1 && it[0] == tagName) {
onEach(it[1])
} else {
false
}
}
override fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R) = mapTagged("e", map)
override fun <R> mapTaggedAddress(map: (address: String) -> R) = mapTagged("a", map)
private fun <R> mapTagged(
tagName: String,
map: (eventId: HexKey) -> R,
) = tags.mapNotNull {
if (it.size > 1 && it[0] == tagName) {
map(it[1])
} else {
null
}
}
override fun taggedUsers() = tags.filter { it.size > 1 && it[0] == "p" }.map { it[1] }
override fun taggedEvents() = tags.filter { it.size > 1 && it[0] == "e" }.map { it[1] }
override fun taggedUrls() = tags.filter { it.size > 1 && it[0] == "r" }.map { it[1] }
override fun firstTag(key: String) = tags.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1] }
override fun firstTagFor(vararg key: String) = tags.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] }
override fun firstTaggedUser() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.let { it[1] }
override fun firstTaggedEvent() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.let { it[1] }
override fun firstTaggedUrl() = tags.firstOrNull { it.size > 1 && it[0] == "r" }?.let { it[1] }
override fun firstTaggedK() = tags.firstOrNull { it.size > 1 && it[0] == "k" }?.let { it[1].toIntOrNull() }
override fun firstTaggedAddress() =
tags
.firstOrNull { it.size > 1 && it[0] == "a" }
?.let {
val aTagValue = it[1]
val relay = it.getOrNull(2)
ATag.parse(aTagValue, relay)
}
override fun taggedEmojis() = tags.filter { it.size > 2 && it[0] == "emoji" }.mapNotNull { EmojiUrl.parse(it) }
override fun isSensitive() =
tags.any {
(it.size > 0 && it[0] == "content-warning") ||
(it.size > 1 && it[0] == "t" && (it[1].equals("nsfw", true) || it[1].equals("nude", true)))
}
override fun subject() = tags.firstOrNull { it.size > 1 && it[0] == "subject" }?.get(1)
override fun zapraiserAmount() = tags.firstOrNull { (it.size > 1 && it[0] == "zapraiser") }?.get(1)?.toLongOrNull()
override fun hasZapSplitSetup() = tags.any { it.size > 1 && it[0] == "zap" }
override fun zapSplitSetup(): List<ZapSplitSetup> =
tags
.filter { it.size > 1 && it[0] == "zap" }
.mapNotNull {
val isLnAddress = it[0].contains("@") || it[0].startsWith("LNURL", true)
val weight = if (isLnAddress) 1.0 else (it.getOrNull(3)?.toDoubleOrNull() ?: 0.0)
if (weight > 0) {
ZapSplitSetup(
it[1],
it.getOrNull(2),
weight,
isLnAddress,
)
} else {
null
}
}
override fun taggedAddresses() =
tags
.filter { it.size > 1 && it[0] == "a" }
.mapNotNull {
val aTagValue = it[1]
val relay = it.getOrNull(2)
ATag.parse(aTagValue, relay)
}
override fun hasHashtags() = tags.any { it.size > 1 && it[0] == "t" }
override fun hasGeohashes() = tags.any { it.size > 1 && it[0] == "g" }
override fun hashtags() = tags.filter { it.size > 1 && it[0] == "t" }.map { it[1] }
override fun geohashes() = tags.filter { it.size > 1 && it[0] == "g" }.map { it[1] }
override fun matchTag1With(text: String) = tags.any { it.size > 1 && it[1].contains(text, true) }
override fun isTagged(
key: String,
tag: String,
) = tags.any { it.size > 1 && it[0] == key && it[1] == tag }
override fun isAnyTagged(
key: String,
tags: Set<String>,
) = this.tags.any { it.size > 1 && it[0] == key && it[1] in tags }
override fun isTaggedWord(word: String) = isTagged("word", word)
override fun isTaggedUser(idHex: String) = isTagged("p", idHex)
override fun isTaggedUsers(idHexes: Set<String>) = isAnyTagged("p", idHexes)
override fun isTaggedEvent(idHex: String) = isTagged("e", idHex)
override fun isTaggedAddressableNote(idHex: String) = isTagged("a", idHex)
override fun isTaggedAddressableNotes(idHexes: Set<String>) = isAnyTagged("a", idHexes)
override fun isTaggedHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
override fun isTaggedGeoHash(hashtag: String) = tags.any { it.size > 1 && it[0] == "g" && it[1].startsWith(hashtag, true) }
override fun isTaggedHashes(hashtags: Set<String>) = tags.any { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }
override fun isTaggedGeoHashes(hashtags: Set<String>) = tags.any { it.size > 1 && it[0] == "g" && it[1].lowercase() in hashtags }
override fun firstIsTaggedHashes(hashtags: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }?.getOrNull(1)
override fun firstIsTaggedAddressableNote(addressableNotes: Set<String>) = tags.firstOrNull { it.size > 1 && it[0] == "a" && it[1] in addressableNotes }?.getOrNull(1)
override fun isTaggedAddressableKind(kind: Int): Boolean {
val kindStr = kind.toString()
return tags.any { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }
}
override fun expiration() =
try {
tags.firstOrNull { it.size > 1 && it[0] == "expiration" }?.get(1)?.toLongOrNull()
} catch (_: Exception) {
null
}
override fun isExpired() = (expiration() ?: Long.MAX_VALUE) < TimeUtils.now()
override fun isExpirationBefore(time: Long) = (expiration() ?: Long.MAX_VALUE) < time
override fun getTagOfAddressableKind(kind: Int): ATag? {
val kindStr = kind.toString()
val aTag =
tags.firstOrNull { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }?.getOrNull(1)
?: return null
return ATag.parse(aTag, null)
}
override fun getPoWRank(): Int {
val commitedPoW = tags.firstOrNull { it.size > 2 && it[0] == "nonce" }?.get(2)?.toIntOrNull()
return PoWRank.getCommited(id, commitedPoW)
}
override fun getGeoHash(): String? =
tags
.filter { it.size > 1 && it[0] == "g" }
.maxByOrNull {
it[1].length
}?.get(1)
?.ifBlank { null }
override fun getReward(): BigDecimal? =
try {
tags.firstOrNull { it.size > 1 && it[0] == "reward" }?.get(1)?.let { BigDecimal(it) }
} catch (e: Exception) {
null
}
fun filterTags(startsWith: Array<String>) = tags.remove(startsWith)
open fun toNIP19(): String =
if (this is AddressableEvent) {
ATag(kind, pubKey, dTag(), null).toNAddr()
} else {
Nip19Bech32.createNEvent(id, pubKey, kind, null)
}
fun toNostrUri(): String = "nostr:${toNIP19()}"
fun hasCorrectIDHash(): Boolean {
if (id.isEmpty()) return false
return id.equals(generateId())
}
fun hasVerifiedSignature(): Boolean {
if (id.isEmpty() || sig.isEmpty()) return false
return CryptoUtils.verifySignature(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))
}
/** Checks if the ID is correct and then if the pubKey's secret key signed the event. */
override fun checkSignature() {
if (!hasCorrectIDHash()) {
throw Exception(
"""
|Unexpected ID.
| Event: ${toJson()}
| Actual ID: $id
| Generated: ${generateId()}
""".trimIndent(),
)
}
if (!hasVerifiedSignature()) {
throw Exception("""Bad signature!""")
}
}
override fun hasValidSignature(): Boolean =
try {
hasCorrectIDHash() && hasVerifiedSignature()
} catch (e: Exception) {
Log.w("Event", "Event $id does not have a valid signature: ${toJson()}", e)
false
}
fun generateId(): String = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
private class EventDeserializer : StdDeserializer<Event>(Event::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): Event = fromJson(jp.codec.readTree<JsonNode>(jp))
}
private class GossipDeserializer : StdDeserializer<Gossip>(Gossip::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): Gossip {
val jsonObject: JsonNode = jp.codec.readTree(jp)
return Gossip(
id = jsonObject.get("id")?.asText()?.intern(),
pubKey = jsonObject.get("pubkey")?.asText()?.intern(),
createdAt = jsonObject.get("created_at")?.asLong(),
kind = jsonObject.get("kind")?.asInt(),
tags =
jsonObject.get("tags").toTypedArray {
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
},
content = jsonObject.get("content")?.asText(),
)
}
}
private class EventSerializer : StdSerializer<Event>(Event::class.java) {
override fun serialize(
event: Event,
gen: JsonGenerator,
provider: SerializerProvider,
) {
gen.writeStartObject()
gen.writeStringField("id", event.id)
gen.writeStringField("pubkey", event.pubKey)
gen.writeNumberField("created_at", event.createdAt)
gen.writeNumberField("kind", event.kind)
gen.writeArrayFieldStart("tags")
event.tags.forEach { tag -> gen.writeArray(tag, 0, tag.size) }
gen.writeEndArray()
gen.writeStringField("content", event.content)
gen.writeStringField("sig", event.sig)
gen.writeEndObject()
}
}
private class GossipSerializer : StdSerializer<Gossip>(Gossip::class.java) {
override fun serialize(
event: Gossip,
gen: JsonGenerator,
provider: SerializerProvider,
) {
gen.writeStartObject()
event.id?.let { gen.writeStringField("id", it) }
event.pubKey?.let { gen.writeStringField("pubkey", it) }
event.createdAt?.let { gen.writeNumberField("created_at", it) }
event.kind?.let { gen.writeNumberField("kind", it) }
event.tags?.let {
gen.writeArrayFieldStart("tags")
event.tags.forEach { tag -> gen.writeArray(tag, 0, tag.size) }
gen.writeEndArray()
}
event.content?.let { gen.writeStringField("content", it) }
gen.writeEndObject()
}
}
fun toJsonObject(): JsonNode {
val factory = mapper.nodeFactory
return factory.objectNode().apply {
put("id", id)
put("pubkey", pubKey)
put("created_at", createdAt)
put("kind", kind)
replace(
"tags",
factory.arrayNode(tags.size).apply {
tags.forEach { tag ->
add(
factory.arrayNode(tag.size).apply { tag.forEach { add(it) } },
)
}
},
)
put("content", content)
put("sig", sig)
}
}
companion object {
val mapper =
jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
.registerModule(
SimpleModule()
.addSerializer(Event::class.java, EventSerializer())
.addDeserializer(Event::class.java, EventDeserializer())
.addSerializer(Gossip::class.java, GossipSerializer())
.addDeserializer(Gossip::class.java, GossipDeserializer())
.addDeserializer(Response::class.java, ResponseDeserializer())
.addDeserializer(Request::class.java, RequestDeserializer())
.addDeserializer(BunkerMessage::class.java, BunkerMessage.BunkerMessageDeserializer())
.addSerializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestSerializer())
.addDeserializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestDeserializer())
.addSerializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseSerializer())
.addDeserializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseDeserializer()),
)
fun fromJson(jsonObject: JsonNode): Event =
EventFactory.create(
id = jsonObject.get("id").asText().intern(),
pubKey = jsonObject.get("pubkey").asText().intern(),
createdAt = jsonObject.get("created_at").asLong(),
kind = jsonObject.get("kind").asInt(),
tags =
jsonObject.get("tags").toTypedArray {
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
},
content = jsonObject.get("content").asText(),
sig = jsonObject.get("sig").asText(),
)
inline fun <reified R> JsonNode.toTypedArray(transform: (JsonNode) -> R): Array<R> = Array(size()) { transform(get(it)) }
fun fromJson(json: String): Event = mapper.readValue(json, Event::class.java)
fun toJson(event: Event): String = mapper.writeValueAsString(event)
fun generateId(
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
): HexKey = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
fun generateIdBytes(
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
): ByteArray = EventHasher.hashIdBytes(pubKey, createdAt, kind, tags, content)
fun create(
signer: NostrSigner,
kind: Int,
tags: Array<Array<String>> = emptyArray(),
content: String = "",
createdAt: Long = TimeUtils.now(),
onReady: (Event) -> Unit,
) = signer.sign(createdAt, kind, tags, content, onReady)
}
}
@Immutable
open class WrappedEvent(
id: HexKey,
@JsonProperty("pubkey") pubKey: HexKey,
@JsonProperty("created_at") createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
@Transient var host: HostStub? = null // host event to broadcast when needed
}
class HostStub(
val id: HexKey,
val pubKey: HexKey,
val kind: Int,
)
@Immutable
interface AddressableEvent {
fun dTag(): String
fun address(relayHint: String? = null): ATag
fun addressTag(): String
}
@Immutable
open class BaseAddressableEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, kind, tags, content, sig),
AddressableEvent {
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
override fun address(relayHint: String?) = ATag(kind, pubKey, dTag(), relayHint)
/**
* Creates the tag in a memory effecient way (without creating the ATag class
*/
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
}
data class ZapSplitSetup(
val lnAddressOrPubKeyHex: String,
val relay: String?,
val weight: Double,
val isLnAddress: Boolean,
)
interface RootScope
@@ -1,169 +0,0 @@
/**
* Copyright (c) 2024 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.events
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.nip46.NostrConnectEvent
class EventFactory {
companion object {
val factories: MutableMap<Int, (HexKey, HexKey, Long, Array<Array<String>>, String, HexKey) -> Event> = mutableMapOf()
fun create(
id: String,
pubKey: String,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: String,
) = when (kind) {
AdvertisedRelayListEvent.KIND -> AdvertisedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
AppDefinitionEvent.KIND -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
AppRecommendationEvent.KIND -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
AppSpecificDataEvent.KIND -> AppSpecificDataEvent(id, pubKey, createdAt, tags, content, sig)
AudioHeaderEvent.KIND -> AudioHeaderEvent(id, pubKey, createdAt, tags, content, sig)
AudioTrackEvent.KIND -> AudioTrackEvent(id, pubKey, createdAt, tags, content, sig)
BadgeAwardEvent.KIND -> BadgeAwardEvent(id, pubKey, createdAt, tags, content, sig)
BadgeDefinitionEvent.KIND -> BadgeDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
BadgeProfilesEvent.KIND -> BadgeProfilesEvent(id, pubKey, createdAt, tags, content, sig)
BlossomServersEvent.KIND -> BlossomServersEvent(id, pubKey, createdAt, tags, content, sig)
BlossomAuthorizationEvent.KIND -> BlossomAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
BookmarkListEvent.KIND -> BookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
CalendarDateSlotEvent.KIND -> CalendarDateSlotEvent(id, pubKey, createdAt, tags, content, sig)
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
ChannelCreateEvent.KIND -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
ChannelHideMessageEvent.KIND -> ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
ChannelListEvent.KIND -> ChannelListEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMessageEvent.KIND -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMetadataEvent.KIND -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMuteUserEvent.KIND -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
ChatMessageEncryptedFileHeaderEvent.KIND -> {
if (id.isBlank()) {
ChatMessageEncryptedFileHeaderEvent(
Event.generateId(pubKey, createdAt, kind, tags, content),
pubKey,
createdAt,
tags,
content,
sig,
)
} else {
ChatMessageEncryptedFileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
}
}
ChatMessageEvent.KIND -> {
if (id.isBlank()) {
ChatMessageEvent(
Event.generateId(pubKey, createdAt, kind, tags, content),
pubKey,
createdAt,
tags,
content,
sig,
)
} else {
ChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
}
}
ChatMessageRelayListEvent.KIND -> ChatMessageRelayListEvent(id, pubKey, createdAt, tags, content, sig)
ClassifiedsEvent.KIND -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
CommentEvent.KIND -> CommentEvent(id, pubKey, createdAt, tags, content, sig)
CommunityDefinitionEvent.KIND -> CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
CommunityListEvent.KIND -> CommunityListEvent(id, pubKey, createdAt, tags, content, sig)
CommunityPostApprovalEvent.KIND -> CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
DeletionEvent.KIND -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
DraftEvent.KIND -> DraftEvent(id, pubKey, createdAt, tags, content, sig)
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
GitReplyEvent.KIND -> GitReplyEvent(id, pubKey, createdAt, tags, content, sig)
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
InteractiveStoryPrologueEvent.KIND -> InteractiveStoryPrologueEvent(id, pubKey, createdAt, tags, content, sig)
InteractiveStorySceneEvent.KIND -> InteractiveStorySceneEvent(id, pubKey, createdAt, tags, content, sig)
InteractiveStoryReadingStateEvent.KIND -> InteractiveStoryReadingStateEvent(id, pubKey, createdAt, tags, content, sig)
LiveActivitiesChatMessageEvent.KIND -> LiveActivitiesChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
LiveActivitiesEvent.KIND -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
NostrConnectEvent.KIND -> NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
NIP90UserDiscoveryRequestEvent.KIND -> NIP90UserDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
NIP90UserDiscoveryResponseEvent.KIND -> NIP90UserDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
PictureEvent.KIND -> PictureEvent(id, pubKey, createdAt, tags, content, sig)
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
PollNoteEvent.KIND -> PollNoteEvent(id, pubKey, createdAt, tags, content, sig)
PrivateDmEvent.KIND -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
PrivateOutboxRelayListEvent.KIND -> PrivateOutboxRelayListEvent(id, pubKey, createdAt, tags, content, sig)
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
RecommendRelayEvent.KIND -> RecommendRelayEvent(id, pubKey, createdAt, tags, content, sig)
RelationshipStatusEvent.KIND -> RelationshipStatusEvent(id, pubKey, createdAt, tags, content, sig)
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
SealedGossipEvent.KIND -> SealedGossipEvent(id, pubKey, createdAt, tags, content, sig)
SearchRelayListEvent.KIND -> SearchRelayListEvent(id, pubKey, createdAt, tags, content, sig)
StatusEvent.KIND -> StatusEvent(id, pubKey, createdAt, tags, content, sig)
TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
TextNoteModificationEvent.KIND -> TextNoteModificationEvent(id, pubKey, createdAt, tags, content, sig)
TorrentEvent.KIND -> TorrentEvent(id, pubKey, createdAt, tags, content, sig)
TorrentCommentEvent.KIND -> TorrentCommentEvent(id, pubKey, createdAt, tags, content, sig)
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
VideoViewEvent.KIND -> VideoViewEvent(id, pubKey, createdAt, tags, content, sig)
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
else -> {
factories[kind]?.let {
return it(id, pubKey, createdAt, tags, content, sig)
}
Event(id, pubKey, createdAt, kind, tags, content, sig)
}
}
}
}
@@ -1,161 +0,0 @@
/**
* Copyright (c) 2024 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.events
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import java.math.BigDecimal
@Immutable
interface EventInterface {
fun isContentEncoded(): Boolean
fun countMemory(): Long
fun id(): HexKey
fun pubKey(): HexKey
fun createdAt(): Long
fun kind(): Int
fun tags(): Array<Array<String>>
fun content(): String
fun sig(): HexKey
fun toJson(): String
fun checkSignature()
fun hasValidSignature(): Boolean
fun isTagged(
key: String,
tag: String,
): Boolean
fun isAnyTagged(
key: String,
tags: Set<String>,
): Boolean
fun isTaggedWord(word: String): Boolean
fun isTaggedUser(idHex: String): Boolean
fun isTaggedUsers(idHexes: Set<String>): Boolean
fun isTaggedEvent(idHex: String): Boolean
fun isTaggedAddressableNote(idHex: String): Boolean
fun isTaggedAddressableNotes(idHexes: Set<String>): Boolean
fun isTaggedHash(hashtag: String): Boolean
fun isTaggedGeoHash(hashtag: String): Boolean
fun isTaggedHashes(hashtags: Set<String>): Boolean
fun isTaggedGeoHashes(hashtags: Set<String>): Boolean
fun firstIsTaggedHashes(hashtags: Set<String>): String?
fun firstIsTaggedAddressableNote(addressableNotes: Set<String>): String?
fun isTaggedAddressableKind(kind: Int): Boolean
fun getTagOfAddressableKind(kind: Int): ATag?
fun expiration(): Long?
fun hasHashtags(): Boolean
fun hasGeohashes(): Boolean
fun hashtags(): List<String>
fun geohashes(): List<String>
fun getReward(): BigDecimal?
fun getPoWRank(): Int
fun getGeoHash(): String?
fun zapSplitSetup(): List<ZapSplitSetup>
fun isSensitive(): Boolean
fun subject(): String?
fun zapraiserAmount(): Long?
fun hasAnyTaggedUser(): Boolean
fun hasTagWithContent(tagName: String): Boolean
fun forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit)
fun forEachHashTag(onEach: (eventId: HexKey) -> Unit)
fun anyHashTag(onEach: (str: String) -> Boolean): Boolean
fun <R> mapTaggedEvent(map: (eventId: HexKey) -> R): List<R>
fun <R> mapTaggedAddress(map: (address: String) -> R): List<R>
fun taggedAddresses(): List<ATag>
fun taggedUsers(): List<HexKey>
fun taggedEvents(): List<HexKey>
fun taggedUrls(): List<String>
fun firstTag(key: String): String?
fun firstTagFor(vararg key: String): String?
fun firstTaggedAddress(): ATag?
fun firstTaggedUser(): HexKey?
fun firstTaggedEvent(): HexKey?
fun firstTaggedUrl(): String?
fun firstTaggedK(): Int?
fun taggedEmojis(): List<EmojiUrl>
fun matchTag1With(text: String): Boolean
fun isExpired(): Boolean
fun isExpirationBefore(time: Long): Boolean
fun hasZapSplitSetup(): Boolean
}
@@ -1,190 +0,0 @@
/**
* Copyright (c) 2024 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.events
import android.util.Log
import androidx.compose.runtime.Immutable
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class LnZapPaymentResponseEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
// Once one of an app user decrypts the payment, all users else can see it.
@Transient private var response: Response? = null
override fun countMemory(): Long = super.countMemory() + pointerSizeInBytes + (response?.countMemory() ?: 0)
fun requestAuthor() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.get(1)
fun requestId() = tags.firstOrNull { it.size > 1 && it[0] == "e" }?.get(1)
fun talkingWith(oneSideHex: String): HexKey = if (pubKey == oneSideHex) requestAuthor() ?: pubKey else pubKey
private fun plainContent(
signer: NostrSigner,
onReady: (String) -> Unit,
) {
try {
signer.decrypt(content, talkingWith(signer.pubKey)) { content -> onReady(content) }
} catch (e: Exception) {
Log.w("PrivateDM", "Error decrypting the message ${e.message}")
}
}
fun response(
signer: NostrSigner,
onReady: (Response) -> Unit,
) {
response?.let {
onReady(it)
return
}
try {
if (content.isNotEmpty()) {
plainContent(signer) {
mapper.readValue(it, Response::class.java)?.let {
response = it
onReady(it)
}
}
}
} catch (e: Exception) {
Log.w("LnZapPaymentResponseEvent", "Can't parse content as a payment response: $content", e)
}
}
companion object {
const val KIND = 23195
const val ALT = "Zap payment response"
}
}
// RESPONSE OBJECTS
abstract class Response(
@JsonProperty("result_type") val resultType: String,
) {
abstract fun countMemory(): Long
}
// PayInvoice Call
class PayInvoiceSuccessResponse(
val result: PayInvoiceResultParams? = null,
) : Response("pay_invoice") {
class PayInvoiceResultParams(
val preimage: String,
) {
fun countMemory(): Long = pointerSizeInBytes + preimage.bytesUsedInMemory()
}
override fun countMemory(): Long = pointerSizeInBytes + (result?.countMemory() ?: 0)
}
class PayInvoiceErrorResponse(
val error: PayInvoiceErrorParams? = null,
) : Response("pay_invoice") {
class PayInvoiceErrorParams(
val code: ErrorType?,
val message: String?,
) {
fun countMemory(): Long = pointerSizeInBytes + pointerSizeInBytes + (message?.bytesUsedInMemory() ?: 0)
}
override fun countMemory(): Long = pointerSizeInBytes + (error?.countMemory() ?: 0)
enum class ErrorType {
@JsonProperty(value = "RATE_LIMITED")
RATE_LIMITED,
// The client is sending commands too fast. It should retry in a few seconds.
@JsonProperty(value = "NOT_IMPLEMENTED")
NOT_IMPLEMENTED,
// The command is not known or is intentionally not implemented.
@JsonProperty(value = "INSUFFICIENT_BALANCE")
INSUFFICIENT_BALANCE,
// The wallet does not have enough funds to cover a fee reserve or the payment amount.
@JsonProperty(value = "QUOTA_EXCEEDED")
QUOTA_EXCEEDED,
// The wallet has exceeded its spending quota.
@JsonProperty(value = "RESTRICTED")
RESTRICTED,
// This public key is not allowed to do this operation.
@JsonProperty(value = "UNAUTHORIZED")
UNAUTHORIZED,
// This public key has no wallet connected.
@JsonProperty(value = "INTERNAL")
INTERNAL,
// An internal error.
@JsonProperty(value = "OTHER")
OTHER, // Other error.
}
}
class ResponseDeserializer : StdDeserializer<Response>(Response::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): Response? {
val jsonObject: JsonNode = jp.codec.readTree(jp)
val resultType = jsonObject.get("result_type")?.asText()
if (resultType == "pay_invoice") {
val result = jsonObject.get("result")
val error = jsonObject.get("error")
if (result != null) {
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
}
if (error != null) {
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
}
} else {
// tries to guess
if (jsonObject.get("result")?.get("preimage") != null) {
return jp.codec.treeToValue(jsonObject, PayInvoiceSuccessResponse::class.java)
}
if (jsonObject.get("error")?.get("code") != null) {
return jp.codec.treeToValue(jsonObject, PayInvoiceErrorResponse::class.java)
}
}
return null
}
}
@@ -1,293 +0,0 @@
/**
* Copyright (c) 2024 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.events
import android.util.Log
import androidx.compose.runtime.Stable
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.signers.NostrSignerSync
import com.vitorpamplona.quartz.utils.TimeUtils
import java.io.ByteArrayInputStream
import java.io.StringWriter
@Stable
abstract class IdentityClaim(
val identity: String,
val proof: String,
) {
abstract fun toProofUrl(): String
abstract fun platform(): String
fun platformIdentity() = "${platform()}:$identity"
companion object {
fun create(
platformIdentity: String,
proof: String,
): IdentityClaim {
val (platform, identity) = platformIdentity.split(':')
return when (platform.lowercase()) {
GitHubIdentity.platform -> GitHubIdentity(identity, proof)
TwitterIdentity.platform -> TwitterIdentity(identity, proof)
TelegramIdentity.platform -> TelegramIdentity(identity, proof)
MastodonIdentity.platform -> MastodonIdentity(identity, proof)
else -> throw IllegalArgumentException("Platform $platform not supported")
}
}
}
}
class GitHubIdentity(
identity: String,
proof: String,
) : IdentityClaim(identity, proof) {
override fun toProofUrl() = "https://gist.github.com/$identity/$proof"
override fun platform() = platform
companion object {
val platform = "github"
fun parseProofUrl(proofUrl: String): GitHubIdentity? {
return try {
if (proofUrl.isBlank()) return null
val path = proofUrl.removePrefix("https://gist.github.com/").split("?")[0].split("/")
GitHubIdentity(path[0], path[1])
} catch (e: Exception) {
null
}
}
}
}
class TwitterIdentity(
identity: String,
proof: String,
) : IdentityClaim(identity, proof) {
override fun toProofUrl() = "https://x.com/$identity/status/$proof"
override fun platform() = platform
companion object {
val platform = "twitter"
fun parseProofUrl(proofUrl: String): TwitterIdentity? {
return try {
if (proofUrl.isBlank()) return null
val path = proofUrl.removePrefix("https://x.com/").split("?")[0].split("/")
TwitterIdentity(path[0], path[2])
} catch (e: Exception) {
null
}
}
}
}
class TelegramIdentity(
identity: String,
proof: String,
) : IdentityClaim(identity, proof) {
override fun toProofUrl() = "https://t.me/$proof"
override fun platform() = platform
companion object {
val platform = "telegram"
}
}
class MastodonIdentity(
identity: String,
proof: String,
) : IdentityClaim(identity, proof) {
override fun toProofUrl() = "https://$identity/$proof"
override fun platform() = platform
companion object {
val platform = "mastodon"
fun parseProofUrl(proofUrl: String): MastodonIdentity? {
return try {
if (proofUrl.isBlank()) return null
val path = proofUrl.removePrefix("https://").split("?")[0].split("/")
return MastodonIdentity("${path[0]}/${path[1]}", path[2])
} catch (e: Exception) {
null
}
}
}
}
class MetadataEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
fun contactMetaData() =
try {
mapper.readValue(content, UserMetadata::class.java)
} catch (e: Exception) {
// e.printStackTrace()
Log.w("MetadataEvent", "Content Parse Error: ${toNostrUri()} ${e.localizedMessage}")
null
}
fun identityClaims() =
tags
.filter { it.firstOrNull() == "i" }
.mapNotNull {
try {
IdentityClaim.create(it[1], it[2])
} catch (e: Exception) {
Log.e("MetadataEvent", "Can't parse identity [${it.joinToString { "," }}]", e)
null
}
}
companion object {
const val KIND = 0
fun newUser(
name: String?,
signer: NostrSignerSync,
createdAt: Long = TimeUtils.now(),
): MetadataEvent? {
// Tries to not delete any existing attribute that we do not work with.
val currentJson = ObjectMapper().createObjectNode()
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
val writer = StringWriter()
ObjectMapper().writeValue(writer, currentJson)
val tags = mutableListOf<Array<String>>()
tags.add(
arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"),
)
return signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString())
}
fun updateFromPast(
latest: MetadataEvent?,
name: String?,
picture: String?,
banner: String?,
website: String?,
about: String?,
nip05: String?,
lnAddress: String?,
lnURL: String?,
pronouns: String?,
twitter: String?,
mastodon: String?,
github: String?,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (MetadataEvent) -> Unit,
) {
// Tries to not delete any existing attribute that we do not work with.
val currentJson =
if (latest != null) {
ObjectMapper()
.readTree(
ByteArrayInputStream(latest.content.toByteArray(Charsets.UTF_8)),
) as ObjectNode
} else {
ObjectMapper().createObjectNode()
}
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
name?.let { addIfNotBlank(currentJson, "display_name", it.trim()) }
picture?.let { addIfNotBlank(currentJson, "picture", it.trim()) }
banner?.let { addIfNotBlank(currentJson, "banner", it.trim()) }
website?.let { addIfNotBlank(currentJson, "website", it.trim()) }
pronouns?.let { addIfNotBlank(currentJson, "pronouns", it.trim()) }
about?.let { addIfNotBlank(currentJson, "about", it.trim()) }
nip05?.let { addIfNotBlank(currentJson, "nip05", it.trim()) }
lnAddress?.let { addIfNotBlank(currentJson, "lud16", it.trim()) }
lnURL?.let { addIfNotBlank(currentJson, "lud06", it.trim()) }
var claims = latest?.identityClaims() ?: emptyList()
if (twitter?.isBlank() == true) {
// delete twitter
claims = claims.filter { it !is TwitterIdentity }
}
if (github?.isBlank() == true) {
// delete github
claims = claims.filter { it !is GitHubIdentity }
}
if (mastodon?.isBlank() == true) {
// delete mastodon
claims = claims.filter { it !is MastodonIdentity }
}
// Updates while keeping other identities intact
val newClaims =
listOfNotNull(
twitter?.let { TwitterIdentity.parseProofUrl(it) },
github?.let { GitHubIdentity.parseProofUrl(it) },
mastodon?.let { MastodonIdentity.parseProofUrl(it) },
) +
claims.filter { it !is TwitterIdentity && it !is GitHubIdentity && it !is MastodonIdentity }
val writer = StringWriter()
ObjectMapper().writeValue(writer, currentJson)
val tags = mutableListOf<Array<String>>()
tags.add(
arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"),
)
newClaims.forEach { tags.add(arrayOf("i", it.platformIdentity(), it.proof)) }
signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString(), onReady)
}
private fun addIfNotBlank(
currentJson: ObjectNode,
key: String,
value: String,
) {
if (value.isBlank() || value == "null") {
currentJson.remove(key)
} else {
currentJson.put(key, value.trim())
}
}
}
}
@@ -18,12 +18,14 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.events
package com.vitorpamplona.quartz.experimental.audio
import androidx.compose.runtime.Immutable
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -43,7 +45,7 @@ class AudioHeaderEvent(
tags
.firstOrNull { it.size > 1 && it[0] == WAVEFORM }
?.get(1)
?.let { mapper.readValue<List<Int>>(it) }
?.let { EventMapper.mapper.readValue<List<Int>>(it) }
companion object {
const val KIND = 1808
@@ -18,11 +18,12 @@
* 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.events
package com.vitorpamplona.quartz.experimental.audio
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2024 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.experimental.bounties
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.firstMapTagged
import java.math.BigDecimal
fun Event.getReward(): BigDecimal? = tags.firstMapTagged("reward") { runCatching { BigDecimal(it[1]) }.getOrNull() }
@@ -18,14 +18,17 @@
* 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.events
package com.vitorpamplona.quartz.experimental.edits
import android.util.Log
import androidx.compose.runtime.Immutable
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@@ -84,7 +87,7 @@ class PrivateOutboxRelayListEvent(
try {
signer.nip44Decrypt(content, pubKey) {
try {
privateTagsCache = mapper.readValue<Array<Array<String>>>(it)
privateTagsCache = EventMapper.mapper.readValue<Array<Array<String>>>(it)
privateTagsCache?.let { onReady(it) }
} catch (e: Throwable) {
Log.w("PrivateOutboxRelayListEvent", "Error parsing the JSON: ${e.message}. Json `$it` from event `${toNostrUri()}`")
@@ -109,7 +112,7 @@ class PrivateOutboxRelayListEvent(
signer: NostrSigner,
onReady: (String) -> Unit,
) {
val msg = mapper.writeValueAsString(privateTags)
val msg = EventMapper.mapper.writeValueAsString(privateTags)
signer.nip44Encrypt(
msg,
@@ -18,11 +18,13 @@
* 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.events
package com.vitorpamplona.quartz.experimental.edits
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.events.firstTaggedEvent
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,8 +18,9 @@
* 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.encoders
package com.vitorpamplona.quartz.experimental.inlineMetadata
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
import java.net.URI
import java.net.URLDecoder
import java.net.URLEncoder
@@ -18,12 +18,20 @@
* 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.events
package com.vitorpamplona.quartz.experimental.interactiveStories
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.IMetaTag
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
import com.vitorpamplona.quartz.nip01Core.geohash.geohashMipMap
import com.vitorpamplona.quartz.nip10Notes.findHashtags
import com.vitorpamplona.quartz.nip10Notes.findURLs
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrl
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
import com.vitorpamplona.quartz.nip92IMeta.Nip92MediaAttachments
open class InteractiveStoryBaseEvent(
id: HexKey,
@@ -34,11 +42,11 @@ open class InteractiveStoryBaseEvent(
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig) {
fun title() = firstTag("title")
fun title() = tags.firstTagValue("title")
fun summary() = firstTag("summary")
fun summary() = tags.firstTagValue("summary")
fun image() = firstTag("image")
fun image() = tags.firstTagValue("image")
fun options() =
tags
@@ -18,12 +18,14 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.events
package com.vitorpamplona.quartz.experimental.interactiveStories
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.IMetaTag
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
import com.vitorpamplona.quartz.utils.TimeUtils
class InteractiveStoryPrologueEvent(
@@ -18,12 +18,16 @@
* 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.events
package com.vitorpamplona.quartz.experimental.interactiveStories
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.firstTag
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.removeTrailingNullsAndEmptyOthers
@@ -36,23 +40,17 @@ class InteractiveStoryReadingStateEvent(
content: String,
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
fun title() = firstTag("title")
fun title() = tags.firstTagValue("title")
fun summary() = firstTag("summary")
fun summary() = tags.firstTagValue("summary")
fun image() = firstTag("image")
fun image() = tags.firstTagValue("image")
fun status() = firstTag("status")
fun status() = tags.firstTagValue("status")
fun root() =
tags.firstOrNull { it.size > 1 && it[0] == "A" }?.let {
ATag.parse(it[1], it.getOrNull(2))
}
fun root() = tags.firstTag("A")?.let { ATag.parse(it[1], it.getOrNull(2)) }
fun currentScene() =
tags.firstOrNull { it.size > 1 && it[0] == "a" }?.let {
ATag.parse(it[1], it.getOrNull(2))
}
fun currentScene() = tags.firstTag("a")?.let { ATag.parse(it[1], it.getOrNull(2)) }
companion object {
const val KIND = 30298
@@ -18,12 +18,14 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.events
package com.vitorpamplona.quartz.experimental.interactiveStories
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.IMetaTag
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
import com.vitorpamplona.quartz.utils.TimeUtils
class InteractiveStorySceneEvent(
@@ -18,11 +18,12 @@
* 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.events
package com.vitorpamplona.quartz.experimental.medical
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,12 +18,13 @@
* 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.events
package com.vitorpamplona.quartz.experimental.nip95
import android.util.Log
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.Base64
@@ -18,12 +18,14 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.events
package com.vitorpamplona.quartz.experimental.nip95
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.Dimension
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,11 +18,12 @@
* 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.events
package com.vitorpamplona.quartz.experimental.nns
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,12 +18,13 @@
* 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.events
package com.vitorpamplona.quartz.experimental.profileGallery
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip51Lists.GeneralListEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,12 +18,13 @@
* 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.events
package com.vitorpamplona.quartz.experimental.profileGallery
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.Dimension
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip94FileMetadata.Dimension
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,11 +18,12 @@
* 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.events
package com.vitorpamplona.quartz.experimental.relationshipStatus
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip51Lists.PrivateTagArrayEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -18,14 +18,18 @@
* 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.events
package com.vitorpamplona.quartz.experimental.zapPolls
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.IMetaTag
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.geohash.geohashMipMap
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip10Notes.BaseTextNoteEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiUrl
import com.vitorpamplona.quartz.nip57Zaps.ZapSplitSetup
import com.vitorpamplona.quartz.nip92IMeta.IMetaTag
import com.vitorpamplona.quartz.nip92IMeta.Nip92MediaAttachments
import com.vitorpamplona.quartz.utils.TimeUtils
const val POLL_OPTION = "poll_option"
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.encoders
package com.vitorpamplona.quartz.lightning
import java.math.BigDecimal
import java.util.Locale
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.encoders
package com.vitorpamplona.quartz.lightning
import java.util.regex.Pattern
@@ -18,9 +18,10 @@
* 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.encoders
package com.vitorpamplona.quartz.lightning
import android.util.Log
import com.vitorpamplona.quartz.nip19Bech32Entities.bech32.Bech32
import java.util.regex.Pattern
class Lud06 {
@@ -0,0 +1,63 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core
import android.util.Log
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.Hex
import com.vitorpamplona.quartz.nip01Core.core.Event
fun Event.generateId(): String = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
fun Event.hasCorrectIDHash(): Boolean {
if (id.isEmpty()) return false
return id == generateId()
}
fun Event.hasVerifiedSignature(): Boolean {
if (id.isEmpty() || sig.isEmpty()) return false
return CryptoUtils.verifySignature(Hex.decode(sig), Hex.decode(id), Hex.decode(pubKey))
}
/** Checks if the ID is correct and then if the pubKey's secret key signed the event. */
fun Event.checkSignature() {
if (!hasCorrectIDHash()) {
throw Exception(
"""
|Unexpected ID.
| Event: ${toJson()}
| Actual ID: $id
| Generated: ${generateId()}
""".trimIndent(),
)
}
if (!hasVerifiedSignature()) {
throw Exception("""Bad signature!""")
}
}
fun Event.hasValidSignature(): Boolean =
try {
hasCorrectIDHash() && hasVerifiedSignature()
} catch (e: Exception) {
Log.w("Event", "Event $id does not have a valid signature: ${toJson()}", e)
false
}
@@ -0,0 +1,262 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core
import com.vitorpamplona.quartz.blossom.BlossomAuthorizationEvent
import com.vitorpamplona.quartz.blossom.BlossomServersEvent
import com.vitorpamplona.quartz.experimental.audio.AudioHeaderEvent
import com.vitorpamplona.quartz.experimental.audio.AudioTrackEvent
import com.vitorpamplona.quartz.experimental.edits.PrivateOutboxRelayListEvent
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryReadingStateEvent
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
import com.vitorpamplona.quartz.experimental.nip95.FileStorageEvent
import com.vitorpamplona.quartz.experimental.nip95.FileStorageHeaderEvent
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryEvent
import com.vitorpamplona.quartz.experimental.relationshipStatus.RelationshipStatusEvent
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip02FollowList.ContactListEvent
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
import com.vitorpamplona.quartz.nip04Dm.PrivateDmEvent
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEncryptedFileHeaderEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageEvent
import com.vitorpamplona.quartz.nip17Dm.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelCreateEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelHideMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelListEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMessageEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMetadataEvent
import com.vitorpamplona.quartz.nip28PublicChat.ChannelMuteUserEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackEvent
import com.vitorpamplona.quartz.nip30CustomEmoji.EmojiPackSelectionEvent
import com.vitorpamplona.quartz.nip34Git.GitIssueEvent
import com.vitorpamplona.quartz.nip34Git.GitPatchEvent
import com.vitorpamplona.quartz.nip34Git.GitReplyEvent
import com.vitorpamplona.quartz.nip34Git.GitRepositoryEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentCommentEvent
import com.vitorpamplona.quartz.nip35Torrents.TorrentEvent
import com.vitorpamplona.quartz.nip37Drafts.DraftEvent
import com.vitorpamplona.quartz.nip38UserStatus.StatusEvent
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentRequestEvent
import com.vitorpamplona.quartz.nip47WalletConnect.LnZapPaymentResponseEvent
import com.vitorpamplona.quartz.nip50Search.SearchRelayListEvent
import com.vitorpamplona.quartz.nip51Lists.BookmarkListEvent
import com.vitorpamplona.quartz.nip51Lists.MuteListEvent
import com.vitorpamplona.quartz.nip51Lists.PeopleListEvent
import com.vitorpamplona.quartz.nip51Lists.PinListEvent
import com.vitorpamplona.quartz.nip51Lists.RelaySetEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarDateSlotEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarRSVPEvent
import com.vitorpamplona.quartz.nip52Calendar.CalendarTimeSlotEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesChatMessageEvent
import com.vitorpamplona.quartz.nip53LiveActivities.LiveActivitiesEvent
import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent
import com.vitorpamplona.quartz.nip56Reports.ReportEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeAwardEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeDefinitionEvent
import com.vitorpamplona.quartz.nip58Badges.BadgeProfilesEvent
import com.vitorpamplona.quartz.nip59Giftwrap.GiftWrapEvent
import com.vitorpamplona.quartz.nip59Giftwrap.SealedGossipEvent
import com.vitorpamplona.quartz.nip65RelayList.AdvertisedRelayListEvent
import com.vitorpamplona.quartz.nip68Picture.PictureEvent
import com.vitorpamplona.quartz.nip71Video.VideoHorizontalEvent
import com.vitorpamplona.quartz.nip71Video.VideoVerticalEvent
import com.vitorpamplona.quartz.nip71Video.VideoViewEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityDefinitionEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityListEvent
import com.vitorpamplona.quartz.nip72ModCommunities.CommunityPostApprovalEvent
import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent
import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent
import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppDefinitionEvent
import com.vitorpamplona.quartz.nip89AppHandlers.AppRecommendationEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryRequestEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90ContentDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90StatusEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryRequestEvent
import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryResponseEvent
import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent
import com.vitorpamplona.quartz.nip96FileStorage.FileServersEvent
import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
class EventFactory {
companion object {
val factories: MutableMap<Int, (HexKey, HexKey, Long, Array<Array<String>>, String, HexKey) -> Event> = mutableMapOf()
fun create(
id: String,
pubKey: String,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: String,
): Event =
when (kind) {
AdvertisedRelayListEvent.KIND -> AdvertisedRelayListEvent(id, pubKey, createdAt, tags, content, sig)
AppDefinitionEvent.KIND -> AppDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
AppRecommendationEvent.KIND -> AppRecommendationEvent(id, pubKey, createdAt, tags, content, sig)
AppSpecificDataEvent.KIND -> AppSpecificDataEvent(id, pubKey, createdAt, tags, content, sig)
AudioHeaderEvent.KIND -> AudioHeaderEvent(id, pubKey, createdAt, tags, content, sig)
AudioTrackEvent.KIND -> AudioTrackEvent(id, pubKey, createdAt, tags, content, sig)
BadgeAwardEvent.KIND -> BadgeAwardEvent(id, pubKey, createdAt, tags, content, sig)
BadgeDefinitionEvent.KIND -> BadgeDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
BadgeProfilesEvent.KIND -> BadgeProfilesEvent(id, pubKey, createdAt, tags, content, sig)
BlossomServersEvent.KIND -> BlossomServersEvent(id, pubKey, createdAt, tags, content, sig)
BlossomAuthorizationEvent.KIND -> BlossomAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
BookmarkListEvent.KIND -> BookmarkListEvent(id, pubKey, createdAt, tags, content, sig)
CalendarDateSlotEvent.KIND -> CalendarDateSlotEvent(id, pubKey, createdAt, tags, content, sig)
CalendarEvent.KIND -> CalendarEvent(id, pubKey, createdAt, tags, content, sig)
CalendarTimeSlotEvent.KIND -> CalendarTimeSlotEvent(id, pubKey, createdAt, tags, content, sig)
CalendarRSVPEvent.KIND -> CalendarRSVPEvent(id, pubKey, createdAt, tags, content, sig)
ChannelCreateEvent.KIND -> ChannelCreateEvent(id, pubKey, createdAt, tags, content, sig)
ChannelHideMessageEvent.KIND -> ChannelHideMessageEvent(id, pubKey, createdAt, tags, content, sig)
ChannelListEvent.KIND -> ChannelListEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMessageEvent.KIND -> ChannelMessageEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMetadataEvent.KIND -> ChannelMetadataEvent(id, pubKey, createdAt, tags, content, sig)
ChannelMuteUserEvent.KIND -> ChannelMuteUserEvent(id, pubKey, createdAt, tags, content, sig)
ChatMessageEncryptedFileHeaderEvent.KIND -> {
if (id.isBlank()) {
ChatMessageEncryptedFileHeaderEvent(
Event.generateId(pubKey, createdAt, kind, tags, content),
pubKey,
createdAt,
tags,
content,
sig,
)
} else {
ChatMessageEncryptedFileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
}
}
ChatMessageEvent.KIND -> {
if (id.isBlank()) {
ChatMessageEvent(
Event.generateId(pubKey, createdAt, kind, tags, content),
pubKey,
createdAt,
tags,
content,
sig,
)
} else {
ChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
}
}
ChatMessageRelayListEvent.KIND -> ChatMessageRelayListEvent(id, pubKey, createdAt, tags, content, sig)
ClassifiedsEvent.KIND -> ClassifiedsEvent(id, pubKey, createdAt, tags, content, sig)
CommentEvent.KIND -> CommentEvent(id, pubKey, createdAt, tags, content, sig)
CommunityDefinitionEvent.KIND -> CommunityDefinitionEvent(id, pubKey, createdAt, tags, content, sig)
CommunityListEvent.KIND -> CommunityListEvent(id, pubKey, createdAt, tags, content, sig)
CommunityPostApprovalEvent.KIND -> CommunityPostApprovalEvent(id, pubKey, createdAt, tags, content, sig)
ContactListEvent.KIND -> ContactListEvent(id, pubKey, createdAt, tags, content, sig)
DeletionEvent.KIND -> DeletionEvent(id, pubKey, createdAt, tags, content, sig)
DraftEvent.KIND -> DraftEvent(id, pubKey, createdAt, tags, content, sig)
EmojiPackEvent.KIND -> EmojiPackEvent(id, pubKey, createdAt, tags, content, sig)
EmojiPackSelectionEvent.KIND -> EmojiPackSelectionEvent(id, pubKey, createdAt, tags, content, sig)
FileHeaderEvent.KIND -> FileHeaderEvent(id, pubKey, createdAt, tags, content, sig)
ProfileGalleryEntryEvent.KIND -> ProfileGalleryEntryEvent(id, pubKey, createdAt, tags, content, sig)
FileServersEvent.KIND -> FileServersEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageEvent.KIND -> FileStorageEvent(id, pubKey, createdAt, tags, content, sig)
FileStorageHeaderEvent.KIND -> FileStorageHeaderEvent(id, pubKey, createdAt, tags, content, sig)
FhirResourceEvent.KIND -> FhirResourceEvent(id, pubKey, createdAt, tags, content, sig)
GenericRepostEvent.KIND -> GenericRepostEvent(id, pubKey, createdAt, tags, content, sig)
GiftWrapEvent.KIND -> GiftWrapEvent(id, pubKey, createdAt, tags, content, sig)
GitIssueEvent.KIND -> GitIssueEvent(id, pubKey, createdAt, tags, content, sig)
GitReplyEvent.KIND -> GitReplyEvent(id, pubKey, createdAt, tags, content, sig)
GitPatchEvent.KIND -> GitPatchEvent(id, pubKey, createdAt, tags, content, sig)
GitRepositoryEvent.KIND -> GitRepositoryEvent(id, pubKey, createdAt, tags, content, sig)
GoalEvent.KIND -> GoalEvent(id, pubKey, createdAt, tags, content, sig)
HighlightEvent.KIND -> HighlightEvent(id, pubKey, createdAt, tags, content, sig)
HTTPAuthorizationEvent.KIND -> HTTPAuthorizationEvent(id, pubKey, createdAt, tags, content, sig)
InteractiveStoryPrologueEvent.KIND -> InteractiveStoryPrologueEvent(id, pubKey, createdAt, tags, content, sig)
InteractiveStorySceneEvent.KIND -> InteractiveStorySceneEvent(id, pubKey, createdAt, tags, content, sig)
InteractiveStoryReadingStateEvent.KIND -> InteractiveStoryReadingStateEvent(id, pubKey, createdAt, tags, content, sig)
LiveActivitiesChatMessageEvent.KIND -> LiveActivitiesChatMessageEvent(id, pubKey, createdAt, tags, content, sig)
LiveActivitiesEvent.KIND -> LiveActivitiesEvent(id, pubKey, createdAt, tags, content, sig)
LnZapEvent.KIND -> LnZapEvent(id, pubKey, createdAt, tags, content, sig)
LnZapPaymentRequestEvent.KIND -> LnZapPaymentRequestEvent(id, pubKey, createdAt, tags, content, sig)
LnZapPaymentResponseEvent.KIND -> LnZapPaymentResponseEvent(id, pubKey, createdAt, tags, content, sig)
LnZapPrivateEvent.KIND -> LnZapPrivateEvent(id, pubKey, createdAt, tags, content, sig)
LnZapRequestEvent.KIND -> LnZapRequestEvent(id, pubKey, createdAt, tags, content, sig)
LongTextNoteEvent.KIND -> LongTextNoteEvent(id, pubKey, createdAt, tags, content, sig)
MetadataEvent.KIND -> MetadataEvent(id, pubKey, createdAt, tags, content, sig)
MuteListEvent.KIND -> MuteListEvent(id, pubKey, createdAt, tags, content, sig)
NNSEvent.KIND -> NNSEvent(id, pubKey, createdAt, tags, content, sig)
com.vitorpamplona.quartz.nip46RemoteSigner.NostrConnectEvent.KIND ->
com.vitorpamplona.quartz.nip46RemoteSigner
.NostrConnectEvent(id, pubKey, createdAt, tags, content, sig)
NIP90StatusEvent.KIND -> NIP90StatusEvent(id, pubKey, createdAt, tags, content, sig)
NIP90ContentDiscoveryRequestEvent.KIND -> NIP90ContentDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
NIP90ContentDiscoveryResponseEvent.KIND -> NIP90ContentDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
NIP90UserDiscoveryRequestEvent.KIND -> NIP90UserDiscoveryRequestEvent(id, pubKey, createdAt, tags, content, sig)
NIP90UserDiscoveryResponseEvent.KIND -> NIP90UserDiscoveryResponseEvent(id, pubKey, createdAt, tags, content, sig)
OtsEvent.KIND -> OtsEvent(id, pubKey, createdAt, tags, content, sig)
PeopleListEvent.KIND -> PeopleListEvent(id, pubKey, createdAt, tags, content, sig)
PictureEvent.KIND -> PictureEvent(id, pubKey, createdAt, tags, content, sig)
PinListEvent.KIND -> PinListEvent(id, pubKey, createdAt, tags, content, sig)
PollNoteEvent.KIND -> PollNoteEvent(id, pubKey, createdAt, tags, content, sig)
PrivateDmEvent.KIND -> PrivateDmEvent(id, pubKey, createdAt, tags, content, sig)
PrivateOutboxRelayListEvent.KIND -> PrivateOutboxRelayListEvent(id, pubKey, createdAt, tags, content, sig)
ReactionEvent.KIND -> ReactionEvent(id, pubKey, createdAt, tags, content, sig)
RelationshipStatusEvent.KIND -> RelationshipStatusEvent(id, pubKey, createdAt, tags, content, sig)
RelayAuthEvent.KIND -> RelayAuthEvent(id, pubKey, createdAt, tags, content, sig)
RelaySetEvent.KIND -> RelaySetEvent(id, pubKey, createdAt, tags, content, sig)
ReportEvent.KIND -> ReportEvent(id, pubKey, createdAt, tags, content, sig)
RepostEvent.KIND -> RepostEvent(id, pubKey, createdAt, tags, content, sig)
SealedGossipEvent.KIND -> SealedGossipEvent(id, pubKey, createdAt, tags, content, sig)
SearchRelayListEvent.KIND -> SearchRelayListEvent(id, pubKey, createdAt, tags, content, sig)
StatusEvent.KIND -> StatusEvent(id, pubKey, createdAt, tags, content, sig)
TextNoteEvent.KIND -> TextNoteEvent(id, pubKey, createdAt, tags, content, sig)
TextNoteModificationEvent.KIND -> TextNoteModificationEvent(id, pubKey, createdAt, tags, content, sig)
TorrentEvent.KIND -> TorrentEvent(id, pubKey, createdAt, tags, content, sig)
TorrentCommentEvent.KIND -> TorrentCommentEvent(id, pubKey, createdAt, tags, content, sig)
VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig)
VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig)
VideoViewEvent.KIND -> VideoViewEvent(id, pubKey, createdAt, tags, content, sig)
WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig)
else -> {
factories[kind]?.let {
return it(id, pubKey, createdAt, tags, content, sig)
}
Event(id, pubKey, createdAt, kind, tags, content, sig)
}
}
}
}
@@ -18,13 +18,11 @@
* 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.crypto.nip01
package com.vitorpamplona.quartz.nip01Core
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.vitorpamplona.quartz.crypto.sha256Hash
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Event.Companion.mapper
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
class EventHasher {
companion object {
@@ -54,7 +52,7 @@ class EventHasher {
add(content)
}
return mapper.writeValueAsString(rawEvent)
return EventMapper.toJson(rawEvent)
}
fun hashIdBytes(
@@ -18,10 +18,11 @@
* 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.encoders
package com.vitorpamplona.quartz.nip01Core
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip19Bech32Entities.entities.NEvent
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@@ -40,9 +41,7 @@ data class EventHint<T : Event>(
event.countMemory() +
(relay?.bytesUsedInMemory() ?: 0)
fun toNEvent(): String = Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay)
fun toNPub(): String = Nip19Bech32.createNPub(event.id)
fun toNEvent(): String = NEvent.create(event.id, event.pubKey, event.kind, relay)
fun toTagArray(tag: String) = listOfNotNull(tag, event.id, relay, event.pubKey).toTypedArray()
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core
import com.vitorpamplona.quartz.crypto.Hex
/** Makes the distinction between String and Hex * */
typealias HexKey = String
fun ByteArray.toHexKey(): HexKey = Hex.encode(this)
fun HexKey.hexToByteArray(): ByteArray = Hex.decode(this)
@@ -0,0 +1,142 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core
import android.util.Log
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ObjectNode
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip21UriScheme.toNostrUri
import com.vitorpamplona.quartz.nip39ExtIdentities.updateClaims
import com.vitorpamplona.quartz.utils.TimeUtils
import java.io.ByteArrayInputStream
import java.io.StringWriter
class MetadataEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
fun contactMetaData() =
try {
EventMapper.mapper.readValue(content, UserMetadata::class.java)
} catch (e: Exception) {
// e.printStackTrace()
Log.w("MetadataEvent", "Content Parse Error: ${toNostrUri()} ${e.localizedMessage}")
null
}
companion object {
const val KIND = 0
fun newUser(
name: String?,
signer: NostrSignerSync,
createdAt: Long = TimeUtils.now(),
): MetadataEvent? {
// Tries to not delete any existing attribute that we do not work with.
val currentJson = ObjectMapper().createObjectNode()
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
val writer = StringWriter()
ObjectMapper().writeValue(writer, currentJson)
val tags = mutableListOf<Array<String>>()
tags.add(
arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"),
)
return signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString())
}
fun updateFromPast(
latest: MetadataEvent?,
name: String?,
picture: String?,
banner: String?,
website: String?,
about: String?,
nip05: String?,
lnAddress: String?,
lnURL: String?,
pronouns: String?,
twitter: String?,
mastodon: String?,
github: String?,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (MetadataEvent) -> Unit,
) {
// Tries to not delete any existing attribute that we do not work with.
val currentJson =
if (latest != null) {
ObjectMapper()
.readTree(
ByteArrayInputStream(latest.content.toByteArray(Charsets.UTF_8)),
) as ObjectNode
} else {
ObjectMapper().createObjectNode()
}
name?.let { addIfNotBlank(currentJson, "name", it.trim()) }
name?.let { addIfNotBlank(currentJson, "display_name", it.trim()) }
picture?.let { addIfNotBlank(currentJson, "picture", it.trim()) }
banner?.let { addIfNotBlank(currentJson, "banner", it.trim()) }
website?.let { addIfNotBlank(currentJson, "website", it.trim()) }
pronouns?.let { addIfNotBlank(currentJson, "pronouns", it.trim()) }
about?.let { addIfNotBlank(currentJson, "about", it.trim()) }
nip05?.let { addIfNotBlank(currentJson, "nip05", it.trim()) }
lnAddress?.let { addIfNotBlank(currentJson, "lud16", it.trim()) }
lnURL?.let { addIfNotBlank(currentJson, "lud06", it.trim()) }
val writer = StringWriter()
ObjectMapper().writeValue(writer, currentJson)
val tags = mutableListOf<Array<String>>()
tags.add(arrayOf("alt", "User profile for ${name ?: currentJson.get("name").asText() ?: ""}"))
latest?.updateClaims(twitter, github, mastodon)?.forEach {
tags.add(it)
}
signer.sign(createdAt, KIND, tags.toTypedArray(), writer.buffer.toString(), onReady)
}
private fun addIfNotBlank(
currentJson: ObjectNode,
key: String,
value: String,
) {
if (value.isBlank() || value == "null") {
currentJson.remove(key)
} else {
currentJson.put(key, value.trim())
}
}
}
}
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.crypto.nip01
package com.vitorpamplona.quartz.nip01Core
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.nextBytes
@@ -40,8 +40,8 @@ class Nip01(
fun sign(
data: ByteArray,
privKey: ByteArray,
auxrand32: ByteArray? = random.nextBytes(32),
): ByteArray = secp256k1.signSchnorr(data, privKey, auxrand32)
nonce: ByteArray? = random.nextBytes(32),
): ByteArray = secp256k1.signSchnorr(data, privKey, nonce)
fun signDeterministic(
data: ByteArray,
@@ -59,6 +59,6 @@ class Nip01(
fun signString(
message: String,
privKey: ByteArray,
auxrand32: ByteArray = random.nextBytes(32),
): ByteArray = sign(CryptoUtils.sha256(message.toByteArray()), privKey, auxrand32)
nonce: ByteArray = random.nextBytes(32),
): ByteArray = sign(CryptoUtils.sha256(message.toByteArray()), privKey, nonce)
}
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core
import androidx.compose.runtime.Stable
import com.fasterxml.jackson.annotation.JsonProperty
import com.vitorpamplona.quartz.nip02FollowList.ImmutableListOfLists
@Stable
class UserMetadata {
var name: String? = null
@Deprecated("Use name instead", replaceWith = ReplaceWith("name"))
var username: String? = null
@JsonProperty("display_name")
var displayName: String? = null
var picture: String? = null
var banner: String? = null
var website: String? = null
var about: String? = null
var bot: Boolean? = null
var pronouns: String? = null
var nip05: String? = null
var nip05Verified: Boolean = false
var nip05LastVerificationTime: Long? = 0
var domain: String? = null
var lud06: String? = null
var lud16: String? = null
var twitter: String? = null
@Transient
var tags: ImmutableListOfLists<String>? = null
fun anyName(): String? = displayName ?: name ?: username
fun anyNameStartsWith(prefix: String): Boolean =
listOfNotNull(name, username, displayName, nip05, lud06, lud16).any {
it.contains(prefix, true)
}
fun lnAddress(): String? = lud16 ?: lud06
fun bestName(): String? = displayName ?: name ?: username
fun nip05(): String? = nip05
fun profilePicture(): String? = picture
fun cleanBlankNames() {
if (pronouns == "null") pronouns = null
if (picture?.isNotEmpty() == true) picture = picture?.trim()
if (nip05?.isNotEmpty() == true) nip05 = nip05?.trim()
if (displayName?.isNotEmpty() == true) displayName = displayName?.trim()
if (name?.isNotEmpty() == true) name = name?.trim()
if (username?.isNotEmpty() == true) username = username?.trim()
if (lud06?.isNotEmpty() == true) lud06 = lud06?.trim()
if (lud16?.isNotEmpty() == true) lud16 = lud16?.trim()
if (pronouns?.isNotEmpty() == true) pronouns = pronouns?.trim()
if (banner?.isNotEmpty() == true) banner = banner?.trim()
if (website?.isNotEmpty() == true) website = website?.trim()
if (domain?.isNotEmpty() == true) domain = domain?.trim()
if (picture?.isBlank() == true) picture = null
if (nip05?.isBlank() == true) nip05 = null
if (displayName?.isBlank() == true) displayName = null
if (name?.isBlank() == true) name = null
if (username?.isBlank() == true) username = null
if (lud06?.isBlank() == true) lud06 = null
if (lud16?.isBlank() == true) lud16 = null
if (banner?.isBlank() == true) banner = null
if (website?.isBlank() == true) website = null
if (domain?.isBlank() == true) domain = null
if (pronouns?.isBlank() == true) pronouns = null
}
}
@@ -0,0 +1,65 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.addressables
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
import com.vitorpamplona.quartz.utils.removeTrailingNullsAndEmptyOthers
@Immutable
data class ATag(
val kind: Int,
val pubKeyHex: String,
val dTag: String,
) {
var relay: String? = null
constructor(
kind: Int,
pubKeyHex: String,
dTag: String,
relayHint: String?,
) : this(kind, pubKeyHex, dTag) {
this.relay = relayHint
}
fun countMemory(): Long =
5 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
8L + // kind
pubKeyHex.bytesUsedInMemory() +
dTag.bytesUsedInMemory() +
(relay?.bytesUsedInMemory() ?: 0)
fun toTag() = assembleATag(kind, pubKeyHex, dTag)
fun toATagArray() = removeTrailingNullsAndEmptyOthers("a", toTag(), relay)
fun toQTagArray() = removeTrailingNullsAndEmptyOthers("q", toTag(), relay)
companion object {
fun assembleATag(
kind: Int,
pubKeyHex: String,
dTag: String,
) = "$kind:$pubKeyHex:$dTag"
}
}
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.addressables
import androidx.compose.runtime.Immutable
@Immutable
interface AddressableEvent {
fun dTag(): String
fun address(relayHint: String? = null): ATag
fun addressTag(): String
}
@@ -18,36 +18,29 @@
* 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.events
package com.vitorpamplona.quartz.nip01Core.addressables
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import java.net.URI
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
@Immutable
class RecommendRelayEvent(
open class BaseAddressableEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
fun relay() = URI.create(content.trim())
) : Event(id, pubKey, createdAt, kind, tags, content, sig),
AddressableEvent {
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
companion object {
const val KIND = 2
override fun address(relayHint: String?) = ATag(kind, pubKey, dTag(), relayHint)
fun create(
relay: URI,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (RecommendRelayEvent) -> Unit,
) {
val content = relay.toString()
signer.sign(createdAt, KIND, emptyArray(), content, onReady)
}
}
/**
* Creates the tag in a memory efficient way (without creating the ATag class
*/
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
}
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.addressables
import com.vitorpamplona.quartz.nip01Core.core.Event
fun <R> Event.mapTaggedAddress(map: (address: String) -> R) = tags.mapTaggedAddress(map)
fun Event.firstIsTaggedAddressableNote(addressableNotes: Set<String>) = tags.firstIsTaggedAddressableNote(addressableNotes)
fun Event.isTaggedAddressableNote(idHex: String) = tags.isTaggedAddressableNote(idHex)
fun Event.isTaggedAddressableNotes(idHexes: Set<String>) = tags.isTaggedAddressableNotes(idHexes)
fun Event.isTaggedAddressableKind(kind: Int) = tags.isTaggedAddressableKind(kind)
fun Event.getTagOfAddressableKind(kind: Int) = tags.getTagOfAddressableKind(kind)
fun Event.taggedAddresses() = tags.taggedAddresses()
fun Event.firstTaggedAddress() = tags.firstTaggedAddress()
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.addressables
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.nip01Core.core.firstMapTagged
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
import com.vitorpamplona.quartz.nip01Core.core.isTagged
import com.vitorpamplona.quartz.nip01Core.core.mapTagged
import com.vitorpamplona.quartz.nip01Core.core.mapValueTagged
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
fun <R> TagArray.mapTaggedAddress(map: (address: String) -> R) = this.mapValueTagged("a", map)
fun TagArray.firstIsTaggedAddressableNote(addressableNotes: Set<String>) =
this
.firstOrNull { it.size > 1 && it[0] == "a" && it[1] in addressableNotes }
?.getOrNull(1)
fun TagArray.isTaggedAddressableNote(idHex: String) = this.isTagged("a", idHex)
fun TagArray.isTaggedAddressableNotes(idHexes: Set<String>) = this.isAnyTagged("a", idHexes)
fun TagArray.isTaggedAddressableKind(kind: Int): Boolean {
val kindStr = kind.toString()
return this.any { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }
}
fun TagArray.getTagOfAddressableKind(kind: Int): ATag? {
val kindStr = kind.toString()
val aTag =
this
.firstOrNull { it.size > 1 && it[0] == "a" && it[1].startsWith(kindStr) }
?.getOrNull(1)
?: return null
return ATag.parse(aTag, null)
}
fun TagArray.taggedAddresses() = this.mapTagged("a") { ATag.parse(it[1], it.getOrNull(2)) }
fun TagArray.firstTaggedAddress() = this.firstMapTagged("a") { ATag.parse(it[1], it.getOrNull(2)) }
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.core
import androidx.compose.runtime.Immutable
import com.fasterxml.jackson.annotation.JsonProperty
import com.vitorpamplona.quartz.nip01Core.EventHasher
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.jackson.EventManualSerializer
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
open class Event(
val id: HexKey,
@JsonProperty("pubkey") val pubKey: HexKey,
@JsonProperty("created_at") val createdAt: Long,
val kind: Int,
val tags: TagArray,
val content: String,
val sig: HexKey,
) {
open fun isContentEncoded() = false
open fun countMemory(): Long =
7 * pointerSizeInBytes + // 7 fields, 4 bytes each reference (32bit)
12L + // createdAt + kind
id.bytesUsedInMemory() +
pubKey.bytesUsedInMemory() +
tags.sumOf { pointerSizeInBytes + it.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() } } +
content.bytesUsedInMemory() +
sig.bytesUsedInMemory()
fun toJson(): String = EventManualSerializer.toJson(id, pubKey, createdAt, kind, tags, content, sig)
companion object {
fun fromJson(json: String): Event = EventMapper.fromJson(json)
fun toJson(event: Event): String = EventMapper.toJson(event)
fun generateId(
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
): HexKey = EventHasher.hashId(pubKey, createdAt, kind, tags, content)
fun generateIdBytes(
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
): ByteArray = EventHasher.hashIdBytes(pubKey, createdAt, kind, tags, content)
fun create(
signer: NostrSigner,
kind: Int,
tags: Array<Array<String>> = emptyArray(),
content: String = "",
createdAt: Long = TimeUtils.now(),
onReady: (Event) -> Unit,
) = signer.sign(createdAt, kind, tags, content, onReady)
}
}
@@ -0,0 +1,122 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.core
import com.vitorpamplona.quartz.nip01Core.HexKey
typealias TagArray = Array<Array<String>>
fun TagArray.forEachTagged(
tagName: String,
onEach: (eventId: HexKey) -> Unit,
) = this.forEach {
if (it.size > 1 && it[0] == tagName) {
onEach(it[1])
}
}
fun TagArray.anyTagged(
tagName: String,
onEach: (tagValue: String) -> Boolean,
) = this.any {
if (it.size > 1 && it[0] == tagName) {
onEach(it[1])
} else {
false
}
}
fun TagArray.anyTagged(tagName: String) = this.any { it.size > 0 && it[0] == tagName }
fun TagArray.anyTagWithValueStartingWithIgnoreCase(
tagName: String,
valuePrefix: String,
): Boolean = this.any { it.size > 1 && it[0] == tagName && it[1].startsWith(valuePrefix, true) }
fun TagArray.hasTagWithContent(tagName: String) = this.any { it.size > 1 && it[0] == tagName }
fun <R> TagArray.mapValueTagged(
tagName: String,
map: (tagValue: String) -> R,
) = this.mapNotNull {
if (it.size > 1 && it[0] == tagName) {
map(it[1])
} else {
null
}
}
fun <R> TagArray.mapTagged(
tagName: String,
map: (tagValue: Array<String>) -> R,
) = this.mapNotNull {
if (it.size > 1 && it[0] == tagName) {
map(it)
} else {
null
}
}
fun TagArray.mapValues(tagName: String) =
this.mapNotNull {
if (it.size > 1 && it[0] == tagName) {
it[1]
} else {
null
}
}
fun <R> TagArray.firstMapTagged(
tagName: String,
map: (tagValue: Array<String>) -> R,
) = this.firstNotNullOfOrNull {
if (it.size > 1 && it[0] == tagName) {
map(it)
} else {
null
}
}
fun TagArray.filterByTag(tagName: String) = this.filter { it.size > 0 && it[0] == tagName }
fun TagArray.filterByTagWithValue(tagName: String) = this.filter { it.size > 1 && it[0] == tagName }
fun TagArray.firstTag(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }
fun TagArray.firstTagValue(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1] }
fun TagArray.firstTagValueAsInt(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1].toIntOrNull() }
fun TagArray.firstTagValueAsLong(key: String) = this.firstOrNull { it.size > 1 && it[0] == key }?.let { it[1].toLongOrNull() }
fun TagArray.firstTagValueFor(vararg key: String) = this.firstOrNull { it.size > 1 && it[0] in key }?.let { it[1] }
fun TagArray.isTagged(
key: String,
tag: String,
) = this.any { it.size > 1 && it[0] == key && it[1] == tag }
fun TagArray.isAnyTagged(
key: String,
tags: Set<String>,
) = this.any { it.size > 1 && it[0] == key && it[1] in tags }
fun TagArray.matchTag1With(text: String) = this.any { it.size > 1 && it[1].contains(text, true) }
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.events
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
fun Event.forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) = tags.forEachTaggedEvent(onEach)
fun <R> Event.mapTaggedEvent(map: (eventId: HexKey) -> R) = tags.mapTaggedEvent(map)
fun Event.taggedEvents() = tags.taggedEvents()
fun Event.firstTaggedEvent() = tags.firstTaggedEvent()
fun Event.isTaggedEvent(idHex: String) = tags.isTaggedEvent(idHex)
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.events
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
import com.vitorpamplona.quartz.nip01Core.core.forEachTagged
import com.vitorpamplona.quartz.nip01Core.core.isTagged
import com.vitorpamplona.quartz.nip01Core.core.mapValueTagged
import com.vitorpamplona.quartz.nip01Core.core.mapValues
fun TagArray.forEachTaggedEvent(onEach: (eventId: HexKey) -> Unit) = this.forEachTagged("e", onEach)
fun <R> TagArray.mapTaggedEvent(map: (eventId: HexKey) -> R) = this.mapValueTagged("e", map)
fun TagArray.taggedEvents() = this.mapValues("e")
fun TagArray.firstTaggedEvent() = this.firstTagValue("e")
fun TagArray.isTaggedEvent(idHex: String) = this.isTagged("e", idHex)
@@ -18,9 +18,9 @@
* 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.encoders
package com.vitorpamplona.quartz.nip01Core.experimental
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.nip01Core.core.Event
import java.nio.ByteBuffer
import java.nio.CharBuffer
import java.nio.charset.CodingErrorAction
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.geohash
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
fun Event.hasGeohashes() =
if (this is CommentEvent) {
this.hasGeohashes()
} else {
tags.hasGeohashes()
}
fun Event.isTaggedGeoHashes(hashtags: Set<String>) =
if (this is CommentEvent) {
this.isTaggedGeoHashes(hashtags)
} else {
tags.isTaggedGeoHashes(hashtags)
}
fun Event.isTaggedGeoHash(hashtag: String) =
if (this is CommentEvent) {
this.isTaggedGeoHash(hashtag)
} else {
tags.isTaggedGeoHash(hashtag)
}
fun Event.geohashes() =
if (this is CommentEvent) {
geohashes()
} else {
tags.geohashes()
}
fun Event.getGeoHash(): String? =
if (this is CommentEvent) {
getGeoHash()
} else {
tags.getGeoHash()
}
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.geohash
fun geohashMipMap(geohash: String): Array<Array<String>> =
geohash.indices
.asSequence()
.map { arrayOf("g", geohash.substring(0, it + 1)) }
.toList()
.reversed()
.toTypedArray()
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.geohash
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.nip01Core.core.anyTagWithValueStartingWithIgnoreCase
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
import com.vitorpamplona.quartz.nip01Core.core.mapValues
fun TagArray.hasGeohashes() = this.hasTagWithContent("g")
fun TagArray.isTaggedGeoHashes(hashtags: Set<String>) = this.isAnyTagged("g", hashtags)
fun TagArray.isTaggedGeoHash(hashtag: String) = this.anyTagWithValueStartingWithIgnoreCase("g", hashtag)
fun TagArray.geohashes() = this.mapValues("g")
fun TagArray.getGeoHash(): String? = geohashes().maxByOrNull { it.length }?.ifBlank { null }
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.hashtags
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
fun Event.forEachHashTag(onEach: (eventId: HexKey) -> Unit) = tags.forEachHashTag(onEach)
fun Event.anyHashTag(onEach: (str: String) -> Boolean) = tags.anyHashTag(onEach)
fun Event.hasHashtags() = tags.hasHashtags()
fun Event.hashtags() = tags.hashtags()
fun Event.isTaggedHash(hashtag: String) = tags.isTaggedHash(hashtag)
fun Event.isTaggedHashes(hashtags: Set<String>) = tags.isTaggedHashes(hashtags)
fun Event.firstIsTaggedHashes(hashtags: Set<String>) = tags.firstIsTaggedHashes(hashtags)
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.hashtags
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.nip01Core.core.anyTagged
import com.vitorpamplona.quartz.nip01Core.core.forEachTagged
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
import com.vitorpamplona.quartz.nip01Core.core.mapValues
fun TagArray.forEachHashTag(onEach: (eventId: HexKey) -> Unit) = this.forEachTagged("t", onEach)
fun TagArray.anyHashTag(onEach: (str: String) -> Boolean) = this.anyTagged("t", onEach)
fun TagArray.hasHashtags() = this.hasTagWithContent("t")
fun TagArray.hashtags() = this.mapValues("t")
fun TagArray.isTaggedHash(hashtag: String) = this.any { it.size > 1 && it[0] == "t" && it[1].equals(hashtag, true) }
fun TagArray.isTaggedHashes(hashtags: Set<String>) = this.any { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }
fun TagArray.firstIsTaggedHashes(hashtags: Set<String>) = this.firstOrNull { it.size > 1 && it[0] == "t" && it[1].lowercase() in hashtags }?.getOrNull(1)
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
import com.vitorpamplona.quartz.nip01Core.core.Event
class EventDeserializer : StdDeserializer<Event>(Event::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): Event = EventManualDeserializer.fromJson(jp.codec.readTree(jp))
}
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.databind.JsonNode
import com.vitorpamplona.quartz.nip01Core.EventFactory
import com.vitorpamplona.quartz.nip01Core.core.Event
class EventManualDeserializer {
companion object {
fun fromJson(jsonObject: JsonNode): Event =
EventFactory.create(
id = jsonObject.get("id").asText().intern(),
pubKey = jsonObject.get("pubkey").asText().intern(),
createdAt = jsonObject.get("created_at").asLong(),
kind = jsonObject.get("kind").asInt(),
tags =
jsonObject.get("tags").toTypedArray {
it.toTypedArray { s -> if (s.isNull) "" else s.asText().intern() }
},
content = jsonObject.get("content").asText(),
sig = jsonObject.get("sig").asText(),
)
}
}
@@ -0,0 +1,62 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.vitorpamplona.quartz.nip01Core.HexKey
class EventManualSerializer {
companion object {
fun toJson(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
kind: Int,
tags: Array<Array<String>>,
content: String,
sig: String,
): String {
val factory = JsonNodeFactory.instance
val obj =
factory.objectNode().apply {
put("id", id)
put("pubkey", pubKey)
put("created_at", createdAt)
put("kind", kind)
replace(
"tags",
factory.arrayNode(tags.size).apply {
tags.forEach { tag ->
add(
factory.arrayNode(tag.size).apply { tag.forEach { add(it) } },
)
}
},
)
put("content", content)
put("sig", sig)
}
return EventMapper.mapper.writeValueAsString(obj)
}
}
}
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.core.json.JsonReadFeature
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerMessage
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
import com.vitorpamplona.quartz.nip47WalletConnect.Request
import com.vitorpamplona.quartz.nip47WalletConnect.RequestDeserializer
import com.vitorpamplona.quartz.nip47WalletConnect.Response
import com.vitorpamplona.quartz.nip47WalletConnect.ResponseDeserializer
import com.vitorpamplona.quartz.nip59Giftwrap.Gossip
import com.vitorpamplona.quartz.nip59Giftwrap.GossipDeserializer
import com.vitorpamplona.quartz.nip59Giftwrap.GossipSerializer
class EventMapper {
companion object {
val mapper =
jacksonObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
.registerModule(
SimpleModule()
.addSerializer(Event::class.java, EventSerializer())
.addDeserializer(Event::class.java, EventDeserializer())
.addSerializer(Gossip::class.java, GossipSerializer())
.addDeserializer(Gossip::class.java, GossipDeserializer())
.addDeserializer(Response::class.java, ResponseDeserializer())
.addDeserializer(Request::class.java, RequestDeserializer())
.addDeserializer(BunkerMessage::class.java, BunkerMessage.BunkerMessageDeserializer())
.addSerializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestSerializer())
.addDeserializer(BunkerRequest::class.java, BunkerRequest.BunkerRequestDeserializer())
.addSerializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseSerializer())
.addDeserializer(BunkerResponse::class.java, BunkerResponse.BunkerResponseDeserializer()),
)
fun fromJson(json: String): Event = mapper.readValue(json, Event::class.java)
fun fromJson(json: JsonNode): Event = EventManualDeserializer.fromJson(json)
fun toJson(event: Event): String = mapper.writeValueAsString(event)
fun toJson(event: ArrayNode?): String = mapper.writeValueAsString(event)
fun toJson(event: ObjectNode?): String = mapper.writeValueAsString(event)
}
}
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.SerializerProvider
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.vitorpamplona.quartz.nip01Core.core.Event
class EventSerializer : StdSerializer<Event>(Event::class.java) {
override fun serialize(
event: Event,
gen: JsonGenerator,
provider: SerializerProvider,
) {
gen.writeStartObject()
gen.writeStringField("id", event.id)
gen.writeStringField("pubkey", event.pubKey)
gen.writeNumberField("created_at", event.createdAt)
gen.writeNumberField("kind", event.kind)
gen.writeArrayFieldStart("tags")
event.tags.forEach { tag -> gen.writeArray(tag, 0, tag.size) }
gen.writeEndArray()
gen.writeStringField("content", event.content)
gen.writeStringField("sig", event.sig)
gen.writeEndObject()
}
}
@@ -0,0 +1,25 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.jackson
import com.fasterxml.jackson.databind.JsonNode
inline fun <reified R> JsonNode.toTypedArray(transform: (JsonNode) -> R): Array<R> = Array(size()) { transform(get(it)) }
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.people
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
import com.vitorpamplona.quartz.nip01Core.core.isTagged
import com.vitorpamplona.quartz.nip01Core.core.mapValues
fun Event.isTaggedUser(idHex: String) = tags.isTagged("p", idHex)
fun Event.isTaggedUsers(idHexes: Set<String>) = tags.isAnyTagged("p", idHexes)
fun Event.taggedUsers() = tags.mapValues("p")
fun Event.firstTaggedUser() = tags.firstTagValue("p")
fun Event.hasAnyTaggedUser() = tags.hasTagWithContent("p")
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip01Core.people
import com.vitorpamplona.quartz.nip01Core.core.TagArray
import com.vitorpamplona.quartz.nip01Core.core.firstTagValue
import com.vitorpamplona.quartz.nip01Core.core.hasTagWithContent
import com.vitorpamplona.quartz.nip01Core.core.isAnyTagged
import com.vitorpamplona.quartz.nip01Core.core.isTagged
import com.vitorpamplona.quartz.nip01Core.core.mapValues
fun TagArray.isTaggedUser(idHex: String) = this.isTagged("p", idHex)
fun TagArray.isTaggedUsers(idHexes: Set<String>) = this.isAnyTagged("p", idHexes)
fun TagArray.taggedUsers() = this.mapValues("p")
fun TagArray.firstTaggedUser() = this.firstTagValue("p")
fun TagArray.hasAnyTaggedUser() = this.hasTagWithContent("p")
@@ -18,14 +18,14 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.signers
package com.vitorpamplona.quartz.nip01Core.signers
import com.vitorpamplona.quartz.crypto.nip04.Nip04
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.EventFactory
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.nip01Core.EventFactory
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip04Dm.Nip04
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
abstract class NostrSigner(
val pubKey: HexKey,
@@ -18,14 +18,14 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.signers
package com.vitorpamplona.quartz.nip01Core.signers
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
class NostrSignerInternal(
val keyPair: KeyPair,
@@ -18,18 +18,19 @@
* 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.signers
package com.vitorpamplona.quartz.nip01Core.signers
import android.util.Log
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.EventFactory
import com.vitorpamplona.quartz.events.LnZapPrivateEvent
import com.vitorpamplona.quartz.events.LnZapRequestEvent
import com.vitorpamplona.quartz.nip01Core.EventFactory
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip57Zaps.LnZapPrivateEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
import com.vitorpamplona.quartz.nip57Zaps.PrivateZapEncryption
class NostrSignerSync(
val keyPair: KeyPair,
@@ -153,7 +154,7 @@ class NostrSignerSync(
val idToGeneratePrivateKey = zappedEvent ?: userHex
val encryptionPrivateKey =
LnZapRequestEvent.createEncryptionPrivateKey(
PrivateZapEncryption.createEncryptionPrivateKey(
keyPair.privKey.toHexKey(),
idToGeneratePrivateKey,
createdAt,
@@ -165,7 +166,7 @@ class NostrSignerSync(
val noteJson = privateEvent.toJson()
val encryptedContent =
LnZapRequestEvent.encryptPrivateZapMessage(
PrivateZapEncryption.encryptPrivateZapMessage(
noteJson,
encryptionPrivateKey,
userHex.hexToByteArray(),
@@ -195,13 +196,13 @@ class NostrSignerSync(
val altPubkeyToUse = recipientPK
val altPrivateKeyToUse =
if (recipientPost != null) {
LnZapRequestEvent.createEncryptionPrivateKey(
PrivateZapEncryption.createEncryptionPrivateKey(
keyPair.privKey.toHexKey(),
recipientPost,
event.createdAt,
)
} else if (recipientPK != null) {
LnZapRequestEvent.createEncryptionPrivateKey(
PrivateZapEncryption.createEncryptionPrivateKey(
keyPair.privKey.toHexKey(),
recipientPK,
event.createdAt,
@@ -18,19 +18,27 @@
* 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.events
package com.vitorpamplona.quartz.nip02FollowList
import android.util.Log
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.decodePublicKey
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.addressables.ATag
import com.vitorpamplona.quartz.nip01Core.addressables.isTaggedAddressableNote
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.events.isTaggedEvent
import com.vitorpamplona.quartz.nip01Core.geohash.isTaggedGeoHash
import com.vitorpamplona.quartz.nip01Core.hashtags.hashtags
import com.vitorpamplona.quartz.nip01Core.hashtags.isTaggedHash
import com.vitorpamplona.quartz.nip01Core.jackson.EventMapper
import com.vitorpamplona.quartz.nip01Core.people.isTaggedUser
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
import com.vitorpamplona.quartz.nip01Core.toHexKey
import com.vitorpamplona.quartz.nip19Bech32Entities.decodePublicKey
import com.vitorpamplona.quartz.nip19Bech32Entities.parse
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable data class Contact(
@@ -83,8 +91,6 @@ class ContactListEvent(
fun countFollowTags() = tags.count { it.size > 1 && it[0] == "t" }
fun unverifiedFollowGeohashSet() = tags.filter { it.size > 1 && it[0] == "g" }.mapNotNull { it.getOrNull(1) }
fun follows() =
tags.mapNotNull {
try {
@@ -104,7 +110,7 @@ class ContactListEvent(
fun relays(): Map<String, ReadWrite>? =
try {
if (content.isNotEmpty()) {
mapper.readValue<Map<String, ReadWrite>>(content)
EventMapper.mapper.readValue<Map<String, ReadWrite>>(content)
} else {
null
}
@@ -131,7 +137,7 @@ class ContactListEvent(
): ContactListEvent? {
val content =
if (relayUse != null) {
mapper.writeValueAsString(relayUse)
EventMapper.mapper.writeValueAsString(relayUse)
} else {
""
}
@@ -162,7 +168,7 @@ class ContactListEvent(
) {
val content =
if (relayUse != null) {
mapper.writeValueAsString(relayUse)
EventMapper.mapper.writeValueAsString(relayUse)
} else {
""
}
@@ -382,7 +388,7 @@ class ContactListEvent(
) {
val content =
if (relayUse != null) {
mapper.writeValueAsString(relayUse)
EventMapper.mapper.writeValueAsString(relayUse)
} else {
""
}
@@ -420,81 +426,6 @@ class ContactListEvent(
)
}
@Stable
class UserMetadata {
var name: String? = null
@Deprecated("Use name instead", replaceWith = ReplaceWith("name"))
var username: String? = null
@JsonProperty("display_name")
var displayName: String? = null
var picture: String? = null
var banner: String? = null
var website: String? = null
var about: String? = null
var bot: Boolean? = null
var pronouns: String? = null
var nip05: String? = null
var nip05Verified: Boolean = false
var nip05LastVerificationTime: Long? = 0
var domain: String? = null
var lud06: String? = null
var lud16: String? = null
var twitter: String? = null
@Transient
var tags: ImmutableListOfLists<String>? = null
fun anyName(): String? = displayName ?: name ?: username
fun anyNameStartsWith(prefix: String): Boolean =
listOfNotNull(name, username, displayName, nip05, lud06, lud16).any {
it.contains(prefix, true)
}
fun lnAddress(): String? = lud16 ?: lud06
fun bestName(): String? = displayName ?: name ?: username
fun nip05(): String? = nip05
fun profilePicture(): String? = picture
fun cleanBlankNames() {
if (pronouns == "null") pronouns = null
if (picture?.isNotEmpty() == true) picture = picture?.trim()
if (nip05?.isNotEmpty() == true) nip05 = nip05?.trim()
if (displayName?.isNotEmpty() == true) displayName = displayName?.trim()
if (name?.isNotEmpty() == true) name = name?.trim()
if (username?.isNotEmpty() == true) username = username?.trim()
if (lud06?.isNotEmpty() == true) lud06 = lud06?.trim()
if (lud16?.isNotEmpty() == true) lud16 = lud16?.trim()
if (pronouns?.isNotEmpty() == true) pronouns = pronouns?.trim()
if (banner?.isNotEmpty() == true) banner = banner?.trim()
if (website?.isNotEmpty() == true) website = website?.trim()
if (domain?.isNotEmpty() == true) domain = domain?.trim()
if (picture?.isBlank() == true) picture = null
if (nip05?.isBlank() == true) nip05 = null
if (displayName?.isBlank() == true) displayName = null
if (name?.isBlank() == true) name = null
if (username?.isBlank() == true) username = null
if (lud06?.isBlank() == true) lud06 = null
if (lud16?.isBlank() == true) lud16 = null
if (banner?.isBlank() == true) banner = null
if (website?.isBlank() == true) website = null
if (domain?.isBlank() == true) domain = null
if (pronouns?.isBlank() == true) pronouns = null
}
}
@Stable class ImmutableListOfLists<T>(
val lists: Array<Array<T>>,
)
@@ -18,46 +18,27 @@
* 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.events
package com.vitorpamplona.quartz.nip03Timestamp
import android.util.Log
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.hexToByteArray
import com.vitorpamplona.quartz.ots.BlockstreamExplorer
import com.vitorpamplona.quartz.ots.CalendarPureJavaBuilder
import com.vitorpamplona.quartz.ots.DetachedTimestampFile
import com.vitorpamplona.quartz.ots.Hash
import com.vitorpamplona.quartz.ots.OpenTimestamps
import com.vitorpamplona.quartz.ots.VerifyResult
import com.vitorpamplona.quartz.ots.exceptions.UrlException
import com.vitorpamplona.quartz.ots.op.OpSHA256
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.nip01Core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.hexToByteArray
import com.vitorpamplona.quartz.nip01Core.signers.NostrSigner
import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockstreamExplorer
import com.vitorpamplona.quartz.nip03Timestamp.ots.CalendarPureJavaBuilder
import com.vitorpamplona.quartz.nip03Timestamp.ots.DetachedTimestampFile
import com.vitorpamplona.quartz.nip03Timestamp.ots.Hash
import com.vitorpamplona.quartz.nip03Timestamp.ots.OpenTimestamps
import com.vitorpamplona.quartz.nip03Timestamp.ots.VerifyResult
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
import kotlinx.coroutines.CancellationException
import java.util.Base64
@Immutable
sealed class VerificationState {
@Immutable object NotStarted : VerificationState()
@Stable
class Verified(
val verifiedTime: Long,
) : VerificationState()
@Immutable class Error(
val errorMessage: String,
) : VerificationState()
@Immutable class NetworkError(
val errorMessage: String,
val time: Long = TimeUtils.now(),
) : VerificationState()
}
@Immutable
class OtsEvent(
id: HexKey,
@@ -110,13 +91,25 @@ class OtsEvent(
const val KIND = 1040
const val ALT = "Opentimestamps Attestation"
var otsInstance = OpenTimestamps(BlockstreamExplorer(), CalendarPureJavaBuilder())
var otsInstance =
OpenTimestamps(
BlockstreamExplorer(),
CalendarPureJavaBuilder(),
)
fun stamp(eventId: HexKey): String {
val hash = Hash(eventId.hexToByteArray(), OpSHA256._TAG)
val hash =
Hash(
eventId.hexToByteArray(),
OpSHA256._TAG,
)
val file = DetachedTimestampFile.from(hash)
val timestamp = otsInstance.stamp(file)
val detachedToSerialize = DetachedTimestampFile(hash.getOp(), timestamp)
val detachedToSerialize =
DetachedTimestampFile(
hash.getOp(),
timestamp,
)
return Base64.getEncoder().encodeToString(detachedToSerialize.serialize())
}
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip03Timestamp
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
sealed class VerificationState {
@Immutable
object NotStarted : VerificationState()
@Stable
class Verified(
val verifiedTime: Long,
) : VerificationState()
@Immutable
class Error(
val errorMessage: String,
) : VerificationState()
@Immutable
class NetworkError(
val errorMessage: String,
val time: Long = TimeUtils.now(),
) : VerificationState()
}
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
public interface BitcoinExplorer {
/**
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
public class BlockHeader {
@@ -1,13 +1,15 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import android.util.Log;
import com.fasterxml.jackson.databind.JsonNode;
import com.vitorpamplona.quartz.ots.http.Request;
import com.vitorpamplona.quartz.ots.http.Response;
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request;
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response;
import java.net.URL;
import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class BlockstreamExplorer implements BitcoinExplorer {
private static final String esploraUrl = "https://blockstream.info/api";
@@ -1,11 +1,11 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import com.vitorpamplona.quartz.ots.exceptions.CommitmentNotFoundException;
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.ots.exceptions.ExceededSizeException;
import com.vitorpamplona.quartz.ots.exceptions.UrlException;
import com.vitorpamplona.quartz.ots.http.Request;
import com.vitorpamplona.quartz.ots.http.Response;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request;
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response;
import java.net.URL;
import java.util.HashMap;
@@ -1,9 +1,7 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import android.util.Log;
import com.vitorpamplona.quartz.ots.http.Request;
import com.vitorpamplona.quartz.ots.http.Response;
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Request;
import com.vitorpamplona.quartz.nip03Timestamp.ots.http.Response;
import java.net.URL;
import java.util.HashMap;
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
public interface CalendarBuilder {
public ICalendar newSyncCalendar(String url);
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
public class CalendarPureJavaBuilder implements CalendarBuilder {
public ICalendar newSyncCalendar(String url) {
@@ -1,9 +1,9 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.ots.op.Op;
import com.vitorpamplona.quartz.ots.op.OpCrypto;
import com.vitorpamplona.quartz.ots.op.OpSHA256;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
import java.io.File;
import java.io.IOException;
@@ -1,11 +1,11 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import com.vitorpamplona.quartz.ots.op.OpCrypto;
import com.vitorpamplona.quartz.ots.op.OpKECCAK256;
import com.vitorpamplona.quartz.ots.op.OpRIPEMD160;
import com.vitorpamplona.quartz.ots.op.OpSHA1;
import com.vitorpamplona.quartz.ots.op.OpSHA256;
import com.vitorpamplona.quartz.encoders.Hex;
import com.vitorpamplona.quartz.crypto.Hex;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpKECCAK256;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpRIPEMD160;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA1;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
import java.io.File;
import java.io.IOException;
@@ -0,0 +1,13 @@
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.CommitmentNotFoundException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.ExceededSizeException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.UrlException;
public interface ICalendar {
Timestamp submit(byte[] digest)
throws ExceededSizeException, UrlException, DeserializationException;
Timestamp getTimestamp(byte[] commitment) throws DeserializationException, ExceededSizeException, CommitmentNotFoundException, UrlException;
}
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import java.util.Optional;
import java.util.concurrent.Callable;
@@ -1,8 +1,8 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import com.vitorpamplona.quartz.ots.op.OpAppend;
import com.vitorpamplona.quartz.ots.op.OpPrepend;
import com.vitorpamplona.quartz.ots.op.OpSHA256;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpPrepend;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
import java.util.ArrayList;
import java.util.List;
@@ -1,17 +1,17 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import android.util.Log;
import com.vitorpamplona.quartz.ots.attestation.BitcoinBlockHeaderAttestation;
import com.vitorpamplona.quartz.ots.attestation.EthereumBlockHeaderAttestation;
import com.vitorpamplona.quartz.ots.attestation.LitecoinBlockHeaderAttestation;
import com.vitorpamplona.quartz.ots.attestation.PendingAttestation;
import com.vitorpamplona.quartz.ots.attestation.TimeAttestation;
import com.vitorpamplona.quartz.encoders.Hex;
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
import com.vitorpamplona.quartz.ots.op.OpAppend;
import com.vitorpamplona.quartz.ots.op.OpCrypto;
import com.vitorpamplona.quartz.ots.op.OpSHA256;
import com.vitorpamplona.quartz.crypto.Hex;
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation;
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.EthereumBlockHeaderAttestation;
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.LitecoinBlockHeaderAttestation;
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.PendingAttestation;
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpAppend;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpCrypto;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpSHA256;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -1,8 +1,8 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
import java.util.Arrays;
import java.util.logging.Logger;
public class StreamDeserializationContext {
@@ -1,9 +1,8 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Logger;
public class StreamSerializationContext {
@@ -1,15 +1,24 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import com.vitorpamplona.quartz.ots.attestation.BitcoinBlockHeaderAttestation;
import com.vitorpamplona.quartz.ots.attestation.TimeAttestation;
import com.vitorpamplona.quartz.encoders.Hex;
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.ots.op.Op;
import com.vitorpamplona.quartz.ots.op.OpBinary;
import com.vitorpamplona.quartz.ots.op.OpSHA256;
import com.vitorpamplona.quartz.crypto.Hex;
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.BitcoinBlockHeaderAttestation;
import com.vitorpamplona.quartz.nip03Timestamp.ots.attestation.TimeAttestation;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.Op;
import com.vitorpamplona.quartz.nip03Timestamp.ots.op.OpBinary;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
/**
* Proof that one or more attestations commit to a message.
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots;
package com.vitorpamplona.quartz.nip03Timestamp.ots;
import java.text.DateFormatSymbols;
import java.text.SimpleDateFormat;
@@ -1,13 +1,12 @@
package com.vitorpamplona.quartz.ots.attestation;
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
import com.vitorpamplona.quartz.ots.BlockHeader;
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.ots.Utils;
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* Bitcoin Block Header Attestation.
@@ -1,11 +1,9 @@
package com.vitorpamplona.quartz.ots.attestation;
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* Ethereum Block Header Attestation.
@@ -1,13 +1,12 @@
package com.vitorpamplona.quartz.ots.attestation;
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
import com.vitorpamplona.quartz.ots.BlockHeader;
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.ots.Utils;
import com.vitorpamplona.quartz.ots.exceptions.VerificationException;
import com.vitorpamplona.quartz.nip03Timestamp.ots.BlockHeader;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.VerificationException;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* Litecoin Block Header Attestation.
@@ -1,15 +1,14 @@
package com.vitorpamplona.quartz.ots.attestation;
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
import android.util.Log;
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* Pending attestations.
@@ -1,15 +1,15 @@
package com.vitorpamplona.quartz.ots.attestation;
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.Timestamp;
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* Class representing {@link com.vitorpamplona.quartz.ots.Timestamp} signature verification
* Class representing {@link Timestamp} signature verification
*/
public abstract class TimeAttestation implements Comparable<TimeAttestation> {
@@ -1,12 +1,11 @@
package com.vitorpamplona.quartz.ots.attestation;
package com.vitorpamplona.quartz.nip03Timestamp.ots.attestation;
import com.vitorpamplona.quartz.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamDeserializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.StreamSerializationContext;
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.exceptions.DeserializationException;
import com.vitorpamplona.quartz.ots.exceptions.DeserializationException;
import java.util.Arrays;
import java.util.logging.Logger;
/**
* Placeholder for attestations that don't support
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots.crypto;
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
/**
* Message digest interface
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots.crypto;
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
public interface ExtendedDigest extends Digest {
/**
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots.crypto;
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
/**
* Base implementation of MD4 family style digest as outlined in
@@ -1,6 +1,6 @@
package com.vitorpamplona.quartz.ots.crypto;
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
import com.vitorpamplona.quartz.ots.Utils;
import com.vitorpamplona.quartz.nip03Timestamp.ots.Utils;
/**
* Implementation of Keccak based on following KeccakNISTInterface.c from http://keccak.noekeon.org/
@@ -1,4 +1,4 @@
package com.vitorpamplona.quartz.ots.crypto;
package com.vitorpamplona.quartz.nip03Timestamp.ots.crypto;
/**
* Interface for Memoable objects. Memoable objects allow the taking of a snapshot of their internal state

Some files were not shown because too many files have changed in this diff Show More