Skip to content

Security: asrayg/encore

Security

SECURITY.md

Security & Privacy

Encore is an offline-first, Bluetooth-mesh group-chat app for live events. There are no servers, no accounts, and no internet path — ever. That shapes everything below: the security model is built for a crowded venue full of strangers' radios, not for a trusted network.

This document describes the v0 threat model, what Encore protects, what it deliberately does not protect, the out-of-band verification mechanisms, and how to report a vulnerability. It is intentionally honest about limitations. If something here reads as a weakness, it is one — we would rather state it plainly than imply confidence we don't have.

Related reading: docs/ARCHITECTURE.md, spec/EVENT-PACK.md, docs/PRODUCT.md.


Threat model

Picture the actual environment Encore runs in:

  • A crowded venue. Hundreds of phones in Bluetooth range, most of them strangers. Anyone can run the app, and anyone can run a passive radio that just listens.
  • No servers, no accounts. There is no backend to authenticate against, no identity provider, no central log. Trust is established device-to-device, in person.
  • Untrusted relays. Encore rides the bitchat BLE mesh: packets flood hop-by-hop with a TTL (default 7 hops), and every nearby phone — including vanilla bitchat phones and hostile ones — relays traffic it cannot read. A relay is not trusted and is not assumed to be honest; it can drop, delay, duplicate, or record anything that passes through it.
  • Physical proximity is the attack surface. Bluetooth LE is short range, so an attacker generally has to be in the venue (or just outside it with a good antenna). But within that range, assume a fully capable adversary: passive sniffing, active injection, and replay are all on the table.

The design goal for v0: protect the contents of 1:1 and group conversations and the authenticity of organizer announcements, while being upfront that metadata, public chat, and presence are observable to anyone with a radio.


What IS protected

1:1 direct messages (Noise XX)

Private DMs are end-to-end encrypted with the vendored bitchat Noise stack: Noise_XX_25519_ChaChaPoly_SHA256 (Curve25519 static keys, ChaCha20-Poly1305, SHA-256). The first DM to a peer lazily initiates the XX handshake; messages are queued until the session is established. Relays in between see only ciphertext — they cannot read or forge DM contents.

Group-chat key distribution (over Noise)

A group is a random 16-byte ID plus a random 32-byte ChaCha20-Poly1305 key generated on the creator's phone. When you join by 6-character code, the group key travels inside a Noise-encrypted pairwise DM — so the key itself is never exposed on the wire to relays. Group messages are then ChaCha20-Poly1305 sealed with that key before flooding. (Read the honest limits of this scheme in What is NOT protected.)

Organizer announcements (Ed25519-signed, verified)

Every event pack carries an Ed25519 root key. Official announcements are signed by that root key over the canonical-CBOR payload {channel, text, ts}. On receipt, the app re-verifies the signature against the pack's root key, checks the frame's wire address matches the claimed channel within your event, and dedupes — and only then renders the pinned "OFFICIAL" / verified-check banner. An unverifiable or tampered announcement is silently dropped and never shown as official. A relay cannot fabricate a verified announcement without the organizer's private root key.

Identity = Noise static-key fingerprint

There are no usernames or accounts. A device's durable identity is the SHA-256 fingerprint of its long-lived Noise static public key. The short 16-hex peer ID is derived from that key and changes only on a panic reset; the fingerprint is the stable thing you pin when you add a friend, and it survives peer-ID rotation and event changes.

No accounts, no phone numbers, no servers

Encore collects nothing because there is nowhere to send it. No sign-up, no email, no phone number, no analytics, no cloud. The only thing that leaves your phone is BLE mesh traffic to devices physically near you.

Messages live only on nearby phones, and auto-purge

Messages are in-memory only — they never touch disk. Per-conversation history is capped (500 messages) and aged out. When an event's time window ends, channel and announcement state is purged and the theme reverts; leaving an event purges all event-scoped data. The only things persisted are the onboarded flag, your display name, the signed event-pack envelope, your friends (by fingerprint), and your group chats (id/name/code/key) — never message bodies.

Your display name is the only thing broadcast

The single piece of personal data on the wire is the display name you chose (or the fun auto-generated one). It is shared only over Bluetooth, only with people in range. No real name is required or requested.


What is NOT protected

These are real v0 limitations. Do not rely on Encore for anything where these gaps matter.

Public / Nearby and channel broadcasts are unencrypted, by design

The Nearby public room and public event channels are broadcast in the clear so that any bitchat-compatible phone in range can participate and relay them. Anyone within Bluetooth range can read public/Nearby and channel chat. Treat these like shouting across a room — that's the intended mental model. (Read-only broadcast channels like Announcements are still public to read; "read-only" controls who may post, not who may see.)

Group chat v0 has no forward secrecy and no revocation

The group's 32-byte symmetric key is long-lived and shared:

  • No forward secrecy. Compromising the key reveals all past and future group traffic captured by an eavesdropper.
  • No member revocation. Anyone who ever held the key can read group traffic forever; there is no way to rotate out a former member.
  • Any member can read everything. Holding the key is the entire access model.
  • Sender authentication is only key-possession. The sole sender binding is the mesh-level Ed25519 packet signature, which authenticates the flooding peer, not group membership. There is no per-message proof that a message came from a specific group member.

