A Flutter mobile application with a Go native library (via gomobile) implementing cryptographic identity management, peer-to-peer networking (libp2p), end-to-end encrypted messaging (ML-KEM-768 + AES-256-GCM), contact exchange via QR codes, a conversation feed, orbit-based contact management, and push notifications.
- Identity Management -- Generate or restore a cryptographic identity from a 12-word BIP39 mnemonic. Ed25519 keypair, libp2p peer ID, and ML-KEM-768 post-quantum keys are all derived or generated at identity creation time. Secrets are stored in platform secure storage (iOS Keychain / Android EncryptedSharedPreferences).
- P2P Networking -- Fully native libp2p node (Go) with QUIC + WebSocket transports, circuit relay v2 for NAT traversal, and rendezvous-based peer discovery. Local WiFi delivery via mDNS + WebSocket for same-network peers.
- QR Code Contact Exchange -- Display a signed QR payload containing your public key, peer ID, rendezvous address, and ML-KEM public key. Scan another user's QR to send a verified contact request.
- Contact Requests -- Incoming contact requests are received over P2P, signature-verified, and stored locally. Users can accept (promoting to contact) or decline.
- Conversations -- Letter-card UI for one-to-one messaging. Messages are encrypted end-to-end when both parties have ML-KEM keys (v2 envelope), or sent as plaintext (v1 envelope) otherwise. Failed messages are automatically retried when connectivity returns.
- Feed -- Incoming message feed showing thread-grouped cards per contact, with unread badges and time-gap dividers. Serves as the primary home screen for returning users.
- Orbit -- Contact management screen with active/archived/blocked views, search, and swipe actions (archive, block, delete). Orbital visualization of your peer network.
- Push Notifications -- Firebase Cloud Messaging (FCM) integration. The relay server stores offline messages and sends push notifications. On resume, the app drains the offline inbox.
- Data-at-Rest Encryption -- SQLCipher-encrypted database with a random 256-bit key stored in platform secure storage. One-time migration moves legacy plaintext secrets out of the database.
+---------------------+
| APP LAUNCH |
| main.dart |
+----------+----------+
|
+--------------------+--------------------+
| | |
v v v
+--------------+ +--------------+ +--------------+
| SecureKeyStore| | Open/Create | | Create |
| (Keychain / | | Encrypted | | Go Bridge |
| EncSharedPref| | Database | | (gomobile) |
+--------------+ +------+-------+ +--------------+
|
v
+--------------------+
| Run Migrations |
| (v1..v8) |
+--------------------+
|
v
+--------------------+
| Migrate Secrets |
| DB -> SecureStorage|
+--------------------+
|
v
+------------------+-------------------+
| Create DI chain: |
| Repos, Bridge, P2PService, |
| MessageRouter, Listeners, Retrier |
+------------------+-------------------+
|
v
+--------------------+
| StartupRouter |
| (Loading Screen) |
+--------+-----------+
|
+--------+----------+
| decideStartupRoute|
+--------+----------+
|
+------------------------+-------------------------+
| | |
v v v
+-------------------+ +--------------------+ +--------------------+
| hasIdentity | | hasIdentity | | needsIdentity |
| WithContacts | | NoContacts | | |
| | | | | -> IdentityChoice |
| -> FeedWired | | -> FirstTimeExp. | | (New / Restore) |
+-------------------+ +--------------------+ +--------------------+
| | |
+------------------------+-------------------------+
|
(background, post-navigation)
|
+------------------+-------------------+
| |
v v
+--------------------+ +------------------------+
| Start P2P Node | | Register FCM Push |
| (Go libp2p) | | Token with Relay |
+--------------------+ +------------------------+
+---------------------------------------------------------------------------------+
| FLUTTER APP |
| |
| +-------------------------------------------------------------------------+ |
| | PRESENTATION LAYER | |
| | | |
| | StartupRouter FeedWired OrbitWired ConversationWired | |
| | IdentityChoice FirstTimeExp QRScanner QRDisplay | |
| | MnemonicInput ConnectionStatusIndicator PendingRequestsBadge | |
| | | |
| | Pattern: pure Screen (StatelessWidget) + Wired (StatefulWidget) | |
| +----+----------------------------+----------------------------+-----------+ |
| | | | |
| +----v----------------------------v----------------------------v-----------+ |
| | APPLICATION LAYER (Use Cases) | |
| | | |
| | Identity: generateIdentity, restoreIdentity, decideStartupRoute | |
| | Contacts: addContact, archiveContact, unarchiveContact, | |
| | blockContact, unblockContact, deleteContact | |
| | Requests: sendContactRequest, handleIncomingMessage, | |
| | acceptContactRequest, declineContactRequest | |
| | Conversation: sendChatMessage, handleIncomingChatMessage, | |
| | loadConversation, markConversationRead, retryFailed | |
| | Feed: loadFeed | |
| | Orbit: loadOrbitData | |
| | P2P: startNode, stopNode, sendMessage, discoverPeer | |
| | QR Code: buildQRPayload, parseQRPayload | |
| | Push: requestPushPermission, registerPushToken | |
| +----+----------------------------+----------------------------+-----------+ |
| | | | |
| +----v----------------------------v----------------------------v-----------+ |
| | DOMAIN LAYER | |
| | | |
| | Models: IdentityModel, ContactModel, ContactRequestModel, | |
| | ConversationMessage, MessagePayload, QRPayloadModel, | |
| | FeedItem (ConnectionFeedItem, ThreadFeedItem, MessageFeedItem),| |
| | NodeState, ChatMessage, DiscoveredPeer, OrbitFriend | |
| | | |
| | Repositories (interfaces): | |
| | IdentityRepository, ContactRepository, ContactRequestRepository, | |
| | MessageRepository | |
| +----+----------------------------+----------------------------+-----------+ |
| | | | |
| +----v----------------------------v----------------------------v-----------+ |
| | INFRASTRUCTURE LAYER | |
| | | |
| | +----------------------+ +-----------------------+ | |
| | | Repository Impls | | Bridge (abstract) | | |
| | | (DB helper fns) | | | | | |
| | +----------+-----------+ | GoBridgeClient | | |
| | | | (MethodChannel/ | | |
| | v | EventChannel) | | |
| | +----------+-----------+ +----------+------------+ | |
| | | SQLCipher Database | | | |
| | | (sqflite_sqlcipher) | v | |
| | +----------------------+ +----------+------------+ | |
| | | Go Native Library | | |
| | +----------------------+ | (.xcframework / .aar) | | |
| | | SecureKeyStore | | | | |
| | | (Keychain/EncShared) | | - BIP39 mnemonic gen | | |
| | +----------------------+ | - Ed25519 keypair | | |
| | | - ML-KEM-768 keygen | | |
| | +----------------------+ | - AES-256-GCM encrypt | | |
| | | IncomingMessageRouter| | - libp2p node | | |
| | | | | | - Rendezvous discovery| | |
| | | +-- ContactRequest | | - Offline inbox | | |
| | | | Listener | | - FCM token register | | |
| | | +-- ChatMessage | +-----------------------+ | |
| | | Listener | | |
| | +----------------------+ +-----------------------+ | |
| | | P2PServiceImpl | | |
| | +----------------------+ | (bridge + local P2P) | | |
| | | PendingMessageRetrier| +-----------------------+ | |
| | +----------------------+ | |
| | +-----------------------+ | |
| | | LocalP2PService | | |
| | | (mDNS + WebSocket) | | |
| | +-----------------------+ | |
| +-----------------------------------------------------------------------+ |
+---------------------------------------------------------------------------------+
Outgoing: Incoming:
ConversationWired Go Node (libp2p stream)
| |
v v
sendChatMessage (use case) EventChannel push event
| |
|-- encrypt if contact has ML-KEM key v
| (v2 envelope with AES-256-GCM) GoBridgeClient._handleEvent()
| |
|-- else v1 plaintext envelope v
| P2PServiceImpl._handleMessageReceived()
v |
P2PService.sendMessage() v
| IncomingMessageRouter
|-- try local WiFi first (mDNS) |
| if peer on same network +-- type: "contact_request"
| | -> ContactRequestListener
|-- else relay (Go libp2p) |
| +-- type: "chat_message"
|-- on failure: store in offline inbox -> ChatMessageListener
| + update status to 'failed' |
| v
v handleIncomingChatMessage
PendingMessageRetrier (decrypt v2 or parse v1)
(auto-retries when relay reconnects) |
v
MessageRepository.insert()
flutter_app/
|
+-- lib/ # Flutter Application Code
| +-- main.dart # Entry point, DI chain, Firebase init
| |
| +-- core/ # Shared Infrastructure
| | +-- bridge/
| | | +-- bridge.dart # Bridge abstract + identity/crypto helpers
| | | +-- go_bridge_client.dart # MethodChannel/EventChannel -> Go native
| | | +-- p2p_bridge_client.dart # P2P-specific bridge helpers (node, peer, inbox)
| | +-- config/
| | | +-- startup_config.dart # Deferred startup mode toggle
| | +-- constants/
| | | +-- network_constants.dart # Relay addresses, timeouts
| | +-- database/
| | | +-- encrypted_db_opener.dart # SQLCipher open/create/migrate
| | | +-- helpers/
| | | | +-- identity_db_helpers.dart
| | | | +-- contacts_db_helpers.dart
| | | | +-- contact_requests_db_helpers.dart
| | | | +-- messages_db_helpers.dart
| | | +-- migrations/
| | | +-- 001_identity_table.dart
| | | +-- 002_messages_table.dart
| | | +-- 003_mlkem_keys.dart
| | | +-- 004_nullify_secret_columns.dart
| | | +-- 005_secret_null_checks.dart
| | | +-- 006_read_at_column.dart
| | | +-- 007_archive_columns.dart
| | | +-- 008_block_columns.dart
| | +-- local_discovery/
| | | +-- local_discovery_service.dart # mDNS discovery interface
| | | +-- bonsoir_discovery_service.dart # Bonsoir mDNS implementation
| | | +-- local_p2p_service.dart # Composed mDNS + WebSocket facade
| | | +-- local_ws_server.dart # Direct WiFi WebSocket server
| | +-- secure_storage/
| | | +-- secure_key_store.dart # Abstract interface
| | | +-- flutter_secure_key_store.dart # Production (Keychain/EncSharedPref)
| | | +-- migrate_secrets_to_secure_storage.dart
| | +-- services/
| | | +-- p2p_service.dart # Abstract P2P interface
| | | +-- p2p_service_impl.dart # Bridge-backed implementation
| | | +-- incoming_message_router.dart # Routes P2P messages by type
| | | +-- pending_message_retrier.dart # Auto-retry on reconnect
| | +-- theme/
| | | +-- app_theme.dart # Dark theme definition
| | | +-- app_colors.dart # Color constants
| | | +-- glassmorphism.dart # Glass effect utilities
| | +-- utils/
| | +-- flow_event_emitter.dart # Structured event logging
| | +-- startup_timing.dart # Startup performance tracking
| | +-- ring_avatar_generator.dart # Deterministic avatar generation
| | +-- ring_avatar_spec.dart # Avatar specification
| | +-- key_conversion.dart # Base64 <-> hex conversions
| | +-- chat_console_logger.dart # Debug message logging
| |
| +-- features/
| +-- identity/ # Identity Feature
| | +-- domain/models/identity_model.dart
| | +-- domain/repositories/identity_repository.dart
| | +-- domain/repositories/identity_repository_impl.dart
| | +-- application/generate_identity_use_case.dart
| | +-- application/restore_identity_use_case.dart
| | +-- application/startup_decision.dart
| | +-- presentation/startup_router.dart
| | +-- presentation/screens/identity_choice_{screen,wired}.dart
| | +-- presentation/screens/mnemonic_input_{screen,wired}.dart
| | +-- presentation/widgets/ (ambient_background, brand_header, etc.)
| |
| +-- contacts/ # Contacts Feature
| | +-- domain/models/contact_model.dart
| | +-- domain/repositories/contact_repository{,_impl}.dart
| | +-- application/ (add, archive, unarchive, block, unblock, delete)
| |
| +-- contact_request/ # Contact Request Feature
| | +-- domain/models/contact_request_model.dart
| | +-- domain/repositories/contact_request_repository{,_impl}.dart
| | +-- application/ (send, handle_incoming, accept, decline, listener)
| | +-- presentation/widgets/ (contact_request_dialog, pending_requests_badge)
| |
| +-- conversation/ # Conversation Feature
| | +-- domain/models/conversation_message.dart
| | +-- domain/models/message_payload.dart
| | +-- domain/repositories/message_repository{,_impl}.dart
| | +-- application/ (send, handle_incoming, load, mark_read, retry, listener)
| | +-- presentation/screens/conversation_{screen,wired}.dart
| | +-- presentation/widgets/ (letter_card, compose_area, conversation_header,
| | | empty_state, origin_marker, date_separator,
| | | blocked_banner)
| | +-- presentation/navigation/conversation_route_transition.dart
| |
| +-- feed/ # Feed Feature
| | +-- domain/models/feed_item.dart
| | +-- domain/utils/ (format_message_time, group_messages_into_threads, etc.)
| | +-- application/load_feed_use_case.dart
| | +-- presentation/screens/feed_{screen,wired}.dart
| | +-- presentation/widgets/ (feed_header, feed_navigation_bar, thread_card,
| | | connection_card, message_feed_card, message_bubble,
| | | session_divider, time_gap_divider, nav_bar_button,
| | | unread_count_badge, checkmark_burst_animation)
| | +-- presentation/navigation/feed_route_transition.dart
| |
| +-- orbit/ # Orbit Feature (Contact Management)
| | +-- domain/models/orbit_friend.dart
| | +-- application/load_orbit_data_use_case.dart
| | +-- presentation/screens/orbit_{screen,wired}.dart
| | +-- presentation/widgets/ (orbital_visualization, orbital_avatar,
| | | orbital_ring_painter, friend_row, swipeable_friend_row,
| | | swipe_action_buttons, friends_filter_toggle,
| | | friends_list_header, orbit_header, orbit_close_button,
| | | orbit_search_dock, orbit_search_trigger,
| | | overflow_badge, qr_action_cards, confirmation_dialog,
| | | archived_empty_state)
| | +-- presentation/navigation/orbit_route_transition.dart
| |
| +-- home/ # Home / First-Time Experience
| | +-- presentation/screens/first_time_experience_{screen,wired}.dart
| | +-- presentation/widgets/ (editable_username, empty_circle_state,
| | profile_avatar, qr_code_section, ring_avatar,
| | ring_avatar_painter, scan_friend_card)
| |
| +-- p2p/ # P2P Feature (Models + Use Cases)
| | +-- domain/models/ (chat_message, connection_state, discovered_peer,
| | | node_state, send_message_result)
| | +-- application/ (start_node, stop_node, send_message, discover_peer)
| | +-- presentation/widgets/connection_status_indicator.dart
| |
| +-- push/ # Push Notifications Feature
| | +-- application/background_message_handler.dart
| | +-- application/register_push_token_use_case.dart
| | +-- application/request_push_permission_use_case.dart
| |
| +-- qr_code/ # QR Code Feature
| +-- domain/models/qr_payload_model.dart
| +-- application/build_qr_payload_use_case.dart
| +-- application/parse_qr_payload_use_case.dart
| +-- presentation/screens/qr_display_{screen,wired}.dart
| +-- presentation/screens/qr_scanner_{screen,wired}.dart
| +-- presentation/widgets/scan_overlay.dart
|
+-- go-mknoon/ # Go Native Library (gomobile)
| +-- bridge/
| | +-- bridge.go # Exported API: identity, crypto, node, inbox
| | +-- bridge_test.go
| | +-- events.go # EventCallback interface for Go -> Flutter
| +-- identity/
| | +-- identity.go # BIP39 mnemonic + Ed25519 keypair generation
| | +-- identity_test.go
| +-- crypto/
| | +-- sign.go # Ed25519 signing
| | +-- mlkem.go # ML-KEM-768 key generation
| | +-- encrypt.go # ML-KEM encapsulate + AES-256-GCM encrypt
| | +-- decrypt.go # ML-KEM decapsulate + AES-256-GCM decrypt
| | +-- *_test.go
| +-- node/
| | +-- node.go # libp2p host, stream handlers, peer mgmt
| | +-- config.go # Node configuration
| | +-- rendezvous.go # Rendezvous register/discover
| | +-- inbox.go # Offline inbox store/retrieve via relay
| | +-- node_test.go
| +-- internal/
| | +-- envelope.go # Message envelope utilities
| +-- integration/
| | +-- relay_test.go # Relay integration test
| +-- stub/
| | +-- gosigar/sigar.go # iOS build stub (no libproc.h)
| +-- Makefile # gomobile bind targets (ios, android)
| +-- go.mod, go.sum, tools.go
|
+-- test/ # Unit Tests (Dart)
| +-- core/bridge/ # Bridge helper tests
| +-- core/local_discovery/ # Local P2P tests + fakes
| +-- core/secure_storage/ # Secrets migration tests + FakeSecureKeyStore
| +-- core/services/ # IncomingMessageRouter tests
| +-- features/identity/ # Identity use case tests
| +-- features/contacts/ # Contact model, repo, use case tests
| +-- features/contact_request/ # Contact request use case tests
| +-- features/conversation/ # Message use case, model, repo, widget tests
| +-- features/feed/ # Feed use case, model, widget tests
| +-- features/orbit/ # Orbit use case, widget tests
| +-- features/qr_code/ # QR payload use case tests
|
+-- integration_test/
| +-- smoke_test.dart # On-device smoke test
|
+-- ios/ # iOS native (GoBridge.swift platform wrapper)
+-- android/ # Android native (GoBridge.kt platform wrapper)
Dependency injection is manual, wired in main():
SecureKeyStore
|
+--> openEncryptedDatabase() --> Database
|
+--> migrateSecretsToSecureStorage()
|
+--> IdentityRepositoryImpl (dbHelpers + secureKeyStore)
|
+--> ContactRepositoryImpl (dbHelpers)
+--> ContactRequestRepositoryImpl (dbHelpers)
+--> MessageRepositoryImpl (dbHelpers)
|
+--> GoBridgeClient --> Bridge (abstract)
|
+--> BonsoirDiscoveryService + LocalWsServer --> LocalP2PService
|
+--> P2PServiceImpl (bridge + localP2PService)
|
+--> IncomingMessageRouter (p2pService)
|
+--> ContactRequestListener (router.contactRequestStream + repos + bridge)
+--> ChatMessageListener (router.chatMessageStream + repos + bridge)
+--> PendingMessageRetrier (p2pService + repos + bridge)
|
+--> MyApp --> StartupRouter --> Feature Screens
Everything is threaded through constructors. No service locator or code generation.
+-------------------------------------------------------------+
| IdentityModel |
+-------------------------------------------------------------+
| |
| peerId: String |
| +-- libp2p peer identifier |
| +-- Derived from Ed25519 public key |
| |
| publicKey: String |
| +-- Base64-encoded Ed25519 public key |
| +-- Used for identity verification + QR payload |
| |
| privateKey: String |
| +-- Base64-encoded Ed25519 private key |
| +-- Stored in SecureKeyStore (NOT in database) |
| +-- Used for signing + P2P node authentication |
| |
| mnemonic12: String |
| +-- 12 BIP39 English words |
| +-- Stored in SecureKeyStore (NOT in database) |
| +-- Can regenerate Ed25519 identity |
| |
| mlKemPublicKey: String? |
| +-- Base64-encoded ML-KEM-768 public key |
| +-- Exchanged via QR payload + contact request |
| |
| mlKemSecretKey: String? |
| +-- Base64-encoded ML-KEM-768 secret key |
| +-- Stored in SecureKeyStore (NOT in database) |
| +-- Used to decrypt incoming v2 messages |
| |
| username: String (default "Username") |
| +-- User-chosen display name |
| |
| avatarBlob: Uint8List? |
| +-- Optional profile image bytes |
| |
| createdAt: String (ISO-8601) |
| updatedAt: String (ISO-8601) |
| |
+-------------------------------------------------------------+
| Table | Key Columns | Notes |
|---|---|---|
identity |
id=1, peer_id, public_key, private_key(NULL), mnemonic12(NULL), ml_kem_public_key, ml_kem_secret_key(NULL), username, avatar_blob, created_at, updated_at | Secrets enforced NULL by CHECK constraints (stored in SecureKeyStore) |
contacts |
peer_id (PK), public_key, rendezvous, username, signature, scanned_at, avatar_path, ml_kem_public_key, is_archived, archived_at, is_blocked, blocked_at | Added via QR scan or accepted contact request |
contact_requests |
peer_id (PK), public_key, rendezvous, username, signature, received_at, status, ml_kem_public_key | status: pending / accepted / declined |
messages |
id (UUID PK), contact_peer_id, sender_peer_id, text, timestamp, status, is_incoming, created_at, read_at | status: sending / sent / delivered / failed |
v1 -- Plaintext envelope (used when recipient has no ML-KEM key):
{
"type": "chat_message",
"version": "1",
"payload": {
"id": "uuid",
"text": "Hello!",
"senderPeerId": "12D3Koo...",
"senderUsername": "Alice",
"timestamp": "2025-01-15T12:00:00Z"
}
}v2 -- Encrypted envelope (ML-KEM-768 + AES-256-GCM):
{
"type": "chat_message",
"version": "2",
"senderPeerId": "12D3Koo...",
"encrypted": {
"kem": "<base64 KEM ciphertext>",
"ciphertext": "<base64 AES-256-GCM ciphertext>",
"nonce": "<base64 96-bit nonce>"
}
}Contact request envelope:
{
"type": "contact_request",
"version": "1",
"payload": {
"pk": "<base64 public key>",
"ns": "<peer ID>",
"rv": "<rendezvous multiaddr>",
"un": "Alice",
"ts": "2025-01-15T12:00:00Z",
"sig": "<base64 Ed25519 signature>",
"mlkem": "<base64 ML-KEM public key>"
}
}User App Go Native SecureKeyStore
| | | |
| Opens app | | |
|-------------------->| | |
| | Check identity in DB | |
| | (none found) | |
| Show choice screen | | |
|<--------------------| | |
| | | |
| Tap "I'm new here" | | |
|-------------------->| | |
| | identity.generate | |
| |----------------------->| |
| | {peerId, publicKey, | |
| | privateKey, mnemonic} | |
| |<-----------------------| |
| | | |
| | mlkem.keygen | |
| |----------------------->| |
| | {publicKey, secretKey}| |
| |<-----------------------| |
| | | |
| | Save secrets | |
| |---------------------------------------------->|
| | Save public fields to DB |
| | | |
| Navigate to FTE | | |
|<--------------------| | |
| | (background) Start P2P node |
| | (background) Register FCM push token |
Alice (scanner) Alice's App Bob's App Bob (displayer)
| | | |
| | | Show QR code |
| | |<--------------------|
| Scan Bob's QR | | |
|----------------------->| | |
| | Verify QR signature | |
| | (Ed25519) | |
| | | |
| | Save Bob as contact | |
| | | |
| | Send contact_request | |
| | via P2P to Bob | |
| |----------------------->| |
| | | Show request dialog|
| | |-------------------->|
| | | |
| | | User accepts |
| | |<--------------------|
| | | Save Alice as |
| | | contact |
Alice's App Go Bridge Bob's App
| | |
| sendChatMessage() | |
| Bob has ML-KEM key? YES | |
| | |
| message.encrypt | |
| (Bob's ML-KEM pubkey, | |
| plaintext JSON) | |
|---------------------------->| |
| {kem, ciphertext, nonce} | |
|<----------------------------| |
| | |
| Build v2 envelope | |
| message:send | |
|---------------------------->| |
| | libp2p stream to Bob |
| |------------------------->|
| | |
| | | message.decrypt
| | | (own ML-KEM secretKey)
| | | -> plaintext JSON
| | | -> save to messages DB
+-------------------------------------------------------------+
| Error Categories |
+-------------------------------------------------------------+
| |
| IDENTITY ERRORS |
| +-- coreLibError Go bridge communication failed |
| +-- dbError Database save operation failed |
| +-- invalidMnemonicFormat Word count != 12 |
| +-- invalidMnemonicCore BIP39 checksum invalid |
| +-- BRIDGE_TIMEOUT Bridge call timed out |
| |
| P2P / NETWORKING ERRORS |
| +-- NODE_START_ERROR Node failed to start |
| +-- DIAL_ERROR Could not connect to peer |
| +-- SEND_ERROR Message send failed |
| +-- RENDEZVOUS_ERROR Discovery failed |
| +-- INBOX_ERROR Offline inbox operation failed |
| +-- NOT_INITIALIZED Bridge not initialized |
| |
| CRYPTO ERRORS |
| +-- INVALID_INPUT Missing or malformed parameters |
| +-- INTERNAL_ERROR ML-KEM or AES operation failed |
| |
| DATABASE ERRORS |
| +-- Migration failure Schema upgrade failed |
| +-- Encryption error SQLCipher key derivation |
| |
| PUSH NOTIFICATION ERRORS |
| +-- Permission denied User declined push permission |
| +-- Token registration FCM token registration failed |
| |
| STARTUP ERRORS |
| +-- Database read failure Show retry screen |
| +-- Bridge init failure Show retry screen |
| |
+-------------------------------------------------------------+
The application emits structured events via emitFlowEvent() at every layer for monitoring and debugging:
| Layer | Event Examples |
|---|---|
| Database | ID_DB_LOAD_IDENTITY_START, ID_DB_UPSERT_IDENTITY_SUCCESS |
| Go Bridge | GO_BRIDGE_INIT_SUCCESS, GO_BRIDGE_SEND, GO_BRIDGE_PLATFORM_ERROR |
| P2P Service | P2P_SERVICE_START_NODE_BEGIN, P2P_SERVICE_SEND_MESSAGE_SUCCESS |
| Message Router | MESSAGE_ROUTER_START, MESSAGE_ROUTER_UNKNOWN_TYPE |
| Listeners | CR_LISTENER_INCOMING, CHAT_LISTENER_MESSAGE_SAVED |
| Use Cases | ID_M1_GENERATE_START, SEND_CHAT_MESSAGE_ENCRYPTED |
| Presentation | ID_STARTUP_ROUTE_FEED, APP_LIFECYCLE_RESUME_COMPLETE |
| Push | PUSH_FOREGROUND_MESSAGE_RECEIVED, PUSH_REGISTER_TOKEN_ERROR |
- Flutter SDK (3.x, Dart SDK ^3.9.0)
- Go (1.22+)
- gomobile:
go install golang.org/x/mobile/cmd/gomobile@latest && gomobile init - Xcode (for iOS builds)
- Android Studio + NDK (for Android builds)
- CocoaPods (for iOS dependency management)
The Go library must be built before running the Flutter app. flutter run alone does NOT rebuild Go code.
# Build for both iOS and Android
cd go-mknoon
PATH="$PATH:$(go env GOPATH)/bin" make all
cd ../ios && pod installThis produces:
ios/Runner/GoMknoon.xcframework(iOS)android/app/libs/GoMknoon.aar(Android)
# Install Flutter dependencies
flutter pub get
# Run on connected device or simulator (Go library must be built first)
flutter run
# Run on specific platform
flutter run -d ios
flutter run -d android# Dart unit tests (no device required)
flutter test
# Go unit tests
cd go-mknoon && go test ./...
# On-device integration smoke test
flutter test integration_test/smoke_test.dart- Android build requires Go < 1.25 or a patched
wlynxg/anet(known gomobile issue). - gomobile symbols use the Go package prefix (
Bridge*), not the framework name (GoMknoon*). - The
go-mknoon/stub/gosigar/stub is required because iOS cannot uselibproc.h. - Always run
make all+pod installafter modifying Go code.
- Framework: Flutter 3.x (Dart ^3.9.0)
- Language: Dart (Flutter), Go (native library via gomobile)
- Database: SQLCipher (via sqflite_sqlcipher) -- encrypted SQLite with 256-bit key
- Secure Storage: flutter_secure_storage (iOS Keychain / Android EncryptedSharedPreferences)
- Cryptography:
- BIP39 (mnemonic generation)
- Ed25519 (keypair derivation, signing, verification)
- ML-KEM-768 / FIPS 203 (post-quantum key encapsulation)
- AES-256-GCM (symmetric message encryption)
- Networking:
- libp2p (QUIC + WebSocket transport, circuit relay v2, rendezvous discovery)
- Bonsoir / mDNS (local WiFi peer discovery)
- WebSocket (local WiFi direct messaging)
- Push Notifications: Firebase Cloud Messaging (FCM)
- QR Code: qr_flutter (display), mobile_scanner (camera scanning)
- Architecture: Clean Architecture with Repository Pattern (DB helpers -> Repos -> Use Cases -> Wired/Screen UI)
- State Management: StatefulWidget with constructor-injected use cases (no service locator, no code generation)
- Platform Bridge: MethodChannel / EventChannel -> Go .xcframework (iOS) / .aar (Android)
- UI Pattern: Screen (StatelessWidget) + Wired (StatefulWidget) pairs per feature
- Logging:
emitFlowEvent()structured events at every layer
This project is proprietary software.