commit a9e97012435af05a42fca05aeb46056e3e3c6f0f
parent cb4adf06f12bee149367829f9b2f0f7278593651
Author: kernelkind <kernelkind@gmail.com>
Date: Sat, 13 Jan 2024 14:19:45 -0500
nip19: add high level bech32 encoding method
Add encode method for converting Bech32Object to a bech32 encoded
string. This will be useful for generalized conversion of Bech32Objects
to its string representation.
Lightning-url: LNURL1DP68GURN8GHJ7EM9W3SKCCNE9E3K7MF0D3H82UNVWQHKWUN9V4HXGCTHDC6RZVGR8SW3G
Signed-off-by: kernelkind <kernelkind@gmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
2 files changed, 198 insertions(+), 0 deletions(-)
diff --git a/damus/Util/Bech32Object.swift b/damus/Util/Bech32Object.swift
@@ -89,6 +89,24 @@ enum Bech32Object : Equatable {
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 decodeCBech32(_ b: nostr_bech32_t) -> Bech32Object? {
@@ -162,3 +180,86 @@ private func getRelayStrings(from relays: relays) -> [String] {
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)
+}
diff --git a/damusTests/Bech32ObjectTests.swift b/damusTests/Bech32ObjectTests.swift
@@ -115,4 +115,101 @@ class Bech32ObjectTests: XCTestCase {
XCTAssertEqual(expectedObject, actualObject)
}
+
+ func testTLVEncoding_NeventHasRelaysNoAuthorNoKind_ValidContent() throws {
+ guard let noteid = hex_decode_noteid("b9f5441e45ca39179320e0031cfb18e34078673dcc3d3e3a3b3a981760aa5696") else {
+ XCTFail("Parsing note ID failed")
+ return
+ }
+
+ let relays = ["wss://nostr-relay.untethr.me", "wss://nostr-pub.wellorder.net"]
+
+ let expectedEncoding = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm"
+
+ let actualEncoding = Bech32Object.encode(.nevent(NEvent(noteid: noteid, relays: relays)))
+
+ XCTAssertEqual(expectedEncoding, actualEncoding)
+ }
+
+ func testTLVEncoding_NeventHasRelaysNoAuthorHasKind_ValidContent() throws {
+ guard let noteid = hex_decode_noteid("b9f5441e45ca39179320e0031cfb18e34078673dcc3d3e3a3b3a981760aa5696") else {
+ XCTFail()
+ return
+ }
+
+ let relays = [
+ "wss://nostr-relay.untethr.me",
+ "wss://nostr-pub.wellorder.net"
+ ]
+
+ let expectedEncoding = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5qvzqqqqqqyjyqz7d"
+
+ let actualEncoding = Bech32Object.encode(.nevent(NEvent(noteid: noteid, relays: relays, kind: 1)))
+
+ XCTAssertEqual(expectedEncoding, actualEncoding)
+ }
+
+ func testTLVEncoding_NeventHasRelaysHasAuthorHasKind_ValidContent() throws {
+ guard let noteid = hex_decode_noteid("b9f5441e45ca39179320e0031cfb18e34078673dcc3d3e3a3b3a981760aa5696") else {
+ XCTFail("Parsing note ID failed")
+ return
+ }
+ guard let author = try bech32_decode("npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6") else {
+ XCTFail()
+ return
+ }
+
+ let relays = ["wss://nostr-relay.untethr.me", "wss://nostr-pub.wellorder.net"]
+
+ let expectedEncoding = "nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5qgsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8grqsqqqqqpw4032x"
+
+ let actualEncoding = Bech32Object.encode(.nevent(NEvent(noteid: noteid, relays: relays, author: Pubkey(author.data), kind: 1)))
+
+ XCTAssertEqual(expectedEncoding, actualEncoding)
+ }
+
+ func testTLVEncoding_NProfileExample_ValidContent() throws {
+ guard let author = try bech32_decode("npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6") else {
+ XCTFail()
+ return
+ }
+
+ let relays = [
+ "wss://r.x.com",
+ "wss://djbas.sadkb.com"
+ ]
+
+ let expectedEncoding = "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p"
+
+ let actualEncoding = Bech32Object.encode(.nprofile(NProfile(author: Pubkey(author.data), relays: relays)))
+
+ XCTAssertEqual(expectedEncoding, actualEncoding)
+ }
+
+ func testTLVEncoding_NRelayExample_ValidContent() throws {
+ let relay = "wss://relay.nostr.band"
+
+ let expectedEncoding = "nrelay1qqt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueq4r295t"
+
+ let actualEncoding = Bech32Object.encode(.nrelay(relay))
+
+ XCTAssertEqual(expectedEncoding, actualEncoding)
+ }
+
+ func testTLVEncoding_NaddrExample_ValidContent() throws {
+ guard let author = try bech32_decode("npub1tx0k0a7lw62vvqax6p3ku90tccgdka7ul4radews2wrdsg0m865szf9fw6") else {
+ XCTFail()
+ return
+ }
+
+ let relays = ["wss://relay.nostr.band"]
+ let identifier = "1700730909108"
+ let kind: UInt32 = 30023
+
+ let expectedEncoding = "naddr1qqxnzdesxqmnxvpexqunzvpcqyt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueqzypve7elhmamff3sr5mgxxms4a0rppkmhmn7504h96pfcdkpplvl2jqcyqqq823cnmhuld"
+
+ let actualEncoding = Bech32Object.encode(.naddr(NAddr(identifier: identifier, author: Pubkey(author.data), relays: relays, kind: kind)))
+
+ XCTAssertEqual(expectedEncoding, actualEncoding)
+ }
}