/* * NOSTR Core Library - NIP-021: nostr: URI scheme */ #include "nip021.h" #include "nip019.h" // For existing bech32 functions #include "utils.h" #include "nostr_common.h" // For error codes #include #include #include #include #include "../cjson/cJSON.h" // Forward declarations for internal parsing functions static int parse_nprofile_data(const uint8_t* data, size_t data_len, nostr_nprofile_t* nprofile); static int parse_nevent_data(const uint8_t* data, size_t data_len, nostr_nevent_t* nevent); static int parse_naddr_data(const uint8_t* data, size_t data_len, nostr_naddr_t* naddr); // Bech32 constants and functions (copied from nip019.c for internal use) static const char bech32_charset[] = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; static const int8_t bech32_charset_rev[128] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 }; static uint32_t bech32_polymod_step(uint32_t pre) { uint8_t b = pre >> 25; return ((pre & 0x1FFFFFF) << 5) ^ (-((b >> 0) & 1) & 0x3b6a57b2UL) ^ (-((b >> 1) & 1) & 0x26508e6dUL) ^ (-((b >> 2) & 1) & 0x1ea119faUL) ^ (-((b >> 3) & 1) & 0x3d4233ddUL) ^ (-((b >> 4) & 1) & 0x2a1462b3UL); } static int convert_bits(uint8_t *out, size_t *outlen, int outbits, const uint8_t *in, size_t inlen, int inbits, int pad) { uint32_t val = 0; int bits = 0; uint32_t maxv = (((uint32_t)1) << outbits) - 1; *outlen = 0; while (inlen--) { val = (val << inbits) | *(in++); bits += inbits; while (bits >= outbits) { bits -= outbits; out[(*outlen)++] = (val >> bits) & maxv; } } if (pad) { if (bits) { out[(*outlen)++] = (val << (outbits - bits)) & maxv; } } else if (((val << (outbits - bits)) & maxv) || bits >= inbits) { return 0; } return 1; } static int bech32_encode(char *output, const char *hrp, const uint8_t *data, size_t data_len) { uint32_t chk = 1; size_t i, hrp_len = strlen(hrp); for (i = 0; i < hrp_len; ++i) { int ch = hrp[i]; if (ch < 33 || ch > 126) return 0; if (ch >= 'A' && ch <= 'Z') return 0; chk = bech32_polymod_step(chk) ^ (ch >> 5); } chk = bech32_polymod_step(chk); for (i = 0; i < hrp_len; ++i) { chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f); *(output++) = hrp[i]; } *(output++) = '1'; for (i = 0; i < data_len; ++i) { if (*data >> 5) return 0; chk = bech32_polymod_step(chk) ^ (*data); *(output++) = bech32_charset[*(data++)]; } for (i = 0; i < 6; ++i) { chk = bech32_polymod_step(chk); } chk ^= 1; for (i = 0; i < 6; ++i) { *(output++) = bech32_charset[(chk >> ((5 - i) * 5)) & 0x1f]; } *output = 0; return 1; } static int bech32_decode(const char* input, const char* hrp, unsigned char* data, size_t* data_len) { if (!input || !hrp || !data || !data_len) { return 0; } size_t input_len = strlen(input); size_t hrp_len = strlen(hrp); if (input_len < hrp_len + 7) return 0; if (strncmp(input, hrp, hrp_len) != 0) return 0; if (input[hrp_len] != '1') return 0; const char* data_part = input + hrp_len + 1; size_t data_part_len = input_len - hrp_len - 1; uint8_t values[256]; for (size_t i = 0; i < data_part_len; i++) { unsigned char c = (unsigned char)data_part[i]; if (c >= 128) return 0; int8_t val = bech32_charset_rev[c]; if (val == -1) return 0; values[i] = (uint8_t)val; } if (data_part_len < 6) return 0; uint32_t chk = 1; for (size_t i = 0; i < hrp_len; i++) { chk = bech32_polymod_step(chk) ^ (hrp[i] >> 5); } chk = bech32_polymod_step(chk); for (size_t i = 0; i < hrp_len; i++) { chk = bech32_polymod_step(chk) ^ (hrp[i] & 0x1f); } for (size_t i = 0; i < data_part_len; i++) { chk = bech32_polymod_step(chk) ^ values[i]; } if (chk != 1) return 0; size_t payload_len = data_part_len - 6; size_t decoded_len; if (!convert_bits(data, &decoded_len, 8, values, payload_len, 5, 0)) { return 0; } *data_len = decoded_len; return 1; } // TLV (Type-Length-Value) constants for structured data #define TLV_SPECIAL 0 #define TLV_RELAY 1 #define TLV_AUTHOR 2 #define TLV_KIND 3 #define TLV_CREATED_AT 4 #define TLV_IDENTIFIER 5 // Forward declarations for internal functions static int tlv_encode(const uint8_t* data, size_t data_len, uint8_t type, uint8_t** output, size_t* output_len); static int encode_structured_bech32(const char* hrp, const uint8_t* data, size_t data_len, char* output, size_t output_size); static int decode_structured_bech32(const char* input, const char* expected_hrp, uint8_t** data, size_t* data_len); // Utility function to duplicate string array (removed - not used) // Free string array static void free_string_array(char** array, int count) { if (!array) return; for (int i = 0; i < count; i++) { free(array[i]); } free(array); } // TLV encoding: Type (1 byte) + Length (1 byte) + Value static int tlv_encode(const uint8_t* data, size_t data_len, uint8_t type, uint8_t** output, size_t* output_len) { if (data_len > 255) return 0; // Length must fit in 1 byte *output_len = 2 + data_len; *output = malloc(*output_len); if (!*output) return 0; (*output)[0] = type; (*output)[1] = (uint8_t)data_len; memcpy(*output + 2, data, data_len); return 1; } // TLV decoding (removed - not used) // Encode structured data to bech32 static int encode_structured_bech32(const char* hrp, const uint8_t* data, size_t data_len, char* output, size_t output_size) { // For simple cases like note (32 bytes), use the existing key encoding if (strcmp(hrp, "note") == 0 && data_len == 32) { return nostr_key_to_bech32(data, "note", output); } uint8_t conv[256]; size_t conv_len; if (!convert_bits(conv, &conv_len, 5, data, data_len, 8, 1)) { return NOSTR_ERROR_INVALID_INPUT; } if (!bech32_encode(output, hrp, conv, conv_len)) { return NOSTR_ERROR_INVALID_INPUT; } if (strlen(output) >= output_size) { return NOSTR_ERROR_INVALID_INPUT; } return NOSTR_SUCCESS; } // Decode structured bech32 data static int decode_structured_bech32(const char* input, const char* expected_hrp, uint8_t** data, size_t* data_len) { // bech32_decode already converts from 5-bit to 8-bit internally *data = malloc(256); // Max size if (!*data) return NOSTR_ERROR_MEMORY_FAILED; if (!bech32_decode(input, expected_hrp, *data, data_len)) { free(*data); return NOSTR_ERROR_INVALID_INPUT; } return NOSTR_SUCCESS; } // Detect URI type from string nostr_uri_type_t nostr_detect_uri_type(const char* uri) { if (!uri) return NOSTR_URI_INVALID; // Check for nostr: prefix if (strncmp(uri, "nostr:", 6) != 0) { return NOSTR_URI_INVALID; } const char* bech32_part = uri + 6; // Check prefixes if (strncmp(bech32_part, "npub1", 5) == 0) return NOSTR_URI_NPUB; if (strncmp(bech32_part, "nsec1", 5) == 0) return NOSTR_URI_NSEC; if (strncmp(bech32_part, "note1", 5) == 0) return NOSTR_URI_NOTE; if (strncmp(bech32_part, "nprofile1", 9) == 0) return NOSTR_URI_NPROFILE; if (strncmp(bech32_part, "nevent1", 7) == 0) return NOSTR_URI_NEVENT; if (strncmp(bech32_part, "naddr1", 6) == 0) return NOSTR_URI_NADDR; return NOSTR_URI_INVALID; } // Free URI result resources void nostr_uri_result_free(nostr_uri_result_t* result) { if (!result) return; switch (result->type) { case NOSTR_URI_NPROFILE: free_string_array(result->data.nprofile.relays, result->data.nprofile.relay_count); break; case NOSTR_URI_NEVENT: free_string_array(result->data.nevent.relays, result->data.nevent.relay_count); free(result->data.nevent.author); free(result->data.nevent.kind); free(result->data.nevent.created_at); break; case NOSTR_URI_NADDR: free(result->data.naddr.identifier); free_string_array(result->data.naddr.relays, result->data.naddr.relay_count); break; default: break; } } // Main URI parsing function int nostr_parse_uri(const char* uri, nostr_uri_result_t* result) { if (!uri || !result) { return NOSTR_ERROR_INVALID_INPUT; } memset(result, 0, sizeof(nostr_uri_result_t)); nostr_uri_type_t type = nostr_detect_uri_type(uri); if (type == NOSTR_URI_INVALID) { return NOSTR_ERROR_INVALID_INPUT; } const char* bech32_part = uri + 6; // Skip "nostr:" result->type = type; int ret; switch (type) { case NOSTR_URI_NPUB: { ret = nostr_decode_npub(bech32_part, result->data.pubkey); break; } case NOSTR_URI_NSEC: { ret = nostr_decode_nsec(bech32_part, result->data.privkey); break; } case NOSTR_URI_NOTE: { // Note is similar to npub but with "note" prefix uint8_t* decoded; size_t decoded_len; ret = decode_structured_bech32(bech32_part, "note", &decoded, &decoded_len); if (ret == NOSTR_SUCCESS) { if (decoded_len == 32) { memcpy(result->data.event_id, decoded, 32); } else { ret = NOSTR_ERROR_INVALID_INPUT; } free(decoded); } break; } case NOSTR_URI_NPROFILE: { uint8_t* decoded; size_t decoded_len; ret = decode_structured_bech32(bech32_part, "nprofile", &decoded, &decoded_len); if (ret == NOSTR_SUCCESS) { ret = parse_nprofile_data(decoded, decoded_len, &result->data.nprofile); free(decoded); } break; } case NOSTR_URI_NEVENT: { uint8_t* decoded; size_t decoded_len; ret = decode_structured_bech32(bech32_part, "nevent", &decoded, &decoded_len); if (ret == NOSTR_SUCCESS) { ret = parse_nevent_data(decoded, decoded_len, &result->data.nevent); free(decoded); } break; } case NOSTR_URI_NADDR: { uint8_t* decoded; size_t decoded_len; ret = decode_structured_bech32(bech32_part, "naddr", &decoded, &decoded_len); if (ret == NOSTR_SUCCESS) { ret = parse_naddr_data(decoded, decoded_len, &result->data.naddr); free(decoded); } break; } default: ret = NOSTR_ERROR_INVALID_INPUT; break; } if (ret != NOSTR_SUCCESS) { nostr_uri_result_free(result); memset(result, 0, sizeof(nostr_uri_result_t)); } return ret; } // Parse nprofile structured data static int parse_nprofile_data(const uint8_t* data, size_t data_len, nostr_nprofile_t* nprofile) { size_t offset = 0; while (offset < data_len) { if (offset + 2 > data_len) return NOSTR_ERROR_INVALID_INPUT; uint8_t type = data[offset]; uint8_t length = data[offset + 1]; offset += 2; if (offset + length > data_len) return NOSTR_ERROR_INVALID_INPUT; switch (type) { case TLV_SPECIAL: // pubkey if (length != 32) return NOSTR_ERROR_INVALID_INPUT; memcpy(nprofile->pubkey, data + offset, 32); break; case TLV_RELAY: // relay URL { char* relay = malloc(length + 1); if (!relay) return NOSTR_ERROR_MEMORY_FAILED; memcpy(relay, data + offset, length); relay[length] = '\0'; char** new_relays = realloc(nprofile->relays, (nprofile->relay_count + 1) * sizeof(char*)); if (!new_relays) { free(relay); return NOSTR_ERROR_MEMORY_FAILED; } nprofile->relays = new_relays; nprofile->relays[nprofile->relay_count++] = relay; } break; default: // Ignore unknown types break; } offset += length; } return NOSTR_SUCCESS; } // Parse nevent structured data static int parse_nevent_data(const uint8_t* data, size_t data_len, nostr_nevent_t* nevent) { size_t offset = 0; while (offset < data_len) { if (offset + 2 > data_len) return NOSTR_ERROR_INVALID_INPUT; uint8_t type = data[offset]; uint8_t length = data[offset + 1]; offset += 2; if (offset + length > data_len) return NOSTR_ERROR_INVALID_INPUT; switch (type) { case TLV_SPECIAL: // event ID if (length != 32) return NOSTR_ERROR_INVALID_INPUT; memcpy(nevent->event_id, data + offset, 32); break; case TLV_RELAY: // relay URL { char* relay = malloc(length + 1); if (!relay) return NOSTR_ERROR_MEMORY_FAILED; memcpy(relay, data + offset, length); relay[length] = '\0'; char** new_relays = realloc(nevent->relays, (nevent->relay_count + 1) * sizeof(char*)); if (!new_relays) { free(relay); return NOSTR_ERROR_MEMORY_FAILED; } nevent->relays = new_relays; nevent->relays[nevent->relay_count++] = relay; } break; case TLV_AUTHOR: // author pubkey if (length != 32) return NOSTR_ERROR_INVALID_INPUT; nevent->author = malloc(32); if (!nevent->author) return NOSTR_ERROR_MEMORY_FAILED; memcpy(nevent->author, data + offset, 32); break; case TLV_KIND: // kind if (length != 4) return NOSTR_ERROR_INVALID_INPUT; nevent->kind = malloc(sizeof(int)); if (!nevent->kind) return NOSTR_ERROR_MEMORY_FAILED; *nevent->kind = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3]; break; case TLV_CREATED_AT: // created_at if (length != 8) return NOSTR_ERROR_INVALID_INPUT; nevent->created_at = malloc(sizeof(time_t)); if (!nevent->created_at) return NOSTR_ERROR_MEMORY_FAILED; *nevent->created_at = ((time_t)data[offset] << 56) | ((time_t)data[offset+1] << 48) | ((time_t)data[offset+2] << 40) | ((time_t)data[offset+3] << 32) | ((time_t)data[offset+4] << 24) | ((time_t)data[offset+5] << 16) | ((time_t)data[offset+6] << 8) | (time_t)data[offset+7]; break; default: // Ignore unknown types break; } offset += length; } return NOSTR_SUCCESS; } // Parse naddr structured data static int parse_naddr_data(const uint8_t* data, size_t data_len, nostr_naddr_t* naddr) { size_t offset = 0; while (offset < data_len) { if (offset + 2 > data_len) return NOSTR_ERROR_INVALID_INPUT; uint8_t type = data[offset]; uint8_t length = data[offset + 1]; offset += 2; if (offset + length > data_len) return NOSTR_ERROR_INVALID_INPUT; switch (type) { case TLV_IDENTIFIER: // identifier naddr->identifier = malloc(length + 1); if (!naddr->identifier) return NOSTR_ERROR_MEMORY_FAILED; memcpy(naddr->identifier, data + offset, length); naddr->identifier[length] = '\0'; break; case TLV_SPECIAL: // pubkey if (length != 32) return NOSTR_ERROR_INVALID_INPUT; memcpy(naddr->pubkey, data + offset, 32); break; case TLV_KIND: // kind if (length != 4) return NOSTR_ERROR_INVALID_INPUT; naddr->kind = (data[offset] << 24) | (data[offset+1] << 16) | (data[offset+2] << 8) | data[offset+3]; break; case TLV_RELAY: // relay URL { char* relay = malloc(length + 1); if (!relay) return NOSTR_ERROR_MEMORY_FAILED; memcpy(relay, data + offset, length); relay[length] = '\0'; char** new_relays = realloc(naddr->relays, (naddr->relay_count + 1) * sizeof(char*)); if (!new_relays) { free(relay); return NOSTR_ERROR_MEMORY_FAILED; } naddr->relays = new_relays; naddr->relays[naddr->relay_count++] = relay; } break; default: // Ignore unknown types break; } offset += length; } return NOSTR_SUCCESS; } // URI construction functions int nostr_build_uri_npub(const unsigned char* pubkey, char* output, size_t output_size) { if (!pubkey || !output || output_size < 70) { return NOSTR_ERROR_INVALID_INPUT; } char bech32[100]; int ret = nostr_key_to_bech32(pubkey, "npub", bech32); if (ret != NOSTR_SUCCESS) return ret; size_t len = strlen(bech32); if (len + 7 >= output_size) return NOSTR_ERROR_INVALID_INPUT; strcpy(output, "nostr:"); strcpy(output + 6, bech32); return NOSTR_SUCCESS; } int nostr_build_uri_nsec(const unsigned char* privkey, char* output, size_t output_size) { if (!privkey || !output || output_size < 70) { return NOSTR_ERROR_INVALID_INPUT; } char bech32[100]; int ret = nostr_key_to_bech32(privkey, "nsec", bech32); if (ret != NOSTR_SUCCESS) return ret; size_t len = strlen(bech32); if (len + 7 >= output_size) return NOSTR_ERROR_INVALID_INPUT; strcpy(output, "nostr:"); strcpy(output + 6, bech32); return NOSTR_SUCCESS; } // Helper to build URI with prefix static int build_uri_with_prefix(const char* bech32, char* output, size_t output_size) { size_t len = strlen(bech32); if (len + 7 >= output_size) return NOSTR_ERROR_INVALID_INPUT; strcpy(output, "nostr:"); strcpy(output + 6, bech32); return NOSTR_SUCCESS; } int nostr_build_uri_note(const unsigned char* event_id, char* output, size_t output_size) { if (!event_id || !output || output_size < 70) { return NOSTR_ERROR_INVALID_INPUT; } char bech32[100]; int ret = encode_structured_bech32("note", event_id, 32, bech32, sizeof(bech32)); if (ret != NOSTR_SUCCESS) return ret; return build_uri_with_prefix(bech32, output, output_size); } int nostr_build_uri_nprofile(const unsigned char* pubkey, const char** relays, int relay_count, char* output, size_t output_size) { if (!pubkey || !output) return NOSTR_ERROR_INVALID_INPUT; // Build TLV data uint8_t* data = NULL; size_t data_len = 0; // Add pubkey (special) uint8_t* pubkey_tlv; size_t pubkey_tlv_len; if (!tlv_encode(pubkey, 32, TLV_SPECIAL, &pubkey_tlv, &pubkey_tlv_len)) { return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + pubkey_tlv_len); if (!data) { free(pubkey_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, pubkey_tlv, pubkey_tlv_len); data_len += pubkey_tlv_len; free(pubkey_tlv); // Add relays for (int i = 0; i < relay_count; i++) { size_t relay_len = strlen(relays[i]); uint8_t* relay_tlv; size_t relay_tlv_len; if (!tlv_encode((uint8_t*)relays[i], relay_len, TLV_RELAY, &relay_tlv, &relay_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + relay_tlv_len); if (!data) { free(relay_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, relay_tlv, relay_tlv_len); data_len += relay_tlv_len; free(relay_tlv); } // Encode to bech32 char bech32[500]; int ret = encode_structured_bech32("nprofile", data, data_len, bech32, sizeof(bech32)); free(data); if (ret != NOSTR_SUCCESS) return ret; return build_uri_with_prefix(bech32, output, output_size); } int nostr_build_uri_nevent(const unsigned char* event_id, const char** relays, int relay_count, const unsigned char* author, int kind, time_t created_at, char* output, size_t output_size) { if (!event_id || !output) return NOSTR_ERROR_INVALID_INPUT; // Build TLV data uint8_t* data = NULL; size_t data_len = 0; // Add event_id (special) uint8_t* event_tlv; size_t event_tlv_len; if (!tlv_encode(event_id, 32, TLV_SPECIAL, &event_tlv, &event_tlv_len)) { return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + event_tlv_len); if (!data) { free(event_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, event_tlv, event_tlv_len); data_len += event_tlv_len; free(event_tlv); // Add relays for (int i = 0; i < relay_count; i++) { size_t relay_len = strlen(relays[i]); uint8_t* relay_tlv; size_t relay_tlv_len; if (!tlv_encode((uint8_t*)relays[i], relay_len, TLV_RELAY, &relay_tlv, &relay_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + relay_tlv_len); if (!data) { free(relay_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, relay_tlv, relay_tlv_len); data_len += relay_tlv_len; free(relay_tlv); } // Add author if provided if (author) { uint8_t* author_tlv; size_t author_tlv_len; if (!tlv_encode(author, 32, TLV_AUTHOR, &author_tlv, &author_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + author_tlv_len); if (!data) { free(author_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, author_tlv, author_tlv_len); data_len += author_tlv_len; free(author_tlv); } // Add kind if provided if (kind >= 0) { uint8_t kind_bytes[4]; kind_bytes[0] = (kind >> 24) & 0xFF; kind_bytes[1] = (kind >> 16) & 0xFF; kind_bytes[2] = (kind >> 8) & 0xFF; kind_bytes[3] = kind & 0xFF; uint8_t* kind_tlv; size_t kind_tlv_len; if (!tlv_encode(kind_bytes, 4, TLV_KIND, &kind_tlv, &kind_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + kind_tlv_len); if (!data) { free(kind_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, kind_tlv, kind_tlv_len); data_len += kind_tlv_len; free(kind_tlv); } // Add created_at if provided if (created_at > 0) { uint8_t time_bytes[8]; time_bytes[0] = (created_at >> 56) & 0xFF; time_bytes[1] = (created_at >> 48) & 0xFF; time_bytes[2] = (created_at >> 40) & 0xFF; time_bytes[3] = (created_at >> 32) & 0xFF; time_bytes[4] = (created_at >> 24) & 0xFF; time_bytes[5] = (created_at >> 16) & 0xFF; time_bytes[6] = (created_at >> 8) & 0xFF; time_bytes[7] = created_at & 0xFF; uint8_t* time_tlv; size_t time_tlv_len; if (!tlv_encode(time_bytes, 8, TLV_CREATED_AT, &time_tlv, &time_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + time_tlv_len); if (!data) { free(time_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, time_tlv, time_tlv_len); data_len += time_tlv_len; free(time_tlv); } // Encode to bech32 char bech32[1000]; int ret = encode_structured_bech32("nevent", data, data_len, bech32, sizeof(bech32)); free(data); if (ret != NOSTR_SUCCESS) return ret; return build_uri_with_prefix(bech32, output, output_size); } int nostr_build_uri_naddr(const char* identifier, const unsigned char* pubkey, int kind, const char** relays, int relay_count, char* output, size_t output_size) { if (!identifier || !pubkey || !output) return NOSTR_ERROR_INVALID_INPUT; // Build TLV data uint8_t* data = NULL; size_t data_len = 0; // Add identifier size_t id_len = strlen(identifier); uint8_t* id_tlv; size_t id_tlv_len; if (!tlv_encode((uint8_t*)identifier, id_len, TLV_IDENTIFIER, &id_tlv, &id_tlv_len)) { return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + id_tlv_len); if (!data) { free(id_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, id_tlv, id_tlv_len); data_len += id_tlv_len; free(id_tlv); // Add pubkey (special) uint8_t* pubkey_tlv; size_t pubkey_tlv_len; if (!tlv_encode(pubkey, 32, TLV_SPECIAL, &pubkey_tlv, &pubkey_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + pubkey_tlv_len); if (!data) { free(pubkey_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, pubkey_tlv, pubkey_tlv_len); data_len += pubkey_tlv_len; free(pubkey_tlv); // Add kind uint8_t kind_bytes[4]; kind_bytes[0] = (kind >> 24) & 0xFF; kind_bytes[1] = (kind >> 16) & 0xFF; kind_bytes[2] = (kind >> 8) & 0xFF; kind_bytes[3] = kind & 0xFF; uint8_t* kind_tlv; size_t kind_tlv_len; if (!tlv_encode(kind_bytes, 4, TLV_KIND, &kind_tlv, &kind_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + kind_tlv_len); if (!data) { free(kind_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, kind_tlv, kind_tlv_len); data_len += kind_tlv_len; free(kind_tlv); // Add relays for (int i = 0; i < relay_count; i++) { size_t relay_len = strlen(relays[i]); uint8_t* relay_tlv; size_t relay_tlv_len; if (!tlv_encode((uint8_t*)relays[i], relay_len, TLV_RELAY, &relay_tlv, &relay_tlv_len)) { free(data); return NOSTR_ERROR_INVALID_INPUT; } data = realloc(data, data_len + relay_tlv_len); if (!data) { free(relay_tlv); return NOSTR_ERROR_MEMORY_FAILED; } memcpy(data + data_len, relay_tlv, relay_tlv_len); data_len += relay_tlv_len; free(relay_tlv); } // Encode to bech32 char bech32[1000]; int ret = encode_structured_bech32("naddr", data, data_len, bech32, sizeof(bech32)); free(data); if (ret != NOSTR_SUCCESS) return ret; return build_uri_with_prefix(bech32, output, output_size); }