Support for NIP-42

This commit is contained in:
Vitor Pamplona
2023-04-25 21:18:33 -04:00
parent 90955515d0
commit 14bc1fb7d4
8 changed files with 85 additions and 1 deletions

View File

@@ -52,12 +52,12 @@ Or get the latest APK from the [Releases Section](https://github.com/vitorpamplo
- [x] External Identity Support (NIP-39) - [x] External Identity Support (NIP-39)
- [x] Multiple Accounts - [x] Multiple Accounts
- [x] Markdown Support - [x] Markdown Support
- [x] Relay Authentication (NIP-42)
- [ ] Local Database - [ ] Local Database
- [ ] View Individual Reactions (Like, Boost, Zaps, Reports) per Post - [ ] View Individual Reactions (Like, Boost, Zaps, Reports) per Post
- [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51) - [ ] Bookmarks, Pinned Posts, Muted Events (NIP-51)
- [ ] Sensitive Content (NIP-36) - [ ] Sensitive Content (NIP-36)
- [ ] Relay Pages (NIP-11) - [ ] Relay Pages (NIP-11)
- [ ] Relay Authentication (NIP-42)
- [ ] Generic Tags (NIP-12) - [ ] Generic Tags (NIP-12)
- [ ] Proof of Work in the Phone (NIP-13, NIP-20) - [ ] Proof of Work in the Phone (NIP-13, NIP-20)
- [ ] Events with a Subject (NIP-14) - [ ] Events with a Subject (NIP-14)

View File

@@ -569,6 +569,12 @@ class Account(
LocalCache.consume(event) LocalCache.consume(event)
} }
fun createAuthEvent(relay: Relay, challenge: String): RelayAuthEvent? {
if (!isWriteable()) return null
return RelayAuthEvent.create(relay.url, challenge, loggedIn.privKey!!)
}
fun removePublicBookmark(note: Note) { fun removePublicBookmark(note: Note) {
if (!isWriteable()) return if (!isWriteable()) return

View File

@@ -14,8 +14,10 @@ import com.vitorpamplona.amethyst.service.model.ReportEvent
import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.RepostEvent
import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent
import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES import com.vitorpamplona.amethyst.service.relays.COMMON_FEED_TYPES
import com.vitorpamplona.amethyst.service.relays.Client
import com.vitorpamplona.amethyst.service.relays.EOSEAccount import com.vitorpamplona.amethyst.service.relays.EOSEAccount
import com.vitorpamplona.amethyst.service.relays.JsonFilter import com.vitorpamplona.amethyst.service.relays.JsonFilter
import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.service.relays.TypedFilter import com.vitorpamplona.amethyst.service.relays.TypedFilter
object NostrAccountDataSource : NostrDataSource("AccountData") { object NostrAccountDataSource : NostrDataSource("AccountData") {
@@ -113,4 +115,19 @@ object NostrAccountDataSource : NostrDataSource("AccountData") {
createAccountBookmarkListFilter() createAccountBookmarkListFilter()
).ifEmpty { null } ).ifEmpty { null }
} }
override fun auth(relay: Relay, challenge: String) {
super.auth(relay, challenge)
if (this::account.isInitialized) {
val event = account.createAuthEvent(relay, challenge)
if (event != null) {
Client.send(
event,
relay.url
)
}
}
}
} }

View File

@@ -126,6 +126,10 @@ abstract class NostrDataSource(val debugName: String) {
override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) { override fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) {
} }
override fun onAuth(relay: Relay, challenge: String) {
auth(relay, challenge)
}
} }
init { init {
@@ -221,4 +225,5 @@ abstract class NostrDataSource(val debugName: String) {
} }
abstract fun updateChannelFilters() abstract fun updateChannelFilters()
open fun auth(relay: Relay, challenge: String) = Unit
} }

View File

