Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 19 additions & 1 deletion lib/data/models/restore_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -65,27 +65,45 @@ class RestoredDispute {
final String orderId;
final int tradeIndex;
final String status;
final String? initiator;

RestoredDispute({
required this.disputeId,
required this.orderId,
required this.tradeIndex,
required this.status,
this.initiator,
});

factory RestoredDispute.fromJson(Map<String, dynamic> json) {
final rawInitiator = json['initiator'] as String?;
final normalizedInitiator = _normalizeInitiator(rawInitiator);

return RestoredDispute(
disputeId: json['dispute_id'] as String,
orderId: json['order_id'] as String,
tradeIndex: json['trade_index'] as int,
status: json['status'] as String,
initiator: normalizedInitiator,
);
}

static String? _normalizeInitiator(String? value) {
if (value == null) return null;

final normalized = value.trim().toLowerCase();
if (normalized == 'buyer' || normalized == 'seller') {
return normalized;
}

return null;
}

Map<String, dynamic> toJson() => {
'dispute_id': disputeId,
'order_id': orderId,
'trade_index': tradeIndex,
'status': status,
if (initiator != null) 'initiator': initiator,
};
}
}
67 changes: 8 additions & 59 deletions lib/features/restore/restore_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -400,47 +400,6 @@ class RestoreService {
}
}

/// Determines if the user initiated the dispute with double verification
///
/// Security checks:
/// 1. Verify session belongs to this order (compare pubkeys based on role)
/// 2. Compare trade_index to determine who initiated the dispute
///
/// The dispute's trade_index indicates which party initiated it.
/// If it matches the user's session trade_index, the user initiated the dispute.
bool _determineIfUserInitiatedDispute({
required RestoredDispute restoredDispute,
required Session session,
required Order order,
}) {
// Security verification: ensure session's trade pubkey matches order's pubkey for the role
final sessionPubkey = session.tradeKey.public;
final sessionRole = session.role;

bool sessionMatchesOrder = false;
if (sessionRole == Role.buyer && order.buyerTradePubkey == sessionPubkey) {
sessionMatchesOrder = true;
} else if (sessionRole == Role.seller &&
order.sellerTradePubkey == sessionPubkey) {
sessionMatchesOrder = true;
}

if (!sessionMatchesOrder) {
logger.w('Restore: session pubkey mismatch for order ${order.id} - '
'session role: $sessionRole, session pubkey: $sessionPubkey, '
'buyer pubkey: ${order.buyerTradePubkey}, seller pubkey: ${order.sellerTradePubkey}');
// Default to peer-initiated if we can't verify session belongs to order
return false;
}

// Compare trade indexes: if dispute trade_index matches user's session trade_index,
// then the user initiated the dispute
final userInitiated = restoredDispute.tradeIndex == session.keyIndex;

//TODO: Improve dispute initiation detection if protocol changes in future
return userInitiated;
}

/// Maps Status to the appropriate Action for restored orders
Action _getActionFromStatus(Status status, Role? userRole) {
switch (status) {
Expand Down Expand Up @@ -627,24 +586,14 @@ class RestoreService {
.read(sessionNotifierProvider.notifier)
.getSessionByOrderId(orderDetail.id);

// We need the session to compare trade indexes
bool userInitiated = false;
if (session == null) {
logger.w(
'Restore: no session found for disputed order ${orderDetail.id}, defaulting to peer-initiated');
action = Action.disputeInitiatedByPeer;
} else {
// Determine if user initiated with double verification TODO : improve if protocol changes
userInitiated = _determineIfUserInitiatedDispute(
restoredDispute: restoredDispute,
session: session,
order: order,
);

action = userInitiated
? Action.disputeInitiatedByYou
: Action.disputeInitiatedByPeer;
}
final userInitiated = restoredDispute.initiator != null && session?.role != null
? (session!.role == Role.buyer && restoredDispute.initiator == 'buyer') ||
(session.role == Role.seller && restoredDispute.initiator == 'seller')
: false;

action = userInitiated
? Action.disputeInitiatedByYou
: Action.disputeInitiatedByPeer;

// Create Dispute object
dispute = Dispute(
Expand Down