/* * NOSTR Core Library - NIP-019: Bech32-encoded Entities */ #include "nip019.h" #include "utils.h" #include #include #include #include #include "../nostr_core/nostr_common.h" #define BECH32_CONST 1 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 ^= BECH32_CONST; 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 != BECH32_CONST) 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; } int nostr_key_to_bech32(const unsigned char* key, const char* hrp, char* output) { if (!key || !hrp || !output) { return NOSTR_ERROR_INVALID_INPUT; } uint8_t conv[64]; size_t conv_len; if (!convert_bits(conv, &conv_len, 5, key, 32, 8, 1)) { return NOSTR_ERROR_CRYPTO_FAILED; } if (!bech32_encode(output, hrp, conv, conv_len)) { return NOSTR_ERROR_CRYPTO_FAILED; } return NOSTR_SUCCESS; } nostr_input_type_t nostr_detect_input_type(const char* input) { if (!input || strlen(input) == 0) { return NOSTR_INPUT_UNKNOWN; } size_t len = strlen(input); // Check for bech32 nsec if (len > 5 && strncmp(input, "nsec1", 5) == 0) { return NOSTR_INPUT_NSEC_BECH32; } // Check for hex nsec (64 characters, all hex) if (len == 64) { int is_hex = 1; for (size_t i = 0; i < len; i++) { if (!isxdigit(input[i])) { is_hex = 0; break; } } if (is_hex) { return NOSTR_INPUT_NSEC_HEX; } } // Check for mnemonic (space-separated words) int word_count = 0; char temp[1024]; strncpy(temp, input, sizeof(temp) - 1); temp[sizeof(temp) - 1] = '\0'; char* token = strtok(temp, " "); while (token != NULL) { word_count++; token = strtok(NULL, " "); } // BIP39 mnemonics are typically 12, 18, or 24 words if (word_count >= 12 && word_count <= 24) { return NOSTR_INPUT_MNEMONIC; } return NOSTR_INPUT_UNKNOWN; } int nostr_decode_nsec(const char* input, unsigned char* private_key) { if (!input || !private_key) { return NOSTR_ERROR_INVALID_INPUT; } nostr_input_type_t type = nostr_detect_input_type(input); if (type == NOSTR_INPUT_NSEC_HEX) { if (nostr_hex_to_bytes(input, private_key, 32) != NOSTR_SUCCESS) { return NOSTR_ERROR_INVALID_INPUT; } } else if (type == NOSTR_INPUT_NSEC_BECH32) { size_t decoded_len; if (!bech32_decode(input, "nsec", private_key, &decoded_len)) { return NOSTR_ERROR_INVALID_INPUT; } if (decoded_len != 32) { return NOSTR_ERROR_INVALID_INPUT; } } else { return NOSTR_ERROR_INVALID_INPUT; } // TODO: Add private key validation if crypto functions are available return NOSTR_SUCCESS; } int nostr_decode_npub(const char* input, unsigned char* public_key) { if (!input || !public_key) { return NOSTR_ERROR_INVALID_INPUT; } nostr_input_type_t type = nostr_detect_input_type(input); if (type == NOSTR_INPUT_NSEC_HEX) { // Actually public key hex if (nostr_hex_to_bytes(input, public_key, 32) != NOSTR_SUCCESS) { return NOSTR_ERROR_INVALID_INPUT; } } else if (strncmp(input, "npub1", 4) == 0) { // Bech32 npub size_t decoded_len; if (!bech32_decode(input, "npub", public_key, &decoded_len)) { return NOSTR_ERROR_INVALID_INPUT; } if (decoded_len != 32) { return NOSTR_ERROR_INVALID_INPUT; } } else { return NOSTR_ERROR_INVALID_INPUT; } return NOSTR_SUCCESS; }