Skip to content

refactor: migrate gift-wrap to mostro-core 0.9.1 nip59 module#164

Merged
grunch merged 2 commits intomainfrom
migrate/mostro-core-0.9.1-nip59
Apr 24, 2026
Merged

refactor: migrate gift-wrap to mostro-core 0.9.1 nip59 module#164
grunch merged 2 commits intomainfrom
migrate/mostro-core-0.9.1-nip59

Conversation

@grunch
Copy link
Copy Markdown
Member

@grunch grunch commented Apr 23, 2026

Summary

  • Bumps mostro-core from 0.6.56 → 0.9.1 and nostr-sdk from 0.43.0 → 0.44.1.
  • Replaces the local NIP-59 pipeline (three duplicated helpers + hand-rolled request-ID matching) with the centralized wrap_message / unwrap_message / validate_response API from mostro_core::nip59.
  • Admin flows now sign the whole pipeline (rumor + seal + inner tuple) with admin_keys. The previous hybrid (admin seal + trade-keys rumor) is not valid under nostr-sdk 0.44, which enforces rumor.pubkey == seal.pubkey (SenderMismatch).
  • Net diff: -124 lines, 24 files touched; 83 tests passing including 6 new ones around the wrap→parse handshake.

Key changes

src/util/messaging.rs — deletes gift_wrap_from_seal_with_pow, create_gift_wrap_event, send_gift_wrap_dm{,_internal}, send_admin_gift_wrap_dm, determine_message_type, create_expiration_tags, and the now-obsolete MessageType enum. send_dm becomes a thin wrapper around wrap_message, with a single signer_keys parameter (identity_keys dropped). print_dm_events uses validate_response with a single documented exception: NewOrder pushes without a request_id (child orders from range trades) are still printed because mostro-core intentionally leaves NewOrder out of the unsolicited-push allow-list.

src/parser/dms.rs — the Kind::GiftWrap branch of parse_dm_events now calls unwrap_message, distinguishing Ok(None) ("not addressed to us") from Err (protocol violation). No more direct calls to nip59::extract_rumor or (Message, Option<String>) tuple deserialization.

Admin signer changestorage::admin_send_dm and take_dispute::execute_take_dispute now pass admin_keys (not ctx.trade_keys) as the signer. This is the semantically correct migration: Mostro identifies admin via rumor.pubkey = admin_keys.pubkey, matching how send_admin_gift_wrap_dm already worked for plain admin DMs.

Out of scope (unchanged):

  • NIP-17 PrivateDirectMessage (kind 14, SECRET/to_user=true branch).
  • Per-dispute shared-key chat transport (send_admin_chat_message_via_shared_key, build_custom_wrap_event, …) — not NIP-59 proper.

Knock-on changes from the dependency bump

  • NOSTR_REPLACEABLE_EVENT_KINDNOSTR_ORDER_EVENT_KIND (same value 38383; preserves BIP32-derived keys).
  • Timestamp::as_u64as_secs (6 sites).
  • RestoredDisputesInfo gains initiator / solver_pubkey fields — test fixture updated.

Tests

Added (6):

  • messaging::tests: send_dm_gift_wrap_roundtrips_via_unwrap_message, secret_env_semantics_drop_inner_signature, wrap_message_respects_pow_option, wrong_keys_yield_none_on_unwrap.
  • tests/parser_dms.rs: parse_dm_events_accepts_wrap_message_output, parse_dm_events_skips_events_for_other_keys.

Total: 83/83 passing. cargo fmt --all clean. cargo clippy --all-targets --all-features -- -D warnings clean (matches the release pre-hook).

Test plan

  • cargo fmt --all
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-targets (83 passing)
  • Smoke test against a live Mostro node: new_ordertake_sell/take_buyadd_invoicefiat_sentreleaserate.
  • Range-order child flow: place a ranged order, take part of it, release, verify the auto-spawned NewOrder still prints.
  • Admin smoke test (ADMIN_NSEC set): adm_send_dm, adm_add_solver, adm_take_dispute, adm_cancel, adm_settle.
  • restore and get_last_trade_index against a user with prior history.

Closes #163

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Updated nostr-sdk to version 0.44.1 and mostro-core to version 0.9.1.
  • Tests

    • Enhanced test coverage for direct message parsing and gift wrap decoding scenarios.

Bump mostro-core to 0.9.1 and nostr-sdk to 0.44.1, then replace the
local NIP-59 implementation with the centralized
`wrap_message` / `unwrap_message` / `validate_response` API exposed
by `mostro_core::nip59`.

- send_dm now delegates to wrap_message; identity_keys parameter is
  dropped because nostr-sdk 0.44 enforces rumor.pubkey == seal.pubkey
  (SenderMismatch). The single signer drives rumor authorship, seal
  signing, and the inner tuple signature.
