1
0
mirror of git://jb55.com/damus synced 2024-09-30 00:40:45 +00:00

nip19: added swift enums

Add enums to reflect Bech32 with TLV encoded data. Update parse method
to call C library for generalized parsing of bech32 data.

Lightning-url: LNURL1DP68GURN8GHJ7EM9W3SKCCNE9E3K7MF0D3H82UNVWQHKWUN9V4HXGCTHDC6RZVGR8SW3G
Signed-off-by: kernelkind <kernelkind@gmail.com>
Reviewed-by: William Casarin <jb55@jb55.com>
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
kernelkind 2024-01-13 14:19:44 -05:00 committed by William Casarin
parent 97fc415b8c
commit cb4adf06f1
4 changed files with 284 additions and 37 deletions

View File

@ -260,8 +260,8 @@
4C9B0DF32A65C46800CBDA21 /* ProfileEditButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9B0DF22A65C46800CBDA21 /* ProfileEditButton.swift */; };
4C9BB83129C0ED4F00FC4E37 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
4C9BB83429C12D9900FC4E37 /* EventProfileName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */; };
4C9D6D1B2B1D35D7004E5CD9 /* PullDownSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */; };
4C9D6D162B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9D6D152B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift */; };
4C9D6D1B2B1D35D7004E5CD9 /* PullDownSearch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */; };
4C9F18E229AA9B6C008C55EC /* CustomizeZapView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */; };
4C9F18E429ABDE6D008C55EC /* MaybeAnonPfpView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */; };
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */; };
@ -610,6 +610,7 @@
D7EDED342B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */; };
D7FB10A72B0C371A00FA8D42 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
D7FF94002AC7AC5300FD969D /* RelayURL.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */; };
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */; };
E4FA1C032A24BB7F00482697 /* SearchSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */; };
E990020F2955F837003BBC5A /* EditMetadataView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E990020E2955F837003BBC5A /* EditMetadataView.swift */; };
E9E4ED0B295867B900DD7078 /* ThreadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9E4ED0A295867B900DD7078 /* ThreadView.swift */; };
@ -1135,8 +1136,8 @@
4C9B0DF22A65C46800CBDA21 /* ProfileEditButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditButton.swift; sourceTree = "<group>"; };
4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayName.swift; sourceTree = "<group>"; };
4C9BB83329C12D9900FC4E37 /* EventProfileName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventProfileName.swift; sourceTree = "<group>"; };
4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullDownSearch.swift; sourceTree = "<group>"; };
4C9D6D152B1AA9C6004E5CD9 /* DisplayTabBarNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTabBarNotify.swift; sourceTree = "<group>"; };
4C9D6D1A2B1D35D7004E5CD9 /* PullDownSearch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PullDownSearch.swift; sourceTree = "<group>"; };
4C9F18E129AA9B6C008C55EC /* CustomizeZapView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomizeZapView.swift; sourceTree = "<group>"; };
4C9F18E329ABDE6D008C55EC /* MaybeAnonPfpView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaybeAnonPfpView.swift; sourceTree = "<group>"; };
4CA2EF9F280E37AC0044ACD8 /* TimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineView.swift; sourceTree = "<group>"; };
@ -1369,6 +1370,7 @@
D7EDED2D2B128E8A0018B19C /* CollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionExtension.swift; sourceTree = "<group>"; };
D7EDED322B12ACAE0018B19C /* DamusUserDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusUserDefaults.swift; sourceTree = "<group>"; };
D7FF93FF2AC7AC5200FD969D /* RelayURL.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayURL.swift; sourceTree = "<group>"; };
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bech32ObjectTests.swift; sourceTree = "<group>"; };
E4FA1C022A24BB7F00482697 /* SearchSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSettingsView.swift; sourceTree = "<group>"; };
E990020E2955F837003BBC5A /* EditMetadataView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditMetadataView.swift; sourceTree = "<group>"; };
E9E4ED0A295867B900DD7078 /* ThreadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThreadView.swift; sourceTree = "<group>"; };
@ -2440,6 +2442,7 @@
F944F56C29EA9CB20067B3BF /* Models */,
50A50A8C29A09E1C00C01BE7 /* RequestTests.swift */,
4C90BD1B283AC38E008EE7EF /* Bech32Tests.swift */,
E02B54172B4DFADA0077FF42 /* Bech32ObjectTests.swift */,
4C363A9F2828A8DD006E126D /* LikeTests.swift */,
4C363A9D2828A822006E126D /* ReplyTests.swift */,
4CE6DEF727F7A08200C66700 /* damusTests.swift */,
@ -3383,6 +3386,7 @@
3A3040F329A91366008A0F29 /* ProfileViewTests.swift in Sources */,
4CF0ABDC2981A19E00D66079 /* ListTests.swift in Sources */,
4C684A552A7E91FE005E6031 /* LongPostTests.swift in Sources */,
E02B54182B4DFADA0077FF42 /* Bech32ObjectTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -60,20 +60,7 @@ func decode_universal_link(_ s: String) -> NostrLink? {
uri = uri.replacingOccurrences(of: "https://damus.io/", with: "")
uri = uri.replacingOccurrences(of: "/", with: "")
guard let decoded = try? bech32_decode(uri),
decoded.data.count == 32
else {
return nil
}
if decoded.hrp == "note" {
return .ref(.event(NoteId(decoded.data)))
} else if decoded.hrp == "npub" {
return .ref(.pubkey(Pubkey(decoded.data)))
}
// TODO: handle nprofile, etc
return nil
return decode_nostr_bech32_uri(uri)
}
func decode_nostr_bech32_uri(_ s: String) -> NostrLink? {
@ -82,16 +69,24 @@ func decode_nostr_bech32_uri(_ s: String) -> NostrLink? {
}
switch obj {
case .nsec(let privkey):
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
return .ref(.pubkey(pubkey))
case .npub(let pubkey):
return .ref(.pubkey(pubkey))
case .note(let id):
return .ref(.event(id))
case .nscript(let data):
return .script(data)
}
case .nsec(let privkey):
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
return .ref(.pubkey(pubkey))
case .npub(let pubkey):
return .ref(.pubkey(pubkey))
case .note(let id):
return .ref(.event(id))
case .nscript(let data):
return .script(data)
case .naddr(let naddr):
return .none // TODO: FIX
case .nevent(let nevent):
return .ref(.event(nevent.noteid))
case .nprofile(let nprofile):
return .ref(.pubkey(nprofile.author))
case .nrelay(_):
return .none
}
}
func decode_nostr_uri(_ s: String) -> NostrLink? {

View File

@ -7,28 +7,158 @@
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)
}
}
enum Bech32Object {
struct NEvent : Equatable {
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 {
let author: Pubkey
let relays: [String]
}
struct NAddr : Equatable {
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? {
guard let decoded = try? bech32_decode(str) else {
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)
}
}
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
}
if decoded.hrp == "npub" {
return .npub(Pubkey(decoded.data))
} else if decoded.hrp == "nsec" {
return .nsec(Privkey(decoded.data))
} else if decoded.hrp == "note" {
return .note(NoteId(decoded.data))
} else if decoded.hrp == "nscript" {
return .nscript(decoded.data.bytes)
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
}

View File

@ -0,0 +1,118 @@
//
// Bech32ObjectTests.swift
// damusTests
//
// Created by KernelKind on 1/5/24.
//
// This file contains tests that are adapted from the nostr-sdk-ios project.
// Original source:
// https://github.com/nostr-sdk/nostr-sdk-ios/blob/main/Tests/NostrSDKTests/MetadataCodingTests.swift
//
import XCTest
@testable import damus
class Bech32ObjectTests: XCTestCase {
func testTLVParsing_NeventHasRelaysNoAuthorNoKind_ValidContent() throws {
let content = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
let expectedNoteIDHex = "b9f5441e45ca39179320e0031cfb18e34078673dcc3d3e3a3b3a981760aa5696"
let relays = ["wss://nostr-relay.untethr.me", "wss://nostr-pub.wellorder.net"]
guard let noteid = hex_decode_noteid(expectedNoteIDHex) else {
XCTFail("Parsing note ID failed")
return
}
let expectedObject = Bech32Object.nevent(NEvent(noteid: noteid, relays: relays))
guard let actualObject = Bech32Object.parse(content) else {
XCTFail("Invalid Object")
return
}
XCTAssertEqual(expectedObject, actualObject)
}
func testTLVParsing_NeventHasRelaysNoAuthorHasKind_ValidContent() throws {
let content = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5qvzqqqqqqyjyqz7d"
let expectedNoteIDHex = "b9f5441e45ca39179320e0031cfb18e34078673dcc3d3e3a3b3a981760aa5696"
let relays = ["wss://nostr-relay.untethr.me", "wss://nostr-pub.wellorder.net"]
guard let noteid = hex_decode_noteid(expectedNoteIDHex) else {
XCTFail("Parsing note ID failed")
return
}
let expectedObject = Bech32Object.nevent(NEvent(noteid: noteid, relays: relays, kind: 1))
guard let actualObject = Bech32Object.parse(content) else {
XCTFail("Invalid Object")
return
}
XCTAssertEqual(expectedObject, actualObject)
}
func testTLVParsing_NeventHasRelaysHasAuthorHasKind_ValidContent() throws {
let content = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5qgsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8grqsqqqqqpw4032x"
let expectedNoteIDHex = "b9f5441e45ca39179320e0031cfb18e34078673dcc3d3e3a3b3a981760aa5696"
let relays = ["wss://nostr-relay.untethr.me", "wss://nostr-pub.wellorder.net"]
guard let noteid = hex_decode_noteid(expectedNoteIDHex) else {
XCTFail("Parsing note ID failed")
return
}
guard let author = try bech32_decode("npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6") else {
XCTFail()
return
}
let expectedObject = Bech32Object.nevent(NEvent(noteid: noteid, relays: relays, author: Pubkey(author.data), kind: 1))
guard let actualObject = Bech32Object.parse(content) else {
XCTFail("Invalid Object")
return
}
XCTAssertEqual(expectedObject, actualObject)
}
func testTLVParsing_NProfileExample_ValidContent() throws {
let content = "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p"
guard let author = try bech32_decode("npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6") else {
XCTFail()
return
}
let relays = ["wss://r.x.com", "wss://djbas.sadkb.com"]
let expectedObject = Bech32Object.nprofile(NProfile(author: Pubkey(author.data), relays: relays))
guard let actualObject = Bech32Object.parse(content) else {
XCTFail("Invalid Object")
return
}
XCTAssertEqual(expectedObject, actualObject)
}
func testTLVParsing_NRelayExample_ValidContent() throws {
let content = "nrelay1qqt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueq4r295t"
let relay = "wss://relay.nostr.band"
let expectedObject = Bech32Object.nrelay(relay)
let actualObject = Bech32Object.parse(content)
XCTAssertEqual(expectedObject, actualObject)
}
func testTLVParsing_NaddrExample_ValidContent() throws {
let content = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
guard let author = try bech32_decode("npub1tx0k0a7lw62vvqax6p3ku90tccgdka7ul4radews2wrdsg0m865szf9fw6") else {
XCTFail("Can't decode npub")
return
}
let relays = ["wss://relay.nostr.band"]
let identifier = "1700730909108"
let kind: UInt32 = 30023
let expectedObject = Bech32Object.naddr(NAddr(identifier: identifier, author: Pubkey(author.data), relays: relays, kind: kind))
let actualObject = Bech32Object.parse(content)
XCTAssertEqual(expectedObject, actualObject)
}
}