Files
nostr_core_lib/plans/nip46_plan.md
T

13 KiB

NIP-46 Remote Signing — Implementation Plan

Overview

NIP-46 defines a protocol for Nostr Remote Signing, enabling 2-way communication between a client application and a remote signer (bunker) over Nostr relays. The remote signer holds the user's private keys and performs cryptographic operations on behalf of the client, reducing the attack surface by keeping keys off the client device.

This plan covers implementing both sides of the protocol:

  • Client-side: sends requests to a remote signer and processes responses
  • Remote-signer-side: receives requests and produces responses (for building bunker applications)

Architecture

Protocol Flow

sequenceDiagram
    participant C as Client
    participant R as Relay
    participant S as Remote Signer

    Note over C: Generate client-keypair
    C->>R: Subscribe to kind:24133 p-tagged to client-pubkey
    C->>R: Publish connect request, kind:24133, p-tag remote-signer-pubkey
    R->>S: Deliver connect request
    S->>R: Publish connect response, kind:24133, p-tag client-pubkey
    R->>C: Deliver connect response
    Note over C: Connection established

    C->>R: Publish get_public_key request
    R->>S: Deliver request
    S->>R: Publish response with user-pubkey
    R->>C: Deliver response
    Note over C: Now knows user-pubkey

    C->>R: Publish sign_event request
    R->>S: Deliver request
    S->>R: Publish signed event response
    R->>C: Deliver signed event

Connection Initiation

flowchart TD
    A[Connection Initiation] --> B{Who initiates?}
    B -->|Remote Signer| C[bunker:// URL]
    B -->|Client| D[nostrconnect:// URL]
    
    C --> E[Client parses bunker URL]
    E --> F[Extract remote-signer-pubkey + relays + secret]
    F --> G[Client sends connect request to remote-signer]
    
    D --> H[Remote signer parses nostrconnect URL]
    H --> I[Extract client-pubkey + relays + secret + perms]
    I --> J[Remote signer sends connect response to client]
    
    G --> K[Connection Established]
    J --> K

Component Dependency Map

graph TD
    NIP46[nip046.c/h] --> NIP44[nip044 - NIP-44 Encryption]
    NIP46 --> NIP04[nip004 - NIP-04 Encryption]
    NIP46 --> NIP01[nip001 - Event Creation/Signing]
    NIP46 --> POOL[core_relay_pool - Relay Communication]
    NIP46 --> UTILS[utils - Hex/Bytes/Random]
    NIP46 --> CJSON[cJSON - JSON Handling]
    NIP46 --> COMMON[nostr_common - Error Codes/Constants]

Data Structures

Core Types

// NIP-46 event kind
#define NOSTR_NIP46_REQUEST_KIND 24133

// Connection types
typedef enum {
    NOSTR_NIP46_CONN_BUNKER,        // bunker:// initiated by remote-signer
    NOSTR_NIP46_CONN_NOSTRCONNECT   // nostrconnect:// initiated by client
} nostr_nip46_connection_type_t;

// RPC Method types
typedef enum {
    NOSTR_NIP46_METHOD_CONNECT,
    NOSTR_NIP46_METHOD_SIGN_EVENT,
    NOSTR_NIP46_METHOD_PING,
    NOSTR_NIP46_METHOD_GET_PUBLIC_KEY,
    NOSTR_NIP46_METHOD_NIP04_ENCRYPT,
    NOSTR_NIP46_METHOD_NIP04_DECRYPT,
    NOSTR_NIP46_METHOD_NIP44_ENCRYPT,
    NOSTR_NIP46_METHOD_NIP44_DECRYPT
} nostr_nip46_method_t;

// Parsed bunker:// URL
typedef struct {
    char remote_signer_pubkey[65];   // hex pubkey
    char relays[8][256];             // up to 8 relay URLs
    int relay_count;
    char secret[128];                // optional secret
} nostr_nip46_bunker_url_t;

// Parsed nostrconnect:// URL
typedef struct {
    char client_pubkey[65];          // hex pubkey
    char relays[8][256];             // relay URLs
    int relay_count;
    char secret[128];                // required secret
    char perms[512];                 // optional permissions
    char name[128];                  // optional app name
    char url[256];                   // optional app URL
    char image[256];                 // optional app image
} nostr_nip46_nostrconnect_url_t;

// JSON-RPC Request
typedef struct {
    char id[65];                     // random request ID
    nostr_nip46_method_t method;
    char method_str[32];             // string form of method
    char** params;                   // array of string params
    int param_count;
} nostr_nip46_request_t;

// JSON-RPC Response
typedef struct {
    char id[65];                     // matching request ID
    char* result;                    // result string
    char* error;                     // error string, NULL if success
} nostr_nip46_response_t;

// Client session state
typedef struct {
    unsigned char client_private_key[32];
    char client_pubkey_hex[65];
    char remote_signer_pubkey_hex[65];
    unsigned char remote_signer_pubkey[32];
    char user_pubkey_hex[65];        // learned via get_public_key
    char relays[8][256];
    int relay_count;
    int connected;
} nostr_nip46_client_session_t;

// Signer session state
typedef struct {
    unsigned char signer_private_key[32];
    char signer_pubkey_hex[65];
    unsigned char user_private_key[32];
    char user_pubkey_hex[65];
    char client_pubkey_hex[65];
    unsigned char client_pubkey[32];
    char relays[8][256];
    int relay_count;
    int connected;
} nostr_nip46_signer_session_t;

Public API Functions

URL Parsing

Function Description
nostr_nip46_parse_bunker_url() Parse bunker://<pubkey>?relay=...&secret=...
nostr_nip46_parse_nostrconnect_url() Parse nostrconnect://<pubkey>?relay=...&secret=...&perms=...
nostr_nip46_create_bunker_url() Generate a bunker:// connection token
nostr_nip46_create_nostrconnect_url() Generate a nostrconnect:// connection token

JSON-RPC Message Construction

Function Description
nostr_nip46_create_request() Build a JSON-RPC request object
nostr_nip46_create_response() Build a JSON-RPC response object
nostr_nip46_parse_request() Parse decrypted JSON into request struct
nostr_nip46_parse_response() Parse decrypted JSON into response struct
nostr_nip46_free_request() Free request struct memory
nostr_nip46_free_response() Free response struct memory

Event Construction (kind: 24133)

Function Description
nostr_nip46_create_request_event() Create NIP-44 encrypted kind:24133 request event
nostr_nip46_create_response_event() Create NIP-44 encrypted kind:24133 response event
nostr_nip46_decrypt_event() Decrypt a kind:24133 event content

Client-Side Operations

Function Description
nostr_nip46_client_session_init() Initialize client session from bunker URL
nostr_nip46_client_session_destroy() Clean up client session
nostr_nip46_client_connect() Send connect request to remote signer
nostr_nip46_client_get_public_key() Request user pubkey from remote signer
nostr_nip46_client_sign_event() Request event signing from remote signer
nostr_nip46_client_ping() Send ping to remote signer
nostr_nip46_client_nip04_encrypt() Request NIP-04 encryption
nostr_nip46_client_nip04_decrypt() Request NIP-04 decryption
nostr_nip46_client_nip44_encrypt() Request NIP-44 encryption
nostr_nip46_client_nip44_decrypt() Request NIP-44 decryption

Remote-Signer-Side Operations

Function Description
nostr_nip46_signer_session_init() Initialize signer session
nostr_nip46_signer_session_destroy() Clean up signer session
nostr_nip46_signer_handle_request() Process an incoming request and produce a response
nostr_nip46_signer_create_bunker_url() Generate bunker URL for distribution

Utility

Function Description
nostr_nip46_generate_request_id() Generate random request ID string
nostr_nip46_method_to_string() Convert method enum to string
nostr_nip46_string_to_method() Convert string to method enum

Error Codes

New error codes to add to nostr_common.h:

// NIP-46 Remote Signing error codes
#define NOSTR_ERROR_NIP46_INVALID_BUNKER_URL    -300
#define NOSTR_ERROR_NIP46_INVALID_NOSTRCONNECT  -301
#define NOSTR_ERROR_NIP46_INVALID_REQUEST       -302
#define NOSTR_ERROR_NIP46_INVALID_RESPONSE      -303
#define NOSTR_ERROR_NIP46_ENCRYPTION_FAILED     -304
#define NOSTR_ERROR_NIP46_DECRYPTION_FAILED     -305
#define NOSTR_ERROR_NIP46_CONNECTION_FAILED     -306
#define NOSTR_ERROR_NIP46_TIMEOUT              -307
#define NOSTR_ERROR_NIP46_SECRET_MISMATCH      -308
#define NOSTR_ERROR_NIP46_UNKNOWN_METHOD       -309
#define NOSTR_ERROR_NIP46_AUTH_CHALLENGE        -310
#define NOSTR_ERROR_NIP46_NOT_CONNECTED        -311

Files to Create/Modify

New Files

File Purpose
nostr_core/nip046.h Header with all NIP-46 types, constants, and function declarations
nostr_core/nip046.c Implementation of all NIP-46 functions
tests/nip46_test.c Comprehensive test suite
examples/nip46_remote_signer.c Example showing both client and signer usage

Modified Files

File Change
nostr_core/nostr_core.h Add #include "nip046.h" and NIP-46 API docs in header comment
nostr_core/nostr_common.h Add NIP-46 error code defines
build.sh Add NIP-046 to auto-detection, --nips=all list, and description mapping

Implementation Details

URL Parsing Strategy

The bunker:// and nostrconnect:// URLs use standard URI format with query parameters. Implementation will:

  1. Validate the scheme prefix
  2. Extract the pubkey from the authority section
  3. Parse query parameters using simple string splitting — no external URL parsing library needed
  4. URL-decode relay values since they contain :// characters

NIP-44 Encryption Integration

All kind:24133 event content is NIP-44 encrypted. The implementation will use the existing nostr_nip44_encrypt() and nostr_nip44_decrypt() functions from nip044.h. The flow:

  1. Build JSON-RPC payload as a string
  2. Encrypt with nostr_nip44_encrypt(client_privkey, remote_signer_pubkey, payload, ...)
  3. Set as event content
  4. On receive: nostr_nip44_decrypt(my_privkey, sender_pubkey, content, ...)
  5. Parse decrypted JSON-RPC payload

Relay Communication

For the initial implementation, the NIP-46 module will provide event construction and parsing only — it will not manage relay connections directly. Users will use the existing relay pool API to:

  1. Subscribe to kind:24133 events p-tagged to their pubkey
  2. Publish kind:24133 request/response events

This keeps the module focused and composable, matching the pattern used by NIP-59 and NIP-17.

A higher-level convenience layer could be added later that wraps the relay pool for a fully managed NIP-46 session.

Auth Challenge Handling

When a response has result: "auth_url", the error field contains a URL. The client-side API will return a specific error code NOSTR_ERROR_NIP46_AUTH_CHALLENGE and provide the URL in the response struct's error field, allowing the application to handle it appropriately.

Test Plan

The test suite will cover:

  1. URL Parsing Tests

    • Parse valid bunker:// URLs with single and multiple relays
    • Parse valid nostrconnect:// URLs with all optional fields
    • Reject malformed URLs
    • Handle URL-encoded relay values
  2. URL Generation Tests

    • Generate bunker:// URLs and verify round-trip parsing
    • Generate nostrconnect:// URLs and verify round-trip parsing
  3. JSON-RPC Message Tests

    • Create and parse each method type request
    • Create and parse success responses
    • Create and parse error responses
    • Verify request ID matching
  4. Event Construction Tests

    • Create kind:24133 request events with proper encryption
    • Create kind:24133 response events with proper encryption
    • Decrypt events and verify content matches
  5. Method Enum Conversion Tests

    • Round-trip all method enum values through string conversion
  6. Signer Request Handling Tests

    • Handle connect request and produce ack response
    • Handle sign_event request and produce signed event response
    • Handle ping request and produce pong response
    • Handle get_public_key and return correct pubkey
    • Handle NIP-04/NIP-44 encrypt/decrypt requests
  7. End-to-End Flow Tests

    • Client creates connect request → Signer handles → Client processes response
    • Client creates sign_event request → Signer signs → Client gets signed event
    • Full bunker:// connection flow
    • Secret validation on connect

Implementation Order

  1. Error codes in nostr_common.h
  2. Header file nip046.h with all declarations
  3. URL parsing functions
  4. JSON-RPC request/response construction and parsing
  5. Kind:24133 event creation and decryption
  6. Client session management
  7. Signer session and request handling
  8. Utility functions
  9. Build system updates
  10. Test suite
  11. Example program
  12. Documentation updates in nostr_core.h