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_nostrSignal Protocol E2EE
Double Ratchet with PreKeys and X3DH. Forward secrecy and post-compromise security per message.
libsignal_protocol_dartSQLite / Drift + SQLCipher
Typed local database. Whole-file AES-256 encryption at rest.
sqlcipher_flutter_libsProvider (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.
01UI
lib/ui/Presentation only. Widgets render state; they never touch crypto or transport.
- Screens
- Widgets
- Theme
02State
lib/providers/ChangeNotifier providers wired through a root MultiProvider. The only layer the UI talks to.
- AuthProvider
- ChatProvider
- DiscoverProvider
03Services
lib/services/Transport and cryptography. Relays in, ratchets out. Stateless where possible.
- NostrRelayService
- SignalMessagingService
- CryptoService
- SignalStore
04Data
lib/models/ · lib/repositories/ · lib/database/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.
centralizedOne 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 meshMany 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://…