ONYX is a self-hosted, anonymous E2EE messenger. Flutter client, separate server component, no phone number or email required for an account. Solo project, beta is rough in places but moving forward.

v1.7-beta out. The thing I actually want to talk about is WardLink.

Even on a self-hosted setup, keeping your favorited chats in sync between your phone and your desktop usually still routes through the server, or you do it by hand. WardLink does it directly between your own devices over LAN instead. Pair two (or more) devices once via QR, and from then on, when they're on the same network, favorited chats sync passively (if you turn it on yourself, of course :D). No central point holding the sync state, no cloud account needed for it. Traffic is sealed with per-device keys.

I'll be upfront about the limits: It only runs while both devices share a network. This is for the "I have a phone and a laptop in the same house and want them in sync without my server being in the loop" case specifically.

Rest of the changelog:

  • chat gallery view
  • search across settings, plus chat search by name/keyword
  • on-demand and scheduled backups
  • adjustable scroll-to-bottom button
  • markdown support in the changelog tab itself
  • redesigned player, dialogs, and settings (also restructured)
  • new storage engine under the hood
  • folders stay unlocked for the session on desktop instead of re-locking constantly
  • removed the message entrance animation: it was slowing chat opening down more than it helped
  • fixed backend bugs affecting message sending, a settings UI arrow glitch, and the biometrics toggle showing up when biometrics aren't available or are disabled on the device

If you're upgrading from an earlier version: this release switches local storage to a new engine, and it migrates your data automatically on first launch. In my own testing it's roughly 80% faster after the migration is done.

Repo and full release notes: https://github.com/wardcore-dev/onyx/releases/tag/v1.7-beta

One-person project, and I lean on AI tooling for parts of the code. Architecture, crypto decisions, and review are mine. Still beta, so expect rough edges. An issue on the repo helps a lot right now if you hit something broken.

 

ONYX doesn't sync your Favorite Chats through the central server. That's intentional — your saved notes, files, and drafts are yours, and routing them through a server defeats the point of a privacy-focused messenger.

But sometimes you need those notes on a different device. You wrote something on your laptop and want it on your phone. WardLink handles that locally, over your own network, without touching any server.


How it works

  1. Open the chat or saved message you want to transfer
  2. Tap Share via WardLink → Send
  3. A QR code appears on your screen
  4. Open ONYX on the second device and scan it
  5. The chat transfers directly, device to device

Works both ways — phone to desktop, desktop to phone.


What happens under the hood

  • No account linking
  • No cloud relay
  • No data leaves your local network
  • Connection encrypted with AES-256-GCM
  • Devices find each other via UDP broadcast on your LAN

WardLink is still experimental. Right now it handles Saved Messages — broader sync support is planned. If you run into issues or have ideas for what else should transfer this way, comments are open.

One direction I'm considering: full passive LAN sync. While your devices are on the same network, all chats sync automatically in the background — no QR codes, no manual steps. Entirely local, optional, off by default. Curious whether that's something people would actually use or if it's overkill?

[–] [S] 2 points 5 months ago (2 children)

