Skip to content
teef22Public

About

Local, encrypted, proximity chat over Bluetooth LE. Built entirely by an AI agent.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

AI-generated by DeepSeek V4.1 Flash Free

Important

AI-generated project

This app was built entirely by an AI coding agent: DeepSeek V4.1 Flash Free (OpenCode model ID deepseek-v4.1-flash-free). The human user supplied the requirements, design decisions and testing direction; all source code, tests and documentation were generated by the model. The project was created as an experiment to test the AI agent's capabilities.

It has not been reviewed by a human security professional. Review the code and the security model before relying on it for sensitive communications.

Blue Room

Local, encrypted, proximity chat over Bluetooth Low Energy. No accounts, no servers, no internet.

Blue Room is a Flutter app: one person hosts a room, others join it when they are nearby, and the host relays messages between everyone connected. History is stored on every device, and people who join late receive the messages they missed.

Download

Prebuilt Android APKs are on the releases page. The release APK uses the standard Android debug key, so it sideloads normally but is not Play Store signed.

There is no prebuilt iOS app: iOS builds require macOS and Xcode. The iOS unsigned build GitHub Actions workflow can produce an unsigned IPA, which only installs through sideloading tools such as AltStore or Sideloadly.

What works

  • Chats list with unread counts, live/offline/ended status, swipe to delete.
  • Create a room (optionally PIN-protected) or join a nearby room.
  • Chat screen with sender names, delivery state, day separators, member list and room fingerprint.
  • Disconnect / reconnect (host or member) without losing history.
  • Late-joiner history sync: the host assigns a monotonic sequence number to every message and streams missed messages to a reconnecting or new member.
  • One live room per device at a time; other chats stay stored offline.

How it works

        BLE GATT (encrypted, one connection per member)

  member A  <──────────────┐
                           │
  member B  <──────────  host  ────────>  member C
                           │
                     local message log
  • The host advertises a GATT service (0xFFF0), accepts connections and relays every message. The host is trusted: it can read messages and it assigns seq numbers.
  • A member scans for the service, reads the room info characteristic, performs a handshake and sends/receives encrypted frames through the host.
  • Messages are stored in SQLite on every device. A joiner sends SYNC_REQ {afterSeq}; the host replies with paginated SYNC_BATCH frames and SYNC_DONE. The first sync is capped at Protocol.initialHistoryLimit messages; the UI tells the user when older history was truncated.
  • Each BLE connection carries framed, chunked payloads. Chunks are 6-byte headers plus a payload sized from the negotiated ATT MTU, reassembled by frame id. Encryption happens per frame, then the frame is chunked.

Security model

  • Each device has long-lived Ed25519 (signing) and X25519 (key exchange) keys, stored in the platform keystore when available.
  • Every connection performs an ephemeral X25519 handshake. The host proves its identity with its long-lived Ed25519 key; the member signs the transcript and its display name. A six-digit room PIN (if set) is mixed into the HKDF input and proved with an HMAC, so a wrong PIN fails the handshake.
  • Session keys are derived with HKDF-SHA256. Frames are encrypted with ChaCha20-Poly1305 using per-direction counters; replayed, reordered or tampered frames are rejected.
  • Every message is signed by its sender (Ed25519) over the chat id, message id, sender identity, timestamp and text. Receivers verify signatures.
  • The room fingerprint is a short hash of the host signing key; members can compare it out of band.
  • Not host-blind end-to-end encryption: the host relays and can read the conversation. There is no metadata anonymity and no protection against a malicious host dropping or reordering messages. PINs are a casual-access barrier, not a strong secret (4–12 digits).

Running

Prerequisites: Flutter 3.47+ with Dart 3.13+, Android SDK for Android builds.

flutter pub get
flutter analyze
flutter test

Desktop (Linux/macOS/Windows)

Desktop uses an in-process fake transport by default, which is useful for UI development but can only simulate one local device:

flutter run -d linux

Create a room; messages you send appear in the chat locally. To exercise the multi-device protocol without phones, run the test suite (test/room_flow_test.dart drives a host and two fake members in one process).