Group chat is suitable for "our crew's thread for the night," not for long-lived secrets. Sender-keys / MLS-style upgrades are explicit future work.

Staff certificates are not implemented

Announcement trust in v0 is root-key only. "Organizer-signed" means signed by the event's single Ed25519 root key. The spec reserves a staff-certificate delegation path, but it is not implemented — there are no per-staff signing identities yet.

Metadata is observable to anyone with a radio

Encryption protects contents, not patterns. An observer with a BLE radio can see:

  • Who is near whom — device presence, peer IDs, and the display names broadcast in advertisements.
  • Traffic timing and volume — when packets fly, how big they are, how often, and which peers are active.
  • Mesh topology — connection/relay structure, since relaying is public by design.

There is no cover traffic, no timing obfuscation, and no metadata protection in v0. If being seen to be present and communicating is itself a risk for you, Encore does not defend against it.

Debug builds use a testnet BLE UUID

Debug builds inherit upstream bitchat's testnet BLE service UUID, so they cannot see release builds (or real bitchat phones) on the mesh, and vice versa. Use release builds for any real interop or any security-relevant testing. Don't draw conclusions about live behavior from a debug build.

Per-platform licensing note (not a vuln, but worth knowing)

The iOS mesh is vendored from bitchat (Unlicense / public domain); the Android :mesh module is vendored from bitchat-android (GPL-3.0), which makes the combined Android APK GPL-3.0. Same product, different licenses per platform.


Out-of-band verification

Because there is no server or certificate authority, the two trust ceremonies that matter are verified in person, eye-to-eye:

Event-pack 4-emoji fingerprint

Each event pack has a packHash = SHA-256 of its signed envelope. The first four bytes map into a fixed 16-emoji table to produce a 4-emoji fingerprint (e.g. 🛸🐙💜🍀 for the spec vector pack). Organizers print/display this fingerprint on posters and venue screens. Before you join, compare the fingerprint shown in the join preview against the one on the venue's official signage. They match → you scanned the real organizer's pack. This is a social defense against a "two fake posters" swap; it is verified by humans, not cryptography, in v0. Scanning a pack means trusting whoever posted it, much like joining a venue's WiFi.

Friend-QR fingerprint

Adding a friend pins their Noise static-key fingerprint, carried in their personal ENCOREF: QR (the raw 32-byte SHA-256 digest of their Noise static key). Scanning their QR (or comparing the fingerprint shown on their device detail sheet) pins a durable, verifiable identity so future DMs are provably from the same device — no accounts required. The fingerprint is stable across peer-ID rotations and across events.


Reporting a vulnerability (responsible disclosure)

We take security reports seriously and welcome responsible disclosure.

There is no email server and no contact inbox — that is by design (Encore has no backend of any kind). Please report privately through GitHub:

  • Preferred: open a GitHub private vulnerability report ("Security" tab → "Report a vulnerability") on the Encore repository. This keeps the report confidential until a fix is available.
  • If private reporting is unavailable to you, do not open a public issue describing the vulnerability. Instead, open a minimal public issue asking a maintainer to enable a private channel, or contact a maintainer through the contact method listed in the repository's profile/README. (Maintainer contact placeholder — to be filled in by the project owner.)

Please include:

  • A clear description of the issue and its impact.
  • Step-by-step reproduction (note whether you tested a release build — debug builds use a testnet BLE UUID and are not representative).
  • Affected platform(s): iOS, Android, or the shared wire format / event-pack spec.
  • Any proof-of-concept, packet captures, or relevant logs.

We ask that you give us a reasonable window to investigate and ship a fix before any public disclosure, and that testing avoids harming attendees on real live-event meshes.

Scope

In scope:

  • The Encore wire format and 0xE0 envelope handling.
  • Event-pack parsing, signing, and verification (Ed25519 root key, canonical CBOR, base45/deflate pipeline, packHash, channel addressing) — see spec/EVENT-PACK.md.
  • The Noise XX pairwise channel as Encore uses it, group-chat key distribution, and announcement verification.
  • The iOS (EncoreKit / app) and Android (com.encore.core / app) code authored by Encore.

Out of scope (please report upstream):

  • Bugs in the vendored BLE-mesh / Noise stack should first be checked against the pinned upstream commits — iOS bitchat 3caf2d7, Android bitchat-android b079ae8 — not against HEAD, and reported to the respective upstream projects where appropriate.

Already-documented limitations are not vulnerabilities. Everything in What is NOT protected — unencrypted public/channel chat, group-chat lack of forward secrecy/revocation, unimplemented staff certs, observable metadata, and the debug testnet UUID — is a known, intentional v0 design boundary, not a bug to report. A report that meaningfully narrows or breaks a protection we do claim (DM/group confidentiality, announcement authenticity, identity pinning) is exactly what we want to hear about.

There aren't any published security advisories