323 lines
12 KiB
Swift
323 lines
12 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 bytes = Data(bytes: s.str, count: Int(s.len))
|
|
self.init(bytes: bytes, encoding: .utf8)
|
|
}
|
|
}
|
|
|
|
struct NEvent : Equatable, Hashable {
|
|
let noteid: NoteId
|
|
let relays: [RelayURL]
|
|
let author: Pubkey?
|
|
let kind: UInt32?
|
|
|
|
init(noteid: NoteId, relays: [RelayURL], author: Pubkey? = nil, kind: UInt32? = nil) {
|
|
self.noteid = noteid
|
|
self.relays = relays
|
|
self.author = author
|
|
self.kind = kind
|
|
}
|
|
|
|
init(event: NostrEvent, relays: [RelayURL]) {
|
|
self.init(noteid: event.id, relays: relays, author: event.pubkey, kind: event.kind)
|
|
}
|
|
}
|
|
|
|
struct NProfile : Equatable, Hashable {
|
|
let author: Pubkey
|
|
let relays: [RelayURL]
|
|
}
|
|
|
|
struct NAddr : Equatable, Hashable {
|
|
let identifier: String
|
|
let author: Pubkey
|
|
let relays: [RelayURL]
|
|
let kind: UInt32
|
|
}
|
|
|
|
extension ndb_relays {
|
|
func as_urls() -> [RelayURL] {
|
|
var urls = [RelayURL]()
|
|
|
|
//
|
|
// This is so incredibly dumb but it's just what the Swift <-> C bridge
|
|
// does and I don't have a better way that doesn't involve complicated
|
|
// and slow stuff like reflection
|
|
//
|
|
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
|
|
|
|
for i in 0..<self.num_relays {
|
|
switch i {
|
|
case 0: if let relay = RelayURL(r0.as_str()) { urls.append(relay) }
|
|
case 1: if let relay = RelayURL(r1.as_str()) { urls.append(relay) }
|
|
case 2: if let relay = RelayURL(r2.as_str()) { urls.append(relay) }
|
|
case 3: if let relay = RelayURL(r3.as_str()) { urls.append(relay) }
|
|
case 4: if let relay = RelayURL(r4.as_str()) { urls.append(relay) }
|
|
case 5: if let relay = RelayURL(r5.as_str()) { urls.append(relay) }
|
|
case 6: if let relay = RelayURL(r6.as_str()) { urls.append(relay) }
|
|
case 7: if let relay = RelayURL(r7.as_str()) { urls.append(relay) }
|
|
case 8: if let relay = RelayURL(r8.as_str()) { urls.append(relay) }
|
|
case 9: if let relay = RelayURL(r9.as_str()) { urls.append(relay) }
|
|
case 10: if let relay = RelayURL(r10.as_str()) { urls.append(relay) }
|
|
case 11: if let relay = RelayURL(r11.as_str()) { urls.append(relay) }
|
|
case 12: if let relay = RelayURL(r12.as_str()) { urls.append(relay) }
|
|
case 13: if let relay = RelayURL(r13.as_str()) { urls.append(relay) }
|
|
case 14: if let relay = RelayURL(r14.as_str()) { urls.append(relay) }
|
|
case 15: if let relay = RelayURL(r15.as_str()) { urls.append(relay) }
|
|
case 16: if let relay = RelayURL(r16.as_str()) { urls.append(relay) }
|
|
case 17: if let relay = RelayURL(r17.as_str()) { urls.append(relay) }
|
|
case 18: if let relay = RelayURL(r18.as_str()) { urls.append(relay) }
|
|
case 19: if let relay = RelayURL(r19.as_str()) { urls.append(relay) }
|
|
case 20: if let relay = RelayURL(r20.as_str()) { urls.append(relay) }
|
|
case 21: if let relay = RelayURL(r21.as_str()) { urls.append(relay) }
|
|
case 22: if let relay = RelayURL(r22.as_str()) { urls.append(relay) }
|
|
case 23: if let relay = RelayURL(r23.as_str()) { urls.append(relay) }
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
|
|
return urls
|
|
}
|
|
|
|
}
|
|
|
|
enum Bech32Object : Equatable, Hashable {
|
|
case nsec(Privkey)
|
|
case npub(Pubkey)
|
|
case note(NoteId)
|
|
case nscript([UInt8])
|
|
case nevent(NEvent)
|
|
case nprofile(NProfile)
|
|
case nrelay(String)
|
|
case naddr(NAddr)
|
|
|
|
func pubkey() -> Pubkey? {
|
|
switch self {
|
|
case .nprofile(let nprofile): return nprofile.author
|
|
case .npub(let pubkey): return pubkey
|
|
case .nevent(let ev): return ev.author
|
|
case .naddr(let naddr): return naddr.author
|
|
case .nscript: return nil
|
|
case .nsec: return nil // TODO privkey_to_pubkey ?
|
|
case .note: return nil
|
|
case .nrelay: return nil
|
|
}
|
|
}
|
|
|
|
init?(block: ndb_mention_bech32_block) {
|
|
let b32 = block.bech32
|
|
switch block.bech32_type {
|
|
case .note:
|
|
let data = b32.note.event_id.as_data(size: 32)
|
|
self = .note(NoteId(data))
|
|
case .npub:
|
|
let data = b32.npub.pubkey.as_data(size: 32)
|
|
self = .npub(Pubkey(data))
|
|
case .nprofile:
|
|
let pk = b32.nprofile.pubkey.as_data(size: 32)
|
|
let relays = b32.nprofile.relays.as_urls()
|
|
self = .nprofile(NProfile(author: Pubkey(pk), relays: relays))
|
|
case .nevent:
|
|
let nevent = b32.nevent
|
|
let note_id = NoteId(nevent.event_id.as_data(size: 32))
|
|
let relays = nevent.relays.as_urls()
|
|
var author: Pubkey? = nil
|
|
if nevent.pubkey != nil {
|
|
author = Pubkey(nevent.pubkey.as_data(size: 32))
|
|
}
|
|
var kind: UInt32? = nil
|
|
if nevent.has_kind {
|
|
kind = nevent.kind
|
|
}
|
|
|
|
self = .nevent(NEvent(noteid: note_id, relays: relays, author: author, kind: kind))
|
|
case .nrelay:
|
|
self = .nrelay(b32.nrelay.relay.as_str())
|
|
case .naddr:
|
|
let identifier = b32.naddr.identifier.as_str()
|
|
let author = Pubkey(b32.naddr.pubkey.as_data(size: 32))
|
|
let relays = b32.naddr.relays.as_urls()
|
|
self = .naddr(NAddr(identifier: identifier, author: author, relays: relays, kind: b32.naddr.kind))
|
|
case .nsec:
|
|
return nil
|
|
case .none:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
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()
|
|
var bytes = Data(capacity: str.utf8.count)
|
|
|
|
let ok = str.withCString { cstr in
|
|
let ok = bytes.withUnsafeMutableBytes { buffer -> Int32 in
|
|
guard let addr = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
|
return 0
|
|
}
|
|
return parse_nostr_bech32(addr, Int32(buffer.count), cstr, str.utf8.count, &b)
|
|
}
|
|
|
|
return ok != 0
|
|
}
|
|
|
|
guard ok else { return nil }
|
|
|
|
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? {
|
|
MentionRef(nip19: self)
|
|
}
|
|
|
|
}
|
|
|
|
func decodeCBech32(_ b: nostr_bech32_t) -> Bech32Object? {
|
|
switch b.type {
|
|
case NOSTR_BECH32_NOTE:
|
|
let note_id = NoteId(Data(bytes: b.note.event_id, count: 32))
|
|
return .note(note_id)
|
|
case NOSTR_BECH32_NEVENT:
|
|
let note_id = NoteId(Data(bytes: b.nevent.event_id, count: 32))
|
|
let pubkey = b.nevent.pubkey != nil ? Pubkey(Data(bytes: b.nevent.pubkey, count: 32)) : nil
|
|
let kind: UInt32? = !b.nevent.has_kind ? nil : b.nevent.kind
|
|
let relays = b.nevent.relays.as_urls()
|
|
return .nevent(NEvent(noteid: note_id, relays: relays, author: pubkey, kind: kind))
|
|
case NOSTR_BECH32_NPUB:
|
|
let pubkey = Pubkey(Data(bytes: b.npub.pubkey, count: 32))
|
|
return .npub(pubkey)
|
|
case NOSTR_BECH32_NSEC:
|
|
let privkey = Privkey(Data(bytes: b.nsec.nsec, count: 32))
|
|
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
|
|
return .npub(pubkey)
|
|
case NOSTR_BECH32_NPROFILE:
|
|
let pubkey = Pubkey(Data(bytes: b.nprofile.pubkey, count: 32))
|
|
return .nprofile(NProfile(author: pubkey, relays: b.nprofile.relays.as_urls()))
|
|
case NOSTR_BECH32_NRELAY:
|
|
return .nrelay(b.nrelay.relay.as_str())
|
|
case NOSTR_BECH32_NADDR:
|
|
let pubkey = Pubkey(Data(bytes: b.naddr.pubkey, count: 32))
|
|
let kind = b.naddr.kind
|
|
let identifier = b.naddr.identifier.as_str()
|
|
|
|
return .naddr(NAddr(identifier: identifier, author: pubkey, relays: b.naddr.relays.as_urls(), kind: kind))
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
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: [RelayURL]) {
|
|
for relay in relays {
|
|
guard let relayData = relay.url.absoluteString.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)
|
|
}
|