18 KiB
Implementation Plan: Nostr Tools
Current Tool Inventory (v0.0.19)
| # | Tool | Kind/NIP | Status |
|---|---|---|---|
| 1 | nostr_post |
Any kind | ✅ Shipped |
| 2 | nostr_post_readme |
30023 / NIP-23 | ✅ Shipped |
| 3 | nostr_query |
Filter-based | ✅ Shipped |
| 4 | nostr_delete |
5 / NIP-09 | ✅ Shipped |
| 5 | nostr_react |
7 / NIP-25 | ✅ Shipped |
| 6 | nostr_profile_get |
0 query | ✅ Shipped |
| 7 | nostr_relay_status |
Pool stats | ✅ Shipped |
| 8 | local_shell_exec |
OS | ✅ Shipped |
| 9 | local_file_read |
OS | ✅ Shipped |
| 10 | local_file_write |
OS | ✅ Shipped |
Pattern for All New Tools
Every tool follows the same three-step pattern in src/tools.c:
- Schema — Add OpenAI function schema block in
tools_build_openai_schema_json() - Executor — Implement
static char* execute_<name>()function - Dispatch — Add
strcmpcase intools_execute()
Use the shared parse_tool_args_json() helper for hardened argument parsing.
Tier 2 Tools — Medium Value, Moderate Implementation
Tool 11: nostr_nip05_lookup — DNS Identity Verification (NIP-05)
Purpose
Verify user@domain.com NIP-05 identifiers. Useful for trust decisions and profile enrichment.
OpenAI Schema
{
"name": "nostr_nip05_lookup",
"description": "Verify or look up a NIP-05 DNS identifier and return the associated pubkey and relays",
"parameters": {
"type": "object",
"properties": {
"identifier": {
"type": "string",
"description": "NIP-05 identifier, e.g. user@domain.com"
},
"pubkey": {
"type": "string",
"description": "Optional 64-char hex pubkey to verify against the identifier"
}
},
"required": ["identifier"]
}
}
Implementation: execute_nostr_nip05_lookup()
1. Parse args_json
2. Extract "identifier" (required string, must contain @)
3. Extract "pubkey" (optional 64-char hex)
4. If pubkey provided:
- Call nostr_nip05_verify(identifier, pubkey, &relays, &relay_count, 10)
- Return { success, verified: true/false, pubkey, relays }
5. If no pubkey:
- Call nostr_nip05_lookup(identifier, pubkey_out, &relays, &relay_count, 10)
- Return { success, pubkey: pubkey_out, relays }
6. Free relay array after serializing
Library API Used
Files Modified
src/tools.c: Schema + executor + dispatch
Notes
- Involves HTTP network I/O (fetches
/.well-known/nostr.jsonfrom the domain) - May block for up to
timeout_seconds— use 10s default - Must free the
relaysarray returned by the library
Tool 12: nostr_encode — Bech32 Entity Encoding (NIP-19/NIP-21)
Purpose
Encode hex pubkeys, event IDs, or addressable coordinates into shareable npub, note, nprofile, nevent, naddr URIs.
OpenAI Schema
{
"name": "nostr_encode",
"description": "Encode a Nostr entity into a bech32 URI: npub, note, nprofile, nevent, or naddr",
"parameters": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "Entity type: npub, note, nprofile, nevent, or naddr"
},
"hex": {
"type": "string",
"description": "64-char hex value: pubkey for npub/nprofile, event_id for note/nevent"
},
"relays": {
"type": "array",
"items": { "type": "string" },
"description": "Optional relay hints for nprofile, nevent, naddr"
},
"kind": {
"type": "integer",
"description": "Kind number, required for naddr"
},
"identifier": {
"type": "string",
"description": "d-tag identifier, required for naddr"
}
},
"required": ["type", "hex"]
}
}
Implementation: execute_nostr_encode()
1. Parse args_json
2. Extract "type" (required: npub|note|nprofile|nevent|naddr)
3. Extract "hex" (required, 64-char hex)
4. Convert hex to 32-byte array via nostr_hex_to_bytes()
5. Switch on type:
- "npub": nostr_build_uri_npub(bytes, output, sizeof output)
- "note": nostr_build_uri_note(bytes, output, sizeof output)
- "nprofile": nostr_build_uri_nprofile(bytes, relays, relay_count, output, sizeof output)
- "nevent": nostr_build_uri_nevent(bytes, relays, relay_count, NULL, -1, 0, output, sizeof output)
- "naddr": nostr_build_uri_naddr(identifier, bytes, kind, relays, relay_count, output, sizeof output)
6. Return { success, type, uri: output }
Library API Used
nostr_build_uri_npub()nostr_build_uri_note()nostr_build_uri_nprofile()nostr_build_uri_nevent()nostr_build_uri_naddr()
Files Modified
src/tools.c: Schema + executor + dispatch
Tool 13: nostr_decode — Bech32 Entity Decoding (NIP-19/NIP-21)
Purpose
Decode npub, note, nprofile, nevent, naddr URIs back into hex values and metadata.
OpenAI Schema
{
"name": "nostr_decode",
"description": "Decode a bech32 Nostr URI into its components: type, hex, relays, kind, identifier",
"parameters": {
"type": "object",
"properties": {
"uri": {
"type": "string",
"description": "Bech32 URI to decode, e.g. npub1..., note1..., nostr:npub1..."
}
},
"required": ["uri"]
}
}
Implementation: execute_nostr_decode()
1. Parse args_json
2. Extract "uri" (required string)
3. Call nostr_parse_uri(uri, &result)
4. Switch on result.type:
- NPUB: convert result.data.pubkey to hex, return { type: "npub", pubkey: hex }
- NOTE: convert result.data.event_id to hex, return { type: "note", event_id: hex }
- NPROFILE: pubkey hex + relays array
- NEVENT: event_id hex + relays + optional author/kind
- NADDR: identifier + pubkey hex + kind + relays
5. Call nostr_uri_result_free(&result)
6. Return JSON
Library API Used
Files Modified
src/tools.c: Schema + executor + dispatch
Tool 14: nostr_dm_send — Send DM as Tool Call
Purpose
Expose DM sending as a tool so the agent can proactively message people (notify admin, reach out to contacts).
OpenAI Schema
{
"name": "nostr_dm_send",
"description": "Send an encrypted direct message to a Nostr user (NIP-04)",
"parameters": {
"type": "object",
"properties": {
"recipient_pubkey": {
"type": "string",
"description": "64-char hex pubkey of the recipient"
},
"message": {
"type": "string",
"description": "Plaintext message to send"
}
},
"required": ["recipient_pubkey", "message"]
}
}
Implementation: execute_nostr_dm_send()
1. Parse args_json
2. Extract "recipient_pubkey" (required, 64-char hex)
3. Extract "message" (required, non-empty string)
4. Call nostr_handler_send_dm(recipient_pubkey, message)
5. Return { success: true/false, recipient_pubkey, message_length }
Library API Used
nostr_handler_send_dm()— already exists
Security Note
- Should be admin-tier only in practice. The tool itself doesn't enforce tier, but the agent's system prompt and skill definitions should restrict usage.
Files Modified
src/tools.c: Schema + executor + dispatch
Tool 15: nostr_relay_info — Relay Information Document (NIP-11)
Purpose
Inspect relay capabilities, supported NIPs, limitations, PoW requirements before publishing.
OpenAI Schema
{
"name": "nostr_relay_info",
"description": "Fetch the NIP-11 relay information document for a relay URL",
"parameters": {
"type": "object",
"properties": {
"relay_url": {
"type": "string",
"description": "WebSocket relay URL, e.g. wss://relay.example.com"
}
},
"required": ["relay_url"]
}
}
Implementation
This requires a new helper in nostr_handler.c since NIP-11 fetching needs an HTTP GET request with Accept: application/nostr+json header.
New function: nostr_handler_relay_info_json()
1. Convert ws:// or wss:// URL to http:// or https://
2. Use libcurl to HTTP GET with header "Accept: application/nostr+json"
3. Return the raw JSON response string
execute_nostr_relay_info() in tools.c:
1. Parse args_json
2. Extract "relay_url" (required string, must start with ws:// or wss://)
3. Call nostr_handler_relay_info_json(relay_url)
4. Parse returned JSON
5. Return { success, relay_url, info: { name, description, pubkey, supported_nips, limitations, ... } }
Files Modified
src/nostr_handler.h: Addnostr_handler_relay_info_json()declarationsrc/nostr_handler.c: Implement using libcurl (already linked)src/tools.c: Schema + executor + dispatch
Tier 3 Tools — High Value, More Complex
Tool 16: nostr_dm_send_nip17 — Private DMs via Gift Wrap (NIP-17/NIP-59)
Purpose
Send modern, metadata-private DMs using the NIP-17 gift wrap protocol instead of legacy NIP-04.
OpenAI Schema
{
"name": "nostr_dm_send_nip17",
"description": "Send a private direct message using NIP-17 gift wrap protocol for metadata privacy",
"parameters": {
"type": "object",
"properties": {
"recipient_pubkey": {
"type": "string",
"description": "64-char hex pubkey of the recipient"
},
"message": {
"type": "string",
"description": "Plaintext message to send"
},
"subject": {
"type": "string",
"description": "Optional conversation subject"
}
},
"required": ["recipient_pubkey", "message"]
}
}
Implementation
This is a multi-step crypto pipeline. The library handles the heavy lifting but we need to orchestrate:
New function: nostr_handler_send_dm_nip17() in nostr_handler.c
1. Query recipient's kind 10050 relay list (DM relays)
- Build filter: { kinds: [10050], authors: [recipient_pubkey], limit: 1 }
- Call nostr_relay_pool_query_sync()
- Extract relay URLs via nostr_nip17_extract_dm_relays()
- Fall back to our own relays if no 10050 found
2. Create kind 14 chat event:
- nostr_nip17_create_chat_event(message, &recipient_pubkey, 1, subject, NULL, NULL, our_pubkey)
3. Gift wrap and send:
- nostr_nip17_send_dm(chat_event, &recipient_pubkey, 1, our_private_key, gift_wraps, max_wraps, 0)
4. Publish each gift wrap to recipient's DM relays
5. Return success/failure + event metadata
execute_nostr_dm_send_nip17() in tools.c:
1. Parse args_json
2. Extract recipient_pubkey, message, optional subject
3. Call nostr_handler_send_dm_nip17(recipient_pubkey, message, subject)
4. Return { success, recipient_pubkey, protocol: "nip17" }
Library API Used
nostr_nip17_create_chat_event()nostr_nip17_send_dm()nostr_nip17_extract_dm_relays()nostr_nip59_create_gift_wrap()
Files Modified
src/nostr_handler.h: Addnostr_handler_send_dm_nip17()declarationsrc/nostr_handler.c: Implement the full NIP-17 send pipelinesrc/tools.c: Schema + executor + dispatch
Tool 17: nostr_encrypt — NIP-44 Encryption
Purpose
Encrypt arbitrary content for a specific recipient using modern NIP-44 encryption.
OpenAI Schema
{
"name": "nostr_encrypt",
"description": "Encrypt plaintext for a recipient using NIP-44 ChaCha20+HMAC encryption",
"parameters": {
"type": "object",
"properties": {
"recipient_pubkey": {
"type": "string",
"description": "64-char hex pubkey of the recipient"
},
"plaintext": {
"type": "string",
"description": "Text to encrypt"
}
},
"required": ["recipient_pubkey", "plaintext"]
}
}
Implementation: execute_nostr_encrypt()
1. Parse args_json
2. Extract recipient_pubkey (64-char hex), plaintext (non-empty string)
3. Convert recipient_pubkey hex to 32-byte array
4. Allocate output buffer (plaintext length * 2 + 256 for base64 overhead)
5. Call nostr_nip44_encrypt(our_private_key, recipient_pubkey_bytes, plaintext, output, output_size)
6. Return { success, recipient_pubkey, ciphertext: output }
Library API Used
Security Note
- Admin-tier only. Uses the agent's private key for ECDH shared secret.
Files Modified
src/tools.c: Schema + executor + dispatch
Tool 18: nostr_decrypt — NIP-44 Decryption
Purpose
Decrypt NIP-44 encrypted content from a known sender.
OpenAI Schema
{
"name": "nostr_decrypt",
"description": "Decrypt NIP-44 encrypted content from a sender",
"parameters": {
"type": "object",
"properties": {
"sender_pubkey": {
"type": "string",
"description": "64-char hex pubkey of the sender"
},
"ciphertext": {
"type": "string",
"description": "Base64-encoded NIP-44 encrypted payload"
}
},
"required": ["sender_pubkey", "ciphertext"]
}
}
Implementation: execute_nostr_decrypt()
1. Parse args_json
2. Extract sender_pubkey (64-char hex), ciphertext (non-empty string)
3. Convert sender_pubkey hex to 32-byte array
4. Allocate output buffer
5. Call nostr_nip44_decrypt(our_private_key, sender_pubkey_bytes, ciphertext, output, output_size)
6. Return { success, sender_pubkey, plaintext: output }
Library API Used
Security Note
- Admin-tier only.
Files Modified
src/tools.c: Schema + executor + dispatch
Tool 19: nostr_list_manage — List Management (NIP-51)
Purpose
Manage mute lists (kind 10000), bookmarks (kind 10003), follow sets, relay sets. Lets the agent curate its social graph.
OpenAI Schema
{
"name": "nostr_list_manage",
"description": "Add or remove items from a Nostr list: mute list, bookmarks, relay sets, etc. (NIP-51)",
"parameters": {
"type": "object",
"properties": {
"list_kind": {
"type": "integer",
"description": "Kind number of the list: 10000=mute, 10001=pins, 10003=bookmarks, 3=follows, 10002=relays"
},
"action": {
"type": "string",
"description": "Action: add or remove"
},
"items": {
"type": "array",
"items": {
"type": "array",
"items": { "type": "string" }
},
"description": "Array of tag tuples to add/remove, e.g. [[p, pubkey], [e, event_id]]"
}
},
"required": ["list_kind", "action", "items"]
}
}
Implementation
This is a read-modify-write pattern for replaceable events:
execute_nostr_list_manage():
1. Parse args_json
2. Extract list_kind (integer), action ("add"|"remove"), items (array of tag arrays)
3. Query existing list:
- Build filter: { kinds: [list_kind], authors: [our_pubkey], limit: 1 }
- Call nostr_handler_query_json(filter, 8000)
4. Parse existing event tags (or start with empty array if none found)
5. If action == "add":
- For each item in items: append to tags if not already present
6. If action == "remove":
- For each item in items: remove matching tag from tags
7. Publish updated event:
- Call nostr_handler_publish_kind_event(list_kind, existing_content_or_empty, updated_tags, &result)
8. Return { success, list_kind, action, items_affected, event_id }
Notes
- Replaceable events (kind 10000-19999) automatically replace previous versions
- Kind 3 (follows) is also replaceable
- Content field may contain NIP-44 encrypted private items — preserve it unchanged
- Must deduplicate tags when adding
Files Modified
src/tools.c: Schema + executor + dispatch
Implementation Order
graph TD
A[nostr_nip05_lookup] --> B[nostr_encode]
B --> C[nostr_decode]
C --> D[nostr_dm_send]
D --> E[nostr_relay_info]
E --> F[nostr_encrypt]
F --> G[nostr_decrypt]
G --> H[nostr_dm_send_nip17]
H --> I[nostr_list_manage]
style A fill:#FFFFE0
style B fill:#FFFFE0
style C fill:#FFFFE0
style D fill:#FFFFE0
style E fill:#FFFFE0
style F fill:#FFD700
style G fill:#FFD700
style H fill:#FFD700
style I fill:#FFD700
Yellow = Tier 2 (moderate), Gold = Tier 3 (complex).
Recommended batching:
Batch A — Pure computation, no new handler functions:
nostr_encode— pure NIP-21 encodingnostr_decode— pure NIP-21 decodingnostr_dm_send— thin wrapper around existingnostr_handler_send_dm()
Batch B — Network I/O tools:
4. nostr_nip05_lookup — HTTP fetch via library
5. nostr_relay_info — HTTP fetch via new handler function + libcurl
Batch C — Crypto tools:
6. nostr_encrypt — NIP-44 encrypt
7. nostr_decrypt — NIP-44 decrypt
Batch D — Complex orchestration:
8. nostr_dm_send_nip17 — multi-step NIP-17/59 pipeline, new handler function
9. nostr_list_manage — read-modify-write replaceable events
Build & Validate
After each batch:
- Run
./build_static.shto verify compilation - Run
./increment_and_push.shwith descriptive message