feat(nip17): support gift-wrapped private direct messages #1

Merged
padreug merged 1 commit from feat/nip17-gift-wrap-support into main 2026-05-03 15:16:29 +00:00
Owner

Summary

Adds NIP-17 (Private Direct Messages) support to the relay so that the
nostrmarket extension's refactor/nip17-messaging branch — and any
other client moving from NIP-04 to NIP-17 — can publish and subscribe
to gift-wrapped DMs.

The relay already accepted kind 1059 events as regular kinds (range
1000–9999) and the #p filter already worked. The remaining gap was
privacy gating under AUTH: the existing _is_direct_message_for_other
hook restricted delivery of NIP-04 kind-4 events to the authenticated
recipient, but did nothing for kind 1059. This PR generalizes that hook
so the same opt-in AUTH gate covers gift wraps.

Changes

  • relay/event.py — add is_seal (kind 13), is_gift_wrap (kind 1059),
    and is_private_message helpers
  • relay/client_connection.py — rename _is_direct_message_for_other
    _is_private_event_for_other; key off is_private_message so kinds 4
    and 1059 share the same recipient-only delivery rule when AUTH is
    required for that kind
  • relay/relay.py — advertise NIPs 17, 44, 59 in NIP-11 supported_nips
  • README.md — document NIP-17/44/59 transport-level support
  • tests/test_nip17.py — unit tests for kind classification, AUTH-gated
    1059 delivery (recipient vs non-recipient vs unauthenticated), and a
    regression check for kind 4 gating

Design notes

  • No automatic gating. Like the existing NIP-04 behaviour, the
    recipient-only rule is opt-in via forcedAuthEvents / requireAuthEvents.
    Surveyed reference relays (strfry, khatru, nostream) do not enforce
    recipient gating for kind 1059 at all — they rely entirely on
    client-side encryption. Our config already supports per-kind AUTH, so
    this PR keeps that capability and extends it cleanly to 1059.
  • No created_at exemption for gift wraps. NIP-59 backdates wraps up
    to 2 days. Our createdAtSecondsPast defaults to 0 (no limit), so
    gift wraps work out of the box. Tracked separately for operators who
    configure tighter windows.
  • Encryption / wrapping are client-side. The relay treats kind 1059
    payloads as opaque ciphertext and persists them like any regular event.

Out of scope (filed as issues)

  • Generic single-letter tag queries (NIP-12) — currently only #e/#p/#d
    hardcoded in the filter struct
  • Optional created_at past-window exemption for kind 1059 to keep
    NIP-59 working under strict timestamp configs
  • Tighter classification of kind 13 (seal) — currently falls through every
    category but is stored correctly as a non-ephemeral event

Test plan

  • Run make test (the new tests/test_nip17.py exercises the gating
    matrix; uv was not available in the authoring environment so the
    suite was syntax-validated only)
  • Manual: with forcedAuthEvents: [1059], confirm a gift wrap is
    delivered only to the AUTH'd recipient named in the p tag
  • Manual: with default config, confirm gift wraps broadcast as before
    and the nostrmarket refactor/nip17-messaging flow round-trips

🤖 Generated with Claude Code

## Summary Adds NIP-17 (Private Direct Messages) support to the relay so that the nostrmarket extension's `refactor/nip17-messaging` branch — and any other client moving from NIP-04 to NIP-17 — can publish and subscribe to gift-wrapped DMs. The relay already accepted `kind 1059` events as regular kinds (range 1000–9999) and the `#p` filter already worked. The remaining gap was **privacy gating under AUTH**: the existing `_is_direct_message_for_other` hook restricted delivery of NIP-04 kind-4 events to the authenticated recipient, but did nothing for kind 1059. This PR generalizes that hook so the same opt-in AUTH gate covers gift wraps. ### Changes - `relay/event.py` — add `is_seal` (kind 13), `is_gift_wrap` (kind 1059), and `is_private_message` helpers - `relay/client_connection.py` — rename `_is_direct_message_for_other` → `_is_private_event_for_other`; key off `is_private_message` so kinds 4 and 1059 share the same recipient-only delivery rule when AUTH is required for that kind - `relay/relay.py` — advertise NIPs 17, 44, 59 in NIP-11 `supported_nips` - `README.md` — document NIP-17/44/59 transport-level support - `tests/test_nip17.py` — unit tests for kind classification, AUTH-gated 1059 delivery (recipient vs non-recipient vs unauthenticated), and a regression check for kind 4 gating ### Design notes - **No automatic gating.** Like the existing NIP-04 behaviour, the recipient-only rule is opt-in via `forcedAuthEvents` / `requireAuthEvents`. Surveyed reference relays (strfry, khatru, nostream) do **not** enforce recipient gating for kind 1059 at all — they rely entirely on client-side encryption. Our config already supports per-kind AUTH, so this PR keeps that capability and extends it cleanly to 1059. - **No `created_at` exemption for gift wraps.** NIP-59 backdates wraps up to 2 days. Our `createdAtSecondsPast` defaults to `0` (no limit), so gift wraps work out of the box. Tracked separately for operators who configure tighter windows. - **Encryption / wrapping are client-side.** The relay treats kind 1059 payloads as opaque ciphertext and persists them like any regular event. ### Out of scope (filed as issues) - Generic single-letter tag queries (NIP-12) — currently only `#e/#p/#d` hardcoded in the filter struct - Optional `created_at` past-window exemption for kind 1059 to keep NIP-59 working under strict timestamp configs - Tighter classification of kind 13 (seal) — currently falls through every category but is stored correctly as a non-ephemeral event ## Test plan - [x] Run `make test` (the new `tests/test_nip17.py` exercises the gating matrix; `uv` was not available in the authoring environment so the suite was syntax-validated only) - [x] Manual: with `forcedAuthEvents: [1059]`, confirm a gift wrap is delivered only to the AUTH'd recipient named in the `p` tag - [x] Manual: with default config, confirm gift wraps broadcast as before and the nostrmarket `refactor/nip17-messaging` flow round-trips 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(nip17): support gift-wrapped private direct messages
Some checks failed
ci.yml / feat(nip17): support gift-wrapped private direct messages (pull_request) Failing after 0s
ci.yml / feat(nip17): support gift-wrapped private direct messages (push) Failing after 0s
4811fcf352
Generalize the AUTH-gated, recipient-only delivery rule from NIP-04 to
also cover NIP-17 kind 1059 gift wraps. When the relay is configured to
require AUTH for kind 1059, only the AUTH'd recipient named in the
event's `p` tag receives it; otherwise gift wraps broadcast like any
regular event.

- relay/event.py: add `is_seal`, `is_gift_wrap`, `is_private_message`
  helpers (kinds 13, 1059)
- relay/client_connection.py: rename `_is_direct_message_for_other` ->
  `_is_private_event_for_other`; key off `is_private_message` so the
  same gating applies to kinds 4 and 1059
- relay/relay.py: advertise NIPs 17, 44, 59 in NIP-11 supported_nips
- README: document NIP-17/44/59 transport-level support
- tests/test_nip17.py: unit tests for kind classification, AUTH-gated
  1059 delivery (recipient vs non-recipient vs unauthenticated), and
  regression coverage for kind 4 gating

NIP-44 (encryption) and NIP-59 (wrap/seal) are client-side concerns;
the relay treats payloads as opaque ciphertext and stores kind 1059
like any regular event.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug deleted branch feat/nip17-gift-wrap-support 2026-05-03 15:16:29 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/nostrrelay!1
No description provided.