Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/DISPUTE_CHAT_MULTIMEDIA_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Currently the app has two completely different chat mechanisms:
---

## Phase 1: Shared Key for Dispute Chat (Protocol Change)
**Status:** `TODO`
**Status:** `DONE`
**PR scope:** Core encryption/protocol change — no UI changes

### What you can test after this phase
Expand Down
38 changes: 38 additions & 0 deletions lib/data/models/session.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class Session {
Role? role;
Peer? _peer;
NostrKeyPairs? _sharedKey;
String? _adminPubkey;
NostrKeyPairs? _adminSharedKey;

Session({
required this.masterKey,
Expand All @@ -30,6 +32,7 @@ class Session {
this.parentOrderId,
this.role,
Peer? peer,
String? adminPubkey,
}) {
_peer = peer;
if (peer != null) {
Expand All @@ -38,6 +41,9 @@ class Session {
peer.publicKey,
);
}
if (adminPubkey != null) {
setAdminPeer(adminPubkey);
}
}

Map<String, dynamic> toJson() => {
Expand All @@ -49,6 +55,7 @@ class Session {
'parent_order_id': parentOrderId,
'role': role?.value,
'peer': peer?.publicKey,
'admin_peer': _adminPubkey,
};

factory Session.fromJson(Map<String, dynamic> json) {
Expand Down Expand Up @@ -146,6 +153,19 @@ class Session {
}
}

// Parse optional admin pubkey
String? adminPubkey;
final adminPeerValue = json['admin_peer'];
if (adminPeerValue != null) {
if (adminPeerValue is String && adminPeerValue.isNotEmpty) {
adminPubkey = adminPeerValue;
} else if (adminPeerValue is! String) {
throw FormatException(
'Invalid admin_peer type: ${adminPeerValue.runtimeType}',
);
}
}

return Session(
masterKey: masterKeyValue,
tradeKey: tradeKeyValue,
Expand All @@ -156,6 +176,7 @@ class Session {
parentOrderId: parentOrderId,
role: role,
peer: peer,
adminPubkey: adminPubkey,
);
} catch (e) {
throw FormatException('Failed to parse Session from JSON: $e');
Expand All @@ -164,6 +185,23 @@ class Session {

NostrKeyPairs? get sharedKey => _sharedKey;

String? get adminPubkey => _adminPubkey;
NostrKeyPairs? get adminSharedKey => _adminSharedKey;

/// Compute and store the admin shared key via ECDH
void setAdminPeer(String adminPubkey) {
if (adminPubkey.isEmpty || adminPubkey.length != 64) {
throw ArgumentError(
'Invalid admin pubkey: expected 64-char hex, got ${adminPubkey.length} chars',
);
}
_adminPubkey = adminPubkey;
_adminSharedKey = NostrUtils.computeSharedKey(
tradeKey.private,
adminPubkey,
);
}

Peer? get peer => _peer;

set peer(Peer? newPeer) {
Expand Down
Loading