damus

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

Bech32Object.swift (11970B)


      1 //
      2 //  Bech32Object.swift
      3 //  damus
      4 //
      5 //  Created by William Casarin on 2023-01-28.
      6 //
      7 
      8 import Foundation
      9 
     10 fileprivate extension String {
     11     /// Failable initializer to build a Swift.String from a C-backed `str_block_t`.
     12     init?(_ s: str_block_t) {
     13         let bytes = Data(bytes: s.str, count: Int(s.len))
     14         self.init(bytes: bytes, encoding: .utf8)
     15     }
     16 }
     17 
     18 struct NEvent : Equatable, Hashable {
     19     let noteid: NoteId
     20     let relays: [RelayURL]
     21     let author: Pubkey?
     22     let kind: UInt32?
     23     
     24     init(noteid: NoteId, relays: [RelayURL], author: Pubkey? = nil, kind: UInt32? = nil) {
     25         self.noteid = noteid
     26         self.relays = relays
     27         self.author = author
     28         self.kind = kind
     29     }
     30 
     31     init(event: NostrEvent, relays: [RelayURL]) {
     32         self.init(noteid: event.id, relays: relays, author: event.pubkey, kind: event.kind)
     33     }
     34 }
     35 
     36 struct NProfile : Equatable, Hashable {
     37     let author: Pubkey
     38     let relays: [RelayURL]
     39 }
     40 
     41 struct NAddr : Equatable, Hashable {
     42     let identifier: String
     43     let author: Pubkey
     44     let relays: [RelayURL]
     45     let kind: UInt32
     46 }
     47 
     48 extension ndb_relays {
     49     func as_urls() -> [RelayURL] {
     50         var urls = [RelayURL]()
     51 
     52         //
     53         // This is so incredibly dumb but it's just what the Swift <-> C bridge
     54         // does and I don't have a better way that doesn't involve complicated 
     55         // and slow stuff like reflection
     56         //
     57         let (r0,r1,r2,r3,r4,r5,r6,r7,r8,r9,r10,r11,r12,r13,r14,r15,r16,r17,r18,r19,r20,r21,r22,r23) = self.relays
     58 
     59         for i in 0..<self.num_relays {
     60             switch i {
     61             case 0:  if let relay = RelayURL(r0.as_str())  { urls.append(relay) }
     62             case 1:  if let relay = RelayURL(r1.as_str())  { urls.append(relay) }
     63             case 2:  if let relay = RelayURL(r2.as_str())  { urls.append(relay) }
     64             case 3:  if let relay = RelayURL(r3.as_str())  { urls.append(relay) }
     65             case 4:  if let relay = RelayURL(r4.as_str())  { urls.append(relay) }
     66             case 5:  if let relay = RelayURL(r5.as_str())  { urls.append(relay) }
     67             case 6:  if let relay = RelayURL(r6.as_str())  { urls.append(relay) }
     68             case 7:  if let relay = RelayURL(r7.as_str())  { urls.append(relay) }
     69             case 8:  if let relay = RelayURL(r8.as_str())  { urls.append(relay) }
     70             case 9:  if let relay = RelayURL(r9.as_str())  { urls.append(relay) }
     71             case 10: if let relay = RelayURL(r10.as_str()) { urls.append(relay) }
     72             case 11: if let relay = RelayURL(r11.as_str()) { urls.append(relay) }
     73             case 12: if let relay = RelayURL(r12.as_str()) { urls.append(relay) }
     74             case 13: if let relay = RelayURL(r13.as_str()) { urls.append(relay) }
     75             case 14: if let relay = RelayURL(r14.as_str()) { urls.append(relay) }
     76             case 15: if let relay = RelayURL(r15.as_str()) { urls.append(relay) }
     77             case 16: if let relay = RelayURL(r16.as_str()) { urls.append(relay) }
     78             case 17: if let relay = RelayURL(r17.as_str()) { urls.append(relay) }
     79             case 18: if let relay = RelayURL(r18.as_str()) { urls.append(relay) }
     80             case 19: if let relay = RelayURL(r19.as_str()) { urls.append(relay) }
     81             case 20: if let relay = RelayURL(r20.as_str()) { urls.append(relay) }
     82             case 21: if let relay = RelayURL(r21.as_str()) { urls.append(relay) }
     83             case 22: if let relay = RelayURL(r22.as_str()) { urls.append(relay) }
     84             case 23: if let relay = RelayURL(r23.as_str()) { urls.append(relay) }
     85             default:
     86                 break
     87             }
     88         }
     89 
     90         return urls
     91     }
     92 
     93 }
     94 
     95 enum Bech32Object : Equatable, Hashable {
     96     case nsec(Privkey)
     97     case npub(Pubkey)
     98     case note(NoteId)
     99     case nscript([UInt8])
    100     case nevent(NEvent)
    101     case nprofile(NProfile)
    102     case nrelay(String)
    103     case naddr(NAddr)
    104 
    105     func pubkey() -> Pubkey? {
    106         switch self {
    107         case .nprofile(let nprofile): return nprofile.author
    108         case .npub(let pubkey): return pubkey
    109         case .nevent(let ev): return ev.author
    110         case .naddr(let naddr): return naddr.author
    111         case .nscript: return nil
    112         case .nsec: return nil // TODO privkey_to_pubkey ?
    113         case .note: return nil
    114         case .nrelay: return nil
    115         }
    116     }
    117 
    118     init?(block: ndb_mention_bech32_block) {
    119         let b32 = block.bech32
    120         switch block.bech32_type {
    121         case .note:
    122             let data = b32.note.event_id.as_data(size: 32)
    123             self = .note(NoteId(data))
    124         case .npub:
    125             let data = b32.npub.pubkey.as_data(size: 32)
    126             self = .npub(Pubkey(data))
    127         case .nprofile:
    128             let pk = b32.nprofile.pubkey.as_data(size: 32)
    129             let relays = b32.nprofile.relays.as_urls()
    130             self = .nprofile(NProfile(author: Pubkey(pk), relays: relays))
    131         case .nevent:
    132             let nevent = b32.nevent
    133             let note_id = NoteId(nevent.event_id.as_data(size: 32))
    134             let relays = nevent.relays.as_urls()
    135             var author: Pubkey? = nil
    136             if nevent.pubkey != nil {
    137                 author = Pubkey(nevent.pubkey.as_data(size: 32))
    138             }
    139             var kind: UInt32? = nil
    140             if nevent.has_kind {
    141                 kind = nevent.kind
    142             }
    143 
    144             self = .nevent(NEvent(noteid: note_id, relays: relays, author: author, kind: kind))
    145         case .nrelay:
    146             self = .nrelay(b32.nrelay.relay.as_str())
    147         case .naddr:
    148             let identifier = b32.naddr.identifier.as_str()
    149             let author = Pubkey(b32.naddr.pubkey.as_data(size: 32))
    150             let relays = b32.naddr.relays.as_urls()
    151             self = .naddr(NAddr(identifier: identifier, author: author, relays: relays, kind: b32.naddr.kind))
    152         case .nsec:
    153             return nil
    154         case .none:
    155             return nil
    156         }
    157     }
    158 
    159     static func parse(_ str: String) -> Bech32Object? {
    160         if str.starts(with: "nscript"), let decoded = try? bech32_decode(str) {
    161             return .nscript(decoded.data.bytes)
    162         }
    163 
    164         var b: nostr_bech32_t = nostr_bech32()
    165         var bytes = Data(capacity: str.utf8.count)
    166 
    167         let ok = str.withCString { cstr in
    168             let ok = bytes.withUnsafeMutableBytes { buffer -> Int32 in
    169                 guard let addr = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
    170                     return 0
    171                 }
    172                 return parse_nostr_bech32(addr, Int32(buffer.count), cstr, str.utf8.count, &b)
    173             }
    174 
    175             return ok != 0
    176         }
    177 
    178         guard ok else { return nil }
    179 
    180         return decodeCBech32(b)
    181     }
    182     
    183     static func encode(_ obj: Bech32Object) -> String {
    184         switch(obj) {
    185         case .note(let noteid):
    186             return bech32_encode(hrp: "note", noteid.bytes)
    187         case .nevent(let nevent): return bech32EncodeNevent(nevent)
    188         case .nprofile(let nprofile): return bech32EncodeNprofile(nprofile)
    189         case .nrelay(let relayURL): return bech32EncodeNrelay(relayURL: relayURL)
    190         case .naddr(let naddr): return bech32EncodeNaddr(naddr)
    191         case .npub(let pubkey):
    192             return bech32_encode(hrp: "npub", pubkey.bytes)
    193         case .nsec(let privkey):
    194             guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return "" }
    195             return bech32_encode(hrp: "npub", pubkey.bytes)
    196         case .nscript(let data):
    197             return bech32_encode(hrp: "nscript", data)
    198         }
    199     }
    200     
    201     func toMentionRef() -> MentionRef? {
    202         MentionRef(nip19: self)
    203     }
    204 
    205 }
    206 
    207 func decodeCBech32(_ b: nostr_bech32_t) -> Bech32Object? {
    208     switch b.type {
    209     case NOSTR_BECH32_NOTE:
    210         let note_id = NoteId(Data(bytes: b.note.event_id, count: 32))
    211         return .note(note_id)
    212     case NOSTR_BECH32_NEVENT:
    213         let note_id = NoteId(Data(bytes: b.nevent.event_id, count: 32))
    214         let pubkey = b.nevent.pubkey != nil ? Pubkey(Data(bytes: b.nevent.pubkey, count: 32)) : nil
    215         let kind: UInt32? = !b.nevent.has_kind ? nil : b.nevent.kind
    216         let relays = b.nevent.relays.as_urls()
    217         return .nevent(NEvent(noteid: note_id, relays: relays, author: pubkey, kind: kind))
    218     case NOSTR_BECH32_NPUB:
    219         let pubkey = Pubkey(Data(bytes: b.npub.pubkey, count: 32))
    220         return .npub(pubkey)
    221     case NOSTR_BECH32_NSEC:
    222         let privkey = Privkey(Data(bytes: b.nsec.nsec, count: 32))
    223         guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
    224         return .npub(pubkey)
    225     case NOSTR_BECH32_NPROFILE:
    226         let pubkey = Pubkey(Data(bytes: b.nprofile.pubkey, count: 32))
    227         return .nprofile(NProfile(author: pubkey, relays: b.nprofile.relays.as_urls()))
    228     case NOSTR_BECH32_NRELAY:
    229         return .nrelay(b.nrelay.relay.as_str())
    230     case NOSTR_BECH32_NADDR:
    231         let pubkey = Pubkey(Data(bytes: b.naddr.pubkey, count: 32))
    232         let kind = b.naddr.kind
    233         let identifier = b.naddr.identifier.as_str()
    234 
    235         return .naddr(NAddr(identifier: identifier, author: pubkey, relays: b.naddr.relays.as_urls(), kind: kind))
    236     default:
    237         return nil
    238     }
    239 }
    240 
    241 private enum TLVType: UInt8 {
    242     case SPECIAL
    243     case RELAY
    244     case AUTHOR
    245     case KIND
    246 }
    247 
    248 private func writeBytesList(bytesList: inout [UInt8], tlvType: TLVType, data: [UInt8]){
    249     bytesList.append(tlvType.rawValue)
    250     bytesList.append(UInt8(data.bytes.count))
    251     bytesList.append(contentsOf: data.bytes)
    252 }
    253 
    254 private func writeBytesRelays(bytesList: inout [UInt8], relays: [RelayURL]) {
    255     for relay in relays {
    256         guard let relayData = relay.url.absoluteString.data(using: .utf8) else {
    257             continue // skip relay if can't read data
    258         }
    259         writeBytesList(bytesList: &bytesList, tlvType: .RELAY, data: relayData.bytes)
    260     }
    261 }
    262 
    263 private func writeBytesKind(bytesList: inout [UInt8], kind: UInt32) {
    264     bytesList.append(TLVType.KIND.rawValue)
    265     bytesList.append(UInt8(4))
    266 
    267     var bigEndianBytes = kind.bigEndian
    268     let data = Data(bytes: &bigEndianBytes, count: MemoryLayout<UInt32>.size)
    269 
    270     bytesList.append(contentsOf: data)
    271 }
    272 
    273 private func bech32EncodeNevent(_ nevent: NEvent) -> String {
    274     var neventBytes = [UInt8]();
    275     writeBytesList(bytesList: &neventBytes, tlvType: .SPECIAL, data: nevent.noteid.bytes)
    276     
    277     writeBytesRelays(bytesList: &neventBytes, relays: nevent.relays)
    278     
    279     if let eventPubkey = nevent.author {
    280         writeBytesList(bytesList: &neventBytes, tlvType: .AUTHOR, data: eventPubkey.bytes)
    281     }
    282     
    283     if let kind = nevent.kind {
    284         writeBytesKind(bytesList: &neventBytes, kind: kind)
    285     }
    286     
    287     return bech32_encode(hrp: "nevent", neventBytes.bytes)
    288 }
    289 
    290 private func bech32EncodeNprofile(_ nprofile: NProfile) -> String {
    291     var nprofileBytes = [UInt8]();
    292 
    293     writeBytesList(bytesList: &nprofileBytes, tlvType: .SPECIAL, data: nprofile.author.bytes)
    294     writeBytesRelays(bytesList: &nprofileBytes, relays: nprofile.relays)
    295     
    296     return bech32_encode(hrp: "nprofile", nprofileBytes.bytes)
    297 }
    298 
    299 private func bech32EncodeNrelay(relayURL: String) -> String {
    300     var nrelayBytes = [UInt8]();
    301     
    302     guard let relayURLBytes = relayURL.data(using: .ascii) else {
    303         return ""
    304     }
    305     
    306     writeBytesList(bytesList: &nrelayBytes, tlvType: .SPECIAL, data: relayURLBytes.bytes)
    307     return bech32_encode(hrp: "nrelay", nrelayBytes.bytes)
    308 }
    309 
    310 private func bech32EncodeNaddr(_ naddr: NAddr) -> String {
    311     var naddrBytes = [UInt8]();
    312     
    313     guard let identifierBytes = naddr.identifier.data(using: .utf8) else {
    314         return ""
    315     }
    316     
    317     writeBytesList(bytesList: &naddrBytes, tlvType: .SPECIAL, data: identifierBytes.bytes)
    318     writeBytesRelays(bytesList: &naddrBytes, relays: naddr.relays)
    319     writeBytesList(bytesList: &naddrBytes, tlvType: .AUTHOR, data: naddr.author.bytes)
    320     writeBytesKind(bytesList: &naddrBytes, kind: naddr.kind)
    321     return bech32_encode(hrp: "naddr", naddrBytes.bytes)
    322 }