You can force the fake transport on a phone with:

flutter run --dart-define=FAKE_TRANSPORT=true

Linux with real Bluetooth (join-only)

The Linux BLE plugin implements the central role only, so a Linux computer can join a room hosted on a phone but cannot host one itself. This is useful when you have a single phone and a Linux laptop with Bluetooth.

Requirements: BlueZ 5.x, a working LE adapter, and the bluetooth service.

sudo rfkill unblock bluetooth
sudo systemctl enable --now bluetooth
bluetoothctl show          # check "Powered: yes"
flutter run -d linux --dart-define=REAL_BLE=true

Create the room on the phone first. On Linux, Add chat lists nearby rooms but hides Create a room. Enable Bluetooth before starting the app; if the app was already running, restart it. Without REAL_BLE=true, Linux continues to use the fake transport.

Android

flutter run -d <device-id>
# or
flutter build apk --debug
adb install -r build/app/outputs/flutter-apk/app-debug.apk

The Android manifest requests:

  • BLUETOOTH_SCAN (neverForLocation), BLUETOOTH_ADVERTISE, BLUETOOTH_CONNECT on Android 12+;
  • legacy BLUETOOTH, BLUETOOTH_ADMIN and ACCESS_FINE_LOCATION up to API 30.

minSdk is 24. BLE does not work in the Android emulator; use two or more physical phones.

iOS

flutter run -d <ios-device-id>

Info.plist includes the Bluetooth usage descriptions. A free provisioning profile is enough for local device testing.

Device test checklist

You need at least two Android phones (or one Android + one iPhone) with Bluetooth enabled and the app installed.

  1. On phone A: create a room with a name and no PIN. It should show Live / Hosting.
  2. On phone B: open Add chat and wait for the room to appear. Tap it, confirm the fingerprint and join. Both devices should show 2 members online.
  3. Send messages both ways. They should appear immediately on both devices with sender names.
  4. Put phone B in airplane mode / walk out of range. Phone A should show the member offline after the timeout; phone B should show Offline with a Reconnect button.
  5. Send a few messages on A while B is away, then reconnect B. B should receive the missed messages in order.
  6. Create a third participant or reinstall the app on B, then join again. The new member should receive the earlier history.
  7. Create a PIN-protected room and verify that joining with a wrong PIN fails, and that the correct PIN works.
  8. Host disconnects: members should become offline/ended and keep local history.
  9. Host deletes the chat: members should see the room ended and retain history until they delete it locally.
  10. Change display name in the menu and confirm it is used on subsequent connections.

Project layout

lib/
  core/constants.dart          protocol constants, UUIDs, timeouts
  crypto/                      identity, handshake, secure channel
  data/                        SQLite schema, repository, local storage
  models/models.dart           Chat, message, member, room info
  protocol/                    frames, chunking, host/member sessions
  services/chat_service.dart   app state + transport event routing
  transport/                   BLE transport, fake transport, factory
  ui/                          onboarding, chats, add-chat, chat screens
test/
  link_test.dart               chunking/reassembly
  handshake_test.dart          key agreement, tamper, PIN, identity checks
  room_flow_test.dart          host relay, late-joiner sync, reconnect, PIN

Known limitations

  • The host is a trusted relay; this is not a distributed mesh and not host-blind.
  • Linux supports the central role only: it can join a phone-hosted room, but cannot host one. Linux discovery may also show the phone's Bluetooth name until the room is probed.
  • Discovery shows the room name on iOS. On Android the scan list shows the host device's Bluetooth name; the room name and fingerprint are shown after tapping the entry (the app probes the room before joining).
  • History sync is capped at 300 messages for a brand-new member; the host does not currently have a "load more" path for older messages.
  • Messages are stored unencrypted in the local app database (protected by OS sandboxing); only the identity seeds and PINs use the platform keystore.
  • No background delivery, multi-hop relay, typing indicators, read receipts or attachments.
  • If the host app is killed, the room ends for everyone.

About

Local, encrypted, proximity chat over Bluetooth LE. Built entirely by an AI agent.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages