commit 8785f31834266e2d12dfc1a797f29f6dc805e268
parent 104205394fe5e441644cd64d934940712dd90b77
Author: William Casarin <jb55@jb55.com>
Date: Wed, 4 Jan 2023 01:30:37 -0800
NIP05 Verification
Changelog-Added: Added NIP05 Verification
Diffstat:
11 files changed, 149 insertions(+), 51 deletions(-)
diff --git a/damus.xcodeproj/project.pbxproj b/damus.xcodeproj/project.pbxproj
@@ -115,6 +115,7 @@
4CACA9DC280C38C000D9BBE8 /* Profiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CACA9DB280C38C000D9BBE8 /* Profiles.swift */; };
4CB55EF3295E5D59007FD187 /* RecommendedRelayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */; };
4CB55EF5295E679D007FD187 /* UserRelaysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB55EF4295E679D007FD187 /* UserRelaysView.swift */; };
+ 4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB8838529656C8B00DC99E7 /* NIP05.swift */; };
4CD7641B28A1641400B6928F /* EndBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CD7641A28A1641400B6928F /* EndBlock.swift */; };
4CE4F8CD281352B30009DFBB /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F8CC281352B30009DFBB /* Notifications.swift */; };
4CE4F9DE2852768D00C00DD9 /* ConfigView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */; };
@@ -304,6 +305,7 @@
4CACA9DB280C38C000D9BBE8 /* Profiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Profiles.swift; sourceTree = "<group>"; };
4CB55EF2295E5D59007FD187 /* RecommendedRelayView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecommendedRelayView.swift; sourceTree = "<group>"; };
4CB55EF4295E679D007FD187 /* UserRelaysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserRelaysView.swift; sourceTree = "<group>"; };
+ 4CB8838529656C8B00DC99E7 /* NIP05.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NIP05.swift; sourceTree = "<group>"; };
4CD7641A28A1641400B6928F /* EndBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EndBlock.swift; sourceTree = "<group>"; };
4CE4F8CC281352B30009DFBB /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = "<group>"; };
4CE4F9DD2852768D00C00DD9 /* ConfigView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigView.swift; sourceTree = "<group>"; };
@@ -550,6 +552,7 @@
3165648A295B70D500C64604 /* LinkView.swift */,
4C3A1D3629637E0500558C0F /* PreviewCache.swift */,
64FBD06E296255C400D9D3B2 /* Theme.swift */,
+ 4CB8838529656C8B00DC99E7 /* NIP05.swift */,
);
path = Util;
sourceTree = "<group>";
@@ -874,6 +877,7 @@
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
4C5F9114283D694D0052CD1C /* FollowTarget.swift in Sources */,
+ 4CB8838629656C8B00DC99E7 /* NIP05.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -544,7 +544,9 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
return
}
+ var old_nip05: String? = nil
if let mprof = profiles.lookup_with_timestamp(id: ev.pubkey) {
+ old_nip05 = mprof.profile.nip05
if mprof.timestamp > ev.created_at {
// skip if we already have an newer profile
return
@@ -554,6 +556,20 @@ func process_metadata_event(profiles: Profiles, ev: NostrEvent) {
let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at)
profiles.add(id: ev.pubkey, profile: tprof)
+ if let nip05 = profile.nip05, old_nip05 != profile.nip05 {
+ Task.detached(priority: .background) {
+ let validated = await validate_nip05(pubkey: ev.pubkey, nip05_str: nip05)
+ if validated != nil {
+ print("validated nip05 for '\(nip05)'")
+ }
+
+ DispatchQueue.main.async {
+ profiles.validated[ev.pubkey] = validated
+ notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
+ }
+ }
+ }
+
// load pfps asap
let picture = tprof.profile.picture ?? robohash(ev.pubkey)
if let _ = URL(string: picture) {
diff --git a/damus/Nostr/Profiles.swift b/damus/Nostr/Profiles.swift
@@ -11,6 +11,11 @@ import UIKit
class Profiles {
var profiles: [String: TimestampedProfile] = [:]
+ var validated: [String: NIP05] = [:]
+
+ func is_validated(_ pk: String) -> NIP05? {
+ return validated[pk]
+ }
func add(id: String, profile: TimestampedProfile) {
profiles[id] = profile
diff --git a/damus/Util/NIP05.swift b/damus/Util/NIP05.swift
@@ -0,0 +1,65 @@
+//
+// NIP05.swift
+// damus
+//
+// Created by William Casarin on 2023-01-04.
+//
+
+import Foundation
+
+struct NIP05 {
+ let username: String
+ let host: String
+
+ var url: URL? {
+ URL(string: "https://\(host)/.well-known/nostr.json?name=\(username)")
+ }
+
+ static func parse(_ nip05: String) -> NIP05? {
+ let parts = nip05.split(separator: "@")
+ guard parts.count == 2 else {
+ return nil
+ }
+ return NIP05(username: String(parts[0]), host: String(parts[1]))
+ }
+}
+
+
+struct NIP05Response: Decodable {
+ let names: [String: String]
+ let relays: [String: [String]]?
+}
+
+enum NIP05Validation {
+ case invalid
+ case valid
+}
+
+func validate_nip05(pubkey: String, nip05_str: String) async -> NIP05? {
+ guard let nip05 = NIP05.parse(nip05_str) else {
+ return nil
+ }
+
+ guard let url = nip05.url else {
+ return nil
+ }
+
+ guard let ret = try? await URLSession.shared.data(from: url) else {
+ return nil
+ }
+ let dat = ret.0
+
+ guard let decoded = try? JSONDecoder().decode(NIP05Response.self, from: dat) else {
+ return nil
+ }
+
+ guard let stored_pk = decoded.names[nip05.username] else {
+ return nil
+ }
+
+ guard stored_pk == pubkey else {
+ return nil
+ }
+
+ return nip05
+}
diff --git a/damus/Views/ChatView.swift b/damus/Views/ChatView.swift
@@ -86,7 +86,7 @@ struct ChatView: View {
VStack(alignment: .leading) {
if just_started {
HStack {
- ProfileName(pubkey: event.pubkey, profile: damus_state.profiles.lookup(id: event.pubkey), contacts: damus_state.contacts, show_friend_confirmed: true)
+ ProfileName(pubkey: event.pubkey, profile: damus_state.profiles.lookup(id: event.pubkey), contacts: damus_state.contacts, show_friend_confirmed: true, profiles: damus_state.profiles)
.foregroundColor(colorScheme == .dark ? id_to_color(event.pubkey) : Color.black)
//.shadow(color: Color.black, radius: 2)
Text("\(format_relative_time(event.created_at))")
diff --git a/damus/Views/DMChatView.swift b/damus/Views/DMChatView.swift
@@ -40,7 +40,7 @@ struct DMChatView: View {
HStack {
ProfilePicView(pubkey: pubkey, size: 24, highlight: .none, profiles: damus_state.profiles)
- ProfileName(pubkey: pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: true)
+ ProfileName(pubkey: pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: true, profiles: damus_state.profiles)
}
}
.buttonStyle(PlainButtonStyle())
diff --git a/damus/Views/EditMetadataView.swift b/damus/Views/EditMetadataView.swift
@@ -15,19 +15,6 @@ func isHttpsUrl(_ string: String) -> Bool {
return urlTest.evaluate(with: string)
}
-struct NIP05 {
- let username: String
- let host: String
-
- static func parse(_ nip05: String) -> NIP05? {
- let parts = nip05.split(separator: "@")
- guard parts.count == 2 else {
- return nil
- }
- return NIP05(username: String(parts[0]), host: String(parts[1]))
- }
-}
-
func isImage(_ urlString: String) -> Bool {
let imageTypes = ["image/jpg", "image/jpeg", "image/png", "image/gif", "image/tiff", "image/bmp", "image/webp"]
diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift
@@ -176,7 +176,7 @@ struct EventView: View {
Image(systemName: "arrow.2.squarepath")
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
- ProfileName(pubkey: event.pubkey, profile: prof, contacts: damus.contacts, show_friend_confirmed: true)
+ ProfileName(pubkey: event.pubkey, profile: prof, contacts: damus.contacts, show_friend_confirmed: true, profiles: damus.profiles)
.font(.footnote.weight(.bold))
.foregroundColor(Color.gray)
Text("Boosted")
@@ -227,7 +227,7 @@ struct EventView: View {
}
}
- EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon, size: size)
+ EventProfileName(pubkey: pubkey, profile: profile, contacts: damus.contacts, show_friend_confirmed: show_friend_icon, profiles: damus.profiles, size: size)
if size != .selected {
Text("\(format_relative_time(event.created_at))")
.font(eventviewsize_to_font(size))
diff --git a/damus/Views/FollowingView.swift b/damus/Views/FollowingView.swift
@@ -24,7 +24,7 @@ struct FollowUserView: View {
VStack(alignment: .leading) {
let profile = damus_state.profiles.lookup(id: target.pubkey)
- ProfileName(pubkey: target.pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: false)
+ ProfileName(pubkey: target.pubkey, profile: profile, contacts: damus_state.contacts, show_friend_confirmed: false, profiles: damus_state.profiles)
if let about = profile?.about {
Text(FollowUserView.markdown.process(about))
.lineLimit(3)
diff --git a/damus/Views/ProfileName.swift b/damus/Views/ProfileName.swift
@@ -7,28 +7,6 @@
import SwiftUI
-struct ProfileFullName: View {
- let pubkey: String
- let profile: Profile?
- let contacts: Contacts
-
- @State var display_name: String?
-
- var body: some View {
- HStack {
- if let real_name = profile?.display_name {
- Text(real_name)
- .bold()
- ProfileName(pubkey: pubkey, profile: profile, prefix: "@", contacts: contacts, show_friend_confirmed: true)
- .font(.footnote)
- .foregroundColor(.gray)
- } else {
-// ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true)
- }
- }
- }
-}
-
struct ProfileName: View {
let pubkey: String
let profile: Profile?
@@ -36,23 +14,27 @@ struct ProfileName: View {
let prefix: String
let show_friend_confirmed: Bool
+ let profiles: Profiles
@State var display_name: String?
+ @State var nip05: NIP05?
- init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool) {
+ init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool, profiles: Profiles) {
self.pubkey = pubkey
self.profile = profile
self.prefix = ""
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
+ self.profiles = profiles
}
- init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool) {
+ init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool, profiles: Profiles) {
self.pubkey = pubkey
self.profile = profile
self.prefix = prefix
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
+ self.profiles = profiles
}
var friend_icon: String? {
@@ -71,12 +53,26 @@ struct ProfileName: View {
return nil
}
+ var nip05_color: Color {
+ contacts.is_friend(pubkey) ? .blue : .yellow
+ }
+
+ var current_nip05: NIP05? {
+ nip05 ?? profiles.is_validated(pubkey)
+ }
+
var body: some View {
- HStack {
+ HStack(spacing: 2) {
Text(prefix + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.font(.body)
.fontWeight(prefix == "@" ? .none : .bold)
- if let friend = friend_icon {
+ if let nip05 = current_nip05 {
+ Image(systemName: "checkmark.seal.fill")
+ .foregroundColor(nip05_color)
+ Text(nip05.host)
+ .foregroundColor(nip05_color)
+ }
+ if let friend = friend_icon, current_nip05 == nil {
Image(systemName: friend)
.foregroundColor(.gray)
}
@@ -87,6 +83,7 @@ struct ProfileName: View {
return
}
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
+ nip05 = profiles.is_validated(pubkey)
}
}
}
@@ -99,27 +96,31 @@ struct EventProfileName: View {
let prefix: String
let show_friend_confirmed: Bool
+ let profiles: Profiles
@State var display_name: String?
+ @State var nip05: NIP05?
let size: EventViewKind
- init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
+ init(pubkey: String, profile: Profile?, contacts: Contacts, show_friend_confirmed: Bool, profiles: Profiles, size: EventViewKind = .normal) {
self.pubkey = pubkey
self.profile = profile
self.prefix = ""
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
self.size = size
+ self.profiles = profiles
}
- init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool, size: EventViewKind = .normal) {
+ init(pubkey: String, profile: Profile?, prefix: String, contacts: Contacts, show_friend_confirmed: Bool, profiles: Profiles, size: EventViewKind = .normal) {
self.pubkey = pubkey
self.profile = profile
self.prefix = prefix
self.contacts = contacts
self.show_friend_confirmed = show_friend_confirmed
self.size = size
+ self.profiles = profiles
}
var friend_icon: String? {
@@ -137,12 +138,21 @@ struct EventProfileName: View {
return nil
}
+
+ var nip05_color: Color {
+ contacts.is_friend(pubkey) ? .blue : .yellow
+ }
+ var current_nip05: NIP05? {
+ nip05 ?? profiles.is_validated(pubkey)
+ }
+
var body: some View {
- HStack {
+ HStack(spacing: 2) {
if let real_name = profile?.display_name {
Text(real_name)
.font(.body.weight(.bold))
+ .padding([.trailing], 4)
Text("@" + String(display_name ?? Profile.displayName(profile: profile, pubkey: pubkey)))
.foregroundColor(.gray)
@@ -153,7 +163,16 @@ struct EventProfileName: View {
.fontWeight(.bold)
}
- if let frend = friend_icon {
+ if let nip05 = current_nip05 {
+ Image(systemName: "checkmark.seal.fill")
+ .foregroundColor(nip05_color)
+ if !contacts.is_friend(pubkey) {
+ Text(nip05.host)
+ .foregroundColor(nip05_color)
+ }
+ }
+
+ if let frend = friend_icon, current_nip05 == nil {
Label("", systemImage: frend)
.foregroundColor(.gray)
.font(.footnote)
@@ -165,6 +184,7 @@ struct EventProfileName: View {
return
}
display_name = Profile.displayName(profile: update.profile, pubkey: pubkey)
+ nip05 = profiles.is_validated(pubkey)
}
}
}
diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift
@@ -49,6 +49,7 @@ struct ProfileNameView: View {
let pubkey: String
let profile: Profile?
let contacts: Contacts
+ let profiles: Profiles
var body: some View {
Group {
@@ -56,7 +57,7 @@ struct ProfileNameView: View {
VStack(alignment: .leading) {
Text(real_name)
.font(.title3.weight(.bold))
- ProfileName(pubkey: pubkey, profile: profile, prefix: "@", contacts: contacts, show_friend_confirmed: true)
+ ProfileName(pubkey: pubkey, profile: profile, prefix: "@", contacts: contacts, show_friend_confirmed: true, profiles: profiles)
.font(.callout)
.foregroundColor(.gray)
KeyView(pubkey: pubkey)
@@ -64,7 +65,7 @@ struct ProfileNameView: View {
}
} else {
VStack(alignment: .leading) {
- ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true)
+ ProfileName(pubkey: pubkey, profile: profile, contacts: contacts, show_friend_confirmed: true, profiles: profiles)
.font(.title3.weight(.bold))
KeyView(pubkey: pubkey)
.pubkey_context_menu(bech32_pubkey: pubkey)
@@ -227,7 +228,7 @@ struct ProfileView: View {
.offset(y: -15.0) // Increase if set a frame
}
- ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts)
+ ProfileNameView(pubkey: profile.pubkey, profile: data, contacts: damus_state.contacts, profiles: damus_state.profiles)
//.padding(.bottom)
.padding(.top,-(pfp_size/2.0))