damus

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

NostrEvent.swift (23502B)


      1 //
      2 //  NostrEvent.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2022-04-11.
      6 //
      7 
      8 import Foundation
      9 import CommonCrypto
     10 import secp256k1
     11 import secp256k1_implementation
     12 import CryptoKit
     13 import NaturalLanguage
     14 
     15 
     16 enum ValidationResult: Decodable {
     17     case unknown
     18     case ok
     19     case bad_id
     20     case bad_sig
     21 }
     22 
     23     /*
     24 class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {
     25     // TODO: memory mapped db events
     26     private var note_data: UnsafeMutablePointer<ndb_note>
     27 
     28     init(data: UnsafeMutablePointer<ndb_note>) {
     29         self.note_data = data
     30     }
     31 
     32     var id: [UInt8] {
     33         let buffer = UnsafeBufferPointer(start: ndb_note_id(note_data), count: 32)
     34         return Array(buffer)
     35     }
     36 
     37     var content: String {
     38         String(cString: ndb_note_content(self.note_data))
     39     }
     40 
     41     var sig: [UInt8] {
     42         let buffer = UnsafeBufferPointer(start: ndb_note_signature(note_data), count: 64)
     43         return Array(buffer)
     44     }
     45 
     46     var tags: TagIterator
     47 
     48     let id: String
     49     let content: String
     50     let sig: String
     51     let tags: Tags
     52 
     53     //var boosted_by: String?
     54 
     55     // cached field for pow calc
     56     //var pow: Int?
     57 
     58     // custom flags for internal use
     59     //var flags: Int = 0
     60 
     61     let pubkey: String
     62     let created_at: UInt32
     63     let kind: UInt32
     64 
     65     // cached stuff
     66     private var _event_refs: [EventRef]? = nil
     67     var decrypted_content: String? = nil
     68     private var _blocks: Blocks? = nil
     69     private lazy var inner_event: NostrEventOld? = {
     70         return event_from_json(dat: self.content)
     71     }()
     72 
     73     static func == (lhs: NostrEventOld, rhs: NostrEventOld) -> Bool {
     74         return lhs.id == rhs.id
     75     }
     76 
     77     static func < (lhs: NostrEventOld, rhs: NostrEventOld) -> Bool {
     78         return lhs.created_at < rhs.created_at
     79     }
     80 
     81     func hash(into hasher: inout Hasher) {
     82         hasher.combine(id)
     83     }
     84 
     85     private enum CodingKeys: String, CodingKey {
     86         case id, sig, tags, pubkey, created_at, kind, content
     87     }
     88 
     89     static func owned_from_json(json: String) -> NostrEventOld? {
     90         let decoder = JSONDecoder()
     91         guard let dat = json.data(using: .utf8) else {
     92             return nil
     93         }
     94         guard let ev = try? decoder.decode(NostrEventOld.self, from: dat) else {
     95             return nil
     96         }
     97 
     98         return ev
     99     }
    100 
    101     init?(content: String, keypair: Keypair, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
    102 
    103         self.content = content
    104         self.pubkey = keypair.pubkey
    105         self.kind = kind
    106         self.tags = tags
    107         self.created_at = createdAt
    108 
    109         if let privkey = keypair.privkey {
    110             self.id = hex_encode(calculate_event_id(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content))
    111             self.sig = sign_id(privkey: privkey, id: self.id)
    112         } else {
    113             self.id = ""
    114             self.sig = ""
    115         }
    116     }
    117 }
    118 
    119 extension NostrEventOld {
    120     var is_textlike: Bool {
    121         return kind == 1 || kind == 42 || kind == 30023
    122     }
    123 
    124     var too_big: Bool {
    125         return known_kind != .longform && self.content.utf8.count > 16000
    126     }
    127 
    128     var should_show_event: Bool {
    129         return !too_big
    130     }
    131 
    132     func blocks(_ privkey: String?) -> Blocks {
    133         if let bs = _blocks {
    134             return bs
    135         }
    136         let blocks = get_blocks(content: self.get_content(privkey))
    137         self._blocks = blocks
    138         return blocks
    139     }
    140 
    141     func get_blocks(content: String) -> Blocks {
    142         return parse_note_content(content: content, tags: self.tags)
    143     }
    144 
    145 
    146     func get_inner_event(cache: EventCache) -> NostrEventOld? {
    147         guard self.known_kind == .boost else {
    148             return nil
    149         }
    150 
    151         if self.content == "", let ref = self.referenced_ids.first {
    152             return cache.lookup(ref.ref_id.string())
    153         }
    154 
    155         return self.inner_event
    156     }
    157 
    158     func event_refs(_ privkey: String?) -> [EventRef] {
    159         if let rs = _event_refs {
    160             return rs
    161         }
    162         let refs = interpret_event_refs(blocks: self.blocks(privkey).blocks, tags: self.tags)
    163         self._event_refs = refs
    164         return refs
    165     }
    166 
    167 
    168     func decrypted(privkey: String?) -> String? {
    169         if let decrypted_content = decrypted_content {
    170             return decrypted_content
    171         }
    172 
    173         guard let key = privkey else {
    174             return nil
    175         }
    176 
    177         guard let our_pubkey = privkey_to_pubkey(privkey: key) else {
    178             return nil
    179         }
    180 
    181         var pubkey = self.pubkey
    182         // This is our DM, we need to use the pubkey of the person we're talking to instead
    183         if our_pubkey == pubkey {
    184             guard let refkey = self.referenced_pubkeys.first else {
    185                 return nil
    186             }
    187 
    188             pubkey = refkey.ref_id
    189         }
    190 
    191         let dec = decrypt_dm(key, pubkey: pubkey, content: self.content, encoding: .base64)
    192         self.decrypted_content = dec
    193 
    194         return dec
    195     }
    196 
    197     func get_content(_ privkey: String?) -> String {
    198         if known_kind == .dm {
    199             return decrypted(privkey: privkey) ?? "*failed to decrypt content*"
    200         }
    201 
    202         return content
    203     }
    204 
    205     var description: String {
    206         return "NostrEvent { id: \(id) pubkey \(pubkey) kind \(kind) tags \(tags) content '\(content)' }"
    207     }
    208 
    209     var known_kind: NostrKind? {
    210         return NostrKind.init(rawValue: kind)
    211     }
    212 
    213     private func get_referenced_ids(key: String) -> [ReferencedId] {
    214         return damus.get_referenced_ids(tags: self.tags, key: key)
    215     }
    216 
    217     public func direct_replies(_ privkey: String?) -> [ReferencedId] {
    218         return event_refs(privkey).reduce(into: []) { acc, evref in
    219             if let direct_reply = evref.is_direct_reply {
    220                 acc.append(direct_reply)
    221             }
    222         }
    223     }
    224 
    225     public func thread_id(privkey: String?) -> String {
    226         for ref in event_refs(privkey) {
    227             if let thread_id = ref.is_thread_id {
    228                 return thread_id.ref_id
    229             }
    230         }
    231 
    232         return self.id
    233     }
    234 
    235     public func last_refid() -> ReferencedId? {
    236         var mlast: Int? = nil
    237         var i: Int = 0
    238         for tag in tags {
    239             if tag.count >= 2 && tag[0] == "e" {
    240                 mlast = i
    241             }
    242             i += 1
    243         }
    244 
    245         guard let last = mlast else {
    246             return nil
    247         }
    248 
    249         return tag_to_refid(tags[last])
    250     }
    251 
    252     public func references(id: String, key: AsciiCharacter) -> Bool {
    253         for tag in tags {
    254             if tag.count >= 2 && tag[0].matches_char(key) {
    255                 if tag[1] == id {
    256                     return true
    257                 }
    258             }
    259         }
    260 
    261         return false
    262     }
    263 
    264     func is_reply(_ privkey: String?) -> Bool {
    265         return event_is_reply(self.event_refs(privkey))
    266     }
    267 
    268     func note_language(_ privkey: String?) -> String? {
    269         // Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
    270         // and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
    271         let originalBlocks = blocks(privkey).blocks
    272         let originalOnlyText = originalBlocks.compactMap { $0.is_text }.joined(separator: " ")
    273 
    274         // Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
    275         let languageRecognizer = NLLanguageRecognizer()
    276         languageRecognizer.processString(originalOnlyText)
    277 
    278         guard let locale = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= 0.5 })?.key.rawValue else {
    279             return nil
    280         }
    281 
    282         // Remove the variant component and just take the language part as translation services typically only supports the variant-less language.
    283         // Moreover, speakers of one variant can generally understand other variants.
    284         return localeToLanguage(locale)
    285     }
    286 
    287     public var referenced_ids: [ReferencedId] {
    288         return get_referenced_ids(key: "e")
    289     }
    290 
    291     public var referenced_pubkeys: [ReferencedId] {
    292         return get_referenced_ids(key: "p")
    293     }
    294 
    295     public var referenced_hashtags: [ReferencedId] {
    296         return get_referenced_ids(key: "t")
    297     }
    298 
    299     var age: TimeInterval {
    300         let event_date = Date(timeIntervalSince1970: TimeInterval(created_at))
    301         return Date.now.timeIntervalSince(event_date)
    302     }
    303 }
    304      */
    305 
    306 func sign_id(privkey: String, id: String) -> String {
    307     let priv_key_bytes = try! privkey.bytes
    308     let key = try! secp256k1.Signing.PrivateKey(rawRepresentation: priv_key_bytes)
    309 
    310     // Extra params for custom signing
    311 
    312     var aux_rand = random_bytes(count: 64).bytes
    313     var digest = try! id.bytes
    314 
    315     // API allows for signing variable length messages
    316     let signature = try! key.schnorr.signature(message: &digest, auxiliaryRand: &aux_rand)
    317 
    318     return hex_encode(signature.rawRepresentation)
    319 }
    320 
    321 func decode_nostr_event(txt: String) -> NostrResponse? {
    322     return NostrResponse.owned_from_json(json: txt)
    323 }
    324 
    325 func encode_json<T: Encodable>(_ val: T) -> String? {
    326     let encoder = JSONEncoder()
    327     encoder.outputFormatting = .withoutEscapingSlashes
    328     return (try? encode_json_data(val)).map { String(decoding: $0, as: UTF8.self) }
    329 }
    330 
    331 func encode_json_data<T: Encodable>(_ val: T) throws -> Data {
    332     let encoder = JSONEncoder()
    333     encoder.outputFormatting = .withoutEscapingSlashes
    334     return try encoder.encode(val)
    335 }
    336 
    337 func decode_nostr_event_json(json: String) -> NostrEvent? {
    338     return NostrEvent.owned_from_json(json: json)
    339 }
    340 
    341 /*
    342 func decode_nostr_event_json(json: String) -> NostrEvent? {
    343     guard let json_str = json.cString(using: .utf8) else {
    344         return nil
    345     }
    346 
    347     // Allocate a double pointer (pointer to pointer) for ndb_note
    348     var notePtr: UnsafeMutablePointer<ndb_note>? = nil
    349 
    350     // Create the buffer
    351     var buf = [Int8](repeating: 0, count: 2<<18)
    352 
    353     // Call the C function
    354     let result = withUnsafeMutablePointer(to: &notePtr) { (ptr) -> Int32 in
    355         return ndb_note_from_json(json_str, Int32(json_str.count), ptr, &buf, Int32(buf.count))
    356     }
    357 
    358     guard result == 0, let note = notePtr?.pointee else {
    359         return nil
    360     }
    361 
    362     return .init(data: note)
    363 }
    364 */
    365 
    366 func decode_json<T: Decodable>(_ val: String) -> T? {
    367     return try? JSONDecoder().decode(T.self, from: Data(val.utf8))
    368 }
    369 
    370 func decode_data<T: Decodable>(_ data: Data) -> T? {
    371     let decoder = JSONDecoder()
    372     do {
    373         return try decoder.decode(T.self, from: data)
    374     } catch {
    375         print("decode_data failed for \(T.self): \(error)")
    376     }
    377 
    378     return nil
    379 }
    380 
    381 func event_commitment(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> String {
    382     let encoder = JSONEncoder()
    383     encoder.outputFormatting = .withoutEscapingSlashes
    384     let str_data = try! encoder.encode(content)
    385     let content = String(decoding: str_data, as: UTF8.self)
    386     
    387     let tags_encoder = JSONEncoder()
    388     tags_encoder.outputFormatting = .withoutEscapingSlashes
    389     let tags_data = try! tags_encoder.encode(tags)
    390     let tags = String(decoding: tags_data, as: UTF8.self)
    391 
    392     return "[0,\"\(pubkey.hex())\",\(created_at),\(kind),\(tags),\(content)]"
    393 }
    394 
    395 func calculate_event_commitment(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data {
    396     let target = event_commitment(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content)
    397     return target.data(using: .utf8)!
    398 }
    399 
    400 func calculate_event_id(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> NoteId {
    401     let commitment = calculate_event_commitment(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content)
    402     return NoteId(sha256(commitment))
    403 }
    404 
    405 
    406 func sha256(_ data: Data) -> Data {
    407     var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
    408     data.withUnsafeBytes {
    409         _ = CC_SHA256($0.baseAddress, CC_LONG(data.count), &hash)
    410     }
    411     return Data(hash)
    412 }
    413 
    414 func hexchar(_ val: UInt8) -> UInt8 {
    415     if val < 10 {
    416         return 48 + val;
    417     }
    418     if val < 16 {
    419         return 97 + val - 10;
    420     }
    421     assertionFailure("impossiburu")
    422     return 0
    423 }
    424 
    425 func random_bytes(count: Int) -> Data {
    426     var bytes = [Int8](repeating: 0, count: count)
    427     guard
    428         SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes) == errSecSuccess
    429     else {
    430         fatalError("can't copy secure random data")
    431     }
    432     return Data(bytes: bytes, count: count)
    433 }
    434 
    435 func make_boost_event(keypair: FullKeypair, boosted: NostrEvent) -> NostrEvent? {
    436     var tags = Array(boosted.referenced_pubkeys).map({ pk in pk.tag })
    437 
    438     tags.append(["e", boosted.id.hex(), "", "root"])
    439     tags.append(["p", boosted.pubkey.hex()])
    440 
    441     let content = event_to_json(ev: boosted)
    442     return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 6, tags: tags)
    443 }
    444 
    445 func make_like_event(keypair: FullKeypair, liked: NostrEvent, content: String = "🤙") -> NostrEvent? {
    446     var tags = liked.tags.reduce(into: [[String]]()) { ts, tag in
    447         guard tag.count >= 2,
    448               (tag[0].matches_char("e") || tag[0].matches_char("p")) else {
    449             return
    450         }
    451         ts.append(tag.strings())
    452     }
    453 
    454     tags.append(["e", liked.id.hex()])
    455     tags.append(["p", liked.pubkey.hex()])
    456 
    457     return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 7, tags: tags)
    458 }
    459 
    460 func generate_private_keypair(our_privkey: Privkey, id: NoteId, created_at: UInt32) -> FullKeypair? {
    461     let to_hash = our_privkey.hex() + id.hex() + String(created_at)
    462     guard let dat = to_hash.data(using: .utf8) else {
    463         return nil
    464     }
    465     let privkey_bytes = sha256(dat)
    466     let privkey = Privkey(privkey_bytes)
    467     guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
    468 
    469     return FullKeypair(pubkey: pubkey, privkey: privkey)
    470 }
    471 
    472 func uniq<T: Hashable>(_ xs: [T]) -> [T] {
    473     var s = Set<T>()
    474     var ys: [T] = []
    475     
    476     for x in xs {
    477         if s.contains(x) {
    478             continue
    479         }
    480         s.insert(x)
    481         ys.append(x)
    482     }
    483     
    484     return ys
    485 }
    486 
    487 func gather_reply_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] {
    488     var ids: [RefId] = from.referenced_ids.first.map({ ref in [ .event(ref) ] }) ?? []
    489 
    490     let pks = from.referenced_pubkeys.reduce(into: [RefId]()) { rs, pk in
    491         if pk == our_pubkey {
    492             return
    493         }
    494         rs.append(.pubkey(pk))
    495     }
    496 
    497     ids.append(.event(from.id))
    498     ids.append(contentsOf: uniq(pks))
    499 
    500     if from.pubkey != our_pubkey {
    501         ids.append(.pubkey(from.pubkey))
    502     }
    503 
    504     return ids
    505 }
    506 
    507 func gather_quote_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] {
    508     var ids: [RefId] = [.quote(from.id.quote_id)]
    509     if from.pubkey != our_pubkey {
    510         ids.append(.pubkey(from.pubkey))
    511     }
    512     return ids
    513 }
    514 
    515 func event_from_json(dat: String) -> NostrEvent? {
    516     return NostrEvent.owned_from_json(json: dat)
    517 }
    518 
    519 func event_to_json(ev: NostrEvent) -> String {
    520     let encoder = JSONEncoder()
    521     guard let res = try? encoder.encode(ev) else {
    522         return "{}"
    523     }
    524     guard let str = String(data: res, encoding: .utf8) else {
    525         return "{}"
    526     }
    527     return str
    528 }
    529 
    530 func decrypt_dm(_ privkey: Privkey?, pubkey: Pubkey, content: String, encoding: EncEncoding) -> String? {
    531     guard let privkey = privkey else {
    532         return nil
    533     }
    534     guard let shared_sec = get_shared_secret(privkey: privkey, pubkey: pubkey) else {
    535         return nil
    536     }
    537     guard let dat = (encoding == .base64 ? decode_dm_base64(content) : decode_dm_bech32(content)) else {
    538         return nil
    539     }
    540     guard let dat = aes_decrypt(data: dat.content, iv: dat.iv, shared_sec: shared_sec) else {
    541         return nil
    542     }
    543     return String(data: dat, encoding: .utf8)
    544 }
    545 
    546 func decrypt_note(our_privkey: Privkey, their_pubkey: Pubkey, enc_note: String, encoding: EncEncoding) -> NostrEvent? {
    547     guard let dec = decrypt_dm(our_privkey, pubkey: their_pubkey, content: enc_note, encoding: encoding) else {
    548         return nil
    549     }
    550     
    551     return decode_nostr_event_json(json: dec)
    552 }
    553 
    554 func get_shared_secret(privkey: Privkey, pubkey: Pubkey) -> [UInt8]? {
    555     let privkey_bytes = privkey.bytes
    556     var pk_bytes = pubkey.bytes
    557 
    558     pk_bytes.insert(2, at: 0)
    559     
    560     var publicKey = secp256k1_pubkey()
    561     var shared_secret = [UInt8](repeating: 0, count: 32)
    562 
    563     var ok =
    564         secp256k1_ec_pubkey_parse(
    565             secp256k1.Context.raw,
    566             &publicKey,
    567             pk_bytes,
    568             pk_bytes.count) != 0
    569 
    570     if !ok {
    571         return nil
    572     }
    573 
    574     ok = secp256k1_ecdh(
    575         secp256k1.Context.raw,
    576         &shared_secret,
    577         &publicKey,
    578         privkey_bytes, {(output,x32,_,_) in
    579             memcpy(output,x32,32)
    580             return 1
    581         }, nil) != 0
    582 
    583     if !ok {
    584         return nil
    585     }
    586 
    587     return shared_secret
    588 }
    589 
    590 enum EncEncoding {
    591     case base64
    592     case bech32
    593 }
    594 
    595 struct DirectMessageBase64 {
    596     let content: [UInt8]
    597     let iv: [UInt8]
    598 }
    599 
    600 
    601 
    602 func encode_dm_bech32(content: [UInt8], iv: [UInt8]) -> String {
    603     let content_bech32 = bech32_encode(hrp: "pzap", content)
    604     let iv_bech32 = bech32_encode(hrp: "iv", iv)
    605     return content_bech32 + "_" + iv_bech32
    606 }
    607 
    608 func decode_dm_bech32(_ all: String) -> DirectMessageBase64? {
    609     let parts = all.split(separator: "_")
    610     guard parts.count == 2 else {
    611         return nil
    612     }
    613     
    614     let content_bech32 = String(parts[0])
    615     let iv_bech32 = String(parts[1])
    616     
    617     guard let content_tup = try? bech32_decode(content_bech32) else {
    618         return nil
    619     }
    620     guard let iv_tup = try? bech32_decode(iv_bech32) else {
    621         return nil
    622     }
    623     guard content_tup.hrp == "pzap" else {
    624         return nil
    625     }
    626     guard iv_tup.hrp == "iv" else {
    627         return nil
    628     }
    629     
    630     return DirectMessageBase64(content: content_tup.data.bytes, iv: iv_tup.data.bytes)
    631 }
    632 
    633 func encode_dm_base64(content: [UInt8], iv: [UInt8]) -> String {
    634     let content_b64 = base64_encode(content)
    635     let iv_b64 = base64_encode(iv)
    636     return content_b64 + "?iv=" + iv_b64
    637 }
    638 
    639 func decode_dm_base64(_ all: String) -> DirectMessageBase64? {
    640     let splits = Array(all.split(separator: "?"))
    641 
    642     if splits.count != 2 {
    643         return nil
    644     }
    645 
    646     guard let content = base64_decode(String(splits[0])) else {
    647         return nil
    648     }
    649 
    650     var sec = String(splits[1])
    651     if !sec.hasPrefix("iv=") {
    652         return nil
    653     }
    654 
    655     sec = String(sec.dropFirst(3))
    656     guard let iv = base64_decode(sec) else {
    657         return nil
    658     }
    659 
    660     return DirectMessageBase64(content: content, iv: iv)
    661 }
    662 
    663 func base64_encode(_ content: [UInt8]) -> String {
    664     return Data(content).base64EncodedString()
    665 }
    666 
    667 func base64_decode(_ content: String) -> [UInt8]? {
    668     guard let dat = Data(base64Encoded: content) else {
    669         return nil
    670     }
    671     return dat.bytes
    672 }
    673 
    674 func aes_decrypt(data: [UInt8], iv: [UInt8], shared_sec: [UInt8]) -> Data? {
    675     return aes_operation(operation: CCOperation(kCCDecrypt), data: data, iv: iv, shared_sec: shared_sec)
    676 }
    677 
    678 func aes_encrypt(data: [UInt8], iv: [UInt8], shared_sec: [UInt8]) -> Data? {
    679     return aes_operation(operation: CCOperation(kCCEncrypt), data: data, iv: iv, shared_sec: shared_sec)
    680 }
    681 
    682 func aes_operation(operation: CCOperation, data: [UInt8], iv: [UInt8], shared_sec: [UInt8]) -> Data? {
    683     let data_len = data.count
    684     let bsize = kCCBlockSizeAES128
    685     let len = Int(data_len) + bsize
    686     var decrypted_data = [UInt8](repeating: 0, count: len)
    687 
    688     let key_length = size_t(kCCKeySizeAES256)
    689     if shared_sec.count != key_length {
    690         assert(false, "unexpected shared_sec len: \(shared_sec.count) != 32")
    691         return nil
    692     }
    693 
    694     let algorithm: CCAlgorithm = UInt32(kCCAlgorithmAES128)
    695     let options:   CCOptions   = UInt32(kCCOptionPKCS7Padding)
    696 
    697     var num_bytes_decrypted :size_t = 0
    698 
    699     let status = CCCrypt(operation,  /*op:*/
    700                          algorithm,  /*alg:*/
    701                          options,    /*options:*/
    702                          shared_sec, /*key:*/
    703                          key_length, /*keyLength:*/
    704                          iv,         /*iv:*/
    705                          data,       /*dataIn:*/
    706                          data_len, /*dataInLength:*/
    707                          &decrypted_data,/*dataOut:*/
    708                          len,/*dataOutAvailable:*/
    709                          &num_bytes_decrypted/*dataOutMoved:*/
    710     )
    711 
    712     if UInt32(status) != UInt32(kCCSuccess) {
    713         return nil
    714     }
    715 
    716     return Data(bytes: decrypted_data, count: num_bytes_decrypted)
    717 
    718 }
    719 
    720 
    721 
    722 func validate_event(ev: NostrEvent) -> ValidationResult {
    723     let id = calculate_event_id(pubkey: ev.pubkey, created_at: ev.created_at, kind: ev.kind, tags: ev.tags.strings(), content: ev.content)
    724 
    725     if id != ev.id {
    726         return .bad_id
    727     }
    728 
    729     let ctx = secp256k1.Context.raw
    730     var xonly_pubkey = secp256k1_xonly_pubkey.init()
    731 
    732     var ev_pubkey = ev.pubkey.id.bytes
    733 
    734     var ok = secp256k1_xonly_pubkey_parse(ctx, &xonly_pubkey, &ev_pubkey) != 0
    735     if !ok {
    736         return .bad_sig
    737     }
    738 
    739     var sig = ev.sig.data.bytes
    740     var idbytes = id.id.bytes
    741 
    742     ok = secp256k1_schnorrsig_verify(ctx, &sig, &idbytes, 32, &xonly_pubkey) > 0
    743     return ok ? .ok : .bad_sig
    744 }
    745 
    746 func first_eref_mention(ev: NostrEvent, keypair: Keypair) -> Mention<NoteId>? {
    747     let blocks = ev.blocks(keypair).blocks.filter { block in
    748         guard case .mention(let mention) = block else {
    749                 return false
    750             }
    751 
    752         switch mention.ref {
    753         case .note, .nevent:
    754             return true
    755         default:
    756             return false
    757         }
    758     }
    759     
    760     /// MARK: - Preview
    761     if let firstBlock = blocks.first,
    762        case .mention(let mention) = firstBlock {
    763         switch mention.ref {
    764         case .note(let note_id):
    765             return .note(note_id)
    766         case .nevent(let nevent):
    767             return .note(nevent.noteid)
    768         default:
    769             return nil
    770         }
    771     }
    772     return nil
    773 }
    774 
    775 func separate_invoices(ev: NostrEvent, keypair: Keypair) -> [Invoice]? {
    776     let invoiceBlocks: [Invoice] = ev.blocks(keypair).blocks.reduce(into: []) { invoices, block in
    777         guard case .invoice(let invoice) = block else {
    778             return
    779         }
    780         invoices.append(invoice)
    781     }
    782     return invoiceBlocks.isEmpty ? nil : invoiceBlocks
    783 }
    784 
    785 /**
    786  Transforms a `NostrEvent` of known kind `NostrKind.like`to a human-readable emoji.
    787  If the known kind is not a `NostrKind.like`, it will return `nil`.
    788  If the event content is an empty string or `+`, it will map that to a heart ❤️ emoji.
    789  If the event content is a "-", it will map that to a dislike 👎 emoji.
    790  Otherwise, it will return the event content at face value without transforming it.
    791  */
    792 func to_reaction_emoji(ev: NostrEvent) -> String? {
    793     guard ev.known_kind == NostrKind.like else {
    794         return nil
    795     }
    796 
    797     switch ev.content {
    798     case "", "+":
    799         return "❤️"
    800     case "-":
    801         return "👎"
    802     default:
    803         return ev.content
    804     }
    805 }
    806 
    807 extension NostrEvent {
    808     /// The mutelist for a given event
    809     ///
    810     /// If the event is not a mutelist it will return `nil`.
    811     var mute_list: Set<MuteItem>? {
    812         if (self.kind == NostrKind.list_deprecated.rawValue && self.referenced_params.contains(where: { p in p.param.matches_str("mute") })) || self.kind == NostrKind.mute_list.rawValue {
    813             return Set(self.referenced_mute_items)
    814         } else {
    815             return nil
    816         }
    817     }
    818 }