@@ -0,0 +1,34 @@
package com.vitorpamplona.amethyst.service.model
import com.vitorpamplona.amethyst.model.HexKey
import com.vitorpamplona.amethyst.model.toHexKey
import nostr.postr.Utils
import java.util.Date
class RelayAuthEvent(
id: HexKey,
pubKey: HexKey,
createdAt: Long,
tags: List<List<String>>,
content: String,
sig: HexKey
) : Event(id, pubKey, createdAt, kind, tags, content, sig) {
fun relay() = tags.firstOrNull() { it.size > 1 && it[0] == "relay" }?.get(1)
fun challenge() = tags.firstOrNull() { it.size > 1 && it[0] == "challenge" }?.get(1)
companion object {
const val kind = 22242
fun create(relay: String, challenge: String, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RelayAuthEvent {
val content = ""
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
val tags = listOf(
listOf("relay", relay),
listOf("challenge", challenge)
)
val id = generateId(pubKey, createdAt, kind, tags, content)
val sig = Utils.sign(id, privateKey)
return RelayAuthEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
}
}
}

View File

@@ -160,6 +160,14 @@ object Client : RelayPool.Listener {
} }
} }
override fun onAuth(relay: Relay, challenge: String) {
// Releases the Web thread for the new payload.
// May need to add a processing queue if processing new events become too costly.
GlobalScope.launch(Dispatchers.Default) {
listeners.forEach { it.onAuth(relay, challenge) }
}
}
fun subscribe(listener: Listener) { fun subscribe(listener: Listener) {
listeners = listeners.plus(listener) listeners = listeners.plus(listener)
} }
@@ -196,5 +204,7 @@ object Client : RelayPool.Listener {
* When an relay saves or rejects a new event. * When an relay saves or rejects a new event.
*/ */
open fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) = Unit open fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) = Unit
open fun onAuth(relay: Relay, challenge: String) = Unit
} }
} }

View File

@@ -122,6 +122,10 @@ class Relay(
// Log.w("Relay", "Relay on OK $url, $channel") // Log.w("Relay", "Relay on OK $url, $channel")
it.onSendResponse(this@Relay, msg[1].asString, msg[2].asBoolean, msg[3].asString) it.onSendResponse(this@Relay, msg[1].asString, msg[2].asBoolean, msg[3].asString)
} }
"AUTH" -> listeners.forEach {
// Log.w("Relay", "Relay AUTH $url, $channel")
it.onAuth(this@Relay, msg[1].asString)
}
else -> listeners.forEach { else -> listeners.forEach {
// Log.w("Relay", "Relay something else $url, $channel") // Log.w("Relay", "Relay something else $url, $channel")
it.onError( it.onError(
@@ -272,6 +276,8 @@ class Relay(
fun onSendResponse(relay: Relay, eventId: String, success: Boolean, message: String) fun onSendResponse(relay: Relay, eventId: String, success: Boolean, message: String)
fun onAuth(relay: Relay, challenge: String)
/** /**
* Connected to or disconnected from a relay * Connected to or disconnected from a relay
* *

View File

@@ -93,6 +93,8 @@ object RelayPool : Relay.Listener {
fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?) fun onRelayStateChange(type: Relay.Type, relay: Relay, channel: String?)
fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay) fun onSendResponse(eventId: String, success: Boolean, message: String, relay: Relay)
fun onAuth(relay: Relay, challenge: String)
} }
override fun onEvent(relay: Relay, subscriptionId: String, event: Event) { override fun onEvent(relay: Relay, subscriptionId: String, event: Event) {
@@ -113,6 +115,10 @@ object RelayPool : Relay.Listener {
listeners.forEach { it.onSendResponse(eventId, success, message, relay) } listeners.forEach { it.onSendResponse(eventId, success, message, relay) }
} }
override fun onAuth(relay: Relay, challenge: String) {
listeners.forEach { it.onAuth(relay, challenge) }
}
// Observers line up here. // Observers line up here.
val live: RelayPoolLiveData = RelayPoolLiveData(this) val live: RelayPoolLiveData = RelayPoolLiveData(this)