nip42: add initial relay auth support
Lightning-Invoice: lnbc1pjcpaakpp5gjs4f626hf8w6slx84xz3wwhlf309z503rjutckdxv6wwg5ldavsdqqcqzpgxqrrs0fppqjaxxw43p7em4g59vedv7pzl76kt0qyjfsp5qcp9de7a7t8h6zs5mcssfaqp4exrnkehqtg2hf0ary3z5cjnasvs9qyyssq55523e4h3cazhkv7f8jqf5qp0n8spykls49crsu5t3m636u3yj4qdqjkdl2nxf6jet5t2r2pfrxmm8rjpqjd3ylrzqq89m4gqt5l6ycqf92c7h Closes: https://github.com/damus-io/damus/issues/940 Signed-off-by: Charlie Fish <contact@charlie.fish> Signed-off-by: William Casarin <jb55@jb55.com> Changelog-Added: Add NIP-42 relay auth support
This commit is contained in:
committed by
William Casarin
parent
4c37bfc128
commit
84cfeb1604
14
damus/Nostr/NostrAuth.swift
Normal file
14
damus/Nostr/NostrAuth.swift
Normal file
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// NostrAuth.swift
|
||||
// damus
|
||||
//
|
||||
// Created by Charlie Fish on 12/18/23.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
func make_auth_request(keypair: FullKeypair, challenge_string: String, relay: Relay) -> NostrEvent? {
|
||||
let tags: [[String]] = [["relay", relay.descriptor.url.id],["challenge", challenge_string]]
|
||||
let event = NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 22242, tags: tags)
|
||||
return event
|
||||
}
|
||||
@@ -38,7 +38,8 @@ enum NostrRequest {
|
||||
case subscribe(NostrSubscribe)
|
||||
case unsubscribe(String)
|
||||
case event(NostrEvent)
|
||||
|
||||
case auth(NostrEvent)
|
||||
|
||||
var is_write: Bool {
|
||||
switch self {
|
||||
case .subscribe:
|
||||
@@ -47,6 +48,8 @@ enum NostrRequest {
|
||||
return false
|
||||
case .event:
|
||||
return true
|
||||
case .auth:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,11 @@ enum NostrResponse {
|
||||
case notice(String)
|
||||
case eose(String)
|
||||
case ok(CommandResult)
|
||||
|
||||
/// An [NIP-42](https://github.com/nostr-protocol/nips/blob/master/42.md) `auth` challenge.
|
||||
///
|
||||
/// The associated type of this case is the challenge string sent by the server.
|
||||
case auth(String)
|
||||
|
||||
var subid: String? {
|
||||
switch self {
|
||||
case .ok:
|
||||
@@ -34,6 +38,8 @@ enum NostrResponse {
|
||||
return sub_id
|
||||
case .notice:
|
||||
return nil
|
||||
case .auth(let challenge_string):
|
||||
return challenge_string
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +100,13 @@ enum NostrResponse {
|
||||
case NDB_TCE_NOTICE:
|
||||
free(data)
|
||||
return .notice("")
|
||||
case NDB_TCE_AUTH:
|
||||
defer { free(data) }
|
||||
|
||||
guard let challenge_string = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
|
||||
return nil
|
||||
}
|
||||
return .auth(challenge_string)
|
||||
default:
|
||||
free(data)
|
||||
return nil
|
||||
|
||||
@@ -57,6 +57,25 @@ enum RelayFlags: Int {
|
||||
case broken = 1
|
||||
}
|
||||
|
||||
enum RelayAuthenticationError {
|
||||
/// Only a public key was provided in keypair to sign challenge.
|
||||
///
|
||||
/// A private key is required to sign `auth` challenge.
|
||||
case no_private_key
|
||||
/// No keypair was provided to sign challenge.
|
||||
case no_key
|
||||
}
|
||||
enum RelayAuthenticationState: Equatable {
|
||||
/// No `auth` request has been made from this relay
|
||||
case none
|
||||
/// We have received an `auth` challenge, but have not yet replied to the challenge
|
||||
case pending
|
||||
/// We have received an `auth` challenge and replied with an `auth` event
|
||||
case verified
|
||||
/// We received an `auth` challenge but failed to reply to the challenge
|
||||
case error(RelayAuthenticationError)
|
||||
}
|
||||
|
||||
struct Limitations: Codable {
|
||||
let payment_required: Bool?
|
||||
|
||||
@@ -85,13 +104,15 @@ struct RelayMetadata: Codable {
|
||||
class Relay: Identifiable {
|
||||
let descriptor: RelayDescriptor
|
||||
let connection: RelayConnection
|
||||
|
||||
var authentication_state: RelayAuthenticationState
|
||||
|
||||
var flags: Int
|
||||
|
||||
init(descriptor: RelayDescriptor, connection: RelayConnection) {
|
||||
self.flags = 0
|
||||
self.descriptor = descriptor
|
||||
self.connection = connection
|
||||
self.authentication_state = RelayAuthenticationState.none
|
||||
}
|
||||
|
||||
var is_broken: Bool {
|
||||
|
||||
@@ -97,7 +97,7 @@ final class RelayConnection: ObservableObject {
|
||||
socket.send(.string(req))
|
||||
}
|
||||
|
||||
func send(_ req: NostrRequestType) {
|
||||
func send(_ req: NostrRequestType, callback: ((String) -> Void)? = nil) {
|
||||
switch req {
|
||||
case .typical(let req):
|
||||
guard let req = make_nostr_req(req) else {
|
||||
@@ -105,9 +105,11 @@ final class RelayConnection: ObservableObject {
|
||||
return
|
||||
}
|
||||
send_raw(req)
|
||||
callback?(req)
|
||||
|
||||
case .custom(let req):
|
||||
send_raw(req)
|
||||
callback?(req)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,9 +203,20 @@ func make_nostr_req(_ req: NostrRequest) -> String? {
|
||||
return make_nostr_unsubscribe_req(sub_id)
|
||||
case .event(let ev):
|
||||
return make_nostr_push_event(ev: ev)
|
||||
case .auth(let ev):
|
||||
return make_nostr_auth_event(ev: ev)
|
||||
}
|
||||
}
|
||||
|
||||
func make_nostr_auth_event(ev: NostrEvent) -> String? {
|
||||
guard let event = encode_json(ev) else {
|
||||
return nil
|
||||
}
|
||||
let encoded = "[\"AUTH\",\(event)]"
|
||||
print(encoded)
|
||||
return encoded
|
||||
}
|
||||
|
||||
func make_nostr_push_event(ev: NostrEvent) -> String? {
|
||||
guard let event = encode_json(ev) else {
|
||||
return nil
|
||||
|
||||
@@ -31,13 +31,17 @@ class RelayPool {
|
||||
var seen: Set<SeenEvent> = Set()
|
||||
var counts: [String: UInt64] = [:]
|
||||
var ndb: Ndb
|
||||
var keypair: Keypair?
|
||||
var message_received_function: (((String, RelayDescriptor)) -> Void)?
|
||||
var message_sent_function: (((String, Relay)) -> Void)?
|
||||
|
||||
private let network_monitor = NWPathMonitor()
|
||||
private let network_monitor_queue = DispatchQueue(label: "io.damus.network_monitor")
|
||||
private var last_network_status: NWPath.Status = .unsatisfied
|
||||
|
||||
init(ndb: Ndb) {
|
||||
init(ndb: Ndb, keypair: Keypair? = nil) {
|
||||
self.ndb = ndb
|
||||
self.keypair = keypair
|
||||
|
||||
network_monitor.pathUpdateHandler = { [weak self] path in
|
||||
if (path.status == .satisfied || path.status == .requiresConnection) && self?.last_network_status != path.status {
|
||||
@@ -121,6 +125,7 @@ class RelayPool {
|
||||
else { return }
|
||||
|
||||
let _ = self.ndb.process_event(str)
|
||||
self.message_received_function?((str, desc))
|
||||
})
|
||||
let relay = Relay(descriptor: desc, connection: conn)
|
||||
self.relays.append(relay)
|
||||
@@ -244,7 +249,9 @@ class RelayPool {
|
||||
continue
|
||||
}
|
||||
|
||||
relay.connection.send(req)
|
||||
relay.connection.send(req, callback: { str in
|
||||
self.message_sent_function?((str, relay))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +305,34 @@ class RelayPool {
|
||||
run_queue(relay_id)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Handle auth
|
||||
if case let .nostr_event(nostrResponse) = event,
|
||||
case let .auth(challenge_string) = nostrResponse {
|
||||
if let relay = get_relay(relay_id) {
|
||||
print("received auth request from \(relay.descriptor.url.id)")
|
||||
relay.authentication_state = .pending
|
||||
if let keypair {
|
||||
if let fullKeypair = keypair.to_full() {
|
||||
if let authRequest = make_auth_request(keypair: fullKeypair, challenge_string: challenge_string, relay: relay) {
|
||||
send(.auth(authRequest), to: [relay_id], skip_ephemeral: false)
|
||||
relay.authentication_state = .verified
|
||||
} else {
|
||||
print("failed to make auth request")
|
||||
}
|
||||
} else {
|
||||
print("keypair provided did not contain private key, can not sign auth request")
|
||||
relay.authentication_state = .error(.no_private_key)
|
||||
}
|
||||
} else {
|
||||
print("no keypair to reply to auth request")
|
||||
relay.authentication_state = .error(.no_key)
|
||||
}
|
||||
} else {
|
||||
print("no relay found for \(relay_id)")
|
||||
}
|
||||
}
|
||||
|
||||
for handler in handlers {
|
||||
handler.callback(relay_id, event)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user