- Admin flows (storage::admin_send_dm, take_dispute) now sign the
  whole pipeline with admin_keys, aligning with how
  send_admin_gift_wrap_dm already worked for plain DMs.
- parse_dm_events uses unwrap_message; print_dm_events uses
  validate_response, with one documented exception for `NewOrder`
  pushes that arrive without a request_id (range-trade child).
- Drops gift_wrap_from_seal_with_pow, create_gift_wrap_event,
  send_gift_wrap_dm{,_internal}, send_admin_gift_wrap_dm,
  determine_message_type, create_expiration_tags, and the
  MessageType enum, all subsumed by the new API.
- NIP-17 `PrivateDirectMessage` (kind 14) and the per-dispute
  shared-key chat transport are out of scope and left untouched.

Knock-on changes from the dependency bump: rename
NOSTR_REPLACEABLE_EVENT_KIND to NOSTR_ORDER_EVENT_KIND (same value
38383, preserves derived BIP32 keys), Timestamp::as_u64 →
as_secs, and add the new `initiator` / `solver_pubkey` fields
to RestoredDisputesInfo in tests.

Tests: 4 new wiring unit tests in messaging.rs (wrap→unwrap
roundtrip, SECRET semantics, PoW, wrong-keys → None) and 2 new
integration tests in tests/parser_dms.rs covering the
wrap_message → parse_dm_events handshake.

Closes #163

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

Walkthrough

The PR migrates gift-wrap DM handling to use mostro-core 0.9.1's centralized wrap_message/unwrap_message/validate_response APIs. Dependencies are bumped (mostro-core 0.6.56→0.9.1, nostr-sdk 0.43.0→0.44.1), and local gift-wrap construction/unwrapping logic is replaced. Key derivation paths updated to use NOSTR_ORDER_EVENT_KIND instead of NOSTR_REPLACEABLE_EVENT_KIND.

Changes

Cohort / File(s) Summary
Dependency Updates
Cargo.toml
Bump nostr-sdk from 0.43.0 to 0.44.1 and mostro-core from 0.6.56 to 0.9.1, maintaining existing feature flags.
Key Derivation Updates
src/db.rs, src/util/events.rs
Update HD wallet derivation from NOSTR_REPLACEABLE_EVENT_KIND to NOSTR_ORDER_EVENT_KIND for identity and trade key generation; adjust event filter in create_seven_days_filter accordingly.
Messaging Layer Refactoring
src/util/messaging.rs
Replace manual gift-wrap assembly with mostro_core::wrap_message; add new send_plain_text_dm for unsigned messages; refactor send_dm to parse POW/SECRET, route to_user as PrivateDirectMessage, and delegate wrapping to library; update response validation via validate_response; convert timestamps from as_u64() to as_secs(); replace gift-wrap tests with wrap_message/unwrap_message roundtrip tests.
Messaging Exports
src/util/mod.rs, src/util/types.rs
Remove exports of send_admin_gift_wrap_dm and send_gift_wrap_dm; add export of send_plain_text_dm; delete internal MessageType enum.
Admin DM Sending
src/util/storage.rs
Update admin_send_dm to pass admin_keys directly to send_dm without ctx.trade_keys; adjust comment to reflect admin-identity binding.
Parser DM/Unwrapping
src/parser/dms.rs
Replace inline gift-wrap unwrapping with unwrap_message(dm, pubkey) helper; consolidate error handling into single warning log; update timestamp conversions from as_u64() to as_secs() for filtering and storage.
Parser Timestamp Conversions
src/parser/disputes.rs, src/parser/orders.rs
Update created_at timestamp extraction from as_u64() to as_secs(); maintain subsequent filtering/sorting behavior.
CLI Commands - Identity Keys Removal
src/cli/{add_invoice.rs, new_order.rs, orders_info.rs, rate_user.rs, send_msg.rs, take_order.rs}
Remove ctx.identity_keys parameter from send_dm calls; preserve all other message construction and response handling logic.
CLI Account-Scoped DM Operations
src/cli/{last_trade_index.rs, restore.rs}
Switch from ctx.trade_keys to ctx.identity_keys for signing/filtering; add comments explaining identity-key-based account lookup; adjust subsequent DM filtering/parsing accordingly.
CLI Admin DM and Routing Updates
src/cli/adm_send_dm.rs, src/cli/take_dispute.rs
Replace send_admin_gift_wrap_dm with send_plain_text_dm in adm_send_dm; refactor take_dispute to pass admin_keys directly and remove ctx.trade_keys from send_dm call with updated comment.
CLI Direct Message Sender Refactoring
src/cli/send_dm.rs
Refactor send_dm call signature, removing explicit Some(&trade_keys) and passing &trade_keys directly; reduce function body length.
CLI Timestamp Utility
src/cli/send_admin_dm_attach.rs
Update Blossom BUD-01 expiration tag generation to use Timestamp::from_secs(...) instead of Timestamp::from(... as_u64()); preserve 3600-second offset semantics.
Test Suite Enhancements
tests/parser_dms.rs
Add fields (initiator, solver_pubkey) to restore/dispute test payloads; introduce two new async tests verifying parse_dm_events correctly decodes wrap_message gift wraps and ignores messages addressed to other recipients.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI Command
    participant Client as nostr-sdk Client
    participant MostroCore as mostro-core 0.9.1
    participant Mostro as Mostro Node
    participant Receiver as Receiver

    Note over CLI,Receiver: Send Flow (New)
    CLI->>CLI: Prepare message payload
    CLI->>MostroCore: wrap_message(payload, WrapOptions)
    MostroCore->>MostroCore: Create rumor, seal, gift-wrap
    MostroCore-->>CLI: Wrapped GiftWrap event
    CLI->>Client: Publish GiftWrap
    Client->>Mostro: Send to relay

    Note over CLI,Receiver: Receive Flow (New)
    Mostro-->>Client: GiftWrap event
    Client-->>CLI: Received event
    CLI->>MostroCore: unwrap_message(event, receiver_keys)
    MostroCore->>MostroCore: Decrypt & extract rumor
    MostroCore-->>CLI: UnwrappedMessage
    CLI->>MostroCore: validate_response(message, request_id)
    MostroCore-->>CLI: Validation result
    CLI->>CLI: Parse & handle response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • Catrya

