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:
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 {