From 70caa9e24a6014de8526907b869ecd337e537ad4 Mon Sep 17 00:00:00 2001 From: Islam <2553451+qalandarov@users.noreply.github.com> Date: Sun, 19 Oct 2025 12:54:32 +0100 Subject: [PATCH] PeerID 24/n: Nostr Transport and Embedding (#830) --- bitchat/Nostr/NostrEmbeddedBitChat.swift | 30 ++++++++++++------------ bitchat/Services/NostrTransport.swift | 14 +++++------ bitchatTests/NostrProtocolTests.swift | 4 ++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/bitchat/Nostr/NostrEmbeddedBitChat.swift b/bitchat/Nostr/NostrEmbeddedBitChat.swift index 30f791db..a6f6e0e6 100644 --- a/bitchat/Nostr/NostrEmbeddedBitChat.swift +++ b/bitchat/Nostr/NostrEmbeddedBitChat.swift @@ -4,7 +4,7 @@ import Foundation struct NostrEmbeddedBitChat { /// Build a `bitchat1:` base64url-encoded BitChat packet carrying a private message for Nostr DMs. - static func encodePMForNostr(content: String, messageID: String, recipientPeerID: String, senderPeerID: String) -> String? { + static func encodePMForNostr(content: String, messageID: String, recipientPeerID: PeerID, senderPeerID: PeerID) -> String? { // TLV-encode the private message let pm = PrivateMessagePacket(messageID: messageID, content: content) guard let tlv = pm.encode() else { return nil } @@ -14,12 +14,12 @@ struct NostrEmbeddedBitChat { payload.append(tlv) // Determine 8-byte recipient ID to embed - let recipientIDHex: String = normalizeRecipientPeerID(recipientPeerID) + let recipientID = normalizeRecipientPeerID(recipientPeerID) let packet = BitchatPacket( type: MessageType.noiseEncrypted.rawValue, - senderID: Data(hexString: senderPeerID) ?? Data(), - recipientID: Data(hexString: recipientIDHex), + senderID: Data(hexString: senderPeerID.id) ?? Data(), + recipientID: Data(hexString: recipientID.id), timestamp: UInt64(Date().timeIntervalSince1970 * 1000), payload: payload, signature: nil, @@ -31,18 +31,18 @@ struct NostrEmbeddedBitChat { } /// Build a `bitchat1:` base64url-encoded BitChat packet carrying a delivery/read ack for Nostr DMs. - static func encodeAckForNostr(type: NoisePayloadType, messageID: String, recipientPeerID: String, senderPeerID: String) -> String? { + static func encodeAckForNostr(type: NoisePayloadType, messageID: String, recipientPeerID: PeerID, senderPeerID: PeerID) -> String? { guard type == .delivered || type == .readReceipt else { return nil } var payload = Data([type.rawValue]) payload.append(Data(messageID.utf8)) - let recipientIDHex: String = normalizeRecipientPeerID(recipientPeerID) + let recipientID = normalizeRecipientPeerID(recipientPeerID) let packet = BitchatPacket( type: MessageType.noiseEncrypted.rawValue, - senderID: Data(hexString: senderPeerID) ?? Data(), - recipientID: Data(hexString: recipientIDHex), + senderID: Data(hexString: senderPeerID.id) ?? Data(), + recipientID: Data(hexString: recipientID.id), timestamp: UInt64(Date().timeIntervalSince1970 * 1000), payload: payload, signature: nil, @@ -54,7 +54,7 @@ struct NostrEmbeddedBitChat { } /// Build a `bitchat1:` ACK (delivered/read) without an embedded recipient peer ID (geohash DMs). - static func encodeAckForNostrNoRecipient(type: NoisePayloadType, messageID: String, senderPeerID: String) -> String? { + static func encodeAckForNostrNoRecipient(type: NoisePayloadType, messageID: String, senderPeerID: PeerID) -> String? { guard type == .delivered || type == .readReceipt else { return nil } var payload = Data([type.rawValue]) @@ -62,7 +62,7 @@ struct NostrEmbeddedBitChat { let packet = BitchatPacket( type: MessageType.noiseEncrypted.rawValue, - senderID: Data(hexString: senderPeerID) ?? Data(), + senderID: Data(hexString: senderPeerID.id) ?? Data(), recipientID: nil, timestamp: UInt64(Date().timeIntervalSince1970 * 1000), payload: payload, @@ -75,7 +75,7 @@ struct NostrEmbeddedBitChat { } /// Build a `bitchat1:` payload without an embedded recipient peer ID (used for geohash DMs). - static func encodePMForNostrNoRecipient(content: String, messageID: String, senderPeerID: String) -> String? { + static func encodePMForNostrNoRecipient(content: String, messageID: String, senderPeerID: PeerID) -> String? { let pm = PrivateMessagePacket(messageID: messageID, content: content) guard let tlv = pm.encode() else { return nil } @@ -84,7 +84,7 @@ struct NostrEmbeddedBitChat { let packet = BitchatPacket( type: MessageType.noiseEncrypted.rawValue, - senderID: Data(hexString: senderPeerID) ?? Data(), + senderID: Data(hexString: senderPeerID.id) ?? Data(), recipientID: nil, timestamp: UInt64(Date().timeIntervalSince1970 * 1000), payload: payload, @@ -96,11 +96,11 @@ struct NostrEmbeddedBitChat { return "bitchat1:" + base64URLEncode(data) } - private static func normalizeRecipientPeerID(_ recipientPeerID: String) -> String { - if let maybeData = Data(hexString: recipientPeerID) { + private static func normalizeRecipientPeerID(_ recipientPeerID: PeerID) -> PeerID { + if let maybeData = Data(hexString: recipientPeerID.id) { if maybeData.count == 32 { // Treat as Noise static public key; derive peerID from fingerprint - return PeerID(publicKey: maybeData).id + return PeerID(publicKey: maybeData) } else if maybeData.count == 8 { // Already an 8-byte peer ID return recipientPeerID diff --git a/bitchat/Services/NostrTransport.swift b/bitchat/Services/NostrTransport.swift index 35607700..4490aa9c 100644 --- a/bitchat/Services/NostrTransport.swift +++ b/bitchat/Services/NostrTransport.swift @@ -82,7 +82,7 @@ final class NostrTransport: Transport { SecureLogger.error("NostrTransport: failed to decode npub -> hex: \(error)", category: .session) return } - guard let embedded = NostrEmbeddedBitChat.encodePMForNostr(content: content, messageID: messageID, recipientPeerID: peerID.id, senderPeerID: senderPeerID.id) else { + guard let embedded = NostrEmbeddedBitChat.encodePMForNostr(content: content, messageID: messageID, recipientPeerID: peerID, senderPeerID: senderPeerID) else { SecureLogger.error("NostrTransport: failed to embed PM packet", category: .session) return } @@ -114,7 +114,7 @@ final class NostrTransport: Transport { guard hrp == "npub" else { return } recipientHex = data.hexEncodedString() } catch { return } - guard let embedded = NostrEmbeddedBitChat.encodePMForNostr(content: content, messageID: UUID().uuidString, recipientPeerID: peerID.id, senderPeerID: senderPeerID.id) else { + guard let embedded = NostrEmbeddedBitChat.encodePMForNostr(content: content, messageID: UUID().uuidString, recipientPeerID: peerID, senderPeerID: senderPeerID) else { SecureLogger.error("NostrTransport: failed to embed favorite notification", category: .session) return } @@ -139,7 +139,7 @@ final class NostrTransport: Transport { guard hrp == "npub" else { return } recipientHex = data.hexEncodedString() } catch { return } - guard let ack = NostrEmbeddedBitChat.encodeAckForNostr(type: .delivered, messageID: messageID, recipientPeerID: peerID.id, senderPeerID: senderPeerID.id) else { + guard let ack = NostrEmbeddedBitChat.encodeAckForNostr(type: .delivered, messageID: messageID, recipientPeerID: peerID, senderPeerID: senderPeerID) else { SecureLogger.error("NostrTransport: failed to embed DELIVERED ack", category: .session) return } @@ -161,7 +161,7 @@ extension NostrTransport { func sendDeliveryAckGeohash(for messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity) { Task { @MainActor in SecureLogger.debug("GeoDM: send DELIVERED -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", category: .session) - guard let embedded = NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .delivered, messageID: messageID, senderPeerID: senderPeerID.id) else { return } + guard let embedded = NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .delivered, messageID: messageID, senderPeerID: senderPeerID) else { return } guard let event = try? NostrProtocol.createPrivateMessage(content: embedded, recipientPubkey: recipientHex, senderIdentity: identity) else { return } NostrRelayManager.registerPendingGiftWrap(id: event.id) NostrRelayManager.shared.sendEvent(event) @@ -171,7 +171,7 @@ extension NostrTransport { func sendReadReceiptGeohash(_ messageID: String, toRecipientHex recipientHex: String, from identity: NostrIdentity) { Task { @MainActor in SecureLogger.debug("GeoDM: send READ -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", category: .session) - guard let embedded = NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .readReceipt, messageID: messageID, senderPeerID: senderPeerID.id) else { return } + guard let embedded = NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .readReceipt, messageID: messageID, senderPeerID: senderPeerID) else { return } guard let event = try? NostrProtocol.createPrivateMessage(content: embedded, recipientPubkey: recipientHex, senderIdentity: identity) else { return } NostrRelayManager.registerPendingGiftWrap(id: event.id) NostrRelayManager.shared.sendEvent(event) @@ -184,7 +184,7 @@ extension NostrTransport { guard !recipientHex.isEmpty else { return } SecureLogger.debug("GeoDM: send PM -> recip=\(recipientHex.prefix(8))… mid=\(messageID.prefix(8))… from=\(identity.publicKeyHex.prefix(8))…", category: .session) // Build embedded BitChat packet without recipient peer ID - guard let embedded = NostrEmbeddedBitChat.encodePMForNostrNoRecipient(content: content, messageID: messageID, senderPeerID: senderPeerID.id) else { + guard let embedded = NostrEmbeddedBitChat.encodePMForNostrNoRecipient(content: content, messageID: messageID, senderPeerID: senderPeerID) else { SecureLogger.error("NostrTransport: failed to embed geohash PM packet", category: .session) return } @@ -223,7 +223,7 @@ extension NostrTransport { guard hrp == "npub" else { scheduleNextReadAck(); return } recipientHex = data.hexEncodedString() } catch { scheduleNextReadAck(); return } - guard let ack = NostrEmbeddedBitChat.encodeAckForNostr(type: .readReceipt, messageID: item.receipt.originalMessageID, recipientPeerID: item.peerID.id, senderPeerID: senderPeerID.id) else { + guard let ack = NostrEmbeddedBitChat.encodeAckForNostr(type: .readReceipt, messageID: item.receipt.originalMessageID, recipientPeerID: item.peerID, senderPeerID: senderPeerID) else { SecureLogger.error("NostrTransport: failed to embed READ ack", category: .session) scheduleNextReadAck(); return } diff --git a/bitchatTests/NostrProtocolTests.swift b/bitchatTests/NostrProtocolTests.swift index 14a1b64a..265ebb55 100644 --- a/bitchatTests/NostrProtocolTests.swift +++ b/bitchatTests/NostrProtocolTests.swift @@ -126,7 +126,7 @@ struct NostrProtocolTests { // Build a DELIVERED ack embedded payload (geohash-style, no recipient peer ID) let messageID = "TEST-MSG-DELIVERED-1" - let senderPeerID = "0123456789abcdef" // 8-byte hex peer ID + let senderPeerID = PeerID(str: "0123456789abcdef") // 8-byte hex peer ID let embedded = try #require( NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .delivered, messageID: messageID, senderPeerID: senderPeerID), @@ -176,7 +176,7 @@ struct NostrProtocolTests { let recipient = try NostrIdentity.generate() let messageID = "TEST-MSG-READ-1" - let senderPeerID = "fedcba9876543210" // 8-byte hex peer ID + let senderPeerID = PeerID(str: "fedcba9876543210") // 8-byte hex peer ID let embedded = try #require( NostrEmbeddedBitChat.encodeAckForNostrNoRecipient(type: .readReceipt, messageID: messageID, senderPeerID: senderPeerID), "Failed to embed read ack"