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 }