6.9 KiB
NIP-17 Messaging Implementation Plan
Background
Didactyl now supports both NIP-04 and NIP-17 DM flows (including receive path for kind 1059 and protocol-aware auto-reply routing). Remaining work in this plan tracks spec-hardening and bootstrap/discovery details.
The nostr_core_lib already has full NIP-17/NIP-59 support including:
nostr_nip17_create_chat_event()— create kind 14 rumornostr_nip17_send_dm()— seal + gift wrap (creates wraps for both recipient AND sender)nostr_nip17_receive_dm()— unwrap gift wrap, unseal rumor, return kind 14nostr_nip17_extract_dm_relays()— parse kind 10050 relay lists
Config-Driven Protocol Selection
New Config Field
Add a dm_protocol field to the top-level config:
{
"dm_protocol": "nip04",
...
}
Valid values:
"nip04"— NIP-04 only (current behavior, default for backward compatibility)"nip17"— NIP-17 only (subscribe to kind 1059, send via gift wrap)"both"— Subscribe to both kind 4 and kind 1059; reply using whichever protocol the message arrived on
Config Struct Change
In config.h, add to didactyl_config_t:
typedef enum {
DM_PROTOCOL_NIP04 = 0,
DM_PROTOCOL_NIP17 = 1,
DM_PROTOCOL_BOTH = 2
} dm_protocol_t;
Add dm_protocol_t dm_protocol; to the config struct.
Implementation Steps
1. Add dm_protocol config parsing
Files: config.h, config.c
- Add
dm_protocol_tenum and field todidactyl_config_t - Parse
"dm_protocol"string from JSON inconfig_load() - Default to
DM_PROTOCOL_NIP04if not specified
2. Add kind 1059 subscription
File: nostr_handler.c — nostr_handler_subscribe_dms()
- When
dm_protocolisNIP17orBOTH, addkind: 1059to the subscription filter - When
dm_protocolisNIP04orBOTH, keepkind: 4in the filter - The subscription filter becomes
kinds: [4, 1059]forBOTHmode
3. Add NIP-17 receive handling in on_event()
File: nostr_handler.c — on_event()
Currently on_event() rejects anything that is not kind 4. Update to:
- If kind == 1059: unwrap gift wrap via
nostr_nip17_receive_dm(), extract sender pubkey from the kind 14 rumor, extract message content, determine sender tier, fireg_dm_callback - If kind == 4: existing NIP-04 decrypt path (unchanged)
- Track which protocol was used per sender pubkey for reply routing
4. Track protocol per sender for reply routing
File: nostr_handler.c
Add a small cache that maps sender_pubkey_hex -> last_protocol_used:
typedef struct {
char pubkey_hex[65];
dm_protocol_t protocol;
} sender_protocol_entry_t;
#define SENDER_PROTOCOL_CACHE_SIZE 64
static sender_protocol_entry_t g_sender_protocol_cache[SENDER_PROTOCOL_CACHE_SIZE];
When a DM arrives via kind 4, record NIP04 for that sender. When via kind 1059, record NIP17.
5. Add protocol-aware send function
File: nostr_handler.c
Add a new function that routes based on config + sender history:
int nostr_handler_send_dm_auto(const char* recipient_pubkey_hex, const char* message);
Logic:
- If
dm_protocol == NIP04: always usenostr_handler_send_dm() - If
dm_protocol == NIP17: always usenostr_handler_send_dm_nip17() - If
dm_protocol == BOTH: check sender protocol cache; use matching protocol, default to NIP-04
Expose in nostr_handler.h.
6. Update agent.c to use auto-routing
File: agent.c
Replace all nostr_handler_send_dm() calls with nostr_handler_send_dm_auto(). This is a mechanical find-and-replace across ~15 call sites.
7. Add kind 10050 startup event support
Status: ✅ Implemented
Files:
src/default_events.h(default startup kinds now include10050)src/setup_wizard.c(builds kind10050tags from current configured relays)genesis.jsonc.example(includes kind10050startup event)genesis.lt.didactyl.jsonc(includes kind10050startup event)
Implementation detail: kind 10050 reuses the same relay set as kind 10002/current config relays, with relay tags as required by NIP-17.
8. Update startup DM to use auto-routing
Status: ✅ Implemented
main.c uses nostr_handler_send_dm_auto() for startup DM flow.
9. Add NIP-17 seal/rumor pubkey verification
Status: ✅ Implemented (in core lib)
File: nostr_core_lib/nostr_core/nip017.c
nostr_nip17_receive_dm() now verifies that seal pubkey matches rumor pubkey and rejects mismatches to prevent sender impersonation.
10. Recipient kind-10050 relay targeting for sends
Status: ⏸ Deferred
Current NIP-17 send path publishes to connected configured relays. Querying and targeting recipient-advertised kind 10050 relays is kept as a future enhancement while debugging current interoperability.
Architecture Diagram
flowchart TD
subgraph Config
CFG[dm_protocol setting]
CFG -->|nip04| NIP04_MODE[NIP-04 only]
CFG -->|nip17| NIP17_MODE[NIP-17 only]
CFG -->|both| BOTH_MODE[Both protocols]
end
subgraph Subscription
SUB[nostr_handler_subscribe_dms]
NIP04_MODE --> SUB_K4[Subscribe kind 4]
NIP17_MODE --> SUB_K1059[Subscribe kind 1059]
BOTH_MODE --> SUB_BOTH[Subscribe kind 4 + 1059]
end
subgraph Receive Path
EVT[on_event callback]
EVT -->|kind 4| DEC4[NIP-04 decrypt]
EVT -->|kind 1059| DEC17[NIP-17 unwrap + unseal]
DEC4 --> CACHE[Record sender protocol]
DEC17 --> CACHE
CACHE --> CB[Fire dm_callback]
end
subgraph Send Path
SEND[nostr_handler_send_dm_auto]
SEND -->|config=nip04| S4[nostr_handler_send_dm - kind 4]
SEND -->|config=nip17| S17[nostr_handler_send_dm_nip17 - kind 1059]
SEND -->|config=both| LOOKUP[Check sender protocol cache]
LOOKUP -->|sender used nip04| S4
LOOKUP -->|sender used nip17| S17
LOOKUP -->|unknown| S4
end
File Change Summary
| File | Changes |
|---|---|
config.h |
Add dm_protocol_t enum and field |
config.c |
Parse dm_protocol from JSON |
nostr_handler.h |
Add nostr_handler_send_dm_auto() declaration |
nostr_handler.c |
Add kind 1059 subscription, NIP-17 receive in on_event(), sender protocol cache, nostr_handler_send_dm_auto() |
agent.c |
Replace nostr_handler_send_dm() with nostr_handler_send_dm_auto() |
main.c |
Replace startup DM send with nostr_handler_send_dm_auto() |
config.json.example |
Add dm_protocol field and kind 10050 startup event example |
Testing Strategy
- NIP-04 mode (default): Verify existing behavior is unchanged
- NIP-17 mode: Send a NIP-17 DM from a client like Amethyst/0xchat, verify Didactyl receives and replies via NIP-17
- Both mode: Send NIP-04 DM, verify NIP-04 reply; send NIP-17 DM, verify NIP-17 reply
- Kind 10050: Verify the relay list event is published on startup and discoverable by NIP-17 clients