From 78fb3f1bf6e1ffde7e7d711a4e23bab899421e53 Mon Sep 17 00:00:00 2001 From: Islam <2553451+qalandarov@users.noreply.github.com> Date: Tue, 30 Sep 2025 11:47:16 +0100 Subject: [PATCH] Unify SHA256 hash and hex usages (#687) --- bitchat/Noise/NoiseProtocol.swift | 4 +-- .../Noise/NoiseSecurityConsiderations.swift | 1 - bitchat/Nostr/NostrIdentity.swift | 5 +--- bitchat/Nostr/NostrProtocol.swift | 5 +--- bitchat/Protocols/BinaryEncodingUtils.swift | 2 +- bitchat/Protocols/PeerID.swift | 14 ---------- bitchat/Protocols/PeerIDUtils.swift | 9 +++++++ bitchat/Services/BLEService.swift | 27 ++++++------------- bitchat/Services/NoiseEncryptionService.swift | 10 ++----- bitchat/Services/UnifiedPeerService.swift | 11 -------- bitchat/Services/VerificationService.swift | 3 +-- bitchat/Utils/Data+SHA256.swift | 22 +++++++++++++++ bitchat/ViewModels/ChatViewModel.swift | 13 +++------ bitchatTests/NostrProtocolTests.swift | 6 ----- 14 files changed, 50 insertions(+), 82 deletions(-) delete mode 100644 bitchat/Protocols/PeerID.swift create mode 100644 bitchat/Protocols/PeerIDUtils.swift create mode 100644 bitchat/Utils/Data+SHA256.swift diff --git a/bitchat/Noise/NoiseProtocol.swift b/bitchat/Noise/NoiseProtocol.swift index 916492f8..ded7fcfb 100644 --- a/bitchat/Noise/NoiseProtocol.swift +++ b/bitchat/Noise/NoiseProtocol.swift @@ -397,7 +397,7 @@ final class NoiseSymmetricState { if nameData.count <= 32 { self.hash = nameData + Data(repeating: 0, count: 32 - nameData.count) } else { - self.hash = Data(SHA256.hash(data: nameData)) + self.hash = nameData.sha256Hash() } self.chainingKey = self.hash } @@ -410,7 +410,7 @@ final class NoiseSymmetricState { } func mixHash(_ data: Data) { - hash = Data(SHA256.hash(data: hash + data)) + hash = (hash + data).sha256Hash() } func mixKeyAndHash(_ inputKeyMaterial: Data) { diff --git a/bitchat/Noise/NoiseSecurityConsiderations.swift b/bitchat/Noise/NoiseSecurityConsiderations.swift index a9e69216..2ae05854 100644 --- a/bitchat/Noise/NoiseSecurityConsiderations.swift +++ b/bitchat/Noise/NoiseSecurityConsiderations.swift @@ -8,7 +8,6 @@ import BitLogger import Foundation -import CryptoKit // MARK: - Security Constants diff --git a/bitchat/Nostr/NostrIdentity.swift b/bitchat/Nostr/NostrIdentity.swift index 9630b403..6aa52757 100644 --- a/bitchat/Nostr/NostrIdentity.swift +++ b/bitchat/Nostr/NostrIdentity.swift @@ -226,10 +226,7 @@ struct NostrIdentityBridge { } } // As a final fallback, hash the seed+msg and try again - var combined = Data() - combined.append(seed) - combined.append(msg) - let fallback = Data(CryptoKit.SHA256.hash(data: combined)) + let fallback = (seed + msg).sha256Hash() return try NostrIdentity(privateKeyData: fallback) } } diff --git a/bitchat/Nostr/NostrProtocol.swift b/bitchat/Nostr/NostrProtocol.swift index 4510a182..2a9e889d 100644 --- a/bitchat/Nostr/NostrProtocol.swift +++ b/bitchat/Nostr/NostrProtocol.swift @@ -521,10 +521,7 @@ struct NostrEvent: Codable { ] as [Any] let data = try JSONSerialization.data(withJSONObject: serialized, options: [.withoutEscapingSlashes]) - let hash = CryptoKit.SHA256.hash(data: data) - let hashData = Data(hash) - let hashHex = hash.compactMap { String(format: "%02x", $0) }.joined() - return (hashHex, hashData) + return (data.sha256Fingerprint(), data.sha256Hash()) } func jsonString() throws -> String { diff --git a/bitchat/Protocols/BinaryEncodingUtils.swift b/bitchat/Protocols/BinaryEncodingUtils.swift index 78b4c361..799385b0 100644 --- a/bitchat/Protocols/BinaryEncodingUtils.swift +++ b/bitchat/Protocols/BinaryEncodingUtils.swift @@ -197,7 +197,7 @@ extension Data { offset += 16 // Convert 16 bytes to UUID string format - let uuid = uuidData.map { String(format: "%02x", $0) }.joined() + let uuid = uuidData.hexEncodedString() // Insert hyphens at proper positions: 8-4-4-4-12 var result = "" diff --git a/bitchat/Protocols/PeerID.swift b/bitchat/Protocols/PeerID.swift deleted file mode 100644 index f77bcff4..00000000 --- a/bitchat/Protocols/PeerID.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation -import CryptoKit - -// MARK: - Peer ID Utilities - -struct PeerIDUtils { - /// Derive the stable 16-hex peer ID from a Noise static public key - static func derivePeerID(fromPublicKey publicKey: Data) -> String { - let digest = SHA256.hash(data: publicKey) - let hex = digest.map { String(format: "%02x", $0) }.joined() - return String(hex.prefix(16)) - } -} - diff --git a/bitchat/Protocols/PeerIDUtils.swift b/bitchat/Protocols/PeerIDUtils.swift new file mode 100644 index 00000000..7a3c345b --- /dev/null +++ b/bitchat/Protocols/PeerIDUtils.swift @@ -0,0 +1,9 @@ +import Foundation + +struct PeerIDUtils { + /// Derive the stable 16-hex peer ID from a Noise static public key + static func derivePeerID(fromPublicKey publicKey: Data) -> String { + String(publicKey.sha256Fingerprint().prefix(16)) + } +} + diff --git a/bitchat/Services/BLEService.swift b/bitchat/Services/BLEService.swift index 55c2b490..2db1f85b 100644 --- a/bitchat/Services/BLEService.swift +++ b/bitchat/Services/BLEService.swift @@ -264,8 +264,7 @@ final class BLEService: NSObject { // MARK: - Helpers: IDs, selection, and write backpressure private func makeMessageID(for packet: BitchatPacket) -> String { let senderID = packet.senderID.hexEncodedString() - let digest = SHA256.hash(data: packet.payload) - let digestPrefix = digest.prefix(4).map { String(format: "%02x", $0) }.joined() + let digestPrefix = packet.payload.sha256Hash().prefix(4).hexEncodedString() return "\(senderID)-\(packet.timestamp)-\(packet.type)-\(digestPrefix)" } @@ -777,12 +776,7 @@ final class BLEService: NSObject { func getPeerFingerprint(_ peerID: String) -> String? { return collectionsQueue.sync { - if let publicKey = peers[peerID]?.noisePublicKey { - // Use the same fingerprinting method as NoiseEncryptionService/UnifiedPeerService (SHA-256 of raw key) - let hash = SHA256.hash(data: publicKey) - return hash.map { String(format: "%02x", $0) }.joined() - } - return nil + return peers[peerID]?.noisePublicKey?.sha256Fingerprint() } } @@ -1705,17 +1699,12 @@ final class BLEService: NSObject { } // Persist cryptographic identity and signing key for robust offline verification - do { - // Derive fingerprint from Noise public key - let hash = SHA256.hash(data: announcement.noisePublicKey) - let fingerprint = hash.map { String(format: "%02x", $0) }.joined() - identityManager.upsertCryptographicIdentity( - fingerprint: fingerprint, - noisePublicKey: announcement.noisePublicKey, - signingPublicKey: announcement.signingPublicKey, - claimedNickname: announcement.nickname - ) - } + identityManager.upsertCryptographicIdentity( + fingerprint: announcement.noisePublicKey.sha256Fingerprint(), + noisePublicKey: announcement.noisePublicKey, + signingPublicKey: announcement.signingPublicKey, + claimedNickname: announcement.nickname + ) // Record this announce for lightweight rebroadcast buffer (exclude self) if peerID != myPeerID { diff --git a/bitchat/Services/NoiseEncryptionService.swift b/bitchat/Services/NoiseEncryptionService.swift index c6f56199..545b1772 100644 --- a/bitchat/Services/NoiseEncryptionService.swift +++ b/bitchat/Services/NoiseEncryptionService.swift @@ -272,8 +272,7 @@ final class NoiseEncryptionService { /// Get our identity fingerprint func getIdentityFingerprint() -> String { - let hash = SHA256.hash(data: staticIdentityPublicKey.rawRepresentation) - return hash.map { String(format: "%02x", $0) }.joined() + staticIdentityPublicKey.rawRepresentation.sha256Fingerprint() } /// Get peer's public key data @@ -554,7 +553,7 @@ final class NoiseEncryptionService { private func handleSessionEstablished(peerID: String, remoteStaticKey: Curve25519.KeyAgreement.PublicKey) { // Calculate fingerprint - let fingerprint = calculateFingerprint(for: remoteStaticKey) + let fingerprint = remoteStaticKey.rawRepresentation.sha256Fingerprint() // Store fingerprint mapping serviceQueue.sync(flags: .barrier) { @@ -572,11 +571,6 @@ final 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() - } // MARK: - Session Maintenance diff --git a/bitchat/Services/UnifiedPeerService.swift b/bitchat/Services/UnifiedPeerService.swift index 962c1c19..ed405cd4 100644 --- a/bitchat/Services/UnifiedPeerService.swift +++ b/bitchat/Services/UnifiedPeerService.swift @@ -10,7 +10,6 @@ import BitLogger import Foundation import Combine import SwiftUI -import CryptoKit /// Single source of truth for peer state, combining mesh connectivity and favorites @MainActor @@ -389,13 +388,3 @@ final 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() - } -} diff --git a/bitchat/Services/VerificationService.swift b/bitchat/Services/VerificationService.swift index 2a69fbb7..cf3ab4de 100644 --- a/bitchat/Services/VerificationService.swift +++ b/bitchat/Services/VerificationService.swift @@ -1,5 +1,4 @@ import Foundation -import CryptoKit /// QR verification scaffolding: schema, signing, and basic challenge/response helpers. final class VerificationService { @@ -95,7 +94,7 @@ final class VerificationService { nickname: payload.nickname, ts: payload.ts, nonceB64: payload.nonceB64, - sigHex: sig.map { String(format: "%02x", $0) }.joined()) + sigHex: sig.hexEncodedString()) let out = signed.toURLString() Cache.last = (nickname, npub, Date(), out) return out diff --git a/bitchat/Utils/Data+SHA256.swift b/bitchat/Utils/Data+SHA256.swift new file mode 100644 index 00000000..cd1ae629 --- /dev/null +++ b/bitchat/Utils/Data+SHA256.swift @@ -0,0 +1,22 @@ +// +// Data+SHA256.swift +// bitchat +// +// Created by Islam on 26/09/2025. +// + +import struct Foundation.Data +import struct CryptoKit.SHA256 + +extension Data { + /// Returns the hex representation of SHA256 hash + func sha256Fingerprint() -> String { + // Implementation matches existing fingerprint generation in NoiseEncryptionService + sha256Hash().hexEncodedString() + } + + /// Returns the SHA256 hash wrapped in Data + func sha256Hash() -> Data { + Data(SHA256.hash(data: self)) + } +} diff --git a/bitchat/ViewModels/ChatViewModel.swift b/bitchat/ViewModels/ChatViewModel.swift index 63afd422..b24f64cb 100644 --- a/bitchat/ViewModels/ChatViewModel.swift +++ b/bitchat/ViewModels/ChatViewModel.swift @@ -80,7 +80,6 @@ import BitLogger import Foundation import SwiftUI -import CryptoKit import Combine import CommonCrypto import CoreBluetooth @@ -1299,10 +1298,7 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { // Called when we receive a peer's public key @MainActor func registerPeerPublicKey(peerID: String, publicKeyData: Data) { - // Create a fingerprint from the public key (full SHA256, not truncated) - let fingerprintStr = SHA256.hash(data: publicKeyData) - .compactMap { String(format: "%02x", $0) } - .joined() + let fingerprintStr = publicKeyData.sha256Fingerprint() // Only register if not already registered if peerIDToPublicKeyFingerprint[peerID] != fingerprintStr { @@ -5184,11 +5180,8 @@ final class ChatViewModel: ObservableObject, BitchatDelegate { } // Validate recipient - if let rid = packet.recipientID { - let ridHex = rid.map { String(format: "%02x", $0) }.joined() - if ridHex != meshService.myPeerID { - return - } + if let rid = packet.recipientID, rid.hexEncodedString() != meshService.myPeerID { + return } // Parse plaintext typed payload diff --git a/bitchatTests/NostrProtocolTests.swift b/bitchatTests/NostrProtocolTests.swift index 0cc92306..f230ffca 100644 --- a/bitchatTests/NostrProtocolTests.swift +++ b/bitchatTests/NostrProtocolTests.swift @@ -6,16 +6,10 @@ // import XCTest -import CryptoKit @testable import bitchat final class NostrProtocolTests: XCTestCase { - override func setUp() { - super.setUp() - // SecureLogger is always enabled - } - func testNIP17MessageRoundTrip() throws { // Create sender and recipient identities let sender = try NostrIdentity.generate()