Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Key management #34

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6dd56f2
added KeyManager class
chebizarro Dec 20, 2024
5d7bca8
Add key manager and related providers
chebizarro Dec 21, 2024
7ce5263
added full privacy mode setting to app settings
chebizarro Dec 21, 2024
dace285
Changed SecureStorageManager to SessionManager
chebizarro Dec 21, 2024
8cd3ac8
Moved session storage/retrieval to SessionManager
chebizarro Dec 23, 2024
90b0c06
Added separate wrap, seal and rumor functions
chebizarro Dec 23, 2024
d60899d
master key and trade key integration
chebizarro Dec 23, 2024
2d53182
minor edits
chebizarro Dec 23, 2024
0144d0e
Minor fixes to session expiration
chebizarro Dec 23, 2024
a27c70b
implementation of key manager features
chebizarro Dec 24, 2024
c731b63
verified sending new orders with full privacy
chebizarro Dec 24, 2024
7d299cb
message and session serialization and retrieval
chebizarro Dec 25, 2024
3290c1f
updated mostro message to handle cant-do
chebizarro Dec 25, 2024
b3615e0
Implementation of order persistence between restarts
chebizarro Dec 25, 2024
a055be9
change order > content to order > payload
chebizarro Dec 26, 2024
e993b95
Initialize and load sessions at startup
chebizarro Dec 26, 2024
76d5971
Change rumor content to array
chebizarro Dec 26, 2024
0bb91c6
Debugging signing
chebizarro Dec 30, 2024
4240723
Added support for cant-do
chebizarro Dec 31, 2024
da5b982
add buy/sell order now working
chebizarro Jan 2, 2025
3d17946
Update lib/data/models/cant_do.dart
chebizarro Jan 3, 2025
f295e4a
should fix https://github.com/MostroP2P/mobile/pull/34#pullrequestrev…
chebizarro Jan 10, 2025
d81f1cb
Update lib/features/take_order/screens/add_lightning_invoice_screen.dart
chebizarro Jan 11, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 38 additions & 21 deletions lib/app/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,51 @@ import 'package:mostro_mobile/app/app_theme.dart';
import 'package:mostro_mobile/features/auth/providers/auth_notifier_provider.dart';
import 'package:mostro_mobile/generated/l10n.dart';
import 'package:mostro_mobile/features/auth/notifiers/auth_state.dart';
import 'package:mostro_mobile/shared/providers/app_init_provider.dart';

class MostroApp extends ConsumerWidget {
const MostroApp({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
ref.listen<AuthState>(authNotifierProvider, (previous, state) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!context.mounted) return;
if (state is AuthAuthenticated || state is AuthRegistrationSuccess) {
context.go('/');
} else if (state is AuthUnregistered || state is AuthUnauthenticated) {
context.go('/welcome');
}
});
});
final initAsyncValue = ref.watch(appInitializerProvider);

return MaterialApp.router(
title: 'Mostro',
theme: AppTheme.theme,
routerConfig: goRouter,
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
return initAsyncValue.when(
data: (_) {
ref.listen<AuthState>(authNotifierProvider, (previous, state) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!context.mounted) return;
if (state is AuthAuthenticated || state is AuthRegistrationSuccess) {
context.go('/');
} else if (state is AuthUnregistered || state is AuthUnauthenticated) {
context.go('/welcome');
}
});
});

