refactor(hex+fingerprint): centralize SHA256 fingerprint helper; reuse PeerIDResolver in ID checks

- Move Data.sha256Fingerprint() to BinaryEncodingUtils for reuse; refactor NoiseEncryptionService to use it.\n- Replace manual short/full hex checks with PeerIDResolver in ChatViewModel, ContentView, and SecureIdentityStateManager.\n- Keep behavior identical; reduce ad-hoc string length/hex parsing.
This commit is contained in:
jack
2025-08-26 19:47:51 +02:00
parent 55187ac5c6
commit dd4e2f6e86
6 changed files with 26 additions and 23 deletions
@@ -310,7 +310,7 @@ class SecureIdentityStateManager {
func getCryptoIdentitiesByPeerIDPrefix(_ peerID: String) -> [CryptographicIdentity] { func getCryptoIdentitiesByPeerIDPrefix(_ peerID: String) -> [CryptographicIdentity] {
queue.sync { queue.sync {
// Defensive: ensure hex and correct length // 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) } return cryptographicIdentities.values.filter { $0.fingerprint.hasPrefix(peerID) }
} }
} }
+11 -1
View File
@@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import CryptoKit
// MARK: - Hex Encoding/Decoding // 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 // MARK: - Binary Encoding Utilities
extension Data { extension Data {
@@ -220,4 +231,3 @@ extension Data {
return data return data
} }
} }
@@ -548,8 +548,7 @@ class NoiseEncryptionService {
} }
private func calculateFingerprint(for publicKey: Curve25519.KeyAgreement.PublicKey) -> String { private func calculateFingerprint(for publicKey: Curve25519.KeyAgreement.PublicKey) -> String {
let hash = SHA256.hash(data: publicKey.rawRepresentation) return publicKey.rawRepresentation.sha256Fingerprint()
return hash.map { String(format: "%02x", $0) }.joined()
} }
// MARK: - Session Maintenance // MARK: - Session Maintenance
+1 -7
View File
@@ -412,10 +412,4 @@ class UnifiedPeerService: ObservableObject, TransportPeerEventsDelegate {
// MARK: - Helper Extensions // MARK: - Helper Extensions
extension Data { // Moved sha256Fingerprint() to BinaryEncodingUtils for reuse
func sha256Fingerprint() -> String {
// Implementation matches existing fingerprint generation in NoiseEncryptionService
let hash = SHA256.hash(data: self)
return hash.map { String(format: "%02x", $0) }.joined()
}
}
+8 -8
View File
@@ -161,7 +161,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
}() }()
let full = (nostrKeyMapping[spid] ?? bare).lowercased() let full = (nostrKeyMapping[spid] ?? bare).lowercased()
return "nostr:" + full 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 return "noise:" + full
} else { } else {
return "mesh:" + spid.lowercased() return "mesh:" + spid.lowercased()
@@ -309,7 +309,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
func getNoiseKeyForShortID(_ shortPeerID: String) -> String? { func getNoiseKeyForShortID(_ shortPeerID: String) -> String? {
if let mapped = shortIDToNoiseKey[shortPeerID] { return mapped } if let mapped = shortIDToNoiseKey[shortPeerID] { return mapped }
// Fallback: derive from active Noise session if available // Fallback: derive from active Noise session if available
if shortPeerID.count == 16, if PeerIDResolver.isShortID(shortPeerID),
let key = meshService.getNoiseService().getPeerPublicKeyData(shortPeerID) { let key = meshService.getNoiseService().getPeerPublicKeyData(shortPeerID) {
let stable = key.hexEncodedString() let stable = key.hexEncodedString()
shortIDToNoiseKey[shortPeerID] = stable shortIDToNoiseKey[shortPeerID] = stable
@@ -970,7 +970,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// Ephemeral peer IDs are 8 bytes = 16 hex characters // Ephemeral peer IDs are 8 bytes = 16 hex characters
// Noise public keys are 32 bytes = 64 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) // This is a stable Noise key hex (used in private chats)
// Find the ephemeral peer ID for this Noise key // Find the ephemeral peer ID for this Noise key
let ephemeralPeerID = unifiedPeerService.peers.first { peer in let ephemeralPeerID = unifiedPeerService.peers.first { peer in
@@ -1033,7 +1033,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
@MainActor @MainActor
func isFavorite(peerID: String) -> Bool { func isFavorite(peerID: String) -> Bool {
// Distinguish between ephemeral peer IDs (16 hex chars) and Noise public keys (64 hex chars) // 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 // This is a Noise public key
if let status = FavoritesPersistenceService.shared.getFavoriteStatus(for: noisePublicKey) { if let status = FavoritesPersistenceService.shared.getFavoriteStatus(for: noisePublicKey) {
return status.isFavorite return status.isFavorite
@@ -3965,7 +3965,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// Check if this might already be a nickname (not a hex peer ID) // 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 // 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 !isHexID {
// If it's already a nickname, just return it // If it's already a nickname, just return it
return peerID return peerID
@@ -4519,7 +4519,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
// Don't remove stable Noise key hexes (64 char hex strings) that have messages // Don't remove stable Noise key hexes (64 char hex strings) that have messages
// These are used for Nostr messages when peer is offline // 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 { if let messages = privateChats[staleID], !messages.isEmpty {
// Keep this ID - it's a stable key with messages // Keep this ID - it's a stable key with messages
continue continue
@@ -5086,7 +5086,7 @@ class ChatViewModel: ObservableObject, BitchatDelegate {
var noiseKey: Data? = nil var noiseKey: Data? = nil
// First try as hex-encoded Noise key (64 chars) // First try as hex-encoded Noise key (64 chars)
if peerID.count == 64 { if PeerIDResolver.isNoiseKeyHex(peerID) {
noiseKey = Data(hexString: 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 // IMPORTANT: Also consolidate messages from stable Noise key if this is an ephemeral peer
// This ensures Nostr messages appear in BLE chats // 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) { if let peer = unifiedPeerService.getPeer(by: peerID) {
let stableKeyHex = peer.noisePublicKey.hexEncodedString() let stableKeyHex = peer.noisePublicKey.hexEncodedString()
+4 -4
View File
@@ -1182,7 +1182,7 @@ struct ContentView: View {
private func privateHeaderContent(for privatePeerID: String) -> some 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. // Prefer short (mesh) ID whenever available for encryption/session status; keep stable key for display resolution only.
let headerPeerID: String = { 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) // Map stable Noise key to short ID if we know it (even if not directly connected)
if let short = viewModel.getShortIDForNoiseKey(privatePeerID) { return short } 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()), if let fav = FavoritesPersistenceService.shared.getFavoriteStatus(for: Data(hexString: headerPeerID) ?? Data()),
!fav.peerNickname.isEmpty { return fav.peerNickname } !fav.peerNickname.isEmpty { return fav.peerNickname }
// Fallback: resolve from persisted social identity via fingerprint mapping // Fallback: resolve from persisted social identity via fingerprint mapping
if headerPeerID.count == 16 { if PeerIDResolver.isShortID(headerPeerID) {
let candidates = SecureIdentityStateManager.shared.getCryptoIdentitiesByPeerIDPrefix(headerPeerID) let candidates = SecureIdentityStateManager.shared.getCryptoIdentitiesByPeerIDPrefix(headerPeerID)
if let id = candidates.first, if let id = candidates.first,
let social = SecureIdentityStateManager.shared.getSocialIdentity(for: id.fingerprint) { let social = SecureIdentityStateManager.shared.getSocialIdentity(for: id.fingerprint) {
if let pet = social.localPetname, !pet.isEmpty { return pet } if let pet = social.localPetname, !pet.isEmpty { return pet }
if !social.claimedNickname.isEmpty { return social.claimedNickname } 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() let fp = keyData.sha256Fingerprint()
if let social = SecureIdentityStateManager.shared.getSocialIdentity(for: fp) { if let social = SecureIdentityStateManager.shared.getSocialIdentity(for: fp) {
if let pet = social.localPetname, !pet.isEmpty { return pet } if let pet = social.localPetname, !pet.isEmpty { return pet }
@@ -1298,7 +1298,7 @@ struct ContentView: View {
if !privatePeerID.hasPrefix("nostr_") { if !privatePeerID.hasPrefix("nostr_") {
// Use short peer ID if available for encryption status (sessions keyed by short ID) // Use short peer ID if available for encryption status (sessions keyed by short ID)
let statusPeerID: String = { let statusPeerID: String = {
if privatePeerID.count == 64, let short = viewModel.getShortIDForNoiseKey(privatePeerID) { if PeerIDResolver.isNoiseKeyHex(privatePeerID), let short = viewModel.getShortIDForNoiseKey(privatePeerID) {
return short return short
} }
return headerPeerID return headerPeerID