Files
damus/nostrdb/src/nostr_bech32.c
William Casarin d8e7b4707e nostrdb: nip19: add kind to naddr & nevent
Add support for type KIND for bech32-encoded entities naddr and nevent
as specified in NIP-19.

Co-authored-by: kernelkind <kernelkind@gmail.com>
Signed-off-by: William Casarin <jb55@jb55.com>
2025-08-11 16:40:01 -07:00

345 lines
7.4 KiB
C

//
// nostr_bech32.c
// damus
//
// Created by William Casarin on 2023-04-09.
//
#include "nostr_bech32.h"
#include <stdlib.h>
#include "cursor.h"
#include "str_block.h"
#include "ccan/endian/endian.h"
#include "nostrdb.h"
#include "bolt11/bech32.h"
#define MAX_TLVS 32
#define TLV_SPECIAL 0
#define TLV_RELAY 1
#define TLV_AUTHOR 2
#define TLV_KIND 3
#define TLV_KNOWN_TLVS 4
struct nostr_tlv {
unsigned char type;
unsigned char len;
const unsigned char *value;
};
static int parse_nostr_tlv(struct cursor *cur, struct nostr_tlv *tlv) {
// get the tlv tag
if (!cursor_pull_byte(cur, &tlv->type))
return 0;
if (tlv->type >= TLV_KNOWN_TLVS)
return 0;
// get the length
if (!cursor_pull_byte(cur, &tlv->len))
return 0;
// is the reported length greater then our buffer? if so fail
if (cur->p + tlv->len > cur->end)
return 0;
tlv->value = cur->p;
cur->p += tlv->len;
return 1;
}
int parse_nostr_bech32_type(const char *prefix, enum nostr_bech32_type *type) {
// Parse type
if (strncmp(prefix, "note", 4) == 0) {
*type = NOSTR_BECH32_NOTE;
return 4;
} else if (strncmp(prefix, "npub", 4) == 0) {
*type = NOSTR_BECH32_NPUB;
return 4;
} else if (strncmp(prefix, "nsec", 4) == 0) {
*type = NOSTR_BECH32_NSEC;
return 4;
} else if (strncmp(prefix, "nprofile", 8) == 0) {
*type = NOSTR_BECH32_NPROFILE;
return 8;
} else if (strncmp(prefix, "nevent", 6) == 0) {
*type = NOSTR_BECH32_NEVENT;
return 6;
} else if (strncmp(prefix, "nrelay", 6) == 0) {
*type = NOSTR_BECH32_NRELAY;
return 6;
} else if (strncmp(prefix, "naddr", 5) == 0) {
*type = NOSTR_BECH32_NADDR;
return 5;
}
return 0;
}
static int parse_nostr_bech32_note(struct cursor *cur, struct bech32_note *note) {
return pull_bytes(cur, 32, &note->event_id);
}
static int parse_nostr_bech32_npub(struct cursor *cur, struct bech32_npub *npub) {
return pull_bytes(cur, 32, &npub->pubkey);
}
static int parse_nostr_bech32_nsec(struct cursor *cur, struct bech32_nsec *nsec) {
return pull_bytes(cur, 32, &nsec->nsec);
}
static int add_relay(struct ndb_relays *relays, struct nostr_tlv *tlv)
{
struct ndb_str_block *str;
if (relays->num_relays + 1 > NDB_MAX_RELAYS)
return 0;
str = &relays->relays[relays->num_relays++];
str->str = (const char*)tlv->value;
str->len = tlv->len;
return 1;
}
static int decode_tlv_u32(const struct nostr_tlv *tlv, uint32_t *ret) {
if (tlv->len != 4) {
return 0;
}
beint32_t *be32_bytes = (beint32_t*)tlv->value;
*ret = be32_to_cpu(*be32_bytes);
return 1;
}
static int parse_nostr_bech32_nevent(struct cursor *cur, struct bech32_nevent *nevent) {
struct nostr_tlv tlv;
int i;
nevent->event_id = NULL;
nevent->pubkey = NULL;
nevent->relays.num_relays = 0;
for (i = 0; i < MAX_TLVS; i++) {
if (!parse_nostr_tlv(cur, &tlv))
break;
switch (tlv.type) {
case TLV_SPECIAL:
if (tlv.len != 32)
return 0;
nevent->event_id = tlv.value;
break;
case TLV_AUTHOR:
if (tlv.len != 32)
return 0;
nevent->pubkey = tlv.value;
break;
case TLV_RELAY:
add_relay(&nevent->relays, &tlv);
break;
case TLV_KIND:
if (decode_tlv_u32(&tlv, &nevent->kind)) {
nevent->has_kind = true;
}
break;
}
}
return nevent->event_id != NULL;
}
static int parse_nostr_bech32_naddr(struct cursor *cur, struct bech32_naddr *naddr) {
struct nostr_tlv tlv;
int i;
naddr->identifier.str = NULL;
naddr->identifier.len = 0;
naddr->pubkey = NULL;
naddr->has_kind = 0;
naddr->relays.num_relays = 0;
for (i = 0; i < MAX_TLVS; i++) {
if (!parse_nostr_tlv(cur, &tlv))
break;
switch (tlv.type) {
case TLV_SPECIAL:
naddr->identifier.str = (const char*)tlv.value;
naddr->identifier.len = tlv.len;
break;
case TLV_AUTHOR:
if (tlv.len != 32) return 0;
naddr->pubkey = tlv.value;
break;
case TLV_RELAY:
add_relay(&naddr->relays, &tlv);
break;
case TLV_KIND:
if (decode_tlv_u32(&tlv, &naddr->kind)) {
has_kind = true;
}
break;
}
}
return naddr->identifier.str != NULL;
}
static int parse_nostr_bech32_nprofile(struct cursor *cur, struct bech32_nprofile *nprofile) {
struct nostr_tlv tlv;
int i;
nprofile->pubkey = NULL;
nprofile->relays.num_relays = 0;
for (i = 0; i < MAX_TLVS; i++) {
if (!parse_nostr_tlv(cur, &tlv))
break;
switch (tlv.type) {
case TLV_SPECIAL:
if (tlv.len != 32) return 0;
nprofile->pubkey = tlv.value;
break;
case TLV_RELAY:
add_relay(&nprofile->relays, &tlv);
break;
}
}
return nprofile->pubkey != NULL;
}
static int parse_nostr_bech32_nrelay(struct cursor *cur, struct bech32_nrelay *nrelay) {
struct nostr_tlv tlv;
int i;
nrelay->relay.str = NULL;
nrelay->relay.len = 0;
for (i = 0; i < MAX_TLVS; i++) {
if (!parse_nostr_tlv(cur, &tlv))
break;
switch (tlv.type) {
case TLV_SPECIAL:
nrelay->relay.str = (const char*)tlv.value;
nrelay->relay.len = tlv.len;
break;
}
}
return nrelay->relay.str != NULL;
}
int parse_nostr_bech32_buffer(struct cursor *cur,
enum nostr_bech32_type type,
struct nostr_bech32 *obj)
{
obj->type = type;
switch (obj->type) {
case NOSTR_BECH32_NOTE:
if (!parse_nostr_bech32_note(cur, &obj->note))
return 0;
break;
case NOSTR_BECH32_NPUB:
if (!parse_nostr_bech32_npub(cur, &obj->npub))
return 0;
break;
case NOSTR_BECH32_NSEC:
if (!parse_nostr_bech32_nsec(cur, &obj->nsec))
return 0;
break;
case NOSTR_BECH32_NEVENT:
if (!parse_nostr_bech32_nevent(cur, &obj->nevent))
return 0;
break;
case NOSTR_BECH32_NADDR:
if (!parse_nostr_bech32_naddr(cur, &obj->naddr))
return 0;
break;
case NOSTR_BECH32_NPROFILE:
if (!parse_nostr_bech32_nprofile(cur, &obj->nprofile))
return 0;
break;
case NOSTR_BECH32_NRELAY:
if (!parse_nostr_bech32_nrelay(cur, &obj->nrelay))
return 0;
break;
}
return 1;
}
int parse_nostr_bech32_str(struct cursor *bech32, enum nostr_bech32_type *type) {
unsigned char *start = bech32->p;
unsigned char *data_start;
int n;
if (!(n = parse_nostr_bech32_type((const char *)bech32->p, type))) {
bech32->p = start;
return 0;
}
data_start = start + n;
if (!consume_until_non_alphanumeric(bech32, 1)) {
bech32->p = start;
return 0;
}
// must be at least 59 chars for the data part
//ndb_debug("bech32_data_size %ld '%c' '%c' '%.*s'\n", bech32->p - data_start, *(bech32->p-1), *data_start, (int)(bech32->p - data_start), data_start);
if (bech32->p - data_start < 59) {
bech32->p = start;
return 0;
}
return 1;
}
int parse_nostr_bech32(unsigned char *buf, int buflen,
const char *bech32_str, size_t bech32_len,
struct nostr_bech32 *obj) {
unsigned char *start;
size_t parsed_len, u5_out_len, u8_out_len;
enum nostr_bech32_type type;
#define MAX_PREFIX 8
struct cursor cur, bech32, u8;
make_cursor(buf, buf + buflen, &cur);
make_cursor((unsigned char*)bech32_str, (unsigned char*)bech32_str + bech32_len, &bech32);
start = bech32.p;
if (!parse_nostr_bech32_str(&bech32, &type))
return 0;
parsed_len = bech32.p - start;
// some random sanity checking
if (parsed_len < 10 || parsed_len > 10000)
return 0;
unsigned char *u5 = malloc(parsed_len);
char prefix[MAX_PREFIX];
if (bech32_decode_len(prefix, u5, &u5_out_len, (const char*)start,
parsed_len, MAX_PREFIX) == BECH32_ENCODING_NONE)
{
return 0;
}
if (!bech32_convert_bits(cur.p, &u8_out_len, 8, u5, u5_out_len, 5, 0))
return 0;
free(u5);
make_cursor(cur.p, cur.p + u8_out_len, &u8);
return parse_nostr_bech32_buffer(&u8, type, obj);
}