ARCHITECTURE

Transport is public. Content never is.

MNDO separates who carries a message from who can read it. Relays carry. Only the two ratchets read. Here is exactly how the pieces fit.

01 · STACK

Five dependencies that matter.

  • Dart

    Flutter (Dart)

    Single codebase for Android today; iOS build shares the same tree.

    flutter
  • Nostr Protocol

    Transport. Messages are signed events published to and read from a set of relays over WebSocket.

    dart_nostr
  • Signal Protocol E2EE

    Double Ratchet with PreKeys and X3DH. Forward secrecy and post-compromise security per message.

    libsignal_protocol_dart
  • SQLite / Drift + SQLCipher

    Typed local database. Whole-file AES-256 encryption at rest.

    sqlcipher_flutter_libs
  • Provider (state + DI)

    A single root MultiProvider builds the dependency graph. UI never constructs services.

    provider

02 · LAYERS

Dependencies point down. Always.

Scroll to trace a message from a tap on screen to an encrypted row on disk.

  1. 01UI

    Presentation only. Widgets render state; they never touch crypto or transport.

    • Screens
    • Widgets
    • Theme
  2. 02State

    ChangeNotifier providers wired through a root MultiProvider. The only layer the UI talks to.

    • AuthProvider
    • ChatProvider
    • DiscoverProvider
  3. 03Services

    Transport and cryptography. Relays in, ratchets out. Stateless where possible.

    • NostrRelayService
    • SignalMessagingService
    • CryptoService
    • SignalStore
  4. 04Data

    Encrypted at rest. Drift over SQLCipher for messages; secure storage for identity keys.

    • ChatRepository (Drift)
    • IdentityRepository (flutter_secure_storage)
    • Models

03 · SIGNAL PROTOCOL

Double Ratchet and PreKeys, in plain language.

PreKeys let you start an encrypted conversation with someone who is offline. Each device publishes a bundle of one-time public keys. A sender picks one, derives a shared secret (X3DH) and can encrypt the first message immediately. No handshake round-trip, no server holding plaintext while it waits.

The Double Ratchet then changes the key for every single message. One ratchet turns with each message sent; the other turns whenever a reply arrives with fresh key material. Compromise one key and you read one message. Not the past, not the future.

In MNDO this lives in SignalMessagingService, with session and key state persisted through SignalStoreinto the encrypted database.

04 · TRANSPORT

Nostr relay mesh vs a central server.

centralized

One server. One point of failure and control.

  • Holds your contact graph and metadata, often your phone number.
  • Can be compelled to log, throttle, or ban.
  • Goes down: everyone goes down.
nostr relay mesh

Many relays. Any of them is optional.

  • Your identity is your key; you can swap relays without losing it.
  • Relays see only signed ciphertext events and a public key.
  • Publish to several at once; read from whichever answers first.
NostrRelayService → dart_nostr → wss://…