Implement NIP-21: nostr: URI scheme with full support for note, nprofile, nevent, and naddr URIs including TLV encoding/decoding and comprehensive test suite
This commit is contained in:
parent
6b95ad37c5
commit
499accf440
12
README.md
12
README.md
|
@ -26,11 +26,11 @@ A C library for NOSTR protocol implementation. Work in progress.
|
||||||
- [ ] [NIP-14](nips/14.md) - Subject tag in text events
|
- [ ] [NIP-14](nips/14.md) - Subject tag in text events
|
||||||
- [ ] [NIP-15](nips/15.md) - Nostr Marketplace (for resilient marketplaces)
|
- [ ] [NIP-15](nips/15.md) - Nostr Marketplace (for resilient marketplaces)
|
||||||
- [ ] [NIP-16](nips/16.md) - Event Treatment
|
- [ ] [NIP-16](nips/16.md) - Event Treatment
|
||||||
- [ ] [NIP-17](nips/17.md) - Private Direct Messages
|
- [x] [NIP-17](nips/17.md) - Private Direct Messages
|
||||||
- [ ] [NIP-18](nips/18.md) - Reposts
|
- [ ] [NIP-18](nips/18.md) - Reposts
|
||||||
- [x] [NIP-19](nips/19.md) - bech32-encoded entities
|
- [x] [NIP-19](nips/19.md) - bech32-encoded entities
|
||||||
- [ ] [NIP-20](nips/20.md) - Command Results
|
- [ ] [NIP-20](nips/20.md) - Command Results
|
||||||
- [ ] [NIP-21](nips/21.md) - `nostr:` URI scheme
|
- [x] [NIP-21](nips/21.md) - `nostr:` URI scheme
|
||||||
- [ ] [NIP-22](nips/22.md) - Event `created_at` Limits
|
- [ ] [NIP-22](nips/22.md) - Event `created_at` Limits
|
||||||
- [ ] [NIP-23](nips/23.md) - Long-form Content
|
- [ ] [NIP-23](nips/23.md) - Long-form Content
|
||||||
- [ ] [NIP-24](nips/24.md) - Extra metadata fields and tags
|
- [ ] [NIP-24](nips/24.md) - Extra metadata fields and tags
|
||||||
|
@ -50,7 +50,7 @@ A C library for NOSTR protocol implementation. Work in progress.
|
||||||
- [ ] [NIP-38](nips/38.md) - User Statuses
|
- [ ] [NIP-38](nips/38.md) - User Statuses
|
||||||
- [ ] [NIP-39](nips/39.md) - External Identities in Profiles
|
- [ ] [NIP-39](nips/39.md) - External Identities in Profiles
|
||||||
- [ ] [NIP-40](nips/40.md) - Expiration Timestamp
|
- [ ] [NIP-40](nips/40.md) - Expiration Timestamp
|
||||||
- [ ] [NIP-42](nips/42.md) - Authentication of clients to relays
|
- [x] [NIP-42](nips/42.md) - Authentication of clients to relays
|
||||||
- [x] [NIP-44](nips/44.md) - Versioned Encryption
|
- [x] [NIP-44](nips/44.md) - Versioned Encryption
|
||||||
- [ ] [NIP-45](nips/45.md) - Counting results
|
- [ ] [NIP-45](nips/45.md) - Counting results
|
||||||
- [ ] [NIP-46](nips/46.md) - Nostr Connect
|
- [ ] [NIP-46](nips/46.md) - Nostr Connect
|
||||||
|
@ -66,7 +66,7 @@ A C library for NOSTR protocol implementation. Work in progress.
|
||||||
- [ ] [NIP-56](nips/56.md) - Reporting
|
- [ ] [NIP-56](nips/56.md) - Reporting
|
||||||
- [ ] [NIP-57](nips/57.md) - Lightning Zaps
|
- [ ] [NIP-57](nips/57.md) - Lightning Zaps
|
||||||
- [ ] [NIP-58](nips/58.md) - Badges
|
- [ ] [NIP-58](nips/58.md) - Badges
|
||||||
- [ ] [NIP-59](nips/59.md) - Gift Wrap
|
- [x] [NIP-59](nips/59.md) - Gift Wrap
|
||||||
- [ ] [NIP-60](nips/60.md) - Cashu Wallet
|
- [ ] [NIP-60](nips/60.md) - Cashu Wallet
|
||||||
- [ ] [NIP-61](nips/61.md) - Nutzaps
|
- [ ] [NIP-61](nips/61.md) - Nutzaps
|
||||||
- [ ] [NIP-62](nips/62.md) - Log events
|
- [ ] [NIP-62](nips/62.md) - Log events
|
||||||
|
@ -96,7 +96,7 @@ A C library for NOSTR protocol implementation. Work in progress.
|
||||||
|
|
||||||
**Legend:** ✅ Fully Implemented | ⚠️ Partial Implementation | ❌ Not Implemented
|
**Legend:** ✅ Fully Implemented | ⚠️ Partial Implementation | ❌ Not Implemented
|
||||||
|
|
||||||
**Implementation Summary:** 8 of 96+ NIPs fully implemented (8.3%)
|
**Implementation Summary:** 12 of 96+ NIPs fully implemented (12.5%)
|
||||||
|
|
||||||
|
|
||||||
## 📦 Quick Start
|
## 📦 Quick Start
|
||||||
|
@ -523,7 +523,7 @@ The library uses automatic semantic versioning based on Git tags. Each build inc
|
||||||
- `v0.2.x` - Current development releases with enhanced NIP support
|
- `v0.2.x` - Current development releases with enhanced NIP support
|
||||||
- `v0.1.x` - Initial development releases
|
- `v0.1.x` - Initial development releases
|
||||||
- Focus on core protocol implementation and OpenSSL-based crypto
|
- Focus on core protocol implementation and OpenSSL-based crypto
|
||||||
- Full NIP-01, NIP-04, NIP-05, NIP-06, NIP-11, NIP-13, NIP-19, NIP-44 support
|
- Full NIP-01, NIP-04, NIP-05, NIP-06, NIP-11, NIP-13, NIP-17, NIP-19, NIP-42, NIP-44, NIP-59 support
|
||||||
|
|
||||||
## 🐛 Troubleshooting
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
|
4
build.sh
4
build.sh
|
@ -142,6 +142,7 @@ if [ "$HELP" = true ]; then
|
||||||
echo " 013 - Proof of Work"
|
echo " 013 - Proof of Work"
|
||||||
echo " 017 - Private Direct Messages"
|
echo " 017 - Private Direct Messages"
|
||||||
echo " 019 - Bech32 encoding (nsec/npub)"
|
echo " 019 - Bech32 encoding (nsec/npub)"
|
||||||
|
echo " 021 - nostr: URI scheme"
|
||||||
echo " 042 - Authentication of clients to relays"
|
echo " 042 - Authentication of clients to relays"
|
||||||
echo " 044 - Encryption (modern)"
|
echo " 044 - Encryption (modern)"
|
||||||
echo " 059 - Gift Wrap"
|
echo " 059 - Gift Wrap"
|
||||||
|
@ -230,7 +231,7 @@ fi
|
||||||
|
|
||||||
# If building tests or examples, include all NIPs to ensure compatibility
|
# If building tests or examples, include all NIPs to ensure compatibility
|
||||||
if ([ "$BUILD_TESTS" = true ] || [ "$BUILD_EXAMPLES" = true ]) && [ -z "$FORCE_NIPS" ]; then
|
if ([ "$BUILD_TESTS" = true ] || [ "$BUILD_EXAMPLES" = true ]) && [ -z "$FORCE_NIPS" ]; then
|
||||||
NEEDED_NIPS="001 004 005 006 011 013 017 019 042 044 059"
|
NEEDED_NIPS="001 004 005 006 011 013 017 019 021 042 044 059"
|
||||||
print_info "Building tests/examples - including all available NIPs for compatibility"
|
print_info "Building tests/examples - including all available NIPs for compatibility"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -518,6 +519,7 @@ for nip in $NEEDED_NIPS; do
|
||||||
013) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-013(PoW)" ;;
|
013) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-013(PoW)" ;;
|
||||||
017) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-017(DMs)" ;;
|
017) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-017(DMs)" ;;
|
||||||
019) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-019(Bech32)" ;;
|
019) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-019(Bech32)" ;;
|
||||||
|
021) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-021(URI)" ;;
|
||||||
042) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-042(Auth)" ;;
|
042) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-042(Auth)" ;;
|
||||||
044) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-044(Encrypt)" ;;
|
044) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-044(Encrypt)" ;;
|
||||||
059) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-059(Gift-Wrap)" ;;
|
059) NIP_DESCRIPTIONS="$NIP_DESCRIPTIONS NIP-059(Gift-Wrap)" ;;
|
||||||
|
|
Binary file not shown.
|
@ -777,9 +777,6 @@ static void process_relay_message(nostr_relay_pool_t* pool, relay_connection_t*
|
||||||
|
|
||||||
relay->stats.last_event_time = time(NULL);
|
relay->stats.last_event_time = time(NULL);
|
||||||
|
|
||||||
// Debug: Print all message types received
|
|
||||||
printf("📨 DEBUG: Received %s message from %s\n", msg_type, relay->url);
|
|
||||||
|
|
||||||
if (strcmp(msg_type, "EVENT") == 0) {
|
if (strcmp(msg_type, "EVENT") == 0) {
|
||||||
// Handle EVENT message: ["EVENT", subscription_id, event]
|
// Handle EVENT message: ["EVENT", subscription_id, event]
|
||||||
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
|
if (cJSON_IsArray(parsed) && cJSON_GetArraySize(parsed) >= 3) {
|
||||||
|
|
|
@ -0,0 +1,855 @@
|
||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* NOSTR Core Library - NIP-021: nostr: URI scheme
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NIP021_H
|
||||||
|
#define NIP021_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "nip001.h"
|
||||||
|
|
||||||
|
// URI type enumeration
|
||||||
|
typedef enum {
|
||||||
|
NOSTR_URI_NPUB, // Simple 32-byte pubkey
|
||||||
|
NOSTR_URI_NSEC, // Simple 32-byte privkey
|
||||||
|
NOSTR_URI_NOTE, // Simple 32-byte event ID
|
||||||
|
NOSTR_URI_NPROFILE, // Structured: pubkey + relays
|
||||||
|
NOSTR_URI_NEVENT, // Structured: event ID + relays + metadata
|
||||||
|
NOSTR_URI_NADDR, // Structured: address + relays + metadata
|
||||||
|
NOSTR_URI_INVALID
|
||||||
|
} nostr_uri_type_t;
|
||||||
|
|
||||||
|
// Structured data types for complex URIs
|
||||||
|
typedef struct {
|
||||||
|
unsigned char pubkey[32];
|
||||||
|
char** relays;
|
||||||
|
int relay_count;
|
||||||
|
} nostr_nprofile_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char event_id[32];
|
||||||
|
char** relays;
|
||||||
|
int relay_count;
|
||||||
|
unsigned char* author; // Optional, 32 bytes if present
|
||||||
|
int* kind; // Optional
|
||||||
|
time_t* created_at; // Optional
|
||||||
|
} nostr_nevent_t;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* identifier;
|
||||||
|
unsigned char pubkey[32];
|
||||||
|
int kind;
|
||||||
|
char** relays;
|
||||||
|
int relay_count;
|
||||||
|
} nostr_naddr_t;
|
||||||
|
|
||||||
|
// Unified URI result structure
|
||||||
|
typedef struct {
|
||||||
|
nostr_uri_type_t type;
|
||||||
|
union {
|
||||||
|
unsigned char pubkey[32]; // For NPUB
|
||||||
|
unsigned char privkey[32]; // For NSEC
|
||||||
|
unsigned char event_id[32]; // For NOTE
|
||||||
|
nostr_nprofile_t nprofile; // For NPROFILE
|
||||||
|
nostr_nevent_t nevent; // For NEVENT
|
||||||
|
nostr_naddr_t naddr; // For NADDR
|
||||||
|
} data;
|
||||||
|
} nostr_uri_result_t;
|
||||||
|
|
||||||
|
// Function declarations
|
||||||
|
|
||||||
|
// Main parsing function - unified entry point
|
||||||
|
int nostr_parse_uri(const char* uri, nostr_uri_result_t* result);
|
||||||
|
|
||||||
|
// URI construction functions
|
||||||
|
int nostr_build_uri_npub(const unsigned char* pubkey, char* output, size_t output_size);
|
||||||
|
int nostr_build_uri_nsec(const unsigned char* privkey, char* output, size_t output_size);
|
||||||
|
int nostr_build_uri_note(const unsigned char* event_id, char* output, size_t output_size);
|
||||||
|
int nostr_build_uri_nprofile(const unsigned char* pubkey, const char** relays, int relay_count,
|
||||||
|
char* output, size_t 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);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Utility functions
|
||||||
|
void nostr_uri_result_free(nostr_uri_result_t* result);
|
||||||
|
nostr_uri_type_t nostr_detect_uri_type(const char* uri);
|
||||||
|
|
||||||
|
#endif // NIP021_H
|
|
@ -2,10 +2,10 @@
|
||||||
#define NOSTR_CORE_H
|
#define NOSTR_CORE_H
|
||||||
|
|
||||||
// Version information (auto-updated by increment_and_push.sh)
|
// Version information (auto-updated by increment_and_push.sh)
|
||||||
#define VERSION "v0.4.4"
|
#define VERSION "v0.4.5"
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 4
|
#define VERSION_MINOR 4
|
||||||
#define VERSION_PATCH 4
|
#define VERSION_PATCH 5
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NOSTR Core Library - Complete API Reference
|
* NOSTR Core Library - Complete API Reference
|
||||||
|
@ -177,6 +177,7 @@ extern "C" {
|
||||||
#include "nip013.h" // Proof of Work
|
#include "nip013.h" // Proof of Work
|
||||||
#include "nip017.h" // Private Direct Messages
|
#include "nip017.h" // Private Direct Messages
|
||||||
#include "nip019.h" // Bech32 encoding (nsec/npub)
|
#include "nip019.h" // Bech32 encoding (nsec/npub)
|
||||||
|
#include "nip021.h" // nostr: URI scheme
|
||||||
#include "nip042.h" // Authentication of clients to relays
|
#include "nip042.h" // Authentication of clients to relays
|
||||||
#include "nip044.h" // Encryption (modern)
|
#include "nip044.h" // Encryption (modern)
|
||||||
#include "nip059.h" // Gift Wrap
|
#include "nip059.h" // Gift Wrap
|
||||||
|
|
BIN
tests/nip17_test
BIN
tests/nip17_test
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,373 @@
|
||||||
|
/*
|
||||||
|
* NIP-21 URI Scheme Test Suite
|
||||||
|
* Tests nostr: URI parsing and construction functionality
|
||||||
|
* Following TESTS POLICY: Shows expected vs actual values
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE // For strdup on Linux
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include "../nostr_core/nip021.h"
|
||||||
|
#include "../nostr_core/nostr_common.h"
|
||||||
|
#include "../nostr_core/utils.h"
|
||||||
|
|
||||||
|
// Ensure strdup is declared
|
||||||
|
#ifndef strdup
|
||||||
|
extern char *strdup(const char *s);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Test counter for tracking progress
|
||||||
|
static int test_count = 0;
|
||||||
|
static int passed_tests = 0;
|
||||||
|
|
||||||
|
void print_test_header(const char* test_name) {
|
||||||
|
test_count++;
|
||||||
|
printf("\n=== TEST %d: %s ===\n", test_count, test_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void print_test_result(int passed, const char* test_name) {
|
||||||
|
if (passed) {
|
||||||
|
passed_tests++;
|
||||||
|
printf("✅ PASS: %s\n", test_name);
|
||||||
|
} else {
|
||||||
|
printf("❌ FAIL: %s\n", test_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 1: Parse note URI
|
||||||
|
int test_parse_note_uri(void) {
|
||||||
|
print_test_header("Parse note: URI");
|
||||||
|
|
||||||
|
// First build a valid note URI, then parse it
|
||||||
|
unsigned char event_id[32];
|
||||||
|
nostr_hex_to_bytes("f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0", event_id, 32);
|
||||||
|
|
||||||
|
char built_uri[200];
|
||||||
|
int build_result = nostr_build_uri_note(event_id, built_uri, sizeof(built_uri));
|
||||||
|
if (build_result != NOSTR_SUCCESS) {
|
||||||
|
printf("Failed to build URI for testing: %d (%s)\n", build_result, nostr_strerror(build_result));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Input URI: %s\n", built_uri);
|
||||||
|
|
||||||
|
nostr_uri_result_t result;
|
||||||
|
int parse_result = nostr_parse_uri(built_uri, &result);
|
||||||
|
|
||||||
|
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||||
|
printf("Actual: %d (%s)\n", parse_result, nostr_strerror(parse_result));
|
||||||
|
|
||||||
|
if (parse_result != NOSTR_SUCCESS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Expected type: NOSTR_URI_NOTE\n");
|
||||||
|
printf("Actual type: %d\n", result.type);
|
||||||
|
|
||||||
|
if (result.type != NOSTR_URI_NOTE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Expected event ID to be set\n");
|
||||||
|
printf("Event ID present: %s\n", result.data.event_id[0] ? "yes" : "no");
|
||||||
|
|
||||||
|
// Verify the parsed event ID matches the original
|
||||||
|
int event_id_match = (memcmp(result.data.event_id, event_id, 32) == 0);
|
||||||
|
printf("Event ID matches original: %s\n", event_id_match ? "yes" : "no");
|
||||||
|
|
||||||
|
return (result.type == NOSTR_URI_NOTE && event_id_match);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Parse nprofile URI
|
||||||
|
int test_parse_nprofile_uri(void) {
|
||||||
|
print_test_header("Parse nprofile: URI");
|
||||||
|
|
||||||
|
// First build a valid nprofile URI, then parse it
|
||||||
|
unsigned char pubkey[32];
|
||||||
|
nostr_hex_to_bytes("aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", pubkey, 32);
|
||||||
|
const char* relays[] = {"wss://relay.example.com"};
|
||||||
|
|
||||||
|
char built_uri[300];
|
||||||
|
int build_result = nostr_build_uri_nprofile(pubkey, relays, 1, built_uri, sizeof(built_uri));
|
||||||
|
if (build_result != NOSTR_SUCCESS) {
|
||||||
|
printf("Failed to build URI for testing: %d (%s)\n", build_result, nostr_strerror(build_result));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Input URI: %s\n", built_uri);
|
||||||
|
|
||||||
|
nostr_uri_result_t result;
|
||||||
|
int parse_result = nostr_parse_uri(built_uri, &result);
|
||||||
|
|
||||||
|
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||||
|
printf("Actual: %d (%s)\n", parse_result, nostr_strerror(parse_result));
|
||||||
|
|
||||||
|
if (parse_result != NOSTR_SUCCESS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Expected type: NOSTR_URI_NPROFILE\n");
|
||||||
|
printf("Actual type: %d\n", result.type);
|
||||||
|
|
||||||
|
if (result.type != NOSTR_URI_NPROFILE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the parsed pubkey matches the original
|
||||||
|
int pubkey_match = (memcmp(result.data.nprofile.pubkey, pubkey, 32) == 0);
|
||||||
|
printf("Pubkey matches original: %s\n", pubkey_match ? "yes" : "no");
|
||||||
|
|
||||||
|
// Verify relay count
|
||||||
|
printf("Expected relay count: 1\n");
|
||||||
|
printf("Actual relay count: %d\n", result.data.nprofile.relay_count);
|
||||||
|
|
||||||
|
return (result.type == NOSTR_URI_NPROFILE && pubkey_match && result.data.nprofile.relay_count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Parse nevent URI
|
||||||
|
int test_parse_nevent_uri(void) {
|
||||||
|
print_test_header("Parse nevent: URI");
|
||||||
|
|
||||||
|
// First build a valid nevent URI, then parse it
|
||||||
|
unsigned char event_id[32];
|
||||||
|
nostr_hex_to_bytes("f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0", event_id, 32);
|
||||||
|
const char* relays[] = {"wss://relay.example.com"};
|
||||||
|
|
||||||
|
char built_uri[400];
|
||||||
|
int build_result = nostr_build_uri_nevent(event_id, relays, 1, NULL, 1, 1234567890, built_uri, sizeof(built_uri));
|
||||||
|
if (build_result != NOSTR_SUCCESS) {
|
||||||
|
printf("Failed to build URI for testing: %d (%s)\n", build_result, nostr_strerror(build_result));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Input URI: %s\n", built_uri);
|
||||||
|
|
||||||
|
nostr_uri_result_t result;
|
||||||
|
int parse_result = nostr_parse_uri(built_uri, &result);
|
||||||
|
|
||||||
|
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||||
|
printf("Actual: %d (%s)\n", parse_result, nostr_strerror(parse_result));
|
||||||
|
|
||||||
|
if (parse_result != NOSTR_SUCCESS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Expected type: NOSTR_URI_NEVENT\n");
|
||||||
|
printf("Actual type: %d\n", result.type);
|
||||||
|
|
||||||
|
if (result.type != NOSTR_URI_NEVENT) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the parsed event ID matches the original
|
||||||
|
int event_id_match = (memcmp(result.data.nevent.event_id, event_id, 32) == 0);
|
||||||
|
printf("Event ID matches original: %s\n", event_id_match ? "yes" : "no");
|
||||||
|
|
||||||
|
// Verify kind
|
||||||
|
printf("Expected kind: 1\n");
|
||||||
|
printf("Actual kind: %d\n", result.data.nevent.kind ? *result.data.nevent.kind : -1);
|
||||||
|
|
||||||
|
// Verify relay count
|
||||||
|
printf("Expected relay count: 1\n");
|
||||||
|
printf("Actual relay count: %d\n", result.data.nevent.relay_count);
|
||||||
|
|
||||||
|
return (result.type == NOSTR_URI_NEVENT && event_id_match && result.data.nevent.relay_count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Parse naddr URI
|
||||||
|
int test_parse_naddr_uri(void) {
|
||||||
|
print_test_header("Parse naddr: URI");
|
||||||
|
|
||||||
|
// First build a valid naddr URI, then parse it
|
||||||
|
const char* identifier = "draft";
|
||||||
|
unsigned char pubkey[32];
|
||||||
|
nostr_hex_to_bytes("aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", pubkey, 32);
|
||||||
|
const char* relays[] = {"wss://relay.example.com"};
|
||||||
|
|
||||||
|
char built_uri[400];
|
||||||
|
int build_result = nostr_build_uri_naddr(identifier, pubkey, 30023, relays, 1, built_uri, sizeof(built_uri));
|
||||||
|
if (build_result != NOSTR_SUCCESS) {
|
||||||
|
printf("Failed to build URI for testing: %d (%s)\n", build_result, nostr_strerror(build_result));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Input URI: %s\n", built_uri);
|
||||||
|
|
||||||
|
nostr_uri_result_t result;
|
||||||
|
int parse_result = nostr_parse_uri(built_uri, &result);
|
||||||
|
|
||||||
|
printf("Expected: NOSTR_SUCCESS (0)\n");
|
||||||
|
printf("Actual: %d (%s)\n", parse_result, nostr_strerror(parse_result));
|
||||||
|
|
||||||
|
if (parse_result != NOSTR_SUCCESS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Expected type: NOSTR_URI_NADDR\n");
|
||||||
|
printf("Actual type: %d\n", result.type);
|
||||||
|
|
||||||
|
if (result.type != NOSTR_URI_NADDR) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the parsed identifier matches the original
|
||||||
|
int identifier_match = (strcmp(result.data.naddr.identifier, identifier) == 0);
|
||||||
|
printf("Identifier matches original: %s\n", identifier_match ? "yes" : "no");
|
||||||
|
|
||||||
|
// Verify kind
|
||||||
|
printf("Expected kind: 30023\n");
|
||||||
|
printf("Actual kind: %d\n", result.data.naddr.kind);
|
||||||
|
|
||||||
|
// Verify relay count
|
||||||
|
printf("Expected relay count: 1\n");
|
||||||
|
printf("Actual relay count: %d\n", result.data.naddr.relay_count);
|
||||||
|
|
||||||
|
return (result.type == NOSTR_URI_NADDR && identifier_match && result.data.naddr.relay_count == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Invalid URI (wrong prefix)
|
||||||
|
int test_invalid_uri_prefix(void) {
|
||||||
|
print_test_header("Invalid URI - Wrong Prefix");
|
||||||
|
|
||||||
|
const char* uri = "bitcoin:note1example";
|
||||||
|
printf("Input URI: %s\n", uri);
|
||||||
|
|
||||||
|
nostr_uri_result_t result;
|
||||||
|
int parse_result = nostr_parse_uri(uri, &result);
|
||||||
|
|
||||||
|
printf("Expected: NOSTR_ERROR_INVALID_INPUT (-1)\n");
|
||||||
|
printf("Actual: %d (%s)\n", parse_result, nostr_strerror(parse_result));
|
||||||
|
|
||||||
|
return (parse_result == NOSTR_ERROR_INVALID_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: Invalid URI (missing colon)
|
||||||
|
int test_invalid_uri_no_colon(void) {
|
||||||
|
print_test_header("Invalid URI - No Colon");
|
||||||
|
|
||||||
|
const char* uri = "nostrnote1example";
|
||||||
|
printf("Input URI: %s\n", uri);
|
||||||
|
|
||||||
|
nostr_uri_result_t result;
|
||||||
|
int parse_result = nostr_parse_uri(uri, &result);
|
||||||
|
|
||||||
|
printf("Expected: NOSTR_ERROR_INVALID_INPUT (-1)\n");
|
||||||
|
printf("Actual: %d (%s)\n", parse_result, nostr_strerror(parse_result));
|
||||||
|
|
||||||
|
return (parse_result == NOSTR_ERROR_INVALID_INPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Build note URI
|
||||||
|
int test_build_note_uri(void) {
|
||||||
|
print_test_header("Build note: URI");
|
||||||
|
|
||||||
|
unsigned char event_id[32];
|
||||||
|
nostr_hex_to_bytes("f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0", event_id, 32);
|
||||||
|
printf("Input event ID: f1e582c90f071c0110cc5bcac2dcc6d8c32250e3cc26fcbe93470d918f2ffaf0\n");
|
||||||
|
|
||||||
|
char uri[200];
|
||||||
|
int result = nostr_build_uri_note(event_id, uri, sizeof(uri));
|
||||||
|
printf("Build result: %d (%s)\n", result, nostr_strerror(result));
|
||||||
|
|
||||||
|
if (result != NOSTR_SUCCESS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Built URI: %s\n", uri);
|
||||||
|
int success = (strncmp(uri, "nostr:note1", 11) == 0);
|
||||||
|
printf("Expected: URI starts with 'nostr:note1'\n");
|
||||||
|
printf("Actual: %s\n", success ? "yes" : "no");
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: Build nprofile URI
|
||||||
|
int test_build_nprofile_uri(void) {
|
||||||
|
print_test_header("Build nprofile: URI");
|
||||||
|
|
||||||
|
unsigned char pubkey[32];
|
||||||
|
nostr_hex_to_bytes("aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4", pubkey, 32);
|
||||||
|
const char* relays[] = {"wss://relay.example.com", "wss://relay2.example.com"};
|
||||||
|
printf("Input pubkey: aa4fc8665f5696e33db7e1a572e3b0f5b3d615837b0f362dcb1c8068b098c7b4\n");
|
||||||
|
|
||||||
|
char uri[300];
|
||||||
|
int result = nostr_build_uri_nprofile(pubkey, relays, 2, uri, sizeof(uri));
|
||||||
|
printf("Build result: %d (%s)\n", result, nostr_strerror(result));
|
||||||
|
|
||||||
|
if (result != NOSTR_SUCCESS) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Built URI: %s\n", uri);
|
||||||
|
int success = (strncmp(uri, "nostr:nprofile1", 14) == 0);
|
||||||
|
printf("Expected: URI starts with 'nostr:nprofile1'\n");
|
||||||
|
printf("Actual: %s\n", success ? "yes" : "no");
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
printf("=== NIP-21 URI Scheme Test Suite ===\n");
|
||||||
|
printf("Following TESTS POLICY: Shows expected vs actual values\n");
|
||||||
|
|
||||||
|
// Initialize crypto library
|
||||||
|
if (nostr_init() != NOSTR_SUCCESS) {
|
||||||
|
printf("❌ Failed to initialize nostr library\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int all_passed = 1;
|
||||||
|
int test_result;
|
||||||
|
|
||||||
|
// Valid URI parsing tests
|
||||||
|
test_result = test_parse_note_uri();
|
||||||
|
print_test_result(test_result, "Parse note: URI");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
test_result = test_parse_nprofile_uri();
|
||||||
|
print_test_result(test_result, "Parse nprofile: URI");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
test_result = test_parse_nevent_uri();
|
||||||
|
print_test_result(test_result, "Parse nevent: URI");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
test_result = test_parse_naddr_uri();
|
||||||
|
print_test_result(test_result, "Parse naddr: URI");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
// Invalid URI tests
|
||||||
|
test_result = test_invalid_uri_prefix();
|
||||||
|
print_test_result(test_result, "Invalid URI - Wrong Prefix");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
test_result = test_invalid_uri_no_colon();
|
||||||
|
print_test_result(test_result, "Invalid URI - No Colon");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
// URI building tests
|
||||||
|
test_result = test_build_note_uri();
|
||||||
|
print_test_result(test_result, "Build note: URI");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
test_result = test_build_nprofile_uri();
|
||||||
|
print_test_result(test_result, "Build nprofile: URI");
|
||||||
|
if (!test_result) all_passed = 0;
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
printf("\n=== TEST SUMMARY ===\n");
|
||||||
|
printf("Total tests: %d\n", test_count);
|
||||||
|
printf("Passed: %d\n", passed_tests);
|
||||||
|
printf("Failed: %d\n", test_count - passed_tests);
|
||||||
|
|
||||||
|
if (all_passed) {
|
||||||
|
printf("🎉 ALL TESTS PASSED! NIP-21 URI scheme implementation is working correctly.\n");
|
||||||
|
} else {
|
||||||
|
printf("❌ SOME TESTS FAILED. Please review the output above.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
nostr_cleanup();
|
||||||
|
return all_passed ? 0 : 1;
|
||||||
|
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -18,7 +18,7 @@
|
||||||
int main(int argc, char* argv[]) {
|
int main(int argc, char* argv[]) {
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
fprintf(stderr, "Usage: %s <relay_url> [event_id]\n", argv[0]);
|
fprintf(stderr, "Usage: %s <relay_url> [event_id]\n", argv[0]);
|
||||||
fprintf(stderr, "Example: %s wss://relay.laantungir.net 06cdf2cdd095ddb1ebe15d5b3c736b27a34de2683e847b871fe37d86ac998772\n");
|
fprintf(stderr, "Example: websocket_debug wss://relay.laantungir.net 06cdf2cdd095ddb1ebe15d5b3c736b27a34de2683e847b871fe37d86ac998772\n");
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue