diff --git a/bitchat/Identity/SecureIdentityStateManager.swift b/bitchat/Identity/SecureIdentityStateManager.swift index dcedf5e3..4eaa69f9 100644 --- a/bitchat/Identity/SecureIdentityStateManager.swift +++ b/bitchat/Identity/SecureIdentityStateManager.swift @@ -310,7 +310,7 @@ class SecureIdentityStateManager { func getCryptoIdentitiesByPeerIDPrefix(_ peerID: String) -> [CryptographicIdentity] { queue.sync { // Defensive: ensure hex and correct length - guard peerID.count == 16, peerID.allSatisfy({ $0.isHexDigit }) else { return [] } + guard PeerIDResolver.isShortID(peerID) else { return [] } return cryptographicIdentities.values.filter { $0.fingerprint.hasPrefix(peerID) } } } diff --git a/bitchat/Protocols/BinaryEncodingUtils.swift b/bitchat/Protocols/BinaryEncodingUtils.swift index 575d2079..dd70930b 100644 --- a/bitchat/Protocols/BinaryEncodingUtils.swift +++ b/bitchat/Protocols/BinaryEncodingUtils.swift @@ -6,6 +6,7 @@ // import Foundation +import CryptoKit // MARK: - Hex Encoding/Decoding @@ -35,6 +36,16 @@ extension Data { } } +// MARK: - Fingerprint Helpers + +extension Data { + /// SHA-256 over data, hex-encoded (lowercase) + func sha256Fingerprint() -> String { + let hash = SHA256.hash(data: self) + return hash.map { String(format: "%02x", $0) }.joined() + } +} + // MARK: - Binary Encoding Utilities extension Data { @@ -220,4 +231,3 @@ extension Data { return data } } - diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift index a484fee7..e0199e7a 100644 --- a/bitchat/Services/NoiseEncryptionService.swift +++ b/bitchat/Services/NoiseEncryptionService.swift @@ -548,8 +548,7 @@ class NoiseEncryptionService { } private func calculateFingerprint(for publicKey: Curve25519.KeyAgreement.PublicKey) -> String { - let hash = SHA256.hash(data: publicKey.rawRepresentation) - return hash.map { String(format: "%02x", $0) }.joined() + return publicKey.rawRepresentation.sha256Fingerprint() } // MARK: - Session Maintenance diff --git a/bitchat/Services/UnifiedPeerService.swift b/bitchat/Services/UnifiedPeerService.swift index 9675a4aa..b63729f3 100644 --- a/bitchat/Services/UnifiedPeerService.swift +++ b/bitchat/Services/UnifiedPeerService.swift @@ -412,10 +412,4 @@ class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate { // MARK: - Helper Extensions -extension Data { - func sha256Fingerprint() -> String { - // Implementation matches existing fingerprint generation in NoiseEncryptionService - let hash = SHA256.hash(data: self) - return hash.map { String(format: "%02x", $0) }.joined() - } -} +// Moved sha256Fingerprint() to BinaryEncodingUtils for reuse diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 40cc8b59..e3767f88 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -161,7 +161,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { }() let full = (nostrKeyMapping[spid] ?? bare).lowercased() return "nostr:" + full - } else if spid.count == 16, let full = getNoiseKeyForShortID(spid)?.lowercased() { + } else if PeerIDResolver.isShortID(spid), let full = getNoiseKeyForShortID(spid)?.lowercased() { return "noise:" + full } else { return "mesh:" + spid.lowercased() @@ -309,7 +309,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { func getNoiseKeyForShortID(_ shortPeerID: String) -> String? { if let mapped = shortIDToNoiseKey[shortPeerID] { return mapped } // Fallback: derive from active Noise session if available - if shortPeerID.count == 16, + if PeerIDResolver.isShortID(shortPeerID), let key = meshService.getNoiseService().getPeerPublicKeyData(shortPeerID) { let stable = key.hexEncodedString() shortIDToNoiseKey[shortPeerID] = stable @@ -970,7 +970,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { // Ephemeral peer IDs are 8 bytes = 16 hex characters // Noise public keys are 32 bytes = 64 hex characters - if peerID.count == 64, let noisePublicKey = Data(hexString: peerID) { + if PeerIDResolver.isNoiseKeyHex(peerID), let noisePublicKey = Data(hexString: peerID) { // This is a stable Noise key hex (used in private chats) // Find the ephemeral peer ID for this Noise key let ephemeralPeerID = unifiedPeerService.peers.first { peer in @@ -1033,7 +1033,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { @MainActor func isFavorite(peerID: String) -> Bool { // Distinguish between ephemeral peer IDs (16 hex chars) and Noise public keys (64 hex chars) - if peerID.count == 64, let noisePublicKey = Data(hexString: peerID) { + if PeerIDResolver.isNoiseKeyHex(peerID), let noisePublicKey = Data(hexString: peerID) { // This is a Noise public key if let status = FavoritesPersistenceService.shared.getFavoriteStatus(for: noisePublicKey) { return status.isFavorite @@ -3965,7 +3965,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { // Check if this might already be a nickname (not a hex peer ID) // Peer IDs are hex strings, so they only contain 0-9 and a-f - let isHexID = peerID.allSatisfy { $0.isHexDigit } + let isHexID = PeerIDResolver.isShortID(peerID) || PeerIDResolver.isNoiseKeyHex(peerID) if !isHexID { // If it's already a nickname, just return it return peerID @@ -4519,7 +4519,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { // Don't remove stable Noise key hexes (64 char hex strings) that have messages // These are used for Nostr messages when peer is offline - if staleID.count == 64, staleID.allSatisfy({ $0.isHexDigit }) { + if PeerIDResolver.isNoiseKeyHex(staleID) { if let messages = privateChats[staleID], !messages.isEmpty { // Keep this ID - it's a stable key with messages continue @@ -5086,7 +5086,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { var noiseKey: Data? = nil // First try as hex-encoded Noise key (64 chars) - if peerID.count == 64 { + if PeerIDResolver.isNoiseKeyHex(peerID) { noiseKey = Data(hexString: peerID) } @@ -5484,7 +5484,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate { // IMPORTANT: Also consolidate messages from stable Noise key if this is an ephemeral peer // This ensures Nostr messages appear in BLE chats - if peerID.count == 16 { // This is an ephemeral peer ID (8 bytes = 16 hex chars) + if PeerIDResolver.isShortID(peerID) { // This is an ephemeral peer ID (8 bytes = 16 hex chars) if let peer = unifiedPeerService.getPeer(by: peerID) { let stableKeyHex = peer.noisePublicKey.hexEncodedString() diff --git a/bitchat/Views/ContentView.swift b/bitchat/Views/ContentView.swift index 83b51bff..c4b46eed 100644 --- a/bitchat/Views/ContentView.swift +++ b/bitchat/Views/ContentView.swift @@ -1182,7 +1182,7 @@ struct ContentView: View { private func privateHeaderContent(for privatePeerID: String) -> some View { // Prefer short (mesh) ID whenever available for encryption/session status; keep stable key for display resolution only. let headerPeerID: String = { - if privatePeerID.count == 64 { + if PeerIDResolver.isNoiseKeyHex(privatePeerID) { // Map stable Noise key to short ID if we know it (even if not directly connected) if let short = viewModel.getShortIDForNoiseKey(privatePeerID) { return short } } @@ -1207,14 +1207,14 @@ struct ContentView: View { if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: Data(hexString: headerPeerID) ?? Data()), !fav.peerNickname.isEmpty { return fav.peerNickname } // Fallback: resolve from persisted social identity via fingerprint mapping - if headerPeerID.count == 16 { + if PeerIDResolver.isShortID(headerPeerID) { let candidates = SecureIdentityStateManager.shared.getCryptoIdentitiesByPeerIDPrefix(headerPeerID) if let id = candidates.first, let social = SecureIdentityStateManager.shared.getSocialIdentity(for: id.fingerprint) { if let pet = social.localPetname, !pet.isEmpty { return pet } if !social.claimedNickname.isEmpty { return social.claimedNickname } } - } else if headerPeerID.count == 64, let keyData = Data(hexString: headerPeerID) { + } else if PeerIDResolver.isNoiseKeyHex(headerPeerID), let keyData = Data(hexString: headerPeerID) { let fp = keyData.sha256Fingerprint() if let social = SecureIdentityStateManager.shared.getSocialIdentity(for: fp) { if let pet = social.localPetname, !pet.isEmpty { return pet } @@ -1298,7 +1298,7 @@ struct ContentView: View { if !privatePeerID.hasPrefix("nostr_") { // Use short peer ID if available for encryption status (sessions keyed by short ID) let statusPeerID: String = { - if privatePeerID.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID) { + if PeerIDResolver.isNoiseKeyHex(privatePeerID), let short = viewModel.getShortIDForNoiseKey(privatePeerID) { return short } return headerPeerID