damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

commit adacdbb7648cd3b4a19b102eba036a7ecff07530
parent cd9bd1b8926c48df746cbde116c50ce1cb9a81bd
Author: William Casarin <jb55@jb55.com>
Date:   Sat,  6 Aug 2022 23:01:56 -0700

switch to bech32 everywhere

You can now use @npub, @note or @nsec to reference notes and pubkeys

Changelog-Changed: use bech32 ids everywhere
Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Mdamus/Models/Post.swift | 67++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mdamus/Util/Keys.swift | 7+++++++
Mdamus/Views/EventView.swift | 14++++++++++++--
Mdamus/Views/ProfileView.swift | 7+++++--
MdamusTests/ReplyTests.swift | 49+++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 137 insertions(+), 7 deletions(-)

diff --git a/damus/Models/Post.swift b/damus/Models/Post.swift @@ -32,15 +32,76 @@ func parse_post_reference(_ p: Parser) -> ReferencedId? { return parse_nostr_ref_uri(p) } - guard let id = parse_hexstr(p, len: 64) else { + if let ref = parse_post_mention(p, mention_type: typ) { + return ref + } + + p.pos = start + + return nil +} + +func is_bech32_char(_ c: Character) -> Bool { + let contains = "qpzry9x8gf2tvdw0s3jn54khce6mua7l".contains(c) + return contains +} + +func parse_post_mention(_ p: Parser, mention_type: MentionType) -> ReferencedId? { + if let id = parse_hexstr(p, len: 64) { + return ReferencedId(ref_id: id, relay_id: nil, key: mention_type.ref) + } else if let bech32_ref = parse_post_bech32_mention(p) { + return bech32_ref + } else { + return nil + } +} + +func parse_post_bech32_mention(_ p: Parser) -> ReferencedId? { + let start = p.pos + if parse_str(p, "note") { + } else if parse_str(p, "npub") { + } else if parse_str(p, "nsec") { + } else { + return nil + } + + if !parse_char(p, "1") { + p.pos = start + return nil + } + + var end = p.pos + if consume_until(p, match: { c in !is_bech32_char(c) }) { + end = p.pos + } else { + p.pos = start + return nil + } + + let sliced = String(substring(p.str, start: start, end: end)) + guard let decoded = try? bech32_decode(sliced) else { p.pos = start return nil } - return ReferencedId(ref_id: id, relay_id: nil, key: typ.ref) + let hex = hex_encode(decoded.data) + switch decoded.hrp { + case "note": + return ReferencedId(ref_id: hex, relay_id: nil, key: "e") + case "npub": + return ReferencedId(ref_id: hex, relay_id: nil, key: "p") + case "nsec": + guard let pubkey = privkey_to_pubkey(privkey: hex) else { + p.pos = start + return nil + } + return ReferencedId(ref_id: pubkey, relay_id: nil, key: "p") + default: + p.pos = start + return nil + } } - /// Return a list of tags func parse_post_blocks(content: String) -> [PostBlock] { let p = Parser(pos: 0, str: content) diff --git a/damus/Util/Keys.swift b/damus/Util/Keys.swift @@ -58,6 +58,13 @@ func bech32_pubkey(_ pubkey: String) -> String? { return bech32_encode(hrp: "npub", bytes) } +func bech32_note_id(_ evid: String) -> String? { + guard let bytes = hex_decode(evid) else { + return nil + } + return bech32_encode(hrp: "note", bytes) +} + func generate_new_keypair() -> Keypair { let key = try! secp256k1.Signing.PrivateKey() let privkey = hex_encode(key.rawRepresentation) diff --git a/damus/Views/EventView.swift b/damus/Views/EventView.swift @@ -148,6 +148,16 @@ struct EventView: View { } extension View { + func pubkey_context_menu(bech32_pubkey: String) -> some View { + return self.contextMenu { + Button { + UIPasteboard.general.string = bech32_pubkey + } label: { + Label("Copy Account ID", systemImage: "doc.on.doc") + } + } + } + func event_context_menu(_ event: NostrEvent, privkey: String?) -> some View { return self.contextMenu { Button { @@ -157,13 +167,13 @@ extension View { } Button { - UIPasteboard.general.string = "@" + event.pubkey + UIPasteboard.general.string = bech32_pubkey(event.pubkey) ?? event.pubkey } label: { Label("Copy User ID", systemImage: "tag") } Button { - UIPasteboard.general.string = "&" + event.id + UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id } label: { Label("Copy Note ID", systemImage: "tag") } diff --git a/damus/Views/ProfileView.swift b/damus/Views/ProfileView.swift @@ -104,6 +104,7 @@ struct ProfileView: View { KeyView(pubkey: profile.pubkey) .padding(.bottom, 10) + .pubkey_context_menu(bech32_pubkey: bech32_pubkey(profile.pubkey) ?? profile.pubkey) Text(data?.about ?? "") @@ -190,12 +191,14 @@ struct KeyView: View { var body: some View { let col = id_to_color(pubkey) + let bech32 = bech32_pubkey(pubkey) ?? pubkey + let half = bech32.count / 2 VStack { - Text("\(String(pubkey.prefix(32)))") + Text("\(String(bech32.prefix(half)))") .foregroundColor(colorScheme == .light ? .black : col) .font(.footnote.monospaced()) - Text("\(String(pubkey.suffix(32)))") + Text("\(String(bech32.suffix(half)))") .font(.footnote.monospaced()) .foregroundColor(colorScheme == .light ? .black : col) } diff --git a/damusTests/ReplyTests.swift b/damusTests/ReplyTests.swift @@ -189,6 +189,55 @@ class ReplyTests: XCTestCase { XCTAssertEqual(parsed.count, 0) } + func testNpubMention() throws { + let evid = "0000000000000000000000000000000000000000000000000000000000000005" + let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s" + let hex_pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" + let content = "this is a @\(pk) mention" + let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e") + let blocks = parse_post_blocks(content: content) + let post = NostrPost(content: content, references: [reply_ref]) + let ev = post_to_event(post: post, privkey: evid, pubkey: pk) + + XCTAssertEqual(ev.tags.count, 2) + XCTAssertEqual(blocks.count, 3) + XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) + XCTAssertEqual(ev.content, "this is a #[1] mention") + } + + func testNoteMention() throws { + let evid = "0000000000000000000000000000000000000000000000000000000000000005" + let pk = "note154fwmp6hdxqnmqdzkt5jeay8l4kxdsrpn02vw9kp4gylkxxur5fsq3ckpy" + let hex_note_id = "a552ed875769813d81a2b2e92cf487fd6c66c0619bd4c716c1aa09fb18dc1d13" + let content = "this is a @\(pk) &\(pk) mention" + let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e") + let blocks = parse_post_blocks(content: content) + let post = NostrPost(content: content, references: [reply_ref]) + let ev = post_to_event(post: post, privkey: evid, pubkey: pk) + + XCTAssertEqual(ev.tags.count, 3) + XCTAssertEqual(blocks.count, 5) + XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_note_id, relay_id: nil, key: "e")) + XCTAssertEqual(blocks[3].is_ref, ReferencedId(ref_id: hex_note_id, relay_id: nil, key: "e")) + XCTAssertEqual(ev.content, "this is a #[1] #[2] mention") + } + + func testNsecMention() throws { + let evid = "0000000000000000000000000000000000000000000000000000000000000005" + let pk = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7" + let hex_pk = "ccf95d668650178defca5ac503693b6668eb77895f610178ff8ed9fe5cf9482e" + let content = "this is a @\(pk) mention" + let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e") + let blocks = parse_post_blocks(content: content) + let post = NostrPost(content: content, references: [reply_ref]) + let ev = post_to_event(post: post, privkey: evid, pubkey: pk) + + XCTAssertEqual(ev.tags.count, 2) + XCTAssertEqual(blocks.count, 3) + XCTAssertEqual(blocks[1].is_ref, ReferencedId(ref_id: hex_pk, relay_id: nil, key: "p")) + XCTAssertEqual(ev.content, "this is a #[1] mention") + } + func testPostWithMentions() throws { let evid = "0000000000000000000000000000000000000000000000000000000000000005" let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"