mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-25 04:45:20 +00:00
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:
@@ -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) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user