What Are the Security Vulnerabilities in bitchat? A Deep Code Review
Key takeaways: bitchat contains several potential security vulnerabilities including insecure key storage, unreliable memory wiping of cryptographic secrets, unbounded proof-of-work difficulty, and unverified Tor relay claims that could enable downgrade attacks.
The permissionlesstech/bitchat repository implements a privacy-focused messenger combining BLE-based Noise sessions, Nostr protocol integration, and custom encryption schemes. While the architecture aims for strong security guarantees, a detailed source code analysis reveals multiple implementation weaknesses that could compromise user confidentiality or enable denial-of-service attacks.
Key Storage and Persistence Vulnerabilities
UserDefaults and File System Key Exposure
The SecureIdentityStateManager.swift component manages cryptographic identities including Noise protocol keys, Nostr signing keys, and pre-keys. However, the implementation stores these sensitive materials using standard file operations without explicitly requesting iOS Data Protection.
// Vulnerable pattern in SecureIdentityStateManager.swift
// Keys written without NSFileProtectionComplete
func saveIdentity(_ identity: Identity) throws {
let data = try JSONEncoder().encode(identity)
try data.write(to: identityURL, options: .atomic)
// Missing: FileManager.setAttributes for protection level
}
Without NSFileProtectionComplete, an attacker with physical device access can extract keys from the filesystem backup or via forensic tools when the device is locked.
Recommended Fix
func storeKeySecurely(_ key: Data, at url: URL) throws {
let options: [FileAttributeKey: Any] = [
.protectionKey: FileProtectionType.complete
]
try key.write(to: url, options: .atomic)
try FileManager.default.setAttributes(options, ofItemAtPath: url.path)
}
Memory Wiping and Side-Channel Risks
Compiler-Optimizable Secret Erasure
The panic wipe functionality in PreviewKeychainManager.swift attempts to clear sensitive memory but uses patterns that optimizing compilers may eliminate:
// From PreviewKeychainManager.swift
func wipe(_ data: inout [UInt8]) {
for i in data.indices {
data[i] = 0 // May be optimized away by Swift compiler
}
}
Modern compilers treat dead stores as eliminable optimizations. Without volatile semantics or memory barriers, secrets may persist in RAM longer than intended, exposing them to cold boot attacks or memory dumping.
Constant-Time Comparison Absence
Cryptographic comparisons throughout the codebase (signature verification, MAC checks) likely use standard Swift equality operators rather than constant-time implementations, creating timing side channels.
BLE Noise Protocol Implementation Weaknesses
The BLENoisePacketHandler.swift file implements the Noise_XX handshake pattern for Bluetooth Low Energy connections. Several implementation choices introduce vulnerabilities:
Missing Length Prefix Validation
// From BLENoisePacketHandler.swift
func processPacket(_ data: Data) throws -> NoiseMessage {
let messageType = data[0] // No bounds check before access
let payload = data.dropFirst()
// ...
}
Absent explicit length validation, malformed packets shorter than expected trigger unhandled out-of-bounds access or, if caught, leave the handshake state machine in an undefined condition.
Manual Payload Construction
The Noise handshake payload is assembled via Data concatenation without adherence to the Noise Protocol specification's exact length-prefix requirements. Subtle encoding deviations could enable version rollback or message format confusion attacks.
Unbounded Proof-of-Work (DoS Vector)
NostrPoW.swift validates proof-of-work difficulty for Nostr events but enforces no upper bound:
// From NostrPoW.swift
func verifyProofOfWork(event: NostrEvent, difficulty: Int) -> Bool {
// Accepts any 32-bit integer difficulty
let target = String(repeating: "0", count: difficulty)
return event.id.prefix(difficulty) == target
}
An attacker can submit events with difficulty = 2_000_000_000, forcing the client to perform computationally infeasible work. This creates a trivial CPU exhaustion denial-of-service vector.
Mitigation
let MAX_DIFFICULTY = 30
func verifyProofOfWork(event: NostrEvent, difficulty: Int) throws -> Bool {
guard (0...MAX_DIFFICULTY).contains(difficulty) else {
throw PoWError.excessiveDifficulty
}
// ... verification logic
}
Tor Routing Verification Gap
NostrRelayManager.swift claims Tor support but relies on unverified assertions:
// From NostrRelayManager.swift
func connectToRelay(_ relay: Relay) async throws {
if relay.supportsTor {
// No cryptographic verification that .onion address is authentic
try await establishTorConnection(to: relay.url)
}
}
The supportsTor boolean derives from URL scheme or user configuration without:
- Certificate pinning to known Tor hidden service keys
- Proof-of-possession of corresponding private keys
- Verification that traffic actually traverses the Tor network
A compromised or malicious relay can advertise Tor support while routing through clearnet, defeating the anonymity guarantees.
Custom Cryptographic Format Risks
NostrEmbeddedBitChat.swift implements a proprietary "private envelope" encryption scheme:
// From NostrEmbeddedBitChat.swift
struct PrivateEnvelope {
let nonce: Data // 12-byte nonce handling
let ciphertext: Data
let authTag: Data
}
This format is not standardized via Nostr Improvement Proposals (NIPs) and lacks:
- Published specification and security proof
- Independent cryptographic audit
- Community review for nonce reuse, key commitment, and domain separation weaknesses
Ad-hoc cryptographic constructions historically contain subtle flaws (e.g., AES-GCM nonce reuse, lack of associated data binding) that only emerge under adversarial analysis.
Input Validation Deficiencies
Bech32 Address Parsing
Bech32.swift processes human-readable addresses with insufficient character range validation:
// From Bech32.swift
func decode(_ string: String) throws -> (hrp: String, data: [UInt8]) {
// Limited validation of character set
let lower = string.lowercased()
// ...
}
Malformed inputs from untrusted relays can propagate through the parsing pipeline, potentially triggering exceptions in dependent components or creating inconsistent state.
GeoRelay Directory Injection
The online_relays_gps.csv file provides relay endpoints that NostrRelayManager.swift consumes without:
- DNSSEC validation
- Certificate transparency logging checks
- Rate-limiting on connection attempts
// From NostrRelayManager.swift
let relays = try parseRelayCSV(from: "online_relays_gps.csv")
for relay in relays {
try await connectToRelay(relay) // No rate limiting
}
Cryptographic API Inconsistency
ChatViewModel.swift mixes two distinct cryptographic libraries:
// From ChatViewModel.swift
import CryptoKit // Apple's modern Swift cryptography
import CommonCrypto // Legacy C-based interface
func hashMessage(_ message: String) -> Data {
// Uses CommonCrypto SHA-256
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CC_SHA256(message, CC_LONG(message.count), &hash)
return Data(hash)
}
func encryptMessage(_ message: Data) -> SealedBox {
// Uses CryptoKit AES-GCM
return try! AES.GCM.seal(message, using: symmetricKey)
}
This inconsistency increases attack surface and risks subtle interoperability bugs (e.g., different endianness handling, divergent error behavior).
Summary
-
Key storage lacks Data Protection —
SecureIdentityStateManager.swiftpersists keys withoutNSFileProtectionComplete, enabling physical extraction attacks. -
Memory wiping is compiler-optimizable — The wipe implementation in
PreviewKeychainManager.swiftmay be eliminated by Swift optimizer optimizations. -
BLE Noise handshake lacks bounds checking —
BLENoisePacketHandler.swiftprocesses packets without validating lengths, risking crashes or state corruption. -
Proof-of-work has no difficulty ceiling —
NostrPoW.swiftaccepts arbitrary difficulty values, creating a CPU exhaustion DoS vector. -
Tor support is assertion-based —
NostrRelayManager.swifttrusts boolean flags without cryptographic verification of .onion authenticity. -
Custom envelope format is unaudited —
NostrEmbeddedBitChat.swiftimplements novel cryptography without standardization or review. -
Input validation is insufficient —
Bech32.swiftand relay parsing accept untrusted data with limited sanitization. -
Cryptographic APIs are inconsistent —
ChatViewModel.swiftmixes CryptoKit and CommonCrypto, increasing complexity and bug risk.
Frequently Asked Questions
How can I verify if my bitchat keys are properly protected?
Check your device's backup encryption settings and examine the SecureIdentityStateManager.swift implementation in your build. Keys should be stored with NSFileProtectionComplete and excluded from iCloud backup. Without these protections, forensic tools can extract keys from device backups or physical storage chips.
What makes the proof-of-work vulnerability particularly dangerous?
The unbounded difficulty check in NostrPoW.swift allows a single malicious Nostr event to trigger CPU-intensive hash calculations that drain battery and freeze the UI. Unlike network-based DoS, this attack propagates through the decentralized relay network and can target specific users by embedding their public key in crafted events.
Is the custom envelope format in bitchat safe to use?
The private envelope scheme in NostrEmbeddedBitChat.swift has not undergone published security analysis or community review. Novel cryptographic constructions frequently contain subtle flaws in nonce handling, key commitment, or associated data binding that only emerge under adversarial testing. Users should prefer established standards like NIP-44 for Nostr encryption until independent audits are published.
Can the Tor routing claim actually be verified?
Currently, no. The NostrRelayManager.swift implementation accepts relay self-attestations of Tor support. True verification requires checking that the relay presents a valid .onion service certificate and that connection handshakes complete through Tor exit nodes—neither of which the current code implements.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →