855 lines
28 KiB
C
855 lines
28 KiB
C
/*
|
|
* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#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);
|
|
} |