You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: feat(getdmuser): show shared-key DMs (identity + admin) in getdmuser - Derive shared key from (trade_keys, identity_keys) and fetch/unwrap gift wraps so DMs sent to the user's identity appear in getdmuser. - Also derive (trade_keys, mostro_pubkey) and fetch so admin replies from the send_admin_dm_attach flow are shown. - Reuse derive_shared_key_bytes from util/messaging (same ECDH as send_admin_dm_attach). No longer use get_all_trade_and_counterparty_keys; counterparty_pubkey is not used in this setup.
-`misc`: small helpers such as `get_mcli_path` and string utilities.
36
36
-`net`: Nostr network connection setup.
37
37
-`storage`: thin storage helpers for orders and DMs.
38
38
-`types`: small shared enums/wrappers.
39
-
- Re-exports commonly used symbols (`create_filter`, `send_dm`, `connect_nostr`, `save_order`, etc.) so other modules can import from `crate::util` directly.
39
+
- Re-exports commonly used symbols (`create_filter`, `send_dm`, `connect_nostr`, `save_order`, **`derive_shared_keys`, `derive_shared_key_hex`, `keys_from_shared_hex`, `send_admin_chat_message_via_shared_key`**, etc.) so other modules can import from `crate::util` directly.
- Provides `new`, `insert_db`, `update_db`, fluent setters, `save`, `save_new_id`, `get_by_id`, `get_all_trade_keys`, **`get_all_trade_and_counterparty_keys`** (distinct `(trade_keys, counterparty_pubkey)` pairs for orders where both are set), and `delete_by_id`.
72
72
- See `database.md` for schema details.
73
73
74
74
### Parsers and protocol types
@@ -82,6 +82,10 @@ This document describes the internal structure of `mostro-cli`, how major module
-**Sending**: `derive_shared_keys(local_keys, counterparty_pubkey)` yields a `Keys` whose public key is used as the NIP-59 gift-wrap recipient; inner content is a signed text note encrypted with NIP-44 to that pubkey. Used by `dmtouser` and `sendadmindmattach`.
87
+
-**Receiving**: `unwrap_giftwrap_with_shared_key(shared_keys, event)` decrypts with NIP-44 and returns `(content, timestamp, sender_pubkey)`; `fetch_gift_wraps_for_shared_key(client, shared_keys)` fetches Kind::GiftWrap events with `#p` = shared key pubkey and unwraps them. Use when implementing flows that read shared-key DMs.
Copy file name to clipboardExpand all lines: docs/commands.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -110,10 +110,10 @@ All commands are part of the `Commands` enum and are dispatched via `Commands::r
110
110
-**Handler**: `execute_send_dm(PublicKey::from_str(pubkey)?, ctx, order_id, &msg)` in `src/cli/send_dm.rs`.
111
111
112
112
-**`dmtouser`**
113
-
-**Description**: Send a gift-wrapped direct message to a user.
113
+
-**Description**: Send a direct message to a user via a **shared-key custom wrap**. Derives an ECDH shared key from the order’s trade keys and the recipient pubkey; the message is sent as a NIP-59 gift wrap addressed to the shared key’s public key (NIP-44 encrypted), so both sides can decrypt.
114
114
-**Args**:
115
115
-`--pubkey <NPUB/HEX>`: Recipient pubkey.
116
-
-`--order-id <UUID>`: Order id to derive ephemeral keys.
116
+
-`--order-id <UUID>`: Order id to derive trade keys and shared key.
117
117
-`--message <STRING>...`: Message parts; joined with spaces.
118
118
-**Handler**: `execute_dm_to_user(PublicKey::from_str(pubkey)?, &ctx.client, order_id, &msg, &ctx.pool)` in `src/cli/dm_to_user.rs`.
Copy file name to clipboardExpand all lines: docs/database.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -77,6 +77,8 @@ This file is created under the CLI data directory returned by `util::get_mcli_pa
77
77
- Loads a single order (and returns an error if no ID is present).
78
78
-`get_all_trade_keys(pool)`:
79
79
- Returns distinct non-null `trade_keys` for all orders.
80
+
-`get_all_trade_and_counterparty_keys(pool)`:
81
+
- Returns distinct `(trade_keys, counterparty_pubkey)` pairs for orders where both columns are non-null; useful for deriving per-order shared keys when fetching or sending shared-key DMs.
Copy file name to clipboardExpand all lines: docs/overview.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,7 @@ The CLI is heavily inspired by the Mostro backend documentation (`mostro/docs` i
7
7
### High-level responsibilities
8
8
9
9
-**Order lifecycle**: create, take, cancel, dispute, and settle orders using `mostro_core` types and the Mostro protocol.
10
-
-**Direct messaging**: send and receive Nostr DMs between users, admins, and solvers, including gift-wrapped messages and encrypted attachments.
10
+
-**Direct messaging**: send and receive Nostr DMs between users, admins, and solvers, including gift-wrapped messages, shared-key custom wraps (ECDH-derived key, NIP-44 inside NIP-59) for `dmtouser` and admin attachment DMs, and encrypted attachments.
11
11
-**Local persistence**: keep a local cache of orders and a deterministic identity in a SQLite database (`mcli.db`) under the CLI data directory.
12
12
-**Admin / solver tooling**: expose admin-only and solver-only flows (e.g. taking disputes, adding solvers, admin DMs) when run with the proper keys.
Once the encrypted blob is uploaded and we have `blossom_url`, the DM payload is built as JSON (`type`, `blossom_url`, `nonce`, `mime_type`, sizes, `filename`). This content is then sent using **shared-key custom wrap** (same pattern as `dmtouser`):
411
326
412
-
-**Outer GiftWrap event** (NIP‑59):
413
-
- Created via `EventBuilder::gift_wrap(&trade_keys, &receiver, rumor, Tags::new())`.
-**Tags**: currently empty (no extra expiration here; optional).
327
+
-**Shared key**: The same ECDH shared key used for file encryption (trade keys + admin pubkey) is turned into a `Keys` via `Keys::new(SecretKey::from_slice(&shared_key)?)`.
328
+
-**Send**: `send_admin_chat_message_via_shared_key(&ctx.client, &trade_keys, &shared_keys, &content)` in `src/util/messaging.rs`:
329
+
- Builds an inner text-note event (sender = trade keys), signs it, encrypts it with NIP-44 to the **shared key’s public key**, and wraps it in a NIP-59 GiftWrap event tagged with that pubkey (`#p`).
330
+
- Both the sender (trade keys) and the admin (who can derive the same shared key) can later fetch and decrypt the event by filtering GiftWrap by the shared key pubkey and using `unwrap_giftwrap_with_shared_key`.
417
331
418
-
-**Relaying**:
419
-
- Final event is sent with `ctx.client.send_event(&event).await`.
420
-
-`ctx.client` is connected to configured Nostr relays via `RELAYS` env var.
332
+
So the attachment metadata is not sent as a plain NIP-59 gift wrap to the admin pubkey; it is sent to the **shared key’s public key**, enabling symmetric decryption for both parties. Relaying is via `ctx.client.send_event(&event)`.
421
333
422
334
### 6. Keys and protocols summary
423
335
424
336
-**Keys**:
425
337
-`trade_keys` (per‑order):
426
338
- Used for:
427
-
- ECDH shared secret with admin pubkey for file encryption.
428
-
- Nostr DM identity for the rumor + giftwrap.
429
-
- (Indirectly) for signing Blossom auth event (kind 24242).
339
+
- ECDH shared secret with admin pubkey (for file encryption and for the shared-key DM).
340
+
- Nostr identity for the inner text note and for signing the outer NIP‑59 wrap.
341
+
- Signing the Blossom auth event (kind 24242).
342
+
-**Shared key** (ECDH from trade_keys + admin pubkey):
343
+
- Same 32-byte secret used for ChaCha20‑Poly1305 file encryption.
344
+
- Wrapped as `Keys` and used as the **recipient** of the DM: the NIP-59 GiftWrap is addressed to the shared key’s public key, and the inner content is NIP-44 encrypted to it, so both sender and admin can derive the key and decrypt.
430
345
-`receiver`:
431
-
- Admin / solver Nostr pubkey; DM destination for the final NIP‑59 GiftWrap.
346
+
- Admin / solver Nostr pubkey; used to derive the shared key and as the human-facing destination (the actual Nostr event recipient is the shared key pubkey).
432
347
433
348
-**Protocols**:
434
349
-**ChaCha20‑Poly1305**:
@@ -441,16 +356,15 @@ println!("✅ Encrypted attachment sent successfully to admin!");
- NIP‑40: optional expiration (not used on the DM here).
446
-
- NIP‑59: GiftWrap envelope:
447
-
- Wraps the text‑note rumor for the admin’s pubkey.
359
+
- NIP‑13: optional POW on the inner text note.
360
+
- NIP‑44: encryption of the inner event to the shared key’s public key.
361
+
- NIP‑59: GiftWrap envelope addressed to the **shared key pubkey** (not directly to the admin), so both parties that know the ECDH secret can fetch and decrypt.
448
362
449
363
End‑to‑end, `sendadmindmattach`:
450
364
451
-
1. Derives a shared ECDH key between trade and admin.
452
-
2. Encrypts the file with ChaCha20‑Poly1305.
365
+
1. Derives a shared ECDH key between trade keys and admin pubkey.
366
+
2. Encrypts the file with ChaCha20‑Poly1305 using that key.
453
367
3. Authenticates to Blossom with a kind‑24242 auth event (BUD‑01) and uploads the encrypted blob (BUD‑02).
454
-
4. Builds a Mostro DM payload with Blossom URL + crypto metadata.
455
-
5. Sends a NIP‑59 gift‑wrapped text note from the trade keys to the admin pubkey with that payload as content.
368
+
4. Builds a Mostro DM payload JSON with Blossom URL and crypto metadata.
369
+
5. Sends a **shared-key custom wrap** (NIP-44 inner content, NIP-59 GiftWrap addressed to the shared key’s public key) via `send_admin_chat_message_via_shared_key`, so both the sender and the admin can decrypt the attachment metadata and fetch the blob.
0 commit comments