6.3 KiB
NIP-17 Gift Wrap Send Failure — Investigation
Status: Resolved — Fixed in code (2026-04-11)
Date Discovered: 2026-04-11
Symptom
Every outbound NIP-17 DM send from the Anvil agent fails with:
NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
The agent successfully falls back to NIP-04 (kind 4), so DMs are still delivered. However, NIP-17 outbound is completely broken.
Inbound NIP-17 works fine — the agent successfully receives and decrypts kind 1059 gift wraps from the admin.
Affected Code Path
Send path (broken):
nostr_handler_send_dm_auto_with_role()insrc/nostr_handler.c:3193— routes to NIP-17 whendm_protocolisnip17orbothnostr_handler_send_dm_nip17_with_role()insrc/nostr_handler.c:4050— creates chat event, callsnostr_nip17_send_dm()nostr_nip17_send_dm()innostr_core_lib/nostr_core/nip017.c:260— iterates recipients, creates seal + gift wrapnostr_nip59_create_seal()innostr_core_lib/nostr_core/nip059.c:150— encrypts rumor JSON with NIP-44nostr_nip59_create_gift_wrap()innostr_core_lib/nostr_core/nip059.c:226— wraps seal with random key
Receive path (working):
on_event()receives kind 1059nostr_nip17_receive_dm()innostr_core_lib/nostr_core/nip017.c:326— unwraps gift, unseals rumor- Successfully extracts kind 14 content
Probable Root Cause: Fixed Buffer Overflow in Seal Encryption
In nostr_core_lib/nostr_core/nip059.c:163-165:
char encrypted_content[4096]; // Should be large enough for most events
int encrypt_result = nostr_nip44_encrypt(sender_private_key, recipient_public_key,
rumor_json, encrypted_content, sizeof(encrypted_content));
The seal creation uses a fixed 4096-byte stack buffer for the NIP-44 encrypted output. The rumor JSON includes the full DM message content. When the agent sends long responses (the FIPS mesh conversation had multi-KB markdown tables), the rumor JSON easily exceeds 4096 bytes after NIP-44 encryption overhead (base64 encoding roughly 4/3x expansion + padding + nonce).
Evidence:
- The agent's DM responses in the log were 2-5KB of markdown content
- After JSON serialization of the kind 14 rumor (adding pubkey, tags, created_at, kind fields), the total is larger
- NIP-44 encryption adds: 32-byte nonce + padding + base64 encoding → roughly 1.5-2x expansion
- A 3KB message → ~4.5KB rumor JSON → ~6-9KB encrypted → overflows 4096 buffer
nostr_nip44_encrypt()returns error,nostr_nip59_create_seal()returns NULLnostr_nip17_send_dm()skips the recipient, returns 0 wrapsnostr_handler_send_dm_nip17_with_role()logs "gift_wrap creation failed"
Why receive works: The receive path uses nostr_nip44_decrypt() which likely allocates dynamically or has a larger buffer.
Secondary Suspect: NIP-44 Encryption Implementation
The NIP-44 encrypt function itself may have issues with large payloads. Need to check:
nostr_nip44_encrypt()innostr_core_lib/nostr_core/nip044.c- Whether it handles the output buffer size parameter correctly
- Whether it returns an error code that distinguishes "buffer too small" from other failures
Anvil Config Context
The Anvil agent's genesis config (/home/user/anvil/genesis.jsonc) has:
"dm_protocol": "nip04"
But the encrypted user-settings on Nostr (kind 30078) may have overridden this to "both" at runtime, which is why NIP-17 send is being attempted.
Recommended Fix
Option A: Dynamic allocation (preferred)
Replace the fixed 4096-byte buffer in nostr_nip59_create_seal() with dynamic allocation:
size_t rumor_len = strlen(rumor_json);
size_t encrypted_buf_size = rumor_len * 2 + 256; // generous overhead for NIP-44
char* encrypted_content = malloc(encrypted_buf_size);
Option B: Larger fixed buffer (quick fix)
Increase the buffer to 64KB or 128KB:
char encrypted_content[131072]; // 128KB should handle any reasonable DM
Note: This is a stack allocation, so very large buffers could cause stack overflow.
Also check nostr_nip59_create_gift_wrap()
The gift wrap function at nip059.c:226 may have a similar fixed-buffer issue for encrypting the seal JSON.
Log Evidence
From /home/user/anvil/debug.log (2026-04-11):
[2026-04-11 09:45:14] [WARN ] NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
[2026-04-11 09:45:14] [WARN ] auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
[2026-04-11 09:50:16] [WARN ] NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
[2026-04-11 09:50:16] [WARN ] auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
[2026-04-11 09:53:42] [WARN ] NIP-17 send aborted: gift_wrap creation failed for 1ec454734dcbf6fe...
[2026-04-11 09:53:42] [WARN ] auto DM fallback to NIP-04 for 1ec454734dcbf6fe... after NIP-17 send failure
This pattern is 100% consistent — every outbound NIP-17 attempt fails, every time.
Related Files
nostr_core_lib/nostr_core/nip059.c— Gift wrap / seal creation (probable bug location)nostr_core_lib/nostr_core/nip044.c— NIP-44 encryption (check buffer handling)nostr_core_lib/nostr_core/nip017.c— NIP-17 DM send/receive orchestrationsrc/nostr_handler.c:4050— Agent-level NIP-17 send functionplans/nip17_messaging.md— Original NIP-17 implementation plan
Related Existing Plan
The plans/nip17_messaging.md document covers the original NIP-17 implementation. This investigation is specifically about the send-side gift wrap failure that emerged in production.
Resolution Summary
Implemented in nostr_core_lib/nostr_core/nip059.c:
- Replaced fixed encryption buffers in
nostr_nip59_create_seal()andnostr_nip59_create_gift_wrap()with dynamically sized heap buffers calculated from NIP-44 padding/base64 overhead. - Replaced fixed decrypt buffers in
nostr_nip59_unwrap_gift()andnostr_nip59_unseal_rumor()with dynamic buffers sized from ciphertext length. - Added missing cleanup path in
nostr_nip59_create_seal()for failure after encryption allocation.
Expected outcome: outbound NIP-17 gift wrap creation no longer fails for multi-KB DM payloads due to fixed buffer limits.