This was broken by the recent nip19 changes, let's fix it again
Fixes: cb4adf06f1 ("nip19: added swift enums")
Changelog-Fixed: Fix nostrscripts not loading
Signed-off-by: William Casarin <jb55@jb55.com>
293 lines
9.6 KiB
Swift
293 lines
9.6 KiB
Swift
//
|
|
// Bech32Object.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-01-28.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
fileprivate extension String {
|
|
/// Failable initializer to build a Swift.String from a C-backed `str_block_t`.
|
|
init?(_ s: str_block_t) {
|
|
let len = s.end - s.start
|
|
let bytes = Data(bytes: s.start, count: len)
|
|
self.init(bytes: bytes, encoding: .utf8)
|
|
}
|
|
}
|
|
|
|
struct NEvent : Equatable, Hashable {
|
|
let noteid: NoteId
|
|
let relays: [String]
|
|
let author: Pubkey?
|
|
let kind: UInt32?
|
|
|
|
init(noteid: NoteId, relays: [String]) {
|
|
self.noteid = noteid
|
|
self.relays = relays
|
|
self.author = nil
|
|
self.kind = nil
|
|
}
|
|
|
|
init(noteid: NoteId, relays: [String], author: Pubkey?) {
|
|
self.noteid = noteid
|
|
self.relays = relays
|
|
self.author = author
|
|
self.kind = nil
|
|
}
|
|
init(noteid: NoteId, relays: [String], kind: UInt32?) {
|
|
self.noteid = noteid
|
|
self.relays = relays
|
|
self.author = nil
|
|
self.kind = kind
|
|
}
|
|
init(noteid: NoteId, relays: [String], author: Pubkey?, kind: UInt32?) {
|
|
self.noteid = noteid
|
|
self.relays = relays
|
|
self.author = author
|
|
self.kind = kind
|
|
}
|
|
}
|
|
|
|
struct NProfile : Equatable, Hashable {
|
|
let author: Pubkey
|
|
let relays: [String]
|
|
}
|
|
|
|
struct NAddr : Equatable, Hashable {
|
|
let identifier: String
|
|
let author: Pubkey
|
|
let relays: [String]
|
|
let kind: UInt32
|
|
}
|
|
|
|
enum Bech32Object : Equatable {
|
|
case nsec(Privkey)
|
|
case npub(Pubkey)
|
|
case note(NoteId)
|
|
case nscript([UInt8])
|
|
case nevent(NEvent)
|
|
case nprofile(NProfile)
|
|
case nrelay(String)
|
|
case naddr(NAddr)
|
|
|
|
static func parse(_ str: String) -> Bech32Object? {
|
|
if str.starts(with: "nscript"), let decoded = try? bech32_decode(str) {
|
|
return .nscript(decoded.data.bytes)
|
|
}
|
|
|
|
var b: nostr_bech32_t = nostr_bech32()
|
|
|
|
let bytes = Array(str.utf8)
|
|
|
|
bytes.withUnsafeBufferPointer { buffer in
|
|
guard let baseAddress = buffer.baseAddress else { return }
|
|
|
|
var cursorInstance = cursor()
|
|
cursorInstance.start = UnsafeMutablePointer(mutating: baseAddress)
|
|
cursorInstance.p = UnsafeMutablePointer(mutating: baseAddress)
|
|
cursorInstance.end = cursorInstance.start.advanced(by: buffer.count)
|
|
|
|
parse_nostr_bech32(&cursorInstance, &b)
|
|
}
|
|
|
|
return decodeCBech32(b)
|
|
}
|
|
|
|
static func encode(_ obj: Bech32Object) -> String {
|
|
switch(obj) {
|
|
case .note(let noteid):
|
|
return bech32_encode(hrp: "note", noteid.bytes)
|
|
case .nevent(let nevent): return bech32EncodeNevent(nevent)
|
|
case .nprofile(let nprofile): return bech32EncodeNprofile(nprofile)
|
|
case .nrelay(let relayURL): return bech32EncodeNrelay(relayURL: relayURL)
|
|
case .naddr(let naddr): return bech32EncodeNaddr(naddr)
|
|
case .npub(let pubkey):
|
|
return bech32_encode(hrp: "npub", pubkey.bytes)
|
|
case .nsec(let privkey):
|
|
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return "" }
|
|
return bech32_encode(hrp: "npub", pubkey.bytes)
|
|
case .nscript(let data):
|
|
return bech32_encode(hrp: "nscript", data)
|
|
}
|
|
}
|
|
|
|
func toMentionRef() -> MentionRef? {
|
|
switch self {
|
|
case .nsec(let privkey):
|
|
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
|
|
return .pubkey(pubkey)
|
|
case .npub(let pubkey):
|
|
return .pubkey(pubkey)
|
|
case .note(let noteid):
|
|
return .note(noteid)
|
|
case .nscript(_):
|
|
return nil
|
|
case .nevent(let nevent):
|
|
return .nevent(nevent)
|
|
case .nprofile(let nprofile):
|
|
return .nprofile(nprofile)
|
|
case .nrelay(let relayURL):
|
|
return .nrelay(relayURL)
|
|
case .naddr(let naddr):
|
|
return .naddr(naddr)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func decodeCBech32(_ b: nostr_bech32_t) -> Bech32Object? {
|
|
switch b.type {
|
|
case NOSTR_BECH32_NOTE:
|
|
let note = b.data.note;
|
|
let note_id = NoteId(Data(bytes: note.event_id, count: 32))
|
|
return .note(note_id)
|
|
case NOSTR_BECH32_NEVENT:
|
|
let nevent = b.data.nevent;
|
|
let note_id = NoteId(Data(bytes: nevent.event_id, count: 32))
|
|
let pubkey = nevent.pubkey != nil ? Pubkey(Data(bytes: nevent.pubkey, count: 32)) : nil
|
|
let kind: UInt32? = nevent.has_kind ? nevent.kind : nil
|
|
let relays = getRelayStrings(from: nevent.relays)
|
|
return .nevent(NEvent(noteid: note_id, relays: relays, author: pubkey, kind: kind))
|
|
case NOSTR_BECH32_NPUB:
|
|
let npub = b.data.npub
|
|
let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32))
|
|
return .npub(pubkey)
|
|
case NOSTR_BECH32_NSEC:
|
|
let nsec = b.data.nsec
|
|
let privkey = Privkey(Data(bytes: nsec.nsec, count: 32))
|
|
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
|
|
return .npub(pubkey)
|
|
case NOSTR_BECH32_NPROFILE:
|
|
let nprofile = b.data.nprofile
|
|
let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32))
|
|
return .nprofile(NProfile(author: pubkey, relays: getRelayStrings(from: nprofile.relays)))
|
|
case NOSTR_BECH32_NRELAY:
|
|
let nrelay = b.data.nrelay
|
|
let str_relay: str_block = nrelay.relay
|
|
guard let relay_str = String(str_relay) else {
|
|
return nil
|
|
}
|
|
return .nrelay(relay_str)
|
|
case NOSTR_BECH32_NADDR:
|
|
let naddr = b.data.naddr
|
|
guard let identifier = String(naddr.identifier) else {
|
|
return nil
|
|
}
|
|
let pubkey = Pubkey(Data(bytes: naddr.pubkey, count: 32))
|
|
let kind = naddr.kind
|
|
|
|
return .naddr(NAddr(identifier: identifier, author: pubkey, relays: getRelayStrings(from: naddr.relays), kind: kind))
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func getRelayStrings(from relays: relays) -> [String] {
|
|
var result = [String]()
|
|
let numRelays = Int(relays.num_relays)
|
|
|
|
func processRelay(_ relay: str_block) {
|
|
if let string = String(relay) {
|
|
result.append(string)
|
|
}
|
|
}
|
|
|
|
// Since relays is a C tuple, the indexes can't be iterated through so they need to be manually processed
|
|
if numRelays > 0 { processRelay(relays.relays.0) }
|
|
if numRelays > 1 { processRelay(relays.relays.1) }
|
|
if numRelays > 2 { processRelay(relays.relays.2) }
|
|
if numRelays > 3 { processRelay(relays.relays.3) }
|
|
if numRelays > 4 { processRelay(relays.relays.4) }
|
|
if numRelays > 5 { processRelay(relays.relays.5) }
|
|
if numRelays > 6 { processRelay(relays.relays.6) }
|
|
if numRelays > 7 { processRelay(relays.relays.7) }
|
|
if numRelays > 8 { processRelay(relays.relays.8) }
|
|
if numRelays > 9 { processRelay(relays.relays.9) }
|
|
|
|
return result
|
|
}
|
|
|
|
private enum TLVType: UInt8 {
|
|
case SPECIAL
|
|
case RELAY
|
|
case AUTHOR
|
|
case KIND
|
|
}
|
|
|
|
private func writeBytesList(bytesList: inout [UInt8], tlvType: TLVType, data: [UInt8]){
|
|
bytesList.append(tlvType.rawValue)
|
|
bytesList.append(UInt8(data.bytes.count))
|
|
bytesList.append(contentsOf: data.bytes)
|
|
}
|
|
|
|
private func writeBytesRelays(bytesList: inout [UInt8], relays: [String]) {
|
|
for relay in relays where !relay.isEmpty {
|
|
guard let relayData = relay.data(using: .utf8) else {
|
|
continue // skip relay if can't read data
|
|
}
|
|
writeBytesList(bytesList: &bytesList, tlvType: .RELAY, data: relayData.bytes)
|
|
}
|
|
}
|
|
|
|
private func writeBytesKind(bytesList: inout [UInt8], kind: UInt32) {
|
|
bytesList.append(TLVType.KIND.rawValue)
|
|
bytesList.append(UInt8(4))
|
|
|
|
var bigEndianBytes = kind.bigEndian
|
|
let data = Data(bytes: &bigEndianBytes, count: MemoryLayout<UInt32>.size)
|
|
|
|
bytesList.append(contentsOf: data)
|
|
}
|
|
|
|
private func bech32EncodeNevent(_ nevent: NEvent) -> String {
|
|
var neventBytes = [UInt8]();
|
|
writeBytesList(bytesList: &neventBytes, tlvType: .SPECIAL, data: nevent.noteid.bytes)
|
|
|
|
writeBytesRelays(bytesList: &neventBytes, relays: nevent.relays)
|
|
|
|
if let eventPubkey = nevent.author {
|
|
writeBytesList(bytesList: &neventBytes, tlvType: .AUTHOR, data: eventPubkey.bytes)
|
|
}
|
|
|
|
if let kind = nevent.kind {
|
|
writeBytesKind(bytesList: &neventBytes, kind: kind)
|
|
}
|
|
|
|
return bech32_encode(hrp: "nevent", neventBytes.bytes)
|
|
}
|
|
|
|
private func bech32EncodeNprofile(_ nprofile: NProfile) -> String {
|
|
var nprofileBytes = [UInt8]();
|
|
|
|
writeBytesList(bytesList: &nprofileBytes, tlvType: .SPECIAL, data: nprofile.author.bytes)
|
|
writeBytesRelays(bytesList: &nprofileBytes, relays: nprofile.relays)
|
|
|
|
return bech32_encode(hrp: "nprofile", nprofileBytes.bytes)
|
|
}
|
|
|
|
private func bech32EncodeNrelay(relayURL: String) -> String {
|
|
var nrelayBytes = [UInt8]();
|
|
|
|
guard let relayURLBytes = relayURL.data(using: .ascii) else {
|
|
return ""
|
|
}
|
|
|
|
writeBytesList(bytesList: &nrelayBytes, tlvType: .SPECIAL, data: relayURLBytes.bytes)
|
|
return bech32_encode(hrp: "nrelay", nrelayBytes.bytes)
|
|
}
|
|
|
|
private func bech32EncodeNaddr(_ naddr: NAddr) -> String {
|
|
var naddrBytes = [UInt8]();
|
|
|
|
guard let identifierBytes = naddr.identifier.data(using: .utf8) else {
|
|
return ""
|
|
}
|
|
|
|
writeBytesList(bytesList: &naddrBytes, tlvType: .SPECIAL, data: identifierBytes.bytes)
|
|
writeBytesRelays(bytesList: &naddrBytes, relays: naddr.relays)
|
|
writeBytesList(bytesList: &naddrBytes, tlvType: .AUTHOR, data: naddr.author.bytes)
|
|
writeBytesKind(bytesList: &naddrBytes, kind: naddr.kind)
|
|
return bech32_encode(hrp: "naddr", naddrBytes.bytes)
|
|
}
|