Files
didactyl/plans/nip17_messaging.md
T

195 lines
6.3 KiB
Markdown

# NIP-17 Messaging Implementation Plan
## Background
Didactyl currently uses NIP-04 (kind 4) for all DM communication. NIP-17 send support exists via `nostr_handler_send_dm_nip17()` and the `nostr_dm_send_nip17` tool, but there is **no ability to receive NIP-17 messages** and **all agent responses always use NIP-04**.
The `nostr_core_lib` already has full NIP-17/NIP-59 support including:
- `nostr_nip17_create_chat_event()` — create kind 14 rumor
- `nostr_nip17_send_dm()` — seal + gift wrap (creates wraps for both recipient AND sender)
- `nostr_nip17_receive_dm()` — unwrap gift wrap, unseal rumor, return kind 14
- `nostr_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:
```json
{
"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`:
```c
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_t` enum and field to `didactyl_config_t`
- Parse `"dm_protocol"` string from JSON in `config_load()`
- Default to `DM_PROTOCOL_NIP04` if not specified
### 2. Add kind 1059 subscription
**File**: `nostr_handler.c` — `nostr_handler_subscribe_dms()`
- When `dm_protocol` is `NIP17` or `BOTH`, add `kind: 1059` to the subscription filter
- When `dm_protocol` is `NIP04` or `BOTH`, keep `kind: 4` in the filter
- The subscription filter becomes `kinds: [4, 1059]` for `BOTH` mode
### 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, fire `g_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`:
```c
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:
```c
int nostr_handler_send_dm_auto(const char* recipient_pubkey_hex, const char* message);
```
Logic:
- If `dm_protocol == NIP04`: always use `nostr_handler_send_dm()`
- If `dm_protocol == NIP17`: always use `nostr_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
**File**: `config.json.example`
Add a kind 10050 startup event example so NIP-17 clients can discover the agent's DM relay preferences:
```json
{
"kind": 10050,
"content": "",
"tags": [
["relay", "wss://relay.damus.io"],
["relay", "wss://nos.lol"]
]
}
```
**File**: `config.c` — startup event parsing already handles arbitrary kinds, so this should work with no code changes.
### 8. Update startup DM to use auto-routing
**File**: `main.c`
The startup status DM at line 255 currently calls `nostr_handler_send_dm()`. Update to `nostr_handler_send_dm_auto()`.
## Architecture Diagram
```mermaid
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
1. **NIP-04 mode** (default): Verify existing behavior is unchanged
2. **NIP-17 mode**: Send a NIP-17 DM from a client like Amethyst/0xchat, verify Didactyl receives and replies via NIP-17
3. **Both mode**: Send NIP-04 DM, verify NIP-04 reply; send NIP-17 DM, verify NIP-17 reply
4. **Kind 10050**: Verify the relay list event is published on startup and discoverable by NIP-17 clients