NIP‑17/44: XChaCha20 v2 gift wraps, DM kind=14, receipts + UI fixes (#506)

* NIP-17/44: adopt NIP-44 v2 (XChaCha20-Poly1305, v2: base64url), switch rumor kind to 14, randomize BIP-340 aux; add XChaCha20Poly1305Compat and wire-up

* Nostr DMs: ensure delivered/read acks are sent even without Noise key mapping by falling back to direct Nostr (geohash-style) acks to sender pubkey

* NIP-44 v2 decrypt: try both Y parities for x-only sender pubkeys (even then odd) to fix unwrap authentication failures

* Tests: add NIP-44 v2 ACK round-trip tests (delivered/read) using bitchat1 embedding and gift-wrap decrypt path

* UI: fix DM autoscroll IDs by using dm:<peer>|<id> for private chat item IDs and preserve anchors, ensuring scrollTo targets exist when opening/switching/new messages

* UI: fix string interpolation in DM/geohash context keys (remove escaped interpolation) to resolve 'unused ch' warnings and ensure proper IDs

* UI: MeshPeerList shows transport state icon: radio for mesh-connected, purple globe for mutual favorite/Nostr; fallback dim person for others

---------

Co-authored-by: jack <jackjackbits@users.noreply.github.com>
This commit is contained in:
jack
2025-08-24 17:56:43 +02:00
committed by GitHub
co-authored by jack
parent 85f99984c4
commit e6903456ab
7 changed files with 371 additions and 130 deletions
+10 -2
View File
@@ -291,13 +291,17 @@ struct ContentView: View {
// Build stable UI IDs with a context key to avoid ID collisions when switching channels
#if os(iOS)
let contextKey: String = {
if let peer = privatePeer { return "dm:\(peer)" }
switch locationManager.selectedChannel {
case .mesh: return "mesh"
case .location(let ch): return "geo:\(ch.geohash)"
}
}()
#else
let contextKey: String = "mesh"
let contextKey: String = {
if let peer = privatePeer { return "dm:\(peer)" }
return "mesh"
}()
#endif
let items = windowedMessages.map { (uiID: "\(contextKey)|\($0.id)", message: $0) }
@@ -396,13 +400,17 @@ struct ContentView: View {
let step = 200
#if os(iOS)
let contextKey: String = {
if let peer = privatePeer { return "dm:\(peer)" }
switch locationManager.selectedChannel {
case .mesh: return "mesh"
case .location(let ch): return "geo:\(ch.geohash)"
}
}()
#else
let contextKey: String = "mesh"
let contextKey: String = {
if let peer = privatePeer { return "dm:\(peer)" }
return "mesh"
}()
#endif
let preserveID = "\(contextKey)|\(message.id)"
if let peer = privatePeer {