commit 02fc065005738abbc84fe8102a48a0e0903f8624
parent 668b0a94df0b17c406b4476d2bd2e2dbae9af01a
Author: William Casarin <jb55@jb55.com>
Date: Sat, 15 Apr 2023 16:01:00 -0700
Always check signatures on profile events
These contain sensitive data (lightning addresses) and it would be
really bad if these were forged.
Changelog-Changed: Always check signatures of profile events
Diffstat:
10 files changed, 100 insertions(+), 85 deletions(-)
diff --git a/damus/Models/FollowersModel.swift b/damus/Models/FollowersModel.swift
@@ -82,7 +82,7 @@ class FollowersModel: ObservableObject {
if ev.known_kind == .contacts {
handle_contact_event(ev)
} else if ev.known_kind == .metadata {
- process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
+ process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
diff --git a/damus/Models/FollowingModel.swift b/damus/Models/FollowingModel.swift
@@ -62,7 +62,7 @@ class FollowingModel {
break
case .event(_, let ev):
if ev.kind == 0 {
- process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
+ process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
case .notice(let msg):
print("followingmodel notice: \(msg)")
diff --git a/damus/Models/HomeModel.swift b/damus/Models/HomeModel.swift
@@ -186,10 +186,6 @@ class HomeModel: ObservableObject {
}
func handle_channel_create(_ ev: NostrEvent) {
- guard ev.is_valid else {
- return
- }
-
self.channels[ev.id] = ev
}
@@ -212,10 +208,6 @@ class HomeModel: ObservableObject {
}
func handle_delete_event(_ ev: NostrEvent) {
- guard ev.is_valid else {
- return
- }
-
self.deleted_events.insert(ev.id)
}
@@ -237,7 +229,7 @@ class HomeModel: ObservableObject {
if let inner_ev = ev.inner_event {
boost_ev_id = inner_ev.id
- guard inner_ev.is_valid else {
+ guard validate_event(ev: inner_ev) == .ok else {
return
}
@@ -453,7 +445,7 @@ class HomeModel: ObservableObject {
}
func handle_metadata_event(_ ev: NostrEvent) {
- process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
+ process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
func get_last_event_of_kind(relay_id: String, kind: Int) -> NostrEvent? {
@@ -664,66 +656,98 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
print("-----")
}
-func process_metadata_event(our_pubkey: String, profiles: Profiles, ev: NostrEvent) {
- DispatchQueue.global(qos: .background).async {
- guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
+func process_metadata_profile(our_pubkey: String, profiles: Profiles, profile: Profile, ev: NostrEvent) {
+ if our_pubkey == ev.pubkey && (profile.deleted ?? false) {
+ DispatchQueue.main.async {
+ notify(.deleted_account, ())
+ }
+ 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
}
-
- DispatchQueue.main.async {
- if our_pubkey == ev.pubkey && (profile.deleted ?? false) {
- DispatchQueue.main.async {
- notify(.deleted_account, ())
- }
- 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
- }
+ let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at, event: ev)
+ 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)'")
}
-
- let tprof = TimestampedProfile(profile: profile, timestamp: ev.created_at, event: ev)
- 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
- profiles.nip05_pubkey[nip05] = ev.pubkey
- notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
- }
- }
+ DispatchQueue.main.async {
+ profiles.validated[ev.pubkey] = validated
+ profiles.nip05_pubkey[nip05] = ev.pubkey
+ notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
}
+ }
+ }
+
+ // load pfps asap
+ let picture = tprof.profile.picture ?? robohash(ev.pubkey)
+ if URL(string: picture) != nil {
+ DispatchQueue.main.async {
+ notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
+ }
+ }
+
+ let banner = tprof.profile.banner ?? ""
+ if URL(string: banner) != nil {
+ DispatchQueue.main.async {
+ notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
+ }
+ }
+
+ notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
+
+}
+
+func guard_valid_event(events: EventCache, ev: NostrEvent, callback: @escaping () -> Void) {
+ let validated = events.is_event_valid(ev.id)
+
+ switch validated {
+ case .unknown:
+ Task {
+ let result = validate_event(ev: ev)
- // load pfps asap
- let picture = tprof.profile.picture ?? robohash(ev.pubkey)
- if URL(string: picture) != nil {
- DispatchQueue.main.async {
- notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
+ DispatchQueue.main.async {
+ events.validation[ev.id] = result
+ guard result == .ok else {
+ return
}
+ callback()
}
-
- let banner = tprof.profile.banner ?? ""
- if URL(string: banner) != nil {
- DispatchQueue.main.async {
- notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
- }
+ }
+
+ case .ok:
+ callback()
+
+ case .bad_id: fallthrough
+ case .bad_sig:
+ break
+ }
+}
+
+func process_metadata_event(events: EventCache, our_pubkey: String, profiles: Profiles, ev: NostrEvent) {
+ guard_valid_event(events: events, ev: ev) {
+ DispatchQueue.global(qos: .background).async {
+ guard let profile: Profile = decode_data(Data(ev.content.utf8)) else {
+ return
}
- notify(.profile_updated, ProfileUpdate(pubkey: ev.pubkey, profile: profile))
+ DispatchQueue.main.async {
+ process_metadata_profile(our_pubkey: our_pubkey, profiles: profiles, profile: profile, ev: ev)
+ }
}
}
-
}
func robohash(_ pk: String) -> String {
diff --git a/damus/Models/ProfileModel.swift b/damus/Models/ProfileModel.swift
@@ -119,7 +119,7 @@ class ProfileModel: ObservableObject, Equatable {
} else if ev.known_kind == .contacts {
handle_profile_contact_event(ev)
} else if ev.known_kind == .metadata {
- process_metadata_event(our_pubkey: damus.pubkey, profiles: damus.profiles, ev: ev)
+ process_metadata_event(events: damus.events, our_pubkey: damus.pubkey, profiles: damus.profiles, ev: ev)
}
seen_event.insert(ev.id)
}
diff --git a/damus/Models/SearchHomeModel.swift b/damus/Models/SearchHomeModel.swift
@@ -161,7 +161,7 @@ func load_profiles(profiles_subid: String, relay_id: String, load: PubkeysToLoad
}
if ev.known_kind == .metadata {
- process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
+ process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
}
}
diff --git a/damus/Models/ThreadModel.swift b/damus/Models/ThreadModel.swift
@@ -129,7 +129,7 @@ class ThreadModel: ObservableObject {
}
if ev.known_kind == .metadata {
- process_metadata_event(our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
+ process_metadata_event(events: damus_state.events, our_pubkey: damus_state.pubkey, profiles: damus_state.profiles, ev: ev)
} else if ev.is_textlike {
self.add_event(ev, privkey: self.damus_state.keypair.privkey)
}
diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift
@@ -13,11 +13,15 @@ import CryptoKit
import NaturalLanguage
-
enum ValidationResult: Decodable {
+ case unknown
case ok
case bad_id
case bad_sig
+
+ var is_bad: Bool {
+ return self == .bad_id || self == .bad_sig
+ }
}
struct OtherEvent {
@@ -93,14 +97,6 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible, Equatable, Has
return calculate_event_id(ev: self) == self.id
}
- var is_valid: Bool {
- return validity == .ok
- }
-
- lazy var validity: ValidationResult = {
- return .ok //validate_event(ev: self)
- }()
-
private var _blocks: [Block]? = nil
func blocks(_ privkey: String?) -> [Block] {
if let bs = _blocks {
diff --git a/damus/Util/EventCache.swift b/damus/Util/EventCache.swift
@@ -15,6 +15,7 @@ class EventCache {
private var cancellable: AnyCancellable?
private var translations: [String: TranslateStatus] = [:]
private var artifacts: [String: NoteArtifacts] = [:]
+ var validation: [String: ValidationResult] = [:]
//private var thread_latest: [String: Int64]
@@ -26,6 +27,14 @@ class EventCache {
}
}
+ func is_event_valid(_ evid: String) -> ValidationResult {
+ guard let result = validation[evid] else {
+ return .unknown
+ }
+
+ return result
+ }
+
func store_translation_artifacts(evid: String, translated: TranslateStatus) {
self.translations[evid] = translated
}
diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift
@@ -69,19 +69,6 @@ func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos
return false
}
-func event_validity_color(_ validation: ValidationResult) -> some View {
- Group {
- switch validation {
- case .ok:
- EmptyView()
- case .bad_id:
- Color.orange.opacity(0.4)
- case .bad_sig:
- Color.red.opacity(0.4)
- }
- }
-}
-
extension View {
func pubkey_context_menu(bech32_pubkey: String) -> some View {
return self.contextMenu {
diff --git a/damus/Views/Events/TextEvent.swift b/damus/Views/Events/TextEvent.swift
@@ -37,7 +37,6 @@ struct TextEvent: View {
}
}
.contentShape(Rectangle())
- .background(event_validity_color(event.validity))
.id(event.id)
.frame(maxWidth: .infinity, minHeight: PFP_SIZE)
.padding([.bottom], 2)