Skip to content

nostr/money: payment gift-wrap write races NIP-42 auth (SDK won't resend a rejected write) + no timeout on buyer writes #176

Description

@orveth

Summary

Two related write-path hygiene gaps found in the nostr audit (projects/mobee-seller-node/NOSTR-AUDIT.md, N-3/N-8). The sharper one is money-adjacent: the buyer's ecash gift-wrap write fires before NIP-42 auth can complete, and the SDK will not re-auth-and-resend a rejected write — so an auth-gated write racing the handshake is dropped, not retried. The neighbouring receipt write gates on auth explicitly; the payment write does not. Line cites are origin/dev-era e14942a.

N-3 — payment gift-wrap does not wait for NIP-42 auth (money, VERIFY-NEEDED trigger)

payment_send.rs:207-216 (send_payment, the buyer→seller kind-1059 payment):

let client = Client::new(self.keys.clone());
client.add_relay(&self.relay).await?;
client.connect().await;                       // no wait_for_connection
let output = client.send_event_to([&self.relay], &gift_wrap).await;   // fired immediately
client.disconnect().await;
  • Auto-auth is on by default (nostr-relay-pool 0.44.1 pool/options.rs:21), so the client will answer a challenge — but asynchronously, on the inbound RelayMessage::Auth path (relay/inner.rs:1057-1064).
  • wait_for_ok returns the first OK for the event verbatim and does not re-authenticate-and-resend on an auth-required rejection (relay/inner.rs:1379-1415).
  • So if the EVENT is processed before the auth round-trip finishes, an auth-gated write is rejected and dropped; disconnect() follows immediately.

Contrast the receipt write (authorize_pay.rs:697-757), which subscribes notifications before connect, wait_for_connection(20s), and gates the send on Authenticated (:726), failing closed otherwise — because "mobee-relay requires NIP-42 AUTH for ALL writes" (:675).

The payment path works in the field (25+ paid), so the race usually wins. VERIFY-NEEDED: does mobee-relay auth-gate kind-1059 writes (not just the p-gated read)? If yes, this is a latent silent payment-write drop under connection latency. Fix: mirror the receipt path's wait_for_nip42_auth gate on the payment write (and set automatic_authentication(true) explicitly, as the receipt/seller paths do, to guard against option drift).

N-8 — no timeout on buyer write awaits

client.connect() + send_event_to have no per-call timeout in publish_draft_async (job_lifecycle.rs:1444-1447), payment_send (:211-215), and profile (:264/:318) — only fetch_events carries one. A hung relay socket blocks the MCP/CLI call indefinitely. The buzz presence loop's send_event (buzz.rs:439) is in the same un-timed class. This is the buyer-side analogue of the seller heartbeat/publish park in #173 (which covers the seller loop); authorize_pay already demonstrates the fix pattern (wait_for_connection(20s) + auth timeout) — it just is not applied uniformly.

Read-only audit; no code changed. Cross-ref #173 (seller-loop park), and the shared write path this would naturally live in under the buyer-client-consolidation issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions