damus

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

Bech32Object.swift (9793B)


      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 len = s.end - s.start
     14         let bytes = Data(bytes: s.start, count: len)
     15         self.init(bytes: bytes, encoding: .utf8)
     16     }
     17 }
     18 
     19 struct NEvent : Equatable, Hashable {
     20     let noteid: NoteId
     21     let relays: [String]
     22     let author: Pubkey?
     23     let kind: UInt32?
     24     
     25     init(noteid: NoteId, relays: [String]) {
     26         self.noteid = noteid
     27         self.relays = relays
     28         self.author = nil
     29         self.kind = nil
     30     }
     31     
     32     init(noteid: NoteId, relays: [String], author: Pubkey?) {
     33         self.noteid = noteid
     34         self.relays = relays
     35         self.author = author
     36         self.kind = nil
     37     }
     38     init(noteid: NoteId, relays: [String], kind: UInt32?) {
     39         self.noteid = noteid
     40         self.relays = relays
     41         self.author = nil
     42         self.kind = kind
     43     }
     44     init(noteid: NoteId, relays: [String], author: Pubkey?, kind: UInt32?) {
     45         self.noteid = noteid
     46         self.relays = relays
     47         self.author = author
     48         self.kind = kind
     49     }
     50 }
     51 
     52 struct NProfile : Equatable, Hashable {
     53     let author: Pubkey
     54     let relays: [String]
     55 }
     56 
     57 struct NAddr : Equatable, Hashable {
     58     let identifier: String
     59     let author: Pubkey
     60     let relays: [String]
     61     let kind: UInt32
     62 }
     63 
     64 enum Bech32Object : Equatable {
     65     case nsec(Privkey)
     66     case npub(Pubkey)
     67     case note(NoteId)
     68     case nscript([UInt8])
     69     case nevent(NEvent)
     70     case nprofile(NProfile)
     71     case nrelay(String)
     72     case naddr(NAddr)
     73 
     74     static func parse(_ str: String) -> Bech32Object? {
     75         if str.starts(with: "nscript"), let decoded = try? bech32_decode(str) {
     76             return .nscript(decoded.data.bytes)
     77         }
     78 
     79         var b: nostr_bech32_t = nostr_bech32()
     80 
     81         let bytes = Array(str.utf8)
     82         
     83         bytes.withUnsafeBufferPointer { buffer in
     84             guard let baseAddress = buffer.baseAddress else { return }
     85             
     86             var cursorInstance = cursor()
     87             cursorInstance.start = UnsafeMutablePointer(mutating: baseAddress)
     88             cursorInstance.p = UnsafeMutablePointer(mutating: baseAddress)
     89             cursorInstance.end = cursorInstance.start.advanced(by: buffer.count)
     90             
     91             parse_nostr_bech32(&cursorInstance, &b)
     92         }
     93         
     94         return decodeCBech32(b)
     95     }
     96     
     97     static func encode(_ obj: Bech32Object) -> String {
     98         switch(obj) {
     99         case .note(let noteid):
    100             return bech32_encode(hrp: "note", noteid.bytes)
    101         case .nevent(let nevent): return bech32EncodeNevent(nevent)
    102         case .nprofile(let nprofile): return bech32EncodeNprofile(nprofile)
    103         case .nrelay(let relayURL): return bech32EncodeNrelay(relayURL: relayURL)
    104         case .naddr(let naddr): return bech32EncodeNaddr(naddr)
    105         case .npub(let pubkey):
    106             return bech32_encode(hrp: "npub", pubkey.bytes)
    107         case .nsec(let privkey):
    108             guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return "" }
    109             return bech32_encode(hrp: "npub", pubkey.bytes)
    110         case .nscript(let data):
    111             return bech32_encode(hrp: "nscript", data)
    112         }
    113     }
    114     
    115     func toMentionRef() -> MentionRef? {
    116         switch self {
    117         case .nsec(let privkey):
    118             guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
    119             return .pubkey(pubkey)
    120         case .npub(let pubkey):
    121             return .pubkey(pubkey)
    122         case .note(let noteid):
    123             return .note(noteid)
    124         case .nscript(_):
    125             return nil
    126         case .nevent(let nevent):
    127             return .nevent(nevent)
    128         case .nprofile(let nprofile):
    129             return .nprofile(nprofile)
    130         case .nrelay(let relayURL):
    131             return .nrelay(relayURL)
    132         case .naddr(let naddr):
    133             return .naddr(naddr)
    134         }
    135     }
    136 
    137 }
    138 
    139 func decodeCBech32(_ b: nostr_bech32_t) -> Bech32Object? {
    140     switch b.type {
    141     case NOSTR_BECH32_NOTE:
    142         let note = b.data.note;
    143         let note_id = NoteId(Data(bytes: note.event_id, count: 32))
    144         return .note(note_id)
    145     case NOSTR_BECH32_NEVENT:
    146         let nevent = b.data.nevent;
    147         let note_id = NoteId(Data(bytes: nevent.event_id, count: 32))
    148         let pubkey = nevent.pubkey != nil ? Pubkey(Data(bytes: nevent.pubkey, count: 32)) : nil
    149         let kind: UInt32? = nevent.has_kind ? nevent.kind : nil
    150         let relays = getRelayStrings(from: nevent.relays)
    151         return .nevent(NEvent(noteid: note_id, relays: relays, author: pubkey, kind: kind))
    152     case NOSTR_BECH32_NPUB:
    153         let npub = b.data.npub
    154         let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32))
    155         return .npub(pubkey)
    156     case NOSTR_BECH32_NSEC:
    157         let nsec = b.data.nsec
    158         let privkey = Privkey(Data(bytes: nsec.nsec, count: 32))
    159         guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
    160         return .npub(pubkey)
    161     case NOSTR_BECH32_NPROFILE:
    162         let nprofile = b.data.nprofile
    163         let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32))
    164         return .nprofile(NProfile(author: pubkey, relays: getRelayStrings(from: nprofile.relays)))
    165     case NOSTR_BECH32_NRELAY:
    166         let nrelay = b.data.nrelay
    167         let str_relay: str_block = nrelay.relay
    168         guard let relay_str = String(str_relay) else {
    169             return nil
    170         }
    171         return .nrelay(relay_str)
    172     case NOSTR_BECH32_NADDR:
    173         let naddr = b.data.naddr
    174         guard let identifier = String(naddr.identifier) else {
    175             return nil
    176         }
    177         let pubkey = Pubkey(Data(bytes: naddr.pubkey, count: 32))
    178         let kind = naddr.kind
    179         
    180         return .naddr(NAddr(identifier: identifier, author: pubkey, relays: getRelayStrings(from: naddr.relays), kind: kind))
    181     default:
    182         return nil
    183     }
    184 }
    185 
    186 private func getRelayStrings(from relays: relays) -> [String] {
    187     var result = [String]()
    188     let numRelays = Int(relays.num_relays)
    189 
    190     func processRelay(_ relay: str_block) {
    191         if let string = String(relay) {
    192             result.append(string)
    193         }
    194     }
    195 
    196     // Since relays is a C tuple, the indexes can't be iterated through so they need to be manually processed
    197     if numRelays > 0 { processRelay(relays.relays.0) }
    198     if numRelays > 1 { processRelay(relays.relays.1) }
    199     if numRelays > 2 { processRelay(relays.relays.2) }
    200     if numRelays > 3 { processRelay(relays.relays.3) }
    201     if numRelays > 4 { processRelay(relays.relays.4) }
    202     if numRelays > 5 { processRelay(relays.relays.5) }
    203     if numRelays > 6 { processRelay(relays.relays.6) }
    204     if numRelays > 7 { processRelay(relays.relays.7) }
    205     if numRelays > 8 { processRelay(relays.relays.8) }
    206     if numRelays > 9 { processRelay(relays.relays.9) }
    207 
    208     return result
    209 }
    210 
    211 private enum TLVType: UInt8 {
    212     case SPECIAL
    213     case RELAY
    214     case AUTHOR
    215     case KIND
    216 }
    217 
    218 private func writeBytesList(bytesList: inout [UInt8], tlvType: TLVType, data: [UInt8]){
    219     bytesList.append(tlvType.rawValue)
    220     bytesList.append(UInt8(data.bytes.count))
    221     bytesList.append(contentsOf: data.bytes)
    222 }
    223 
    224 private func writeBytesRelays(bytesList: inout [UInt8], relays: [String]) {
    225     for relay in relays where !relay.isEmpty {
    226         guard let relayData = relay.data(using: .utf8) else {
    227             continue // skip relay if can't read data
    228         }
    229         writeBytesList(bytesList: &bytesList, tlvType: .RELAY, data: relayData.bytes)
    230     }
    231 }
    232 
    233 private func writeBytesKind(bytesList: inout [UInt8], kind: UInt32) {
    234     bytesList.append(TLVType.KIND.rawValue)
    235     bytesList.append(UInt8(4))
    236 
    237     var bigEndianBytes = kind.bigEndian
    238     let data = Data(bytes: &bigEndianBytes, count: MemoryLayout<UInt32>.size)
    239 
    240     bytesList.append(contentsOf: data)
    241 }
    242 
    243 private func bech32EncodeNevent(_ nevent: NEvent) -> String {
    244     var neventBytes = [UInt8]();
    245     writeBytesList(bytesList: &neventBytes, tlvType: .SPECIAL, data: nevent.noteid.bytes)
    246     
    247     writeBytesRelays(bytesList: &neventBytes, relays: nevent.relays)
    248     
    249     if let eventPubkey = nevent.author {
    250         writeBytesList(bytesList: &neventBytes, tlvType: .AUTHOR, data: eventPubkey.bytes)
    251     }
    252     
    253     if let kind = nevent.kind {
    254         writeBytesKind(bytesList: &neventBytes, kind: kind)
    255     }
    256     
    257     return bech32_encode(hrp: "nevent", neventBytes.bytes)
    258 }
    259 
    260 private func bech32EncodeNprofile(_ nprofile: NProfile) -> String {
    261     var nprofileBytes = [UInt8]();
    262 
    263     writeBytesList(bytesList: &nprofileBytes, tlvType: .SPECIAL, data: nprofile.author.bytes)
    264     writeBytesRelays(bytesList: &nprofileBytes, relays: nprofile.relays)
    265     
    266     return bech32_encode(hrp: "nprofile", nprofileBytes.bytes)
    267 }
    268 
    269 private func bech32EncodeNrelay(relayURL: String) -> String {
    270     var nrelayBytes = [UInt8]();
    271     
    272     guard let relayURLBytes = relayURL.data(using: .ascii) else {
    273         return ""
    274     }
    275     
    276     writeBytesList(bytesList: &nrelayBytes, tlvType: .SPECIAL, data: relayURLBytes.bytes)
    277     return bech32_encode(hrp: "nrelay", nrelayBytes.bytes)
    278 }
    279 
    280 private func bech32EncodeNaddr(_ naddr: NAddr) -> String {
    281     var naddrBytes = [UInt8]();
    282     
    283     guard let identifierBytes = naddr.identifier.data(using: .utf8) else {
    284         return ""
    285     }
    286     
    287     writeBytesList(bytesList: &naddrBytes, tlvType: .SPECIAL, data: identifierBytes.bytes)
    288     writeBytesRelays(bytesList: &naddrBytes, relays: naddr.relays)
    289     writeBytesList(bytesList: &naddrBytes, tlvType: .AUTHOR, data: naddr.author.bytes)
    290     writeBytesKind(bytesList: &naddrBytes, kind: naddr.kind)
    291     return bech32_encode(hrp: "naddr", naddrBytes.bytes)
    292 }