Adds basic support for NIP-46 events.

This commit is contained in:
Vitor Pamplona 2024-08-28 17:39:25 -04:00
parent 7d9efcd1d9
commit b5709f9527
24 changed files with 1251 additions and 2 deletions

View File

@ -0,0 +1,254 @@
/**
* 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.nip46
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.vitorpamplona.quartz.crypto.KeyPair
import com.vitorpamplona.quartz.events.ContactListEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.signers.NostrSignerInternal
import com.vitorpamplona.quartz.utils.TimeUtils
import junit.framework.TestCase.assertEquals
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
internal class Nip46Test {
val remoteKey = NostrSignerInternal(KeyPair())
val signer = NostrSignerInternal(KeyPair())
val peer = NostrSignerInternal(KeyPair())
val dummyEvent =
Event(
id = "",
pubKey = "",
createdAt = TimeUtils.now(),
kind = 1,
tags = emptyArray(),
content = "test",
sig = "",
)
fun <T : BunkerMessage> encodeDecodeEvent(req: T): T {
var countDownLatch = CountDownLatch(1)
var eventStr: String? = null
NostrConnectEvent.create(req, remoteKey.pubKey, signer) {
eventStr = it.toJson()
countDownLatch.countDown()
}
countDownLatch.await()
countDownLatch = CountDownLatch(1)
var innerMessage: T? = null
(Event.fromJson(eventStr!!) as NostrConnectEvent).plainContent(signer) {
innerMessage = it as T
countDownLatch.countDown()
}
countDownLatch.await()
return innerMessage!!
}
@Test
fun signEncoder() {
val expected = BunkerRequestSign(event = dummyEvent)
val actual = encodeDecodeEvent(expected)
assertEquals(BunkerRequestSign.METHOD_NAME, actual.method)
assertEquals(expected.id, actual.id)
assertEquals(dummyEvent.id, actual.event.id)
}
@Test
fun connectEncoder() {
val expected = BunkerRequestConnect(remoteKey = remoteKey.pubKey)
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
}
@Test
fun pingEncoder() {
val expected = BunkerRequestPing()
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
}
@Test
fun getPubkeyEncoder() {
val expected = BunkerRequestGetPublicKey()
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
}
@Test
fun getRelaysEncoder() {
val expected = BunkerRequestGetRelays()
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
}
@Test
fun testNip04Encrypt() {
val expected = BunkerRequestNip04Encrypt(pubKey = peer.pubKey, message = "Test")
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
assertEquals(expected.pubKey, actual.pubKey)
assertEquals(expected.message, actual.message)
}
@Test
fun testNip44Encrypt() {
val expected = BunkerRequestNip44Encrypt(pubKey = peer.pubKey, message = "Test")
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
assertEquals(expected.pubKey, actual.pubKey)
assertEquals(expected.message, actual.message)
}
@Test
fun testNip04Decrypt() {
val expected = BunkerRequestNip04Decrypt(pubKey = peer.pubKey, ciphertext = "Test")
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
assertEquals(expected.pubKey, actual.pubKey)
assertEquals(expected.ciphertext, actual.ciphertext)
}
@Test
fun testNip44Decrypt() {
val expected = BunkerRequestNip44Decrypt(pubKey = peer.pubKey, ciphertext = "Test")
val actual = encodeDecodeEvent(expected)
assertEquals(expected.method, actual.method)
assertEquals(expected.id, actual.id)
assertEquals(expected.pubKey, actual.pubKey)
assertEquals(expected.ciphertext, actual.ciphertext)
}
// Responses
@Test
fun testAckResponse() {
val expected = BunkerResponseAck()
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
}
@Test
fun testPongResponse() {
val expected = BunkerResponsePong()
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
}
@Test
fun testErrorResponse() {
val expected = BunkerResponseError(error = "Error")
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
}
@Test
fun testEventResponse() {
val expected = BunkerResponseEvent(event = dummyEvent)
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
assertEquals(dummyEvent.id, actual.event.id)
}
@Test
fun testPubkeyResponse() {
val expected = BunkerResponsePublicKey(pubkey = peer.pubKey)
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
assertEquals(expected.pubkey, actual.pubkey)
}
@Test
fun testRelaysResponse() {
val expected = BunkerResponseGetRelays(relays = mapOf("url" to ContactListEvent.ReadWrite(true, false)))
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
assertEquals(expected.relays["url"], actual.relays["url"])
}
@Test
@Ignore("Impossible to recreate the class since there are no hints on the json")
fun testDecryptResponse() {
val expected = BunkerResponseDecrypt(plaintext = "Test")
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
assertEquals(expected.plaintext, actual.plaintext)
}
@Test
@Ignore("Impossible to recreate the class since there are no hints on the json")
fun testEncryptResponse() {
val expected = BunkerResponseEncrypt(ciphertext = "Test")
val actual = encodeDecodeEvent(expected)
assertEquals(expected.id, actual.id)
assertEquals(expected.result, actual.result)
assertEquals(expected.error, actual.error)
assertEquals(expected.ciphertext, actual.ciphertext)
}
}

View File

@ -43,6 +43,9 @@ import com.vitorpamplona.quartz.encoders.Nip01Serializer
import com.vitorpamplona.quartz.encoders.Nip19Bech32
import com.vitorpamplona.quartz.encoders.PoWRank
import com.vitorpamplona.quartz.encoders.toHexKey
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
@ -435,7 +438,12 @@ open class Event(
.addSerializer(Gossip::class.java, GossipSerializer())
.addDeserializer(Gossip::class.java, GossipDeserializer())
.addDeserializer(Response::class.java, ResponseDeserializer())
.addDeserializer(Request::class.java, RequestDeserializer()),
.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 =
@ -452,7 +460,7 @@ open class Event(
sig = jsonObject.get("sig").asText(),
)
private inline fun <reified R> JsonNode.toTypedArray(transform: (JsonNode) -> R): Array<R> = Array(size()) { transform(get(it)) }
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)

View File

@ -21,6 +21,7 @@
package com.vitorpamplona.quartz.events
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.nip46.NostrConnectEvent
class EventFactory {
companion object {
@ -120,6 +121,7 @@ class EventFactory {
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)

View File

@ -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.events.nip46
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
abstract class BunkerMessage {
abstract fun countMemory(): Long
class BunkerMessageDeserializer : StdDeserializer<BunkerMessage>(BunkerMessage::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): BunkerMessage {
val jsonObject: JsonNode = jp.codec.readTree(jp)
val isRequest = jsonObject.has("method")
if (isRequest) {
return jp.codec.treeToValue(jsonObject, BunkerRequest::class.java)
} else {
return jp.codec.treeToValue(jsonObject, BunkerResponse::class.java)
}
}
}
}

View File

@ -0,0 +1,89 @@
/**
* 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.nip46
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
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.ser.std.StdSerializer
import com.vitorpamplona.quartz.events.Event.Companion.toTypedArray
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
open class BunkerRequest(
val id: String,
val method: String,
val params: Array<String> = emptyArray(),
) : BunkerMessage() {
override fun countMemory(): Long =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
id.bytesUsedInMemory() +
method.bytesUsedInMemory() +
params.sumOf { pointerSizeInBytes + it.bytesUsedInMemory() }
class BunkerRequestDeserializer : StdDeserializer<BunkerRequest>(BunkerRequest::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): BunkerRequest {
val jsonObject: JsonNode = jp.codec.readTree(jp)
val id = jsonObject.get("id").asText().intern()
val method = jsonObject.get("method").asText().intern()
val params = jsonObject.get("params")?.toTypedArray { it.asText().intern() } ?: emptyArray()
return when (method) {
BunkerRequestConnect.METHOD_NAME -> BunkerRequestConnect.parse(id, params)
BunkerRequestGetPublicKey.METHOD_NAME -> BunkerRequestGetPublicKey.parse(id, params)
BunkerRequestGetRelays.METHOD_NAME -> BunkerRequestGetRelays.parse(id, params)
BunkerRequestNip04Decrypt.METHOD_NAME -> BunkerRequestNip04Decrypt.parse(id, params)
BunkerRequestNip04Encrypt.METHOD_NAME -> BunkerRequestNip04Encrypt.parse(id, params)
BunkerRequestNip44Decrypt.METHOD_NAME -> BunkerRequestNip44Decrypt.parse(id, params)
BunkerRequestNip44Encrypt.METHOD_NAME -> BunkerRequestNip44Encrypt.parse(id, params)
BunkerRequestPing.METHOD_NAME -> BunkerRequestPing.parse(id, params)
BunkerRequestSign.METHOD_NAME -> BunkerRequestSign.parse(id, params)
else -> BunkerRequest(id, method, params)
}
}
}
class BunkerRequestSerializer : StdSerializer<BunkerRequest>(BunkerRequest::class.java) {
override fun serialize(
value: BunkerRequest,
gen: JsonGenerator,
provider: SerializerProvider,
) {
gen.writeStartObject()
gen.writeStringField("id", value.id)
gen.writeStringField("method", value.method)
gen.writeArrayFieldStart("params")
value.params.forEach {
gen.writeString(it)
}
gen.writeEndArray()
gen.writeEndObject()
}
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.encoders.HexKey
import java.util.UUID
class BunkerRequestConnect(
id: String = UUID.randomUUID().toString(),
val remoteKey: HexKey,
val secret: HexKey? = null,
val permissions: String? = null,
) : BunkerRequest(id, METHOD_NAME, listOfNotNull(remoteKey, secret, permissions).toTypedArray()) {
companion object {
val METHOD_NAME = "connect"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestConnect = BunkerRequestConnect(id, params[0], params.getOrNull(1), params.getOrNull(2))
}
}
public fun <T> Array<T>.getOrNull(index: Int): T? = if (index in 0..<size) get(index) else null

View File

@ -0,0 +1,36 @@
/**
* 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.nip46
import java.util.UUID
class BunkerRequestGetPublicKey(
id: String = UUID.randomUUID().toString(),
) : BunkerRequest(id, METHOD_NAME, arrayOf()) {
companion object {
val METHOD_NAME = "get_public_key"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestGetPublicKey = BunkerRequestGetPublicKey(id)
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.nip46
import java.util.UUID
class BunkerRequestGetRelays(
id: String = UUID.randomUUID().toString(),
) : BunkerRequest(id, METHOD_NAME, arrayOf()) {
companion object {
val METHOD_NAME = "get_relays"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestGetRelays = BunkerRequestGetRelays(id)
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.encoders.HexKey
import java.util.UUID
class BunkerRequestNip04Decrypt(
id: String = UUID.randomUUID().toString(),
val pubKey: HexKey,
val ciphertext: String,
) : BunkerRequest(id, METHOD_NAME, listOfNotNull(pubKey, ciphertext).toTypedArray()) {
companion object {
val METHOD_NAME = "nip04_decrypt"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestNip04Decrypt = BunkerRequestNip04Decrypt(id, params[0], params.get(1))
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.encoders.HexKey
import java.util.UUID
class BunkerRequestNip04Encrypt(
id: String = UUID.randomUUID().toString(),
val pubKey: HexKey,
val message: String,
) : BunkerRequest(id, METHOD_NAME, listOfNotNull(pubKey, message).toTypedArray()) {
companion object {
val METHOD_NAME = "nip04_encrypt"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestNip04Encrypt = BunkerRequestNip04Encrypt(id, params[0], params.get(1))
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.encoders.HexKey
import java.util.UUID
class BunkerRequestNip44Decrypt(
id: String = UUID.randomUUID().toString(),
val pubKey: HexKey,
val ciphertext: String,
) : BunkerRequest(id, METHOD_NAME, listOfNotNull(pubKey, ciphertext).toTypedArray()) {
companion object {
val METHOD_NAME = "nip44_decrypt"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestNip44Decrypt = BunkerRequestNip44Decrypt(id, params[0], params.get(1))
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.encoders.HexKey
import java.util.UUID
class BunkerRequestNip44Encrypt(
id: String = UUID.randomUUID().toString(),
val pubKey: HexKey,
val message: String,
) : BunkerRequest(id, METHOD_NAME, listOfNotNull(pubKey, message).toTypedArray()) {
companion object {
val METHOD_NAME = "nip44_encrypt"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestNip44Encrypt = BunkerRequestNip44Encrypt(id, params[0], params.get(1))
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.nip46
import java.util.UUID
class BunkerRequestPing(
id: String = UUID.randomUUID().toString(),
) : BunkerRequest(id, METHOD_NAME, arrayOf()) {
companion object {
val METHOD_NAME = "ping"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestPing = BunkerRequestPing(id)
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.events.Event
import java.util.UUID
class BunkerRequestSign(
id: String = UUID.randomUUID().toString(),
val event: Event,
) : BunkerRequest(id, METHOD_NAME, arrayOf(event.toJson())) {
companion object {
val METHOD_NAME = "sign_event"
fun parse(
id: String,
params: Array<String>,
): BunkerRequestSign = BunkerRequestSign(id, Event.fromJson(params[0]))
}
}

View File

@ -0,0 +1,103 @@
/**
* 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.nip46
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
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.ser.std.StdSerializer
import com.vitorpamplona.quartz.encoders.HexValidator
import com.vitorpamplona.quartz.utils.bytesUsedInMemory
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
open class BunkerResponse(
val id: String,
val result: String?,
val error: String?,
) : BunkerMessage() {
override fun countMemory(): Long =
3 * pointerSizeInBytes + // 3 fields, 4 bytes each reference (32bit)
id.bytesUsedInMemory() +
(result?.bytesUsedInMemory() ?: 0) +
(error?.bytesUsedInMemory() ?: 0)
class BunkerResponseDeserializer : StdDeserializer<BunkerResponse>(BunkerResponse::class.java) {
override fun deserialize(
jp: JsonParser,
ctxt: DeserializationContext,
): BunkerResponse {
val jsonObject: JsonNode = jp.codec.readTree(jp)
val id = jsonObject.get("id").asText().intern()
val result = jsonObject.get("result")?.asText()
val error = jsonObject.get("error")?.asText()
if (error != null) {
return BunkerResponseError.parse(id, result, error)
}
if (result != null) {
when (result) {
BunkerResponseAck.RESULT -> return BunkerResponseAck.parse(id, result, error)
BunkerResponsePong.RESULT -> return BunkerResponsePong.parse(id, result, error)
else -> {
if (result.length == 64 && HexValidator.isHex(result)) {
return BunkerResponsePublicKey.parse(id, result)
}
if (result.get(0) == '{') {
try {
return BunkerResponseEvent.parse(id, result)
} catch (_: Exception) {
}
try {
return BunkerResponseGetRelays.parse(id, result)
} catch (_: Exception) {
}
}
return BunkerResponse(id, result, error)
}
}
}
return BunkerResponse(id, result, error)
}
}
class BunkerResponseSerializer : StdSerializer<BunkerResponse>(BunkerResponse::class.java) {
override fun serialize(
value: BunkerResponse,
gen: JsonGenerator,
provider: SerializerProvider,
) {
gen.writeStartObject()
gen.writeStringField("id", value.id)
value.result?.let { gen.writeStringField("result", value.result) }
value.error?.let { gen.writeStringField("error", it) }
gen.writeEndObject()
}
}
}

View File

@ -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.events.nip46
import java.util.UUID
class BunkerResponseAck(
id: String = UUID.randomUUID().toString(),
) : BunkerResponse(id, RESULT, null) {
companion object {
val RESULT = "ack"
fun parse(
id: String,
result: String,
error: String? = null,
) = BunkerResponseAck(id)
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.nip46
import java.util.UUID
class BunkerResponseDecrypt(
id: String = UUID.randomUUID().toString(),
val plaintext: String,
) : BunkerResponse(id, plaintext, null) {
companion object {
fun parse(
id: String,
result: String,
error: String? = null,
) = BunkerResponseDecrypt(id, result)
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.nip46
import java.util.UUID
class BunkerResponseEncrypt(
id: String = UUID.randomUUID().toString(),
val ciphertext: String,
) : BunkerResponse(id, ciphertext, null) {
companion object {
fun parse(
id: String,
result: String,
error: String? = null,
) = BunkerResponseEncrypt(id, result)
}
}

View File

@ -0,0 +1,36 @@
/**
* 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.nip46
import java.util.UUID
class BunkerResponseError(
id: String = UUID.randomUUID().toString(),
error: String,
) : BunkerResponse(id, null, error) {
companion object {
fun parse(
id: String,
result: String? = null,
error: String,
) = BunkerResponseError(id, error)
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.events.Event
import java.util.UUID
class BunkerResponseEvent(
id: String = UUID.randomUUID().toString(),
val event: Event,
) : BunkerResponse(id, event.toJson(), null) {
companion object {
fun parse(
id: String,
result: String,
error: String? = null,
) = BunkerResponseEvent(id, Event.fromJson(result))
}
}

View File

@ -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.events.nip46
import com.fasterxml.jackson.core.type.TypeReference
import com.vitorpamplona.quartz.events.ContactListEvent.ReadWrite
import com.vitorpamplona.quartz.events.Event
import java.util.UUID
class BunkerResponseGetRelays(
id: String = UUID.randomUUID().toString(),
val relays: Map<String, ReadWrite>,
) : BunkerResponse(id, Event.mapper.writeValueAsString(relays), null) {
companion object {
fun parse(
id: String,
result: String,
error: String? = null,
) = BunkerResponseGetRelays(id, Event.mapper.readValue(result, object : TypeReference<Map<String, ReadWrite>>() {}))
}
}

View File

@ -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.events.nip46
import java.util.UUID
class BunkerResponsePong(
id: String = UUID.randomUUID().toString(),
) : BunkerResponse(id, RESULT, null) {
companion object {
val RESULT = "pong"
fun parse(
id: String,
result: String,
error: String? = null,
) = BunkerResponsePong(id)
}
}

View File

@ -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.events.nip46
import com.vitorpamplona.quartz.encoders.HexKey
import java.util.UUID
class BunkerResponsePublicKey(
id: String = UUID.randomUUID().toString(),
val pubkey: HexKey,
) : BunkerResponse(id, pubkey, null) {
companion object {
fun parse(
id: String,
result: String,
error: String? = null,
) = BunkerResponsePublicKey(id, result)
}
}

View File

@ -0,0 +1,108 @@
/**
* 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.nip46
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.Hex
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.HexValidator
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.pointerSizeInBytes
@Immutable
class NostrConnectEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig) {
@Transient private var decryptedContent: Map<HexKey, BunkerMessage> = mapOf()
override fun countMemory(): Long =
super.countMemory() +
pointerSizeInBytes + (decryptedContent.values.sumOf { pointerSizeInBytes + it.countMemory() })
override fun isContentEncoded() = true
private fun recipientPubKey() = tags.firstOrNull { it.size > 1 && it[0] == "p" }?.get(1)
fun recipientPubKeyBytes() = recipientPubKey()?.runCatching { Hex.decode(this) }?.getOrNull()
fun verifiedRecipientPubKey(): HexKey? {
val recipient = recipientPubKey()
return if (HexValidator.isHex(recipient)) {
recipient
} else {
null
}
}
fun talkingWith(oneSideHex: String): HexKey = if (pubKey == oneSideHex) verifiedRecipientPubKey() ?: pubKey else pubKey
fun plainContent(
signer: NostrSigner,
onReady: (BunkerMessage) -> Unit,
) {
decryptedContent[signer.pubKey]?.let {
onReady(it)
return
}
// decrypts using NIP-04 or NIP-44
signer.decrypt(content, talkingWith(signer.pubKey)) { retVal ->
val content = Event.mapper.readValue(retVal, BunkerMessage::class.java)
decryptedContent = decryptedContent + Pair(signer.pubKey, content)
onReady(content)
}
}
companion object {
const val KIND = 24133
const val ALT = "Nostr Connect Event"
fun create(
message: BunkerMessage,
remoteKey: HexKey,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (NostrConnectEvent) -> Unit,
) {
val tags =
arrayOf(
arrayOf("alt", ALT),
arrayOf("p", remoteKey),
)
signer.sign(createdAt, KIND, tags, "", onReady)
val encrypted = mapper.writeValueAsString(message)
signer.nip44Encrypt(encrypted, remoteKey) { content ->
signer.sign(createdAt, KIND, tags, content, onReady)
}
}
}
}