From f646d8790f120862e6a7889c15d56354c70f794e Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Fri, 26 Dec 2025 23:28:13 -0300 Subject: [PATCH 1/7] Add Firebase configuration for push notifications --- .firebaserc | 5 ++ .gitignore | 4 +- android/app/build.gradle | 3 ++ android/app/google-services.json | 29 +++++++++++ android/settings.gradle | 3 ++ firebase.json | 1 + lib/firebase_options.dart | 86 ++++++++++++++++++++++++++++++++ pubspec.lock | 56 +++++++++++++++++++++ pubspec.yaml | 4 ++ 9 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 .firebaserc create mode 100644 android/app/google-services.json create mode 100644 firebase.json create mode 100644 lib/firebase_options.dart diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 00000000..cd20bcea --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "mostro-mobile" + } +} diff --git a/.gitignore b/.gitignore index 20dd50a4..4a3b90ce 100644 --- a/.gitignore +++ b/.gitignore @@ -45,9 +45,7 @@ android/app/upload-keystore.jks *.apk .cxx/ -# Firebase and Google Services -android/app/google-services.json -ios/Runner/GoogleService-Info.plist +# Firebase and Google Services (public credentials - safe to commit) # Web web/.dart_tool/ diff --git a/android/app/build.gradle b/android/app/build.gradle index 582938fd..a69bfeff 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,5 +1,8 @@ plugins { id "com.android.application" + // START: FlutterFire Configuration + id 'com.google.gms.google-services' + // END: FlutterFire Configuration id "kotlin-android" // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 00000000..6dcf16b2 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "375342057498", + "project_id": "mostro-mobile", + "storage_bucket": "mostro-mobile.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:375342057498:android:4ef7011ab12de38e86e9a3", + "android_client_info": { + "package_name": "network.mostro.app" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCOrsfYrkIItBLAFbnYmh4HnsxBTnT3y14" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index 4f520718..db468a47 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,6 +19,9 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.6.0" apply false + // START: FlutterFire Configuration + id "com.google.gms.google-services" version "4.3.15" apply false + // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "2.1.0" apply false } diff --git a/firebase.json b/firebase.json new file mode 100644 index 00000000..7dbb7a01 --- /dev/null +++ b/firebase.json @@ -0,0 +1 @@ +{"flutter":{"platforms":{"android":{"default":{"projectId":"mostro-mobile","appId":"1:375342057498:android:4ef7011ab12de38e86e9a3","fileOutput":"android/app/google-services.json"}},"dart":{"lib/firebase_options.dart":{"projectId":"mostro-mobile","configurations":{"android":"1:375342057498:android:4ef7011ab12de38e86e9a3","ios":"1:375342057498:ios:1cfcc24cfe51fbd386e9a3","macos":"1:375342057498:ios:1cfcc24cfe51fbd386e9a3","web":"1:375342057498:web:64f481ab355feea386e9a3","windows":"1:375342057498:web:2cd68bf87a368a4886e9a3"}}}}}} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 00000000..29903d6c --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,86 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: type=lint +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + return macos; + case TargetPlatform.windows: + return windows; + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyCcKUG4IkZ51YfTjZSCqNdZmT5dVH_ebnA', + appId: '1:375342057498:web:64f481ab355feea386e9a3', + messagingSenderId: '375342057498', + projectId: 'mostro-mobile', + authDomain: 'mostro-mobile.firebaseapp.com', + storageBucket: 'mostro-mobile.firebasestorage.app', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyCOrsfYrkIItBLAFbnYmh4HnsxBTnT3y14', + appId: '1:375342057498:android:4ef7011ab12de38e86e9a3', + messagingSenderId: '375342057498', + projectId: 'mostro-mobile', + storageBucket: 'mostro-mobile.firebasestorage.app', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyDXIc0i_xrVcNz_HILS2jFXUEcgbLKrfKo', + appId: '1:375342057498:ios:1cfcc24cfe51fbd386e9a3', + messagingSenderId: '375342057498', + projectId: 'mostro-mobile', + storageBucket: 'mostro-mobile.firebasestorage.app', + iosBundleId: 'network.mostro.app', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyDXIc0i_xrVcNz_HILS2jFXUEcgbLKrfKo', + appId: '1:375342057498:ios:1cfcc24cfe51fbd386e9a3', + messagingSenderId: '375342057498', + projectId: 'mostro-mobile', + storageBucket: 'mostro-mobile.firebasestorage.app', + iosBundleId: 'network.mostro.app', + ); + + static const FirebaseOptions windows = FirebaseOptions( + apiKey: 'AIzaSyCcKUG4IkZ51YfTjZSCqNdZmT5dVH_ebnA', + appId: '1:375342057498:web:2cd68bf87a368a4886e9a3', + messagingSenderId: '375342057498', + projectId: 'mostro-mobile', + authDomain: 'mostro-mobile.firebaseapp.com', + storageBucket: 'mostro-mobile.firebasestorage.app', + ); +} diff --git a/pubspec.lock b/pubspec.lock index 31690a4d..baa67012 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "85.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + url: "https://pub.dev" + source: hosted + version: "1.3.59" analyzer: dependency: transitive description: @@ -465,6 +473,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+5" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + url: "https://pub.dev" + source: hosted + version: "3.15.2" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + url: "https://pub.dev" + source: hosted + version: "2.24.1" + firebase_messaging: + dependency: "direct main" + description: + name: firebase_messaging + sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" + url: "https://pub.dev" + source: hosted + version: "15.2.10" + firebase_messaging_platform_interface: + dependency: transitive + description: + name: firebase_messaging_platform_interface + sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" + url: "https://pub.dev" + source: hosted + version: "4.6.10" + firebase_messaging_web: + dependency: transitive + description: + name: firebase_messaging_web + sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" + url: "https://pub.dev" + source: hosted + version: "3.10.10" fixnum: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4fd0632b..87c3e9b1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -100,6 +100,10 @@ dependencies: # Unicode normalization for filename sanitization diacritic: ^0.1.6 + # Firebase + firebase_core: ^3.8.0 + firebase_messaging: ^15.1.4 + dev_dependencies: flutter_test: From 0efa225b12924d18537461d1b4fc32d0e33d0056 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Fri, 26 Dec 2025 23:34:57 -0300 Subject: [PATCH 2/7] Add Firebase Linux compatibility documentation and notes --- docs/FIREBASE_LINUX_NOTE.md | 43 +++++++++++++++++++++++++++++++++++++ lib/firebase_options.dart | 3 +++ pubspec.yaml | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 docs/FIREBASE_LINUX_NOTE.md diff --git a/docs/FIREBASE_LINUX_NOTE.md b/docs/FIREBASE_LINUX_NOTE.md new file mode 100644 index 00000000..bc64a2d2 --- /dev/null +++ b/docs/FIREBASE_LINUX_NOTE.md @@ -0,0 +1,43 @@ +# Firebase on Linux + +## Important Note + +Firebase is **not supported on Linux**. The app will compile and run on Linux, but Firebase features (push notifications) will not be available. + +## Implementation + +When initializing Firebase in your code, always check the platform first: + +```dart +import 'dart:io' show Platform; +import 'package:flutter/foundation.dart' show kIsWeb; + +Future initializeFirebase() async { + // Skip Firebase initialization on Linux + if (!kIsWeb && Platform.isLinux) { + print('Firebase not supported on Linux - skipping initialization'); + return; + } + + // Initialize Firebase for supported platforms + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); +} +``` + +## Why This Approach? + +- `pubspec.yaml` doesn't support conditional dependencies per platform +- Flutter will include Firebase packages but they won't be used on Linux +- The app compiles successfully on all platforms +- Push notifications gracefully degrade on Linux + +## Supported Platforms + +- ✅ Android +- ✅ iOS +- ✅ Web +- ✅ macOS +- ✅ Windows +- ❌ Linux (compiles but Firebase features disabled) diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index 29903d6c..c0cfa229 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -4,6 +4,9 @@ import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform; +// Firebase is not supported on Linux +// This file should only be imported on supported platforms + /// Default [FirebaseOptions] for use with your Firebase apps. /// /// Example: diff --git a/pubspec.yaml b/pubspec.yaml index 87c3e9b1..3be5deb3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -100,7 +100,7 @@ dependencies: # Unicode normalization for filename sanitization diacritic: ^0.1.6 - # Firebase + # Firebase (not supported on Linux - use conditional imports in code) firebase_core: ^3.8.0 firebase_messaging: ^15.1.4 From 4ae10ed43a1e2e1b09f5db4b7d04d4c6a5484bb2 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Fri, 26 Dec 2025 23:54:18 -0300 Subject: [PATCH 3/7] Add comprehensive FCM implementation documentation --- docs/FCM_IMPLEMENTATION.md | 372 +++++++++++++++++++++++++++++++++++++ 1 file changed, 372 insertions(+) create mode 100644 docs/FCM_IMPLEMENTATION.md diff --git a/docs/FCM_IMPLEMENTATION.md b/docs/FCM_IMPLEMENTATION.md new file mode 100644 index 00000000..66d7c9ea --- /dev/null +++ b/docs/FCM_IMPLEMENTATION.md @@ -0,0 +1,372 @@ +# FCM Implementation for MostroP2P Mobile + +## Overview + +This document outlines the implementation of Firebase Cloud Messaging (FCM) for push notifications in the MostroP2P mobile application. The implementation follows a privacy-preserving approach similar to **[MIP-05 (Marmot Push Notifications)](https://github.com/marmot-protocol/marmot/pull/18)**, where: + +- **Silent push notifications** wake up the app without exposing message content +- **All message decryption happens locally** on the device +- **Minimal metadata** is exposed to Firebase/Google services +- **Custom notification server** ([mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server)) handles token management and notification delivery + +### Why a Custom Server? + +The initial approach was to use **Firebase Cloud Functions** as the notification server. However, this proved to be **unstable and limiting** because: + +- ❌ **No WebSocket support:** Cloud Functions cannot maintain persistent WebSocket connections with Nostr relays +- ❌ **Cold starts:** Functions experience significant latency on cold starts, delaying notifications +- ❌ **Unreliable for real-time:** Not suitable for monitoring relay events in real-time +- ❌ **Complexity:** Additional overhead for managing function deployments and monitoring + +The custom server approach ([mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server)) provides: + +- ✅ **Persistent WebSocket connections** to Nostr relays for real-time event monitoring +- ✅ **Stable and reliable** notification delivery +- ✅ **Lower latency** - no cold starts +- ✅ **Full control** over server behavior and monitoring +- ✅ **Simpler architecture** - dedicated service for one purpose + +## Privacy Properties + +### What Firebase/Google Learn +- A notification occurred for a specific device +- The device owner's platform identity (Google account) via FCM token + +### What Firebase/Google CANNOT Learn +- Message content (notifications are silent/empty) +- Sender's Nostr identity +- Recipient's Nostr identity +- Order details or trade information +- Any relationship between the device and specific Nostr accounts + +### What the Notification Server Learns +- Timing of notification events +- Encrypted device tokens (cannot decrypt to actual FCM tokens) + +### What the Notification Server CANNOT Learn +- Message content +- Sender's Nostr identity +- Recipient's Nostr identity +- Which Nostr user owns which device token +- User IP addresses (only relays observe IPs) + +## Architecture + +``` +┌─────────────────┐ +│ MostroP2P App │ +│ (Flutter) │ +└────────┬────────┘ + │ + │ 1. Register encrypted token + │ (ChaCha20-Poly1305) + ▼ +┌─────────────────┐ +│ Notification │ +│ Server │◄──── 2. Nostr relay monitors +│ (Separate Repo) │ for new events +└────────┬────────┘ + │ + │ 3. Send silent push + ▼ +┌─────────────────┐ +│ Firebase Cloud │ +│ Messaging (FCM) │ +└────────┬────────┘ + │ + │ 4. Wake app (silent notification) + ▼ +┌─────────────────┐ +│ MostroP2P App │ +│ (Background) │──► 5. Existing background +└─────────────────┘ notification system + handles the rest +``` + +**Note:** The app already has a background notification system implemented. FCM is only used to **wake up the app** with silent push notifications. Once awake, the existing notification system takes over to fetch, decrypt, and display messages. + +## Implementation Phases + +The implementation is divided into multiple phases (Pull Requests) to facilitate easier code review and incremental testing. + +--- + +## Phase 1: Firebase Basic Configuration ✅ + +**Branch:** `feature/firebase-fcm-setup` + +**Objective:** Set up Firebase project and basic dependencies without any notification logic. + +### Changes +- Configure Firebase project `mostro-mobile` (ID: 375342057498) +- Add `firebase_core` and `firebase_messaging` dependencies +- Configure Android `build.gradle` for Firebase +- Generate `firebase_options.dart` for all platforms +- Include `google-services.json` in Git (public credentials - safe to commit) +- Document Linux compatibility (Firebase not supported on Linux) + +### Files Added/Modified +- `.firebaserc` - Firebase project configuration +- `firebase.json` - FlutterFire CLI configuration +- `lib/firebase_options.dart` - Firebase options for all platforms +- `android/app/google-services.json` - Android Firebase configuration +- `android/app/build.gradle` - Firebase dependencies +- `android/settings.gradle` - Google Services plugin +- `pubspec.yaml` - Firebase dependencies +- `.gitignore` - Allow Firebase config files +- `docs/FIREBASE_LINUX_NOTE.md` - Linux compatibility notes + +### Testing +- ✅ `flutter analyze` passes without errors +- ✅ App compiles on all supported platforms +- ✅ No Firebase initialization yet (just configuration) + +--- + +## Phase 2: FCM Service Implementation + +**Branch:** `feature/fcm-service` (to be created from `main` after Phase 1 merge) + +**Objective:** Implement FCM token management and integrate with existing background notification system. + +### Changes +- Create `FCMService` class for Firebase Cloud Messaging operations +- Implement FCM token generation and retrieval +- Handle FCM token refresh events +- Request notification permissions (Android/iOS) +- Implement background message handler to trigger existing notification system +- Integrate FCM initialization in app startup (with Platform.isLinux check) + +### Files to Add/Modify +- `lib/services/fcm_service.dart` - Core FCM service with background handler +- `lib/main.dart` - Initialize Firebase and FCM on app startup +- `lib/core/providers.dart` - Riverpod provider for FCMService +- `lib/features/notifications/services/background_notification_service.dart` - Integration point +- `android/app/src/main/AndroidManifest.xml` - Background execution permissions (if needed) + +### Background Handler Integration +```dart +@pragma('vm:entry-point') +Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // 1. Initialize minimal Firebase + await Firebase.initializeApp(); + + // 2. Silent notification received (empty - no data in message) + // 3. Trigger existing background notification system + // (app already has flutter_background_service implemented) + // 4. Existing system handles: + // - Fetching new messages from Nostr relays + // - Decrypting messages locally + // - Displaying local notifications + // - Updating badge count +} +``` + +### Key Features +- **Token Management:** Generate, store, and refresh FCM tokens +- **Permission Handling:** Request notification permissions on first launch +- **Platform Detection:** Skip Firebase initialization on Linux +- **Token Persistence:** Store token locally using `shared_preferences` +- **Background Wake-up:** Silent push wakes app to trigger existing notification system +- **No Duplication:** Leverage existing `flutter_background_service` and `flutter_local_notifications` + +### Testing +- Verify FCM token generation on Android +- Test permission request flow +- Verify token refresh on app restart +- Confirm Linux builds skip Firebase initialization +- Test background notification reception (app closed) +- Verify existing notification system is triggered correctly +- Confirm no duplicate notifications + +--- + +## Phase 3: Push Notification Service with Encryption + +**Branch:** `feature/push-notification-service` (to be created from `main` after Phase 2 merge) + +**Objective:** Implement encrypted token registration with the custom notification server. + +### Changes +- Create `PushNotificationService` for server communication +- Implement token encryption using ChaCha20-Poly1305 +- Implement ECDH key derivation with server's public key +- Register encrypted tokens with notification server +- Handle token unregistration on logout/disable + +### Files to Add/Modify +- `lib/services/push_notification_service.dart` - Encrypted token registration +- `lib/core/config.dart` - Add notification server public key and endpoints +- `lib/services/fcm_service.dart` - Integration with PushNotificationService + +### Encryption Implementation +Following MIP-05 approach: + +```dart +// 1. Generate ephemeral keypair for this token encryption +ephemeral_privkey = random_bytes(32) +ephemeral_pubkey = secp256k1.get_pubkey(ephemeral_privkey) + +// 2. Derive encryption key using ECDH + HKDF +shared_x = secp256k1_ecdh(ephemeral_privkey, server_pubkey) +prk = HKDF-Extract(salt="mostro-fcm-v1", IKM=shared_x) +encryption_key = HKDF-Expand(prk, "mostro-token-encryption", 32) + +// 3. Generate random nonce for probabilistic encryption +nonce = random_bytes(12) + +// 4. Construct padded payload (uniform size) +padded_payload = platform_byte || token_length || device_token || random_padding + +// 5. Encrypt with ChaCha20-Poly1305 +ciphertext = ChaCha20-Poly1305.encrypt( + key: encryption_key, + nonce: nonce, + plaintext: padded_payload, + aad: "" +) + +// 6. Token package +encrypted_token = ephemeral_pubkey || nonce || ciphertext +``` + +### Key Features +- **Probabilistic Encryption:** Same FCM token produces different ciphertexts each time +- **Server Cannot Decrypt:** Only the notification server (with private key) can decrypt +- **Payload Padding:** Uniform token size prevents platform fingerprinting +- **HTTPS Communication:** Secure token registration endpoint + +### Testing +- Verify token encryption produces different ciphertexts +- Test successful registration with notification server +- Verify encrypted token format (ephemeral_pubkey || nonce || ciphertext) +- Test token unregistration flow + +--- + +## Phase 4: User Settings and Opt-Out + +**Branch:** `feature/notification-settings` (to be created from `main` after Phase 3 merge) + +**Objective:** Provide user controls for notification preferences. + +### Changes +- Add notification settings screen +- Implement enable/disable toggle for push notifications +- Add notification sound/vibration preferences +- Implement token unregistration on disable +- Add notification preview in settings + +### Files to Add/Modify +- `lib/features/settings/screens/notification_settings_screen.dart` - Settings UI +- `lib/features/settings/providers/notification_settings_provider.dart` - Settings state +- `lib/services/push_notification_service.dart` - Handle enable/disable + +### Key Features +- **Enable/Disable Toggle:** Complete opt-out from push notifications +- **Token Cleanup:** Unregister token from server when disabled +- **Preferences:** Sound, vibration, notification preview settings +- **Privacy Notice:** Explain what data is shared with Firebase/Google + +### Testing +- Test enable/disable toggle +- Verify token unregistration on disable +- Test notification preferences (sound, vibration) +- Verify settings persistence across app restarts + +--- + +## Implementation Notes + +### Platform Support +- ✅ **Android:** Full FCM support +- ✅ **iOS:** Will require APNs configuration (future work) +- ❌ **Linux:** Firebase not supported, notifications disabled +- ⚠️ **Web/Windows/macOS:** Firebase supported but notifications may have limitations + +### Security Considerations +- **No Message Content in Push:** FCM notifications are always silent/empty +- **Encrypted Tokens:** Device tokens encrypted before sending to server +- **Local Decryption:** All Nostr message decryption happens on device +- **Ephemeral Keys:** Each token encryption uses fresh ephemeral keypair +- **Server Cannot Correlate:** Probabilistic encryption prevents cross-user correlation + +### Dependencies +- `firebase_core: ^3.8.0` - Firebase initialization +- `firebase_messaging: ^15.1.4` - FCM functionality (silent push only) +- `flutter_local_notifications: ^19.0.0` - Already implemented (existing system) +- `flutter_background_service: ^5.1.0` - Already implemented (existing system) +- `pointycastle: ^3.9.1` - ChaCha20-Poly1305 encryption +- `crypto: ^3.0.5` - HKDF key derivation + +### Related Documentation +- `docs/FIREBASE_LINUX_NOTE.md` - Linux compatibility notes +- [MIP-05 specification](https://github.com/marmot-protocol/marmot/pull/18) - Privacy-preserving push notifications approach +- [mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server) - Custom notification server repository +- [Firebase Cloud Messaging documentation](https://firebase.google.com/docs/cloud-messaging) +- [NIP-59 (Gift Wrap)](https://github.com/nostr-protocol/nips/blob/master/59.md) - Nostr event encryption standard + +--- + +## Future Enhancements + +### Potential Improvements (Not in Current Scope) +- **iOS APNs Integration:** Native iOS push notifications +- **Notification Grouping:** Group multiple order updates +- **Rich Notifications:** Add action buttons (Accept/Reject) +- **Notification History:** Store notification history locally +- **Advanced Privacy:** Tor support for server communication +- **Rate Limiting:** Client-side notification throttling +- **Custom Sounds:** Per-order-type notification sounds + +### Server Repository +The notification server is maintained in a **separate repository** and handles: +- Nostr relay monitoring for new events +- Token decryption and FCM delivery +- Rate limiting and spam protection +- Multiple relay support for redundancy + +--- + +## Testing Strategy + +### Unit Tests +- FCM token generation and refresh +- Token encryption/decryption logic +- ECDH key derivation +- Payload padding and parsing + +### Integration Tests +- End-to-end notification flow (send → receive → display) +- Background message handling +- Token registration with server +- Notification tap navigation + +### Manual Testing +- Test on physical Android devices +- Test with app in foreground/background/closed states +- Test notification permissions flow +- Test enable/disable toggle +- Verify no notifications on Linux builds + +--- + +## Success Metrics + +- ✅ Notifications delivered within 5 seconds of new message +- ✅ Background notifications work with app closed +- ✅ Zero message content exposed to Firebase/Google +- ✅ Encrypted tokens successfully registered with server +- ✅ User can completely opt-out of push notifications +- ✅ App compiles and runs on Linux (notifications gracefully disabled) + +--- + +## References + +- [MIP-05: Marmot Push Notifications](https://github.com/marmot-protocol/marmot/pull/18) - Privacy-preserving push notification approach +- [mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server) - Custom notification server repository +- [Firebase Cloud Messaging Documentation](https://firebase.google.com/docs/cloud-messaging) +- [NIP-59: Gift Wrap](https://github.com/nostr-protocol/nips/blob/master/59.md) - Nostr event encryption +- [ChaCha20-Poly1305 AEAD](https://datatracker.ietf.org/doc/html/rfc8439) - Authenticated encryption +- [HKDF Key Derivation](https://datatracker.ietf.org/doc/html/rfc5869) - Key derivation function From 3f8990c071dba636b5c79f68d6753cac00bedf98 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Mon, 29 Dec 2025 16:02:32 -0300 Subject: [PATCH 4/7] Update Firebase dependencies to latest versions --- android/settings.gradle | 2 +- pubspec.lock | 24 ++++++++++++------------ pubspec.yaml | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/android/settings.gradle b/android/settings.gradle index db468a47..52097a4c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -20,7 +20,7 @@ plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.6.0" apply false // START: FlutterFire Configuration - id "com.google.gms.google-services" version "4.3.15" apply false + id "com.google.gms.google-services" version "4.4.4" apply false // END: FlutterFire Configuration id "org.jetbrains.kotlin.android" version "2.1.0" apply false } diff --git a/pubspec.lock b/pubspec.lock index baa67012..44ac1f43 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: ff0a84a2734d9e1089f8aedd5c0af0061b82fb94e95260d943404e0ef2134b11 + sha256: e4a1b612fd2955908e26116075b3a4baf10c353418ca645b4deae231c82bf144 url: "https://pub.dev" source: hosted - version: "1.3.59" + version: "1.3.65" analyzer: dependency: transitive description: @@ -477,10 +477,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: "7be63a3f841fc9663342f7f3a011a42aef6a61066943c90b1c434d79d5c995c5" + sha256: "29cfa93c771d8105484acac340b5ea0835be371672c91405a300303986f4eba9" url: "https://pub.dev" source: hosted - version: "3.15.2" + version: "4.3.0" firebase_core_platform_interface: dependency: transitive description: @@ -493,34 +493,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: "0ed0dc292e8f9ac50992e2394e9d336a0275b6ae400d64163fdf0a8a8b556c37" + sha256: a631bbfbfa26963d68046aed949df80b228964020e9155b086eff94f462bbf1f url: "https://pub.dev" source: hosted - version: "2.24.1" + version: "3.3.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "60be38574f8b5658e2f22b7e311ff2064bea835c248424a383783464e8e02fcc" + sha256: "1ad663fbb6758acec09d7e84a2e6478265f0a517f40ef77c573efd5e0089f400" url: "https://pub.dev" source: hosted - version: "15.2.10" + version: "16.1.0" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "685e1771b3d1f9c8502771ccc9f91485b376ffe16d553533f335b9183ea99754" + sha256: ea620e841fbcec62a96984295fc628f53ef5a8da4f53238159719ed0af7db834 url: "https://pub.dev" source: hosted - version: "4.6.10" + version: "4.7.5" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: "0d1be17bc89ed3ff5001789c92df678b2e963a51b6fa2bdb467532cc9dbed390" + sha256: "7d0fb6256202515bba8489a3d69c6bc9d52d69a4999bad789053b486c8e7323e" url: "https://pub.dev" source: hosted - version: "3.10.10" + version: "4.1.1" fixnum: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3be5deb3..458bff4f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -101,8 +101,8 @@ dependencies: diacritic: ^0.1.6 # Firebase (not supported on Linux - use conditional imports in code) - firebase_core: ^3.8.0 - firebase_messaging: ^15.1.4 + firebase_core: ^4.3.0 + firebase_messaging: ^16.1.0 dev_dependencies: From 5459e8338dfa5677ce9a56ed9a6812156845289d Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Mon, 29 Dec 2025 16:08:51 -0300 Subject: [PATCH 5/7] Update mostro-push-server repository URL to organization account --- docs/FCM_IMPLEMENTATION.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/FCM_IMPLEMENTATION.md b/docs/FCM_IMPLEMENTATION.md index 66d7c9ea..e0c1b9cc 100644 --- a/docs/FCM_IMPLEMENTATION.md +++ b/docs/FCM_IMPLEMENTATION.md @@ -7,7 +7,7 @@ This document outlines the implementation of Firebase Cloud Messaging (FCM) for - **Silent push notifications** wake up the app without exposing message content - **All message decryption happens locally** on the device - **Minimal metadata** is exposed to Firebase/Google services -- **Custom notification server** ([mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server)) handles token management and notification delivery +- **Custom notification server** ([mostro-push-server](https://github.com/MostroP2P/mostro-push-server)) handles token management and notification delivery ### Why a Custom Server? @@ -18,7 +18,7 @@ The initial approach was to use **Firebase Cloud Functions** as the notification - ❌ **Unreliable for real-time:** Not suitable for monitoring relay events in real-time - ❌ **Complexity:** Additional overhead for managing function deployments and monitoring -The custom server approach ([mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server)) provides: +The custom server approach ([mostro-push-server](https://github.com/MostroP2P/mostro-push-server)) provides: - ✅ **Persistent WebSocket connections** to Nostr relays for real-time event monitoring - ✅ **Stable and reliable** notification delivery @@ -302,7 +302,7 @@ encrypted_token = ephemeral_pubkey || nonce || ciphertext ### Related Documentation - `docs/FIREBASE_LINUX_NOTE.md` - Linux compatibility notes - [MIP-05 specification](https://github.com/marmot-protocol/marmot/pull/18) - Privacy-preserving push notifications approach -- [mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server) - Custom notification server repository +- [mostro-push-server](https://github.com/MostroP2P/mostro-push-server) - Custom notification server repository - [Firebase Cloud Messaging documentation](https://firebase.google.com/docs/cloud-messaging) - [NIP-59 (Gift Wrap)](https://github.com/nostr-protocol/nips/blob/master/59.md) - Nostr event encryption standard @@ -365,7 +365,7 @@ The notification server is maintained in a **separate repository** and handles: ## References - [MIP-05: Marmot Push Notifications](https://github.com/marmot-protocol/marmot/pull/18) - Privacy-preserving push notification approach -- [mostro-push-server](https://github.com/AndreaDiazCorreia/mostro-push-server) - Custom notification server repository +- [mostro-push-server](https://github.com/MostroP2P/mostro-push-server) - Custom notification server repository - [Firebase Cloud Messaging Documentation](https://firebase.google.com/docs/cloud-messaging) - [NIP-59: Gift Wrap](https://github.com/nostr-protocol/nips/blob/master/59.md) - Nostr event encryption - [ChaCha20-Poly1305 AEAD](https://datatracker.ietf.org/doc/html/rfc8439) - Authenticated encryption From 3cf0ce8c3e578b9065bf062c55a8a83e5c961ef0 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Mon, 29 Dec 2025 16:10:30 -0300 Subject: [PATCH 6/7] Update FCM implementation documentation with phase completion status --- docs/FCM_IMPLEMENTATION.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/FCM_IMPLEMENTATION.md b/docs/FCM_IMPLEMENTATION.md index e0c1b9cc..c070ec1a 100644 --- a/docs/FCM_IMPLEMENTATION.md +++ b/docs/FCM_IMPLEMENTATION.md @@ -91,7 +91,7 @@ The implementation is divided into multiple phases (Pull Requests) to facilitate --- -## Phase 1: Firebase Basic Configuration ✅ +## Phase 1: Firebase Basic Configuration ✅ COMPLETE **Branch:** `feature/firebase-fcm-setup` @@ -123,7 +123,7 @@ The implementation is divided into multiple phases (Pull Requests) to facilitate --- -## Phase 2: FCM Service Implementation +## Phase 2: FCM Service Implementation ⚠️ TO IMPLEMENT **Branch:** `feature/fcm-service` (to be created from `main` after Phase 1 merge) @@ -181,7 +181,7 @@ Future firebaseMessagingBackgroundHandler(RemoteMessage message) async { --- -## Phase 3: Push Notification Service with Encryption +## Phase 3: Push Notification Service with Encryption ⚠️ TO IMPLEMENT **Branch:** `feature/push-notification-service` (to be created from `main` after Phase 2 merge) @@ -244,7 +244,7 @@ encrypted_token = ephemeral_pubkey || nonce || ciphertext --- -## Phase 4: User Settings and Opt-Out +## Phase 4: User Settings and Opt-Out ⚠️ TO IMPLEMENT **Branch:** `feature/notification-settings` (to be created from `main` after Phase 3 merge) From 27e6cbad2cbb774cdef88c6c9bd2fe44eae451f0 Mon Sep 17 00:00:00 2001 From: Andrea Diaz Correia Date: Mon, 29 Dec 2025 16:15:06 -0300 Subject: [PATCH 7/7] Add detailed MIP-05 implementation comparison and privacy analysis --- docs/FCM_IMPLEMENTATION.md | 131 ++++++++++++++++++++++++++++++++++--- 1 file changed, 123 insertions(+), 8 deletions(-) diff --git a/docs/FCM_IMPLEMENTATION.md b/docs/FCM_IMPLEMENTATION.md index c070ec1a..3a57e310 100644 --- a/docs/FCM_IMPLEMENTATION.md +++ b/docs/FCM_IMPLEMENTATION.md @@ -1,13 +1,128 @@ # FCM Implementation for MostroP2P Mobile -## Overview - -This document outlines the implementation of Firebase Cloud Messaging (FCM) for push notifications in the MostroP2P mobile application. The implementation follows a privacy-preserving approach similar to **[MIP-05 (Marmot Push Notifications)](https://github.com/marmot-protocol/marmot/pull/18)**, where: - -- **Silent push notifications** wake up the app without exposing message content -- **All message decryption happens locally** on the device -- **Minimal metadata** is exposed to Firebase/Google services -- **Custom notification server** ([mostro-push-server](https://github.com/MostroP2P/mostro-push-server)) handles token management and notification delivery +## MIP-05 Implementation Overview + +This implementation follows the **MIP-05 (Marmot Push Notifications)** specification for privacy-preserving push notifications. Below is an overview of which aspects are being implemented and why. + +### ✅ Implemented Aspects + +#### 1. Silent Push Notifications +**What:** FCM sends empty notifications with no message content +**Why:** Prevents Firebase/Google from learning message content, sender identity, or group membership +**Implementation:** +- FCM notifications contain only `content-available` flag +- App fetches and decrypts actual messages from Nostr relays when awakened + +#### 2. Encrypted Token Registration +**What:** Device tokens are encrypted using ChaCha20-Poly1305 with ECDH key derivation +**Why:** Prevents the notification server from linking tokens to user identities or correlating across groups +**Implementation:** +- Generate ephemeral secp256k1 keypair per token +- Derive encryption key via ECDH + HKDF (salt: "mostro-fcm-v1", info: "mostro-token-encryption") +- Encrypt with random 12-byte nonce for probabilistic encryption +- Token format: `ephemeral_pubkey || nonce || ciphertext` (280 bytes total) + +#### 3. Payload Padding +**What:** All encrypted tokens padded to uniform 220 bytes before encryption +**Why:** Prevents group members from inferring platforms (APNs vs FCM) from token sizes +**Implementation:** +- Format: `platform_byte (1) || token_length (2) || device_token || random_padding` +- Platform bytes: 0x01 = APNs, 0x02 = FCM +- Ensures uniform 280-byte encrypted tokens + +#### 4. Custom Notification Server +**What:** Dedicated server (mostro-push-server) handles token registration and FCM delivery +**Why:** Provides persistent WebSocket connections to Nostr relays, avoiding Cloud Functions limitations +**Implementation:** +- Server monitors Nostr relays for new events +- Decrypts tokens and sends silent push notifications via FCM +- State-minimized design with no persistent token storage + +### ⚠️ Partially Implemented / Simplified Aspects + +#### 5. Token Distribution (Simplified) +**MIP-05 Approach:** Gossip-based protocol with kind 447/448/449 events in MLS groups +**MostroP2P Approach:** Direct registration with notification server +**Why Simplified:** MostroP2P uses a different architecture (not MLS-based groups) +**Implementation:** +- Device registers encrypted token directly with notification server +- Server maintains token list for each user +- No group-based token sharing required + +#### 6. Gift-Wrapped Notification Triggers (Simplified) +**MIP-05 Approach:** NIP-59 gift-wrapped events to trigger notifications +**MostroP2P Approach:** Server monitors Nostr relays directly for relevant events +**Why Simplified:** MostroP2P has a simpler event model that doesn't require gift wrapping +**Implementation:** +- Server monitors relays for new trade events +- No need for encrypted event wrapping in MostroP2P context + +### ❌ Not Implemented Aspects + +#### 7. MLS Group Integration +**Reason:** MostroP2P doesn't use MLS (Message Layer Security) for group management +**Alternative:** MostroP2P uses direct user-to-user trades and simple group concepts + +#### 8. Multi-Server Token Management +**Reason:** MostroP2P uses a single notification server (mostro-push-server) +**Alternative:** Simplified architecture with single server reduces complexity + +#### 9. Tor Support +**Reason:** Not prioritized for initial implementation +**Future:** Could be added as an optional privacy enhancement + +#### 10. Decoy Tokens +**Reason:** MostroP2P's trade model doesn't require group size obfuscation +**Alternative:** Privacy is maintained through other means (encrypted tokens, silent push) + +### 🔒 Privacy Properties Maintained + +#### What Firebase/Google Learn: +- ✅ A notification occurred for a device (unavoidable with push) +- ✅ Device owner's platform identity (Google account via FCM token) + +#### What Firebase/Google CANNOT Learn: +- ✅ Message content (notifications are silent/empty) +- ✅ Sender's Nostr identity +- ✅ Recipient's Nostr identity +- ✅ Trade details or order information +- ✅ Group membership (not applicable to MostroP2P) + +#### What Notification Server Learns: +- ✅ Timing of notification events +- ✅ Encrypted device tokens (cannot decrypt to actual FCM tokens) + +#### What Notification Server CANNOT Learn: +- ✅ Message content +- ✅ Sender's Nostr identity +- ✅ Recipient's Nostr identity +- ✅ Which Nostr user owns which device token +- ✅ User IP addresses (relays observe IPs) + +### 📋 Implementation Phases + +The implementation is divided into phases to match MostroP2P's architecture while maintaining MIP-05 privacy principles: + +- **Phase 1:** Firebase basic configuration ✅ COMPLETE +- **Phase 2:** FCM service with background integration ⚠️ TO IMPLEMENT +- **Phase 3:** Encrypted token registration with server ⚠️ TO IMPLEMENT +- **Phase 4:** User settings and opt-out controls ⚠️ TO IMPLEMENT + +### 🎯 Key Differences from MIP-05 + +| Aspect | MIP-05 | MostroP2P Implementation | +|--------|--------|---------------------------| +| **Token Distribution** | Gossip protocol in MLS groups | Direct server registration | +| **Event Triggers** | Gift-wrapped NIP-59 events | Direct relay monitoring | +| **Group Model** | MLS-based encrypted groups | Simple user-to-user trades | +| **Multi-Server** | Support for multiple servers | Single dedicated server | +| **Decoys** | Required for privacy | Not needed for trade model | + +Despite these differences, the core privacy properties of MIP-05 are maintained: +- Silent push notifications +- Encrypted token registration +- Minimal metadata exposure +- User opt-out capability ### Why a Custom Server?