damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit 708d2d7b38c5f2bac442ade4c6c965bbc3eb0ff8
parent adacdbb7648cd3b4a19b102eba036a7ecff07530
Author: William Casarin <jb55@jb55.com>
Date:   Sat,  6 Aug 2022 23:03:01 -0700

validation: actually validate events

Check to see if id and/or signature are good

Changelog-Changed: Check note ids and signatures on every note
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus/Nostr/NostrEvent.swift | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mdamus/Views/EventView.swift | 14++++++++++++++
2 files changed, 79 insertions(+), 4 deletions(-)

diff --git a/damus/Nostr/NostrEvent.swift b/damus/Nostr/NostrEvent.swift @@ -11,6 +11,12 @@ import secp256k1 import secp256k1_implementation import CryptoKit +enum ValidationResult: Decodable { + case ok + case bad_id + case bad_sig +} + struct OtherEvent { let event_id: String let relay_url: String @@ -67,7 +73,19 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible { var should_show_event: Bool { return !too_big } - + + var is_valid_id: Bool { + return calculate_event_id(ev: self) == self.id + } + + var is_valid: Bool { + return validity == .ok + } + + lazy var validity: ValidationResult = { + return validate_event(ev: self) + }() + private var _blocks: [Block]? = nil func blocks(_ privkey: String?) -> [Block] { if let bs = _blocks { @@ -127,7 +145,15 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible { if known_kind == .dm { return decrypted(privkey: privkey) ?? "*failed to decrypt content*" } - return content + + switch validity { + case .ok: + return content + case .bad_id: + return content + "\n\n*WARNING: invalid note id, could be forged!*" + case .bad_sig: + return content + "\n\n*WARNING: invalid signature, could be forged!*" + } } var description: String { @@ -336,7 +362,7 @@ func event_commitment(ev: NostrEvent, tags: String) -> String { return commit } -func calculate_event_id(ev: NostrEvent) -> String { +func calculate_event_commitment(ev: NostrEvent) -> Data { let tags_encoder = JSONEncoder() tags_encoder.outputFormatting = .withoutEscapingSlashes let tags_data = try! tags_encoder.encode(ev.tags) @@ -344,7 +370,12 @@ func calculate_event_id(ev: NostrEvent) -> String { let target = event_commitment(ev: ev, tags: tags) let target_data = target.data(using: .utf8)! - let hash = sha256(target_data) + return target_data +} + +func calculate_event_id(ev: NostrEvent) -> String { + let commitment = calculate_event_commitment(ev: ev) + let hash = sha256(commitment) return hex_encode(hash) } @@ -674,3 +705,33 @@ func aes_operation(operation: CCOperation, data: [UInt8], iv: [UInt8], shared_se } + + +func validate_event(ev: NostrEvent) -> ValidationResult { + let raw_id = sha256(calculate_event_commitment(ev: ev)) + let id = hex_encode(raw_id) + + if id != ev.id { + return .bad_id + } + + // TODO: implement verify + guard var sig64 = hex_decode(ev.sig)?.bytes else { + return .bad_sig + } + + guard var ev_pubkey = hex_decode(ev.pubkey)?.bytes else { + return .bad_sig + } + + let ctx = secp256k1.Context.raw + var xonly_pubkey = secp256k1_xonly_pubkey.init() + var ok = secp256k1_xonly_pubkey_parse(ctx, &xonly_pubkey, &ev_pubkey) != 0 + if !ok { + return .bad_sig + } + var raw_id_bytes = raw_id.bytes + + ok = secp256k1_schnorrsig_verify(ctx, &sig64, &raw_id_bytes, raw_id.count, &xonly_pubkey) > 0 + return ok ? .ok : .bad_sig +} diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift @@ -140,6 +140,7 @@ struct EventView: View { .padding([.leading], 2) } .contentShape(Rectangle()) + .background(event_validity_color(event.validity)) .id(event.id) .frame(minHeight: PFP_SIZE) .padding([.bottom], 4) @@ -147,6 +148,19 @@ struct EventView: View { } } +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 {