mirror of
https://github.com/permissionlesstech/bitchat.git
synced 2026-07-26 06:25:19 +00:00
Implement Noise Protocol Framework and peer ID rotation for enhanced security and privacy
This major update replaces the basic encryption with the Noise Protocol Framework and adds ephemeral peer ID rotation for enhanced privacy. Key Changes: Security Infrastructure: - Implemented Noise Protocol Framework (XX handshake pattern) - End-to-end encryption with forward secrecy and identity hiding - Session management with automatic rekey support - Channel encryption with password-derived keys Privacy Enhancements: - Ephemeral peer ID rotation (5-15 minute random intervals) - Persistent identity through public key fingerprints - Favorites and verification persist across ID rotations - Block list based on fingerprints, not ephemeral IDs Core Components Added: - NoiseEncryptionService: Main encryption service - NoiseSession: Individual peer session management - NoiseChannelEncryption: Password-protected channel support - SecureIdentityStateManager: Persistent identity storage - FingerprintView: Visual fingerprint verification UI Bug Fixes: - Fixed handshake storm with tie-breaker mechanism - Fixed missing connect messages during peer rotation - Fixed delivery ACK compression issues - Fixed race conditions in message queue - Fixed nickname resolution for rotated peer IDs Testing: - Comprehensive test suite for Noise implementation - Security validator tests - Channel encryption tests - Identity persistence tests - Rate limiter tests Documentation: - BRING_THE_NOISE.md: Technical implementation details - Updated WHITEPAPER.md: Simplified and focused on core innovations - Removed temporary debug documentation The implementation maintains backward compatibility while significantly improving security and privacy. All existing features (channels, private messages, favorites, blocking) work seamlessly with the new system.
This commit is contained in:
@@ -41,9 +41,20 @@ struct MessagePadding {
|
||||
|
||||
// Last byte tells us how much padding to remove
|
||||
let paddingLength = Int(data[data.count - 1])
|
||||
guard paddingLength > 0 && paddingLength <= data.count else { return data }
|
||||
guard paddingLength > 0 && paddingLength <= data.count else {
|
||||
// Debug logging for 243-byte packets
|
||||
if data.count == 243 {
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
return data.prefix(data.count - paddingLength)
|
||||
let result = data.prefix(data.count - paddingLength)
|
||||
|
||||
// Debug logging for 243-byte packets
|
||||
if data.count == 243 {
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Find optimal block size for data
|
||||
@@ -66,7 +77,7 @@ struct MessagePadding {
|
||||
|
||||
enum MessageType: UInt8 {
|
||||
case announce = 0x01
|
||||
case keyExchange = 0x02
|
||||
// 0x02 was legacy keyExchange - removed
|
||||
case leave = 0x03
|
||||
case message = 0x04 // All user messages (private and broadcast)
|
||||
case fragmentStart = 0x05
|
||||
@@ -77,6 +88,40 @@ enum MessageType: UInt8 {
|
||||
case deliveryAck = 0x0A // Acknowledge message received
|
||||
case deliveryStatusRequest = 0x0B // Request delivery status update
|
||||
case readReceipt = 0x0C // Message has been read/viewed
|
||||
|
||||
// Noise Protocol messages
|
||||
case noiseHandshakeInit = 0x10 // Noise handshake initiation
|
||||
case noiseHandshakeResp = 0x11 // Noise handshake response
|
||||
case noiseEncrypted = 0x12 // Noise encrypted transport message
|
||||
case noiseIdentityAnnounce = 0x13 // Announce static public key for discovery
|
||||
case channelKeyVerifyRequest = 0x14 // Request key verification for a channel
|
||||
case channelKeyVerifyResponse = 0x15 // Response to key verification request
|
||||
case channelPasswordUpdate = 0x16 // Distribute new password to channel members
|
||||
case channelMetadata = 0x17 // Announce channel creator and metadata
|
||||
|
||||
var description: String {
|
||||
switch self {
|
||||
case .announce: return "announce"
|
||||
case .leave: return "leave"
|
||||
case .message: return "message"
|
||||
case .fragmentStart: return "fragmentStart"
|
||||
case .fragmentContinue: return "fragmentContinue"
|
||||
case .fragmentEnd: return "fragmentEnd"
|
||||
case .channelAnnounce: return "channelAnnounce"
|
||||
case .channelRetention: return "channelRetention"
|
||||
case .deliveryAck: return "deliveryAck"
|
||||
case .deliveryStatusRequest: return "deliveryStatusRequest"
|
||||
case .readReceipt: return "readReceipt"
|
||||
case .noiseHandshakeInit: return "noiseHandshakeInit"
|
||||
case .noiseHandshakeResp: return "noiseHandshakeResp"
|
||||
case .noiseEncrypted: return "noiseEncrypted"
|
||||
case .noiseIdentityAnnounce: return "noiseIdentityAnnounce"
|
||||
case .channelKeyVerifyRequest: return "channelKeyVerifyRequest"
|
||||
case .channelKeyVerifyResponse: return "channelKeyVerifyResponse"
|
||||
case .channelPasswordUpdate: return "channelPasswordUpdate"
|
||||
case .channelMetadata: return "channelMetadata"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Special recipient ID for broadcast messages
|
||||
@@ -109,7 +154,17 @@ struct BitchatPacket: Codable {
|
||||
init(type: UInt8, ttl: UInt8, senderID: String, payload: Data) {
|
||||
self.version = 1
|
||||
self.type = type
|
||||
self.senderID = senderID.data(using: .utf8)!
|
||||
// Convert hex string peer ID to binary data (8 bytes)
|
||||
var senderData = Data()
|
||||
var tempID = senderID
|
||||
while tempID.count >= 2 {
|
||||
let hexByte = String(tempID.prefix(2))
|
||||
if let byte = UInt8(hexByte, radix: 16) {
|
||||
senderData.append(byte)
|
||||
}
|
||||
tempID = String(tempID.dropFirst(2))
|
||||
}
|
||||
self.senderID = senderData
|
||||
self.recipientID = nil
|
||||
self.timestamp = UInt64(Date().timeIntervalSince1970 * 1000) // milliseconds
|
||||
self.payload = payload
|
||||
@@ -182,6 +237,151 @@ struct ReadReceipt: Codable {
|
||||
}
|
||||
}
|
||||
|
||||
// Channel key verification request
|
||||
struct ChannelKeyVerifyRequest: Codable {
|
||||
let channel: String
|
||||
let requesterID: String
|
||||
let keyCommitment: String // SHA256 hash of the key they have
|
||||
let timestamp: Date
|
||||
|
||||
init(channel: String, requesterID: String, keyCommitment: String) {
|
||||
self.channel = channel
|
||||
self.requesterID = requesterID
|
||||
self.keyCommitment = keyCommitment
|
||||
self.timestamp = Date()
|
||||
}
|
||||
|
||||
func encode() -> Data? {
|
||||
return try? JSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
static func decode(from data: Data) -> ChannelKeyVerifyRequest? {
|
||||
try? JSONDecoder().decode(ChannelKeyVerifyRequest.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
// Channel key verification response
|
||||
struct ChannelKeyVerifyResponse: Codable {
|
||||
let channel: String
|
||||
let responderID: String
|
||||
let verified: Bool // Whether the key commitment matches
|
||||
let timestamp: Date
|
||||
|
||||
init(channel: String, responderID: String, verified: Bool) {
|
||||
self.channel = channel
|
||||
self.responderID = responderID
|
||||
self.verified = verified
|
||||
self.timestamp = Date()
|
||||
}
|
||||
|
||||
func encode() -> Data? {
|
||||
return try? JSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
static func decode(from data: Data) -> ChannelKeyVerifyResponse? {
|
||||
try? JSONDecoder().decode(ChannelKeyVerifyResponse.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
// Channel password update (sent by owner to members)
|
||||
struct ChannelPasswordUpdate: Codable {
|
||||
let channel: String
|
||||
let ownerID: String // Deprecated, kept for backward compatibility
|
||||
let ownerFingerprint: String // Noise protocol fingerprint of owner
|
||||
let encryptedPassword: Data // New password encrypted with recipient's Noise session
|
||||
let newKeyCommitment: String // SHA256 of new key for verification
|
||||
let timestamp: Date
|
||||
|
||||
init(channel: String, ownerID: String, ownerFingerprint: String, encryptedPassword: Data, newKeyCommitment: String) {
|
||||
self.channel = channel
|
||||
self.ownerID = ownerID
|
||||
self.ownerFingerprint = ownerFingerprint
|
||||
self.encryptedPassword = encryptedPassword
|
||||
self.newKeyCommitment = newKeyCommitment
|
||||
self.timestamp = Date()
|
||||
}
|
||||
|
||||
func encode() -> Data? {
|
||||
return try? JSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
static func decode(from data: Data) -> ChannelPasswordUpdate? {
|
||||
try? JSONDecoder().decode(ChannelPasswordUpdate.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
// Channel metadata announcement
|
||||
struct ChannelMetadata: Codable {
|
||||
let channel: String
|
||||
let creatorID: String
|
||||
let creatorFingerprint: String // Noise protocol fingerprint
|
||||
let createdAt: Date
|
||||
let isPasswordProtected: Bool
|
||||
let keyCommitment: String? // SHA256 of channel key if password-protected
|
||||
|
||||
init(channel: String, creatorID: String, creatorFingerprint: String, isPasswordProtected: Bool, keyCommitment: String?) {
|
||||
self.channel = channel
|
||||
self.creatorID = creatorID
|
||||
self.creatorFingerprint = creatorFingerprint
|
||||
self.createdAt = Date()
|
||||
self.isPasswordProtected = isPasswordProtected
|
||||
self.keyCommitment = keyCommitment
|
||||
}
|
||||
|
||||
func encode() -> Data? {
|
||||
return try? JSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
static func decode(from data: Data) -> ChannelMetadata? {
|
||||
try? JSONDecoder().decode(ChannelMetadata.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Peer Identity Rotation
|
||||
|
||||
// Enhanced identity announcement with rotation support
|
||||
struct NoiseIdentityAnnouncement: Codable {
|
||||
let peerID: String // Current ephemeral peer ID
|
||||
let publicKey: Data // Noise static public key
|
||||
let nickname: String // Current nickname
|
||||
let timestamp: Date // When this binding was created
|
||||
let previousPeerID: String? // Previous peer ID (for smooth transition)
|
||||
let signature: Data // Signature proving ownership
|
||||
|
||||
init(peerID: String, publicKey: Data, nickname: String, previousPeerID: String? = nil, signature: Data) {
|
||||
self.peerID = peerID
|
||||
self.publicKey = publicKey
|
||||
self.nickname = nickname
|
||||
self.timestamp = Date()
|
||||
self.previousPeerID = previousPeerID
|
||||
self.signature = signature
|
||||
}
|
||||
|
||||
func encode() -> Data? {
|
||||
return try? JSONEncoder().encode(self)
|
||||
}
|
||||
|
||||
static func decode(from data: Data) -> NoiseIdentityAnnouncement? {
|
||||
try? JSONDecoder().decode(NoiseIdentityAnnouncement.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
// Binding between ephemeral peer ID and cryptographic identity
|
||||
struct PeerIdentityBinding {
|
||||
let currentPeerID: String // Current ephemeral ID
|
||||
let fingerprint: String // Permanent cryptographic identity
|
||||
let publicKey: Data // Noise static public key
|
||||
let nickname: String // Last known nickname
|
||||
let bindingTimestamp: Date // When this binding was created
|
||||
let signature: Data // Cryptographic proof of binding
|
||||
|
||||
// Verify the binding signature
|
||||
func verify() -> Bool {
|
||||
// TODO: Implement signature verification
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Delivery status for messages
|
||||
enum DeliveryStatus: Codable, Equatable {
|
||||
case sending
|
||||
@@ -260,6 +460,14 @@ protocol BitchatDelegate: AnyObject {
|
||||
func didReceiveDeliveryAck(_ ack: DeliveryAck)
|
||||
func didReceiveReadReceipt(_ receipt: ReadReceipt)
|
||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus)
|
||||
|
||||
// Channel key verification methods
|
||||
func didReceiveChannelKeyVerifyRequest(_ request: ChannelKeyVerifyRequest, from peerID: String)
|
||||
func didReceiveChannelKeyVerifyResponse(_ response: ChannelKeyVerifyResponse, from peerID: String)
|
||||
func didReceiveChannelPasswordUpdate(_ update: ChannelPasswordUpdate, from peerID: String)
|
||||
|
||||
// Channel metadata methods
|
||||
func didReceiveChannelMetadata(_ metadata: ChannelMetadata, from peerID: String)
|
||||
}
|
||||
|
||||
// Provide default implementation to make it effectively optional
|
||||
@@ -296,4 +504,20 @@ extension BitchatDelegate {
|
||||
func didUpdateMessageDeliveryStatus(_ messageID: String, status: DeliveryStatus) {
|
||||
// Default empty implementation
|
||||
}
|
||||
|
||||
func didReceiveChannelKeyVerifyRequest(_ request: ChannelKeyVerifyRequest, from peerID: String) {
|
||||
// Default empty implementation
|
||||
}
|
||||
|
||||
func didReceiveChannelKeyVerifyResponse(_ response: ChannelKeyVerifyResponse, from peerID: String) {
|
||||
// Default empty implementation
|
||||
}
|
||||
|
||||
func didReceiveChannelPasswordUpdate(_ update: ChannelPasswordUpdate, from peerID: String) {
|
||||
// Default empty implementation
|
||||
}
|
||||
|
||||
func didReceiveChannelMetadata(_ metadata: ChannelMetadata, from peerID: String) {
|
||||
// Default empty implementation
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user