On mobile the key comes from the platform keychain — tied to device unlock, not the app. On desktop it's machine-derived. The decoy PIN threat model is coercion — someone watching you unlock. If an attacker already has filesystem access, you've got bigger problems than the PIN.

  • source
  • parent
  • context
  • [–] [S] 1 point 5 months ago (4 children)

    No, it's not one file with two decryption keys — you've got the architecture slightly wrong. There's one storage, encrypted with AES-256-GCM. The PIN doesn't decrypt the file — it's verified at the application level. If the real PIN matches, the real environment loads. If the decoy PIN matches, the decoy environment loads with its own data, stored in the same storage under separate keys. Two layers of data in one container, not two containers with different keys.

  • source
  • parent
  • context
  •  

    I built an orbital graph as an alternative navigation for my open-source messenger

    Most messengers display conversations as a static list. I wanted to try something different.

    In ONYX v1.5-beta I added Account Graph — an alternative navigation mode where your chats, groups and categories become nodes in a dynamic orbital system centered around your account.

    The idea came from Obsidian's graph view. I adapted the concept for a messenger context.

    Honestly, when I started building it I was pretty sure it would end up useless. Just a visual gimmick. But somewhere during development that changed. When you have a lot of chats across multiple categories, the graph gives you a better spatial sense of where everything is. You navigate by structure rather than scrolling through a flat list.

    How it works:

    • chats, groups and categories are displayed as connected orbiting nodes
    • online users are highlighted via presence glow
    • orbit speed is adjustable
    • animations can be paused entirely
    • graph position persists between sessions

    The entire thing is optional and can be disabled at any time.

    GitHub: https://github.com/wardcore-dev/onyx/releases/tag/v1.5-beta

    Feedback is welcome!

    [–] [S] 4 points 5 months ago (6 children)

    Good points, but the architecture is a bit different. It's not two separate encrypted files — from the outside there's no indication there are two PINs at all. The decoy environment looks like a fully functional account: its own chats, groups, contacts, message history. The attacker doesn't know there's a second layer because nothing tells them the first layer is a decoy.

    Your approach of generating a random DB for any wrong PIN is interesting too, but it has a different problem: there's no "correct" result, any PIN "works", and a paranoid attacker can figure that out. Here the decoy environment is convincing precisely because it's a real-looking account, not a placeholder.

    On encryption: AES-256-GCM, keys derived via HKDF-SHA256 with salt. The code is fully open source on GitHub — that is the documentation. Separate security docs don't make sense for me to write right now, but if the project grows — definitely yes.

  • source
  • parent
  • context
  •  

    ONYX v1.5-beta: Emergency PIN with a full decoy environment

    Released v1.5-beta of ONYX, and one of the things we added is Emergency PIN support.

    A secondary PIN opens a completely separate decoy environment instead of your real account. You configure what's in it — chats, avatars, display names — so it looks like a normal, lived-in account. There's no visual indicator that it's a decoy.

    The use case is straightforward: situations where you're pressured into unlocking your messenger. The Emergency PIN is kept entirely separate from your main PIN and can be changed at any time.

    Happy to answer questions.

     

    ONYX v1.5-beta

    It is one of the biggest ONYX updates so far.

    This update focuses on interaction, privacy and improving the overall feeling of using the messenger. Some parts of the interface were completely rethought from scratch, while others were redesigned to become more practical and flexible.

    Account Graph

    One of the biggest additions in open-beta 1.5 is the new Account Graph system.

    Instead of navigating chats through a traditional static list, ONYX can now display your conversations as a dynamic orbital system.

    Chats, groups and categories become connected nodes orbiting around your account like a living network. Online users are highlighted through presence glow, orbit speed can be customized, animations can be paused entirely, and the graph position can persist between sessions.

    Surprisingly, this ended up becoming much more useful than we initially expected during development. It changes the way navigation feels and creates a completely different perspective on your conversations.

    The entire system is optional and can be disabled at any time.

    Emergency PIN & Decoy Environment

    beta 1.5 introduces Emergency PIN support.

    A secondary PIN can now open a completely separate decoy environment instead of your real account.

    This environment can contain custom chats, avatars, names and manually configured content in order to appear believable and natural. The Emergency PIN can be changed at any time and always remains separated from the main unlock PIN.

    This feature is designed for users who want an additional layer of privacy and control over access to their messenger.

    Redesigned Media Cache Manager

    The media cache management system has been completely redesigned.

    Instead of separate buttons for local and server cache cleanup, ONYX now provides a unified “Manage Media Cache” interface with dedicated Local and Server tabs.

    Users can:

    • inspect locally stored files
    • selectively remove specific media
    • view categorized storage usage
    • manage server-side files
    • monitor storage quota usage through a visual progress bar

    This makes cache management significantly cleaner and more practical compared to previous versions.

    Redesigned Message Input

    The message input field was rebuilt with a stronger focus on animations and interaction feel.

    The new design introduces:

    • smoother focus transitions
    • animated voice recording buttons
    • updated attachment button animations

    While relatively small on paper, this is one of the most frequently used parts of any messenger, and improving it noticeably changes the overall experience of using the application.

    Privacy & Security Improvements

    Update also adds several new privacy and security related features.

    Users can now:

    • hide their account from global search
    • manually lock the application through a dedicated Lock button
    • receive explicit session expiration warnings when re-authentication is required

    Interface & UX Improvements

    Additional improvements in this update include:

    • improved loading performance across multiple interface elements
    • fixed opacity behavior for opponent messages
    • ability to hide username/display name inside the account panel
    • updated “Add Chat” button placement in Favorites and Groups tabs
    • moved group/channel token actions into the overflow menu

    Github - https://github.com/wardcore-dev/onyx/releases/tag/v1.5-beta

    Always open to feedback!

     

    Just dropped v1.4-beta of ONYX — an open-source, privacy-first messenger with E2E encryption for private chats and self-hostable groups/channels.


    This release is mostly about media experience.

    Voice Channels — but with a twist. If you're self-hosting an ONYX server on your local network, you now get real-time voice inside your group. No cloud, no third-party servers.

    Media Player — vinyl record animation when playing music, seek ±10 seconds, playback speed from 0.25x to 4x with presets, next/previous track, auto-repeat. Audio files now show their filename instead of a generic label.

    Audio Settings — microphone and output device selection, so you can actually choose what hardware ONYX uses.


    Why ONYX?

    https://lemmy.world/post/44633944


    ONYX: https://github.com/wardcore-dev/onyx
    Self-hosted server: https://github.com/wardcore-dev/onyx-server


    Would love feedback, especially from anyone running it on LAN setups.

     

    ONYX development update: v1.2a and v1.3-beta

    A quick update on what's changed since my last post. Two beta dropped over the past few weeks. I would like to share this here so that I can possibly find my audience.


    [1.2a-beta] — 2026-04-07

    Added

    • Application autostart — the app can now start automatically on system boot, Windows.
    • Message animations in external groups and channels — animations were missing in external (self-hosted) instances, now consistent across all chat types

    Improved

    • Editing and deleting messages in groups and channels — this was broken or limited in non-external groups/channels, now works properly

    Fixed

    • Favorite icon color display in edit mode
    • Voice message duration display in send preview

    [1.3-beta] — 2026-04-21

    Added

    • Message reactions — react to messages
    • User blocking — block list and the ability to block individual users
    • Hide content in notifications — shows "New message" instead of actual content on the lock screen
    • PIN and biometric prompt when switching accounts — multi-account users now need to authenticate when switching, prevents unauthorized access if someone has physical access to the device
    • Relog notification — notifies when session token is expiring instead of silently failing
    • Video download progress — progress indicator when downloading videos from the server

    About the project

    ONYX is an open source messenger with E2EE private chats, LAN mode (works without internet via UDP broadcast), and self-hostable groups and channels.

    Bugs and issues — reach out to @support directly in ONYX.

    [–] [S] 3 points 6 months ago (1 child)

    Messages are stored on the server only until the recipient comes online. Once they connect, a 30-second timer starts — after that the message is permanently deleted from the server. There's no copy left server-side after that point. Since private chats use E2EE, the server only ever sees ciphertext anyway. After delivery, messages exist only locally on both devices. If you want no local record either, you can delete the conversation manually from within the app.

  • source
  • parent
  • context
  •  

    ONYX 1.3-beta — reactions, blocking, and notification privacy

    A follow-up to my previous post. The last post got a decent amount of feedback — some might call it small, but every response genuinely matters and it was great to see people's reactions to the project.

    New beta is out today. I would like to share some changes.


    ONYX — what's new

    Message reactions — you can now react to messages. Basic, but frequently requested.

    User blocking — block list and the ability to block users. Should have been there from the start, honestly.

    Hide content in notifications — notifications now show "New message" instead of the actual content. Relevant if you do not want your decrypted messages to be stored in the device cache.

    PIN and biometric prompt when switching accounts — switching between accounts now requires authentication. Prevents someone with physical access to your phone from jumping between accounts freely. This only works if you have PIN or biometric unlocking enabled in settings.

    Relog notification — the app now notifies you when your session token is about to expire.

    Video download progress — progress indicator when receiving videos from the server.


    ONYX Server

    The self-hosted server software also got an update — reaction support has been added, along with a handful of bug fixes.


    Bugs and issues — reach out to @support directly in ONYX.

    [–] [S] 2 points 7 months ago

    I understand it's not for everyone. But the 16-character minimum is there for a reason — your password is the only key to your account, no fallbacks, no recovery via phone or email. That requires a strong password. There's a built-in password generator in the app — one tap, cryptographically secure, 16 characters, done. Save it once and you won't need to type it again. Think of it like a crypto wallet seed phrase — you store it once somewhere safe and that's it. If the priority is speed over security, Telegram is a better fit. ONYX was built for people who actually care about privacy, and that comes with a slightly higher entry bar. That said, I'll consider dropping the hard minimum to 8 characters with a strong recommendation to use 16 — so people have the choice but know the tradeoffs.

  • source
  • parent
  • context
  • [–] [S] 1 point 7 months ago (2 children)

    Yes, 16 characters minimum. Since there's no phone number, no email, and no alternative recovery method - the password is the only thing protecting your account. A weak password with no fallback is a real risk, so I set the bar higher intentionally. It also reduces brute force viability. Passphrases are supported but currently not used for login - just the password for now.

  • source
  • parent
  • context
  •  

    ONYX v1.2-beta - now actually comfortable to use

    I've been working on ONYX for a while now, and v1.1 was honest about one thing — it was secure, but using it every day felt like a chore. This update is mostly me fixing that.


    What's new

    • File uploads now show a preview and progress.
    • Message forwarding — took longer than it should have, but it's in
    • Keyword search across chats
    • Smooth send/receive animations — on by default if you search the settings, and I liked having them
    • Tap a reply to jump to the original message
    • Message pinning (local only for now — I want to add it for both sides in next patches)

    Improved

    • LAN mode stability — this one was long overdue
    • General performance

    Fixed

    • LAN mode connection issues
    • Various bug fixes

    Still a lot to do.

    If you want to follow what's coming — there's an update channel inside the app: 12e01467-c154-447b-84f8-133ae76684a1 (channel token)

    https://github.com/wardcore-dev/onyx/releases/tag/v1.2-beta

    Feedback in the comments is welcome.

     

    I got tired of every messenger asking for a phone number before letting you talk to anyone. Most privacy-focused messengers make you choose between convenience and privacy — either you get a polished experience with questionable data practices, or you get strong privacy with a painful setup. I tried to find the balance between the two. The result is ONYX. I want to be upfront about what it does and doesn't do privacy-wise — because "private messenger" is a claim that deserves scrutiny.


    What the server actually knows about you

    • Your username (not tied to phone, email, or anything real-world)
    • That your devices exist (but not their private keys)
    • Your IP address when connected (unavoidable)
    • Message content in groups and channels — these are not E2EE encrypted, that's a real limitation

    What it doesn't know:

    • Your phone number or email — never collected
    • Content of private chats — server only sees ciphertext
    • Your favorites — stored locally only, server has no knowledge of them

    How the E2EE actually works

    Private chats use X25519 ECDH for key exchange (ephemeral per session) and HKDF-SHA256 for key derivation. I chose XChaCha20-Poly1305 over AES-GCM because it runs in constant time without AES-NI hardware acceleration — relevant for mobile devices — and has a 192-bit nonce vs 96-bit in GCM, which reduces collision risk in long sessions. LAN mode uses AES-GCM-256 — sessions are short-lived and never leave the local network.

    Packet format (internet):

    E2EEv1:[base64 JSON envelope: {eph pubkey 32B, nonce 24B, ciphertext, mac 16B}]
    

    Packet format (LAN):

    [pubkey 32B] [nonce 12B] [ciphertext] [mac 16B]
    

    Multi-device works without the server touching your keys — new device sends an auth request to an existing trusted device, which must explicitly approve it before any key exchange happens.


    The honest limitations

    • Groups and channels have no E2EE — the only way to get full control over group communication is to self-host your own instance, so at least no third party has access to the data.
    • Only incoming messages sync across devices — outgoing stay on the sending device only
    • The server knows your IP when you're connected

    LAN mode

    There's a mode that works with no server and no internet at all — devices discover each other via UDP broadcast on the local network, exchange X25519 public keys in the broadcast packet itself, and communicate directly encrypted. Just hold the "Send" button in a private chat to choose this mode. Useful if you don't want any data leaving your network.


    Self-hosted groups and channels

    If you don't want to rely on the central ONYX server at all, you can run your own. There's a separate server software written in Rust — you download it, run it, and host your own groups or channels for your community or just a small group of friends. No web interface, runs entirely from the command line. Available for both Windows and Linux.

    https://github.com/wardcore-dev/onyx-server/releases


    Registration and account deletion

    Username and password only. No phone, no email. Accounts can be deleted at any time and take all server-side data and media with them.


    The project is still in early beta — rough edges are expected. If you run into any bugs or weird behavior, feel free to reach out directly to @support in ONYX. Just write your issue there and I'll look into it.

    Available for Windows, Linux, macOS, and Android: https://github.com/wardcore-dev/onyx/releases

     

    When you look at existing self-hosted messengers, you usually see one of two things: either complex infrastructure that's hard to deploy (Matrix/Synapse), or minimalism with no encryption. ONYX is an attempt to find the middle ground: easy to deploy, real E2E encryption, and the ability to work entirely in a local network without internet at all.


    Project architecture

    Component Technology
    Client Flutter (Android, Windows, macOS, Linux)
    Server Node.js — Express + express-ws + ws
    Database MariaDB + Redis (sessions, cache)
    File storage S3-compatible (AWS SDK v3)
    Transport WebSocket (wss://) + HTTP/REST
    Encryption X25519 + XChaCha20-Poly1305 + AES-256-GCM

    LAN mode: works without internet

    One of the key features of ONYX is the ability to communicate in a local network without internet at all. A custom device auto-discovery mechanism handles this entirely.

    Discovery protocol via UDP broadcast

    Every client broadcasts a JSON packet to 255.255.255.255:45678 every 5 seconds:

    {
      "username": "alice",
      "timestamp": 1710000000000,
      "pubkey": "<X25519 public key, base64>"
    }
    

    All other clients listen on that port and update two local tables:

    • username → source IP address
    • username → X25519 public key

    No mDNS, no manual IP entry — just pure UDP broadcast. The public key is included directly in the broadcast packet, so encrypted communication can start immediately without an additional handshake.

    Media transfer in LAN

    Media files go through a separate channel on port 45679, split into ~32 KB chunks. Each chunk is encrypted independently with AES-256-GCM, which allows decryption and rendering to begin before the full file is received.


    Encryption: two layers on elliptic curves

    No RSA anywhere in the project — only a modern elliptic curve stack.

    E2EE scheme for private chats

    1. Key exchange: X25519 ECDH with an ephemeral key pair per session
    2. Key derivation: HKDF-SHA256 with a context label (onyx-lan-v2 for LAN, separate labels for E2EE chats)
    3. Encryption: XChaCha20-Poly1305 AEAD

    Packet format:

    [pubkey 32B] [nonce 12B] [ciphertext] [mac 16B]
    

    Why XChaCha20-Poly1305 and not AES-GCM?

    AES-GCM requires hardware acceleration (AES-NI) for decent performance. XChaCha20-Poly1305 runs in constant time on any hardware — important for mobile devices without AES-NI. It also has a wider nonce (192 bits vs 96 for GCM), which reduces collision risk in long sessions.

    AES-256-GCM is used for LAN media transfers — chunked delivery, and hardware acceleration is available on most desktops.


    Multi-device and E2EE

    When a new device connects to an account, it sends an authorization request to a trusted device. The trusted device must explicitly approve the new one — only then does the key exchange happen. The server never has access to decrypted content, even when adding a new device.

    Multi-device sync

    When a message arrives, the server sends it to each of your devices separately — encrypted with that device's specific public key. Technically these are different encrypted messages for each device, just with the same plaintext inside.

    One honest limitation: only incoming messages sync across devices. Outgoing messages are visible only on the device they were sent from. Full bidirectional sync with E2EE requires either a "copy to self" encryption mechanism or server-side plaintext storage — both are worse tradeoffs.


    Why Flutter and not Electron or native development

    The requirement from day one: one codebase for Windows, macOS, Linux and Android. Three options were considered:

    Option Problem
    Native development 3–5 separate codebases, constant desync
    Electron +150–200 MB RAM, DOM rendering
    Flutter Single codebase, Skia/Impeller, real 60fps

    Flutter Desktop required writing 10+ separate optimization modules (fps_booster, fps_optimizer, fps_stimulator, message_load_optimizer, chat_preloader) — Flutter on desktop lags noticeably without tuning. But the result is smooth UI across all four platforms from one repo.


    Desktop-specific integrations

    • System tray — app minimizes to tray instead of closing.
    • Single-instance — prevents multiple copies via IPC. A second launch focuses the existing window.
    • Custom titlebar — system titlebar hidden, custom header with drag zone
    • Windows-native notifications — separate module, not Flutter overlay
    • Autostart on system boot

    Security beyond E2EE

    • PIN + biometrics — Face ID / fingerprint via Flutter Secure Storage
    • Proxy support — proxy_manager.dart, routing through any proxy
    • Secure storage — all sensitive data through OS secure storage (Keychain / Android Keystore)
    • Active session management — all connected devices visible, any session can be terminated remotely — but only from a trusted device

    Self-hosted groups and channels

    Two types of groups and channels in ONYX — fundamentally different models.

    Built-in (via ONYX server)

    Standard groups and channels work through the central ONYX server and are not encrypted — a deliberate tradeoff for reliable sync. Suitable for open communities where E2EE is not a requirement.

    External (self-hosted)

    Anyone can run their own instance — on a VPS, home server, or local network:

    Use case Description
    Local network File sharing and chat within office/home network, no internet
    Private community Closed group on your VPS, join by invite
    Public channel You host it, subscribers read posts
    • Group — two-way, all participants can write
    • Channel — one-way broadcast, only admins publish

    Connect to any external server directly from the app — enter the instance address and join.

    Deploying your own instance

    Server software — ONYX Server: github.com/wardcore-dev/onyx-server


    Favorites: local notes and storage

    ONYX has a dedicated Favorites tab — not a "Saved Messages" clone, but a proper local notebook. Create any number of favorite chats, each with its own avatar and name, as separate categories: passwords, ideas, saved media, links.

    Everything is stored locally on the device — nothing sent to the server, nothing synced. The server knows nothing about your favorites.


    Accounts: anonymity, multi-account and deletion

    • Registration — username and password only. No phone number, no email.
    • Username — chosen once, permanent. Can never be changed. You can change your display name, but not the username itself.
    • Multi-account — register and hold any number of accounts, switch freely.
    • Account deletion — delete at any time with all media and server-side data. No traces left.

    Current state

    Project is in working beta. Development is ongoing. Happy to answer questions in the comments.

    Try it out

    view more: next ›