Poem

🐰 Hop, wrap, and seal with care,
Messages cloaked in layers fair,
mostro-core now leads the dance,
No local code—just lib's romance!
Unwraps bloom where secrets hide 🎁

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and specifically describes the main refactoring work: migrating gift-wrap functionality to mostro-core 0.9.1's NIP-59 module.
Linked Issues check ✅ Passed All primary objectives from issue #163 are addressed: dependency upgrades (mostro-core 0.9.1, nostr-sdk 0.44.1), replacement of local gift-wrap helpers with mostro_core::wrap_message, integration of unwrap_message in parse_dm_events, adoption of validate_response for request handling, and POW/SECRET/expiration semantics preserved.
Out of Scope Changes check ✅ Passed Changes are focused on the stated scope: NIP-59 gift-wrap migration, dependency updates, and related call-site adjustments. NIP-17 PrivateDirectMessage and dispute shared-key chat transport remain unchanged as specified.
Docstring Coverage ✅ Passed Docstring coverage is 89.19% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate/mostro-core-0.9.1-nip59

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0c4551d6db

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/util/messaging.rs
Comment on lines 330 to 333
pub async fn send_dm(
client: &Client,
identity_keys: Option<&Keys>,
trade_keys: &Keys,
signer_keys: &Keys,
receiver_pubkey: &PublicKey,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve identity signer for account-scoped requests

Collapsing send_dm to a single signer_keys identity removes the old ability to publish account-scoped requests with an identity sender while still using trade keys for the message flow, and the call sites for execute_restore/execute_last_trade_index now send with ctx.trade_keys. On Mostro’s request handlers, user lookup is keyed by the sender pubkey, so these requests can be treated as unknown users (or invalid trade-index state) when the trade key is not the account identity key, which breaks restore/last-trade-index recovery flows.

Useful? React with 👍 / 👎.

@grunch
Copy link
Copy Markdown
Member Author

grunch commented Apr 23, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 23, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Restore and LastTradeIndex are account-scoped queries: Mostro looks up
the user by the sender pubkey on the gift wrap, so signing with a
per-trade key leaves the request either resolving to an unknown user
or to stale trade-index state — either way, recovery flows silently
return nothing.

Revert these two call sites to `identity_keys` as the signer, and
route `wait_for_dm` / `parse_dm_events` on the same key so the
response (which Mostro addresses back to the sender pubkey) is the
one we subscribe to and can decrypt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@grunch
Copy link
Copy Markdown
Member Author

grunch commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/util/messaging.rs (1)

100-104: Reuse parse_pow_env() here to avoid duplicated POW parsing.

build_custom_wrap_event manually re-implements the same POW env parsing that now lives in parse_pow_env() (lines 288–293). Since the whole point of the new helpers is to centralize this, please call the helper to prevent future drift (e.g., differing defaults or error messages).

♻️ Proposed diff
-    // Reuse POW behaviour from existing DM helpers, but fail on invalid values
-    let pow: u8 = var("POW")
-        .unwrap_or_else(|_| "0".to_string())
-        .parse()
-        .map_err(|e| anyhow::anyhow!("Failed to parse POW: {}", e))?;
+    // Reuse POW behaviour from existing DM helpers, but fail on invalid values
+    let pow: u8 = parse_pow_env()?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/util/messaging.rs` around lines 100 - 104, In build_custom_wrap_event
replace the manual POW env parsing with a call to the existing parse_pow_env()
helper: remove the var("POW")...parse() block and call parse_pow_env() to obtain
the u8 POW value, propagating its Result/anyhow error as before; ensure you
reference parse_pow_env() return (u8) where the previous local pow variable was
used so defaults and error messages remain centralized and consistent.
tests/parser_dms.rs (1)

235-292: Solid new coverage — consider adding a signed: false variant.

The new wrap→parse handshake tests both use WrapOptions::default() (i.e. signed: true), yet the production paths that matter most (send_plain_text_dm, admin DM flows, execute_restore, execute_last_trade_index) all use signed: false. Adding one assertion exercising that knob would catch any future unwrap_message regression on the tuple-without-signature branch.

🧪 Proposed follow-up test
+#[tokio::test]
+async fn parse_dm_events_accepts_unsigned_wrap_message_output() {
+    let sender_trade_keys = Keys::generate();
+    let receiver_keys = Keys::generate();
+
+    let inner = Message::new_order(None, Some(1), Some(1), Action::NewOrder, None);
+    let wrapped = wrap_message(
+        &inner,
+        &sender_trade_keys,
+        receiver_keys.public_key(),
+        WrapOptions { signed: false, ..Default::default() },
+    )
+    .await
+    .expect("wrap");
+
+    let mut events = Events::new(&Filter::new());
+    events.insert(wrapped);
+
+    let parsed = parse_dm_events(events, &receiver_keys, None).await;
+    assert_eq!(parsed.len(), 1);
+    assert_eq!(parsed[0].2, sender_trade_keys.public_key());
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/parser_dms.rs` around lines 235 - 292, Add a companion test that
mirrors parse_dm_events_accepts_wrap_message_output but creates the wrapped
message with unsigned wrap options (use WrapOptions { signed: false } or the
equivalent constructor instead of WrapOptions::default()) so parse_dm_events is
exercised for the unsigned branch; locate the test using wrap_message and
WrapOptions in parse_dm_events_accepts_wrap_message_output and replicate the
same assertions (message roundtrip and sender public key) while passing the
receiver keys to parse_dm_events as before.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/util/messaging.rs`:
- Around line 100-104: In build_custom_wrap_event replace the manual POW env
parsing with a call to the existing parse_pow_env() helper: remove the
var("POW")...parse() block and call parse_pow_env() to obtain the u8 POW value,
propagating its Result/anyhow error as before; ensure you reference
parse_pow_env() return (u8) where the previous local pow variable was used so
defaults and error messages remain centralized and consistent.

In `@tests/parser_dms.rs`:
- Around line 235-292: Add a companion test that mirrors
parse_dm_events_accepts_wrap_message_output but creates the wrapped message with
unsigned wrap options (use WrapOptions { signed: false } or the equivalent
constructor instead of WrapOptions::default()) so parse_dm_events is exercised
for the unsigned branch; locate the test using wrap_message and WrapOptions in
parse_dm_events_accepts_wrap_message_output and replicate the same assertions
(message roundtrip and sender public key) while passing the receiver keys to
parse_dm_events as before.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b48da61e-a8a6-4f0f-9a29-fd12f1d8e6e9

📥 Commits

Reviewing files that changed from the base of the PR and between 92e5e02 and 2fab244.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • Cargo.toml
  • src/cli/add_invoice.rs
  • src/cli/adm_send_dm.rs
  • src/cli/last_trade_index.rs
  • src/cli/new_order.rs
  • src/cli/orders_info.rs
  • src/cli/rate_user.rs
  • src/cli/restore.rs
  • src/cli/send_admin_dm_attach.rs
  • src/cli/send_dm.rs
  • src/cli/send_msg.rs
  • src/cli/take_dispute.rs
  • src/cli/take_order.rs
  • src/db.rs
  • src/parser/disputes.rs
  • src/parser/dms.rs
  • src/parser/orders.rs
  • src/util/events.rs
  • src/util/messaging.rs
  • src/util/mod.rs
  • src/util/storage.rs
  • src/util/types.rs
  • tests/parser_dms.rs
💤 Files with no reviewable changes (7)
  • src/cli/rate_user.rs
  • src/cli/orders_info.rs
  • src/cli/take_order.rs
  • src/cli/send_msg.rs
  • src/util/types.rs
  • src/cli/new_order.rs
  • src/cli/add_invoice.rs

@grunch grunch merged commit e30a1d5 into main Apr 24, 2026
6 checks passed
@grunch grunch deleted the migrate/mostro-core-0.9.1-nip59 branch April 24, 2026 00:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Migrate gift wrap handling to mostro-core 0.9.1 centralized API (wrap_message / unwrap_message / validate_response)

1 participant