SkillAgentSearch skills...

PoolChat

End-to-end encrypted chat for iOS/macOS with Curve25519 key agreement, AES-256-GCM, voice messages, polls, and reactions.

Install / Use

/learn @Olib-AI/PoolChat

README

PoolChat

End-to-end encrypted mesh chat for iOS and macOS by Olib AI

Used in StealthOS - The privacy-focused operating environment.

Swift 6.0 iOS 17+ macOS 14+ License: MIT


PoolChat is a Swift package that provides fully encrypted, serverless group and private messaging over a local mesh network. It sits on top of ConnectionPool and adds Curve25519 key agreement, AES-256-GCM message encryption, Trust-On-First-Use identity verification, rich message types, encrypted history persistence, and ready-made SwiftUI views. No internet connection, no servers, no accounts -- just devices talking directly to each other with end-to-end encryption.

Why PoolChat Exists

Because chat should not require trusting a third party. Every mainstream messenger routes your messages through corporate servers, where they can be stored, analyzed, or handed over on request -- even when "end-to-end encrypted." PoolChat removes the server entirely. Messages travel directly between devices over a local mesh network, encrypted before they leave the sender and decryptable only by the intended recipient. There is no metadata to harvest because there is no central point to collect it.

Features

Encryption & Security

  • End-to-end encryption -- Curve25519 ECDH key agreement, HKDF-SHA256 key derivation, AES-256-GCM authenticated encryption
  • Trust-On-First-Use (TOFU) -- Automatically records peer identities on first contact and alerts on key changes
  • Key fingerprint verification -- Human-readable fingerprints for out-of-band MITM detection
  • Encryption downgrade prevention -- Unencrypted messages rejected by default (configurable)
  • Image metadata stripping -- EXIF, GPS, and all metadata stripped from images before transmission
  • Encrypted storage -- Chat history persisted through an injectable SecureStorageProvider (AES-256-GCM)
  • Relay-aware key exchange -- E2E encryption works across relay hops in the mesh network
  • Session teardown -- Cryptographic material securely cleared when sessions end

Messaging

  • Rich message types -- Text, images, voice notes, emoji, polls, and system messages
  • Message reactions -- Quick-react with emoji on any message, synced across all peers
  • Polls -- Create polls with multiple options, optional vote-change policy, live vote counts
  • Replies -- Reply to specific messages with preview context
  • @Mentions -- Mention peers by name with autocomplete support and notification triggers
  • Group and private chat -- Switch between group conversation and 1-on-1 private messaging
  • Message status tracking -- Sending, sent, delivered, read, and failed states

Infrastructure

  • Works over ConnectionPool -- Peer discovery, connection management, and message routing handled by the mesh layer
  • Chat history sync -- Host sends encrypted history to newly joined peers (configurable)
  • Local notifications -- Background message notifications with deep link support, reply actions, and thread grouping
  • Notification bridge -- Notifications work even when the chat window is closed
  • Voice recording -- AVFoundation-based recording with playback, seek, and progress tracking
  • Configurable logging -- Inject your own logger or use the built-in os.Logger fallback
  • Cross-platform -- iOS and macOS from a single codebase with platform-adaptive SwiftUI views
  • Swift 6 strict concurrency -- No data races, proper actor isolation, Sendable throughout

Architecture

graph TD
    subgraph YourApp["Your App"]
        PoolChatView["PoolChatView\nSwiftUI, cross-platform"]
        PoolChatVM["PoolChatViewModel\nMessages, UI state, chat mode\npolls, reactions, mentions\nimage/voice send"]

        PoolChatView --> PoolChatVM

        PoolChatVM --> ChatHistory
        PoolChatVM --> ChatEncryption
        PoolChatVM --> VoiceRecording

        ChatHistory["ChatHistoryService\nEncrypted persistence"]
        ChatEncryption["ChatEncryptionService\nCurve25519 + AES-256-GCM"]
        VoiceRecording["VoiceRecordingService\nAVFoundation record/playback"]

        ChatHistory --> SecureStorage["SecureStorageProvider"]
        ChatEncryption --> ConnectionPool["ConnectionPool\nMesh network transport"]
        VoiceRecording --> ConnectionPool
    end

Message flow (send):

  1. User composes a message in PoolChatView
  2. PoolChatViewModel creates a RichChatMessage and strips image metadata if applicable
  3. Message is serialized to RichChatPayload (or PrivateChatPayload for DMs)
  4. ChatEncryptionService encrypts the payload with the recipient's shared AES-256-GCM key
  5. Encrypted payload is wrapped in EncryptedChatPayload and sent via ConnectionPoolManager
  6. ChatHistoryService persists the message through SecureStorageProvider

Message flow (receive):

  1. ConnectionPoolManager delivers an incoming PoolMessage
  2. PoolChatViewModel unwraps the EncryptedChatPayload
  3. ChatEncryptionService decrypts using the sender's shared key
  4. Decrypted payload is deserialized into a RichChatMessage and displayed
  5. If the chat window is closed, ChatNotificationBridge sends a local notification

Security

End-to-End Encryption

Every chat message is encrypted before it leaves the sending device. The encryption pipeline:

  1. Key Agreement -- Each peer generates an ephemeral Curve25519 key pair on session start. Public keys are exchanged over the mesh network.
  2. Shared Secret -- Curve25519 ECDH produces a shared secret between each pair of peers.
  3. Key Derivation -- HKDF-SHA256 derives a 256-bit symmetric key from the shared secret. The salt is the SHA-256 hash of both public keys (sorted lexicographically), ensuring both peers derive the same key regardless of who initiated the exchange.
  4. Encryption -- AES-256-GCM encrypts the message payload. Each message gets a unique nonce. The sealed box (nonce + ciphertext + authentication tag) is transmitted.
  5. Decryption -- The recipient uses the same derived symmetric key to open the AES-GCM sealed box. Authentication tag verification prevents tampering.

Trust-On-First-Use (TOFU)

PoolChat implements a TOFU model similar to SSH:

  • First contact: The peer's public key is recorded as the "known" key. A newPeerTrusted event is emitted with the key fingerprint.
  • Subsequent contacts: The presented key is compared against the stored key. If it matches, the connection proceeds silently.
  • Key change detected: If a peer presents a different public key, a peerKeyChanged event is emitted with both old and new fingerprints. This may indicate a MITM attack or legitimate key regeneration.
  • Explicit verification: Users can verify fingerprints out-of-band (in person, phone call) and mark peers as explicitly trusted. Verified status is cleared if the key changes.

Limitation: TOFU does not protect against MITM during the very first contact. Users who require stronger guarantees should verify fingerprints through a separate channel.

Key Fingerprint Verification

Both public key fingerprints and shared key fingerprints are available for out-of-band verification:

// Your public key fingerprint (share with peers)
let myFingerprint = ChatEncryptionService.shared.publicKeyFingerprint
// e.g., "A3:4F:B2:19:CC:87:D1:E6"

// Shared key fingerprint with a specific peer (both sides should match)
let sharedFingerprint = ChatEncryptionService.shared.sharedKeyFingerprint(for: peerID)

If both peers see the same shared key fingerprint, no MITM interception occurred during key exchange.

Encryption Downgrade Prevention

By default, PoolChat rejects unencrypted messages:

// Default: unencrypted messages are silently dropped
PoolChatConfiguration.rejectUnencryptedMessages = true

// Migration period only: accept with warning marker
PoolChatConfiguration.rejectUnencryptedMessages = false

Setting this to false is an encryption downgrade vector and should only be used during migration periods when legacy clients are still in the network.

Image Metadata Stripping

Before any image is sent, PoolChat strips all EXIF metadata, GPS coordinates, camera information, and other embedded metadata. The image is re-encoded as a clean JPEG/PNG with no identifying information.

Encrypted Storage

Chat history is persisted through the SecureStorageProvider protocol. The host application injects its own implementation (e.g., AES-256-GCM encrypted file storage). PoolChat never writes plaintext messages to disk.

Media (images, voice notes) is stored separately from message metadata with independent encryption keys, and referenced by opaque storage keys.

What Relay Nodes Can See

In a mesh network, messages may travel through relay nodes to reach non-adjacent peers. Here is what relay nodes can and cannot observe:

| Data | Visible to Relay? | |------|-------------------| | Message content | No (AES-256-GCM encrypted) | | Sender/receiver peer IDs | Yes (routing metadata) | | Message type (chat, reaction, poll) | Yes (envelope metadata) | | Message size | Yes (encrypted blob size) | | Timing | Yes (when message transits) | | Public keys during exchange | Yes (but cannot derive shared secret without private keys) |

Relay nodes forward encrypted blobs. They cannot decrypt content, forge messages, or modify payloads without detection (GCM authentication tag verification will fail).

Installation

Swift Package Manager

Add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/Olib-AI/PoolChat.git", from: "1.3.0")
]

Then add the dependency to your target:

View on GitHub
GitHub Stars11
CategoryDevelopment
Updated1d ago
Forks1

Languages

Swift

Security Score

95/100

Audited on Mar 21, 2026

No findings