return MaterialApp.router(
title: 'Mostro',
theme: AppTheme.theme,
routerConfig: goRouter,
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: S.delegate.supportedLocales,
);
},
loading: () => const MaterialApp(
home: Scaffold(
body: Center(child: CircularProgressIndicator()),
),
),
error: (err, stack) => MaterialApp(
home: Scaffold(
body: Center(child: Text('Initialization Error: $err')),
),
),
);
}
}
1 change: 0 additions & 1 deletion lib/app/app_routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ final goRouter = GoRouter(
routes: [
ShellRoute(
builder: (BuildContext context, GoRouterState state, Widget child) {
// Wrap the Navigator with your listener widgets
return NotificationListenerWidget(
child: NavigationListenerWidget(
child: child,
Expand Down
11 changes: 6 additions & 5 deletions lib/app/app_settings.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
class AppSettings {
final bool isFirstLaunch;
final bool fullPrivacyMode;

AppSettings(this.isFirstLaunch);
AppSettings({required this.fullPrivacyMode});

factory AppSettings.intial() => AppSettings(true);
factory AppSettings.intial() => AppSettings(fullPrivacyMode: false);

AppSettings copyWith({bool isFirstLaunch = false}) {
return AppSettings(isFirstLaunch);
AppSettings copyWith({required bool fullPrivacyMode}) {
return AppSettings(fullPrivacyMode: fullPrivacyMode);
}

}
7 changes: 2 additions & 5 deletions lib/app/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@ import 'package:flutter/foundation.dart';
class Config {
// Configuración de Nostr
static const List<String> nostrRelays = [
'ws://127.0.0.1:7000',
//'ws://127.0.0.1:7000', // localhost
//'ws://10.0.2.2:7000', // mobile emulator
'wss://relay.mostro.network',
//'ws://10.0.2.2:7000',
//'wss://relay.damus.io',
//'wss://relay.nostr.net',
// Agrega más relays aquí si es necesario
];

// hexkey de Mostro
Expand Down
21 changes: 21 additions & 0 deletions lib/data/models/cant_do.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import 'package:mostro_mobile/data/models/payload.dart';

class CantDo implements Payload {
final String cantDo;

factory CantDo.fromJson(Map<String, dynamic> json) {
return CantDo(cantDo: json['cant_do']);
}

CantDo({required this.cantDo});

@override
Map<String, dynamic> toJson() {
// TODO: implement toJson
throw UnimplementedError();
}

@override
// TODO: implement type
String get type => 'cant_do';
}
53 changes: 53 additions & 0 deletions lib/data/models/enums/storage_keys.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
enum SharedPreferencesKeys {
keyIndex('key_index'),
fullPrivacy('full_privacy');

final String value;

const SharedPreferencesKeys(this.value);

static final _valueMap = {
for (var key in SharedPreferencesKeys.values) key.value: key
};

static SharedPreferencesKeys fromString(String value) {
final key = _valueMap[value];
if (key == null) {
throw ArgumentError('Invalid Shared Preferences Key: $value');
}
return key;
}

@override
String toString() {
return value;
}
}

enum SecureStorageKeys {
masterKey('master_key'),
mnemonic('mnemonic'),
message('message-'),
sessionKey('session-');

final String value;

const SecureStorageKeys(this.value);

static final _valueMap = {
for (var key in SecureStorageKeys.values) key.value: key
};

static SecureStorageKeys fromString(String value) {
final key = _valueMap[value];
if (key == null) {
throw ArgumentError('Invalid Secure Storage Key: $value');
}
return key;
}

@override
String toString() {
return value;
}
}
28 changes: 17 additions & 11 deletions lib/data/models/mostro_message.dart
Original file line number Diff line number Diff line change
@@ -1,48 +1,54 @@
import 'dart:convert';

import 'package:mostro_mobile/app/config.dart';
import 'package:mostro_mobile/data/models/enums/action.dart';
import 'package:mostro_mobile/data/models/payload.dart';

class MostroMessage<T extends Payload> {
String? requestId;
String? id;
final Action action;
int? tradeIndex;
T? _payload;

MostroMessage({required this.action, this.requestId, T? payload})
MostroMessage({required this.action, this.id, T? payload, this.tradeIndex})
: _payload = payload;

Map<String, dynamic> toJson() {
return {
'order': {
'version': Config.mostroVersion,
'id': requestId,
'request_id': id,
'trade_index': tradeIndex,
'action': action.value,
'content': _payload?.toJson(),
'payload': _payload?.toJson(),
},
};
}

factory MostroMessage.deserialized(String data) {
try {
final decoded = jsonDecode(data);
final event = decoded as Map<String, dynamic>;
final event = decoded[0] as Map<String, dynamic>;
final order = event['order'] != null
? event['order'] as Map<String, dynamic>
: throw FormatException('Missing order object');
: event['cant-do'] != null
? event['cant-do'] as Map<String, dynamic>
: throw FormatException('Missing order object');

final action = order['action'] != null
? Action.fromString(order['action'])
: throw FormatException('Missing action field');

final content = order['content'] != null
? Payload.fromJson(event['order']['content']) as T
final payload = order['payload'] != null
? Payload.fromJson(order['payload']) as T
: null;

final tradeIndex = order['trade_index'];

return MostroMessage<T>(
action: action,
requestId: order['id'],
payload: content,
id: order['id'],
payload: payload,
tradeIndex: tradeIndex,
);
} catch (e) {
throw FormatException('Failed to deserialize MostroMessage: $e');
Expand Down
15 changes: 7 additions & 8 deletions lib/data/models/order.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Order implements Payload {
this.maxAmount,
required this.fiatAmount,
required this.paymentMethod,
this.premium = 1,
this.premium = 0,
this.masterBuyerPubkey,
this.masterSellerPubkey,
this.buyerInvoice,
Expand All @@ -51,27 +51,26 @@ class Order implements Payload {
'status': status.value,
'amount': amount,
'fiat_code': fiatCode,
'min_amount': minAmount,
'max_amount': maxAmount,
'fiat_amount': fiatAmount,
'payment_method': paymentMethod,
'premium': premium,
'created_at': createdAt,
'expires_at': expiresAt,
'buyer_token': buyerToken,
'seller_token': sellerToken,
}
};

if (id != null) data[type]['id'] = id;
if (minAmount != null) data[type]['min_amount'] = minAmount;
if (maxAmount != null) data[type]['max_amount'] = maxAmount;
if (masterBuyerPubkey != null) {
data[type]['master_buyer_pubkey'] = masterBuyerPubkey;
}
if (masterSellerPubkey != null) {
data[type]['master_seller_pubkey'] = masterSellerPubkey;
}
if (buyerInvoice != null) data[type]['buyer_invoice'] = buyerInvoice;
if (createdAt != null) data[type]['created_at'] = createdAt;
if (expiresAt != null) data[type]['expires_at'] = expiresAt;
if (buyerToken != null) data[type]['buyer_token'] = buyerToken;
if (sellerToken != null) data[type]['seller_token'] = sellerToken;

return data;
}

Expand Down
3 changes: 3 additions & 0 deletions lib/data/models/payload.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:mostro_mobile/data/models/cant_do.dart';
import 'package:mostro_mobile/data/models/order.dart';
import 'package:mostro_mobile/data/models/payment_request.dart';

Expand All @@ -10,6 +11,8 @@ abstract class Payload {
return Order.fromJson(json['order']);
} else if (json.containsKey('payment_request')) {
return PaymentRequest.fromJson(json['payment_request']);
} else if (json.containsKey('cant_do')) {
return CantDo.fromJson(json);
}
throw UnsupportedError('Unknown content type');
}
Expand Down
46 changes: 23 additions & 23 deletions lib/data/models/session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,38 @@ import 'package:dart_nostr/dart_nostr.dart';
/// Represents a User session
///
/// This class is used to store details of a user session
/// which is connected to a single Buy/Sell order and is
/// persisted for a maximum of 48hrs or until the order is
/// completed
class Session {
final String sessionId;
final NostrKeyPairs masterKey;
final NostrKeyPairs tradeKey;
final int keyIndex;
final bool fullPrivacy;
final DateTime startTime;
final NostrKeyPairs keyPair;
String? eventId;
String? orderId;

Session({
required this.sessionId,
required this.startTime,
required this.keyPair,
this.eventId,
});
Session(
{required this.masterKey,
required this.tradeKey,
required this.keyIndex,
required this.startTime,
required this.fullPrivacy,
this.orderId});

// We don't store the keys in the session files
Map<String, dynamic> toJson() => {
'sessionId': sessionId,
'startTime': startTime.toIso8601String(),
'privateKey': keyPair.private,
'eventId' : eventId,
'start_time': startTime.toIso8601String(),
'event_id': orderId,
'key_index': keyIndex,
'full_privacy': fullPrivacy,
};

factory Session.fromJson(Map<String, dynamic> json) {
return Session(
sessionId: json['sessionId'],
startTime: DateTime.parse(json['startTime']),
keyPair: NostrKeyPairs(private: json['privateKey']),
eventId: json['eventId'],
startTime: DateTime.parse(json['start_time']),
masterKey: json['master_key'],
orderId: json['event_id'],
keyIndex: json['key_index'],
tradeKey: json['trade_key'],
fullPrivacy: json['full_privacy'],
);
}

String get privateKey => keyPair.private;
String get publicKey => keyPair.public;
}
Loading