Last version before deleting Makefile and CmakeLists.txt
This commit is contained in:
400
nostr_core/crypto/nostr_aes.c
Normal file
400
nostr_core/crypto/nostr_aes.c
Normal file
@@ -0,0 +1,400 @@
|
||||
/*
|
||||
* NOSTR AES Implementation
|
||||
*
|
||||
* Based on tiny-AES-c by kokke (public domain)
|
||||
* Configured specifically for NIP-04: AES-256-CBC only
|
||||
*
|
||||
* This is an implementation of the AES algorithm, specifically CBC mode.
|
||||
* Configured for AES-256 as required by NIP-04.
|
||||
*/
|
||||
|
||||
#include <string.h> // CBC mode, for memset
|
||||
#include "nostr_aes.h"
|
||||
|
||||
// The number of columns comprising a state in AES. This is a constant in AES. Value=4
|
||||
#define Nb 4
|
||||
|
||||
#if defined(AES256) && (AES256 == 1)
|
||||
#define Nk 8
|
||||
#define Nr 14
|
||||
#elif defined(AES192) && (AES192 == 1)
|
||||
#define Nk 6
|
||||
#define Nr 12
|
||||
#else
|
||||
#define Nk 4 // The number of 32 bit words in a key.
|
||||
#define Nr 10 // The number of rounds in AES Cipher.
|
||||
#endif
|
||||
|
||||
// state - array holding the intermediate results during decryption.
|
||||
typedef uint8_t state_t[4][4];
|
||||
|
||||
// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM
|
||||
static const uint8_t sbox[256] = {
|
||||
//0 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
|
||||
|
||||
static const uint8_t rsbox[256] = {
|
||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
|
||||
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
|
||||
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
|
||||
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
|
||||
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
|
||||
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
|
||||
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
|
||||
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
|
||||
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
|
||||
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
|
||||
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
|
||||
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
|
||||
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
|
||||
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
|
||||
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
|
||||
|
||||
// The round constant word array, Rcon[i], contains the values given by
|
||||
// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8)
|
||||
static const uint8_t Rcon[11] = {
|
||||
0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 };
|
||||
|
||||
#define getSBoxValue(num) (sbox[(num)])
|
||||
#define getSBoxInvert(num) (rsbox[(num)])
|
||||
|
||||
// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states.
|
||||
static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key)
|
||||
{
|
||||
unsigned i, j, k;
|
||||
uint8_t tempa[4]; // Used for the column/row operations
|
||||
|
||||
// The first round key is the key itself.
|
||||
for (i = 0; i < Nk; ++i)
|
||||
{
|
||||
RoundKey[(i * 4) + 0] = Key[(i * 4) + 0];
|
||||
RoundKey[(i * 4) + 1] = Key[(i * 4) + 1];
|
||||
RoundKey[(i * 4) + 2] = Key[(i * 4) + 2];
|
||||
RoundKey[(i * 4) + 3] = Key[(i * 4) + 3];
|
||||
}
|
||||
|
||||
// All other round keys are found from the previous round keys.
|
||||
for (i = Nk; i < Nb * (Nr + 1); ++i)
|
||||
{
|
||||
{
|
||||
k = (i - 1) * 4;
|
||||
tempa[0]=RoundKey[k + 0];
|
||||
tempa[1]=RoundKey[k + 1];
|
||||
tempa[2]=RoundKey[k + 2];
|
||||
tempa[3]=RoundKey[k + 3];
|
||||
}
|
||||
|
||||
if (i % Nk == 0)
|
||||
{
|
||||
// This function shifts the 4 bytes in a word to the left once.
|
||||
// [a0,a1,a2,a3] becomes [a1,a2,a3,a0]
|
||||
|
||||
// Function RotWord()
|
||||
{
|
||||
const uint8_t u8tmp = tempa[0];
|
||||
tempa[0] = tempa[1];
|
||||
tempa[1] = tempa[2];
|
||||
tempa[2] = tempa[3];
|
||||
tempa[3] = u8tmp;
|
||||
}
|
||||
|
||||
// SubWord() is a function that takes a four-byte input word and
|
||||
// applies the S-box to each of the four bytes to produce an output word.
|
||||
|
||||
// Function Subword()
|
||||
{
|
||||
tempa[0] = getSBoxValue(tempa[0]);
|
||||
tempa[1] = getSBoxValue(tempa[1]);
|
||||
tempa[2] = getSBoxValue(tempa[2]);
|
||||
tempa[3] = getSBoxValue(tempa[3]);
|
||||
}
|
||||
|
||||
tempa[0] = tempa[0] ^ Rcon[i/Nk];
|
||||
}
|
||||
#if defined(AES256) && (AES256 == 1)
|
||||
if (i % Nk == 4)
|
||||
{
|
||||
// Function Subword()
|
||||
{
|
||||
tempa[0] = getSBoxValue(tempa[0]);
|
||||
tempa[1] = getSBoxValue(tempa[1]);
|
||||
tempa[2] = getSBoxValue(tempa[2]);
|
||||
tempa[3] = getSBoxValue(tempa[3]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
j = i * 4; k=(i - Nk) * 4;
|
||||
RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0];
|
||||
RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1];
|
||||
RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2];
|
||||
RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3];
|
||||
}
|
||||
}
|
||||
|
||||
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key)
|
||||
{
|
||||
KeyExpansion(ctx->RoundKey, key);
|
||||
}
|
||||
|
||||
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv)
|
||||
{
|
||||
KeyExpansion(ctx->RoundKey, key);
|
||||
memcpy (ctx->Iv, iv, AES_BLOCKLEN);
|
||||
}
|
||||
|
||||
void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv)
|
||||
{
|
||||
memcpy (ctx->Iv, iv, AES_BLOCKLEN);
|
||||
}
|
||||
|
||||
// This function adds the round key to state.
|
||||
// The round key is added to the state by an XOR function.
|
||||
static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey)
|
||||
{
|
||||
uint8_t i,j;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
for (j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The SubBytes Function Substitutes the values in the
|
||||
// state matrix with values in an S-box.
|
||||
static void SubBytes(state_t* state)
|
||||
{
|
||||
uint8_t i, j;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
for (j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[j][i] = getSBoxValue((*state)[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The ShiftRows() function shifts the rows in the state to the left.
|
||||
// Each row is shifted with different offset.
|
||||
// Offset = Row number. So the first row is not shifted.
|
||||
static void ShiftRows(state_t* state)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// Rotate first row 1 columns to left
|
||||
temp = (*state)[0][1];
|
||||
(*state)[0][1] = (*state)[1][1];
|
||||
(*state)[1][1] = (*state)[2][1];
|
||||
(*state)[2][1] = (*state)[3][1];
|
||||
(*state)[3][1] = temp;
|
||||
|
||||
// Rotate second row 2 columns to left
|
||||
temp = (*state)[0][2];
|
||||
(*state)[0][2] = (*state)[2][2];
|
||||
(*state)[2][2] = temp;
|
||||
|
||||
temp = (*state)[1][2];
|
||||
(*state)[1][2] = (*state)[3][2];
|
||||
(*state)[3][2] = temp;
|
||||
|
||||
// Rotate third row 3 columns to left
|
||||
temp = (*state)[0][3];
|
||||
(*state)[0][3] = (*state)[3][3];
|
||||
(*state)[3][3] = (*state)[2][3];
|
||||
(*state)[2][3] = (*state)[1][3];
|
||||
(*state)[1][3] = temp;
|
||||
}
|
||||
|
||||
static uint8_t xtime(uint8_t x)
|
||||
{
|
||||
return ((x<<1) ^ (((x>>7) & 1) * 0x1b));
|
||||
}
|
||||
|
||||
// MixColumns function mixes the columns of the state matrix
|
||||
static void MixColumns(state_t* state)
|
||||
{
|
||||
uint8_t i;
|
||||
uint8_t Tmp, Tm, t;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
t = (*state)[i][0];
|
||||
Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ;
|
||||
Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ;
|
||||
Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ;
|
||||
}
|
||||
}
|
||||
|
||||
// Multiply is used to multiply numbers in the field GF(2^8)
|
||||
#define Multiply(x, y) \
|
||||
( ((y & 1) * x) ^ \
|
||||
((y>>1 & 1) * xtime(x)) ^ \
|
||||
((y>>2 & 1) * xtime(xtime(x))) ^ \
|
||||
((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \
|
||||
((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \
|
||||
|
||||
// MixColumns function mixes the columns of the state matrix.
|
||||
static void InvMixColumns(state_t* state)
|
||||
{
|
||||
int i;
|
||||
uint8_t a, b, c, d;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
a = (*state)[i][0];
|
||||
b = (*state)[i][1];
|
||||
c = (*state)[i][2];
|
||||
d = (*state)[i][3];
|
||||
|
||||
(*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09);
|
||||
(*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d);
|
||||
(*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b);
|
||||
(*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e);
|
||||
}
|
||||
}
|
||||
|
||||
// The SubBytes Function Substitutes the values in the
|
||||
// state matrix with values in an S-box.
|
||||
static void InvSubBytes(state_t* state)
|
||||
{
|
||||
uint8_t i, j;
|
||||
for (i = 0; i < 4; ++i)
|
||||
{
|
||||
for (j = 0; j < 4; ++j)
|
||||
{
|
||||
(*state)[j][i] = getSBoxInvert((*state)[j][i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void InvShiftRows(state_t* state)
|
||||
{
|
||||
uint8_t temp;
|
||||
|
||||
// Rotate first row 1 columns to right
|
||||
temp = (*state)[3][1];
|
||||
(*state)[3][1] = (*state)[2][1];
|
||||
(*state)[2][1] = (*state)[1][1];
|
||||
(*state)[1][1] = (*state)[0][1];
|
||||
(*state)[0][1] = temp;
|
||||
|
||||
// Rotate second row 2 columns to right
|
||||
temp = (*state)[0][2];
|
||||
(*state)[0][2] = (*state)[2][2];
|
||||
(*state)[2][2] = temp;
|
||||
|
||||
temp = (*state)[1][2];
|
||||
(*state)[1][2] = (*state)[3][2];
|
||||
(*state)[3][2] = temp;
|
||||
|
||||
// Rotate third row 3 columns to right
|
||||
temp = (*state)[0][3];
|
||||
(*state)[0][3] = (*state)[1][3];
|
||||
(*state)[1][3] = (*state)[2][3];
|
||||
(*state)[2][3] = (*state)[3][3];
|
||||
(*state)[3][3] = temp;
|
||||
}
|
||||
|
||||
// Cipher is the main function that encrypts the PlainText.
|
||||
static void Cipher(state_t* state, const uint8_t* RoundKey)
|
||||
{
|
||||
uint8_t round = 0;
|
||||
|
||||
// Add the First round key to the state before starting the rounds.
|
||||
AddRoundKey(0, state, RoundKey);
|
||||
|
||||
// There will be Nr rounds.
|
||||
// The first Nr-1 rounds are identical.
|
||||
// These Nr rounds are executed in the loop below.
|
||||
// Last one without MixColumns()
|
||||
for (round = 1; ; ++round)
|
||||
{
|
||||
SubBytes(state);
|
||||
ShiftRows(state);
|
||||
if (round == Nr) {
|
||||
break;
|
||||
}
|
||||
MixColumns(state);
|
||||
AddRoundKey(round, state, RoundKey);
|
||||
}
|
||||
// Add round key to last round
|
||||
AddRoundKey(Nr, state, RoundKey);
|
||||
}
|
||||
|
||||
static void InvCipher(state_t* state, const uint8_t* RoundKey)
|
||||
{
|
||||
uint8_t round = 0;
|
||||
|
||||
// Add the First round key to the state before starting the rounds.
|
||||
AddRoundKey(Nr, state, RoundKey);
|
||||
|
||||
// There will be Nr rounds.
|
||||
// The first Nr-1 rounds are identical.
|
||||
// These Nr rounds are executed in the loop below.
|
||||
// Last one without InvMixColumn()
|
||||
for (round = (Nr - 1); ; --round)
|
||||
{
|
||||
InvShiftRows(state);
|
||||
InvSubBytes(state);
|
||||
AddRoundKey(round, state, RoundKey);
|
||||
if (round == 0) {
|
||||
break;
|
||||
}
|
||||
InvMixColumns(state);
|
||||
}
|
||||
}
|
||||
|
||||
static void XorWithIv(uint8_t* buf, const uint8_t* Iv)
|
||||
{
|
||||
uint8_t i;
|
||||
for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size
|
||||
{
|
||||
buf[i] ^= Iv[i];
|
||||
}
|
||||
}
|
||||
|
||||
void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length)
|
||||
{
|
||||
size_t i;
|
||||
uint8_t *Iv = ctx->Iv;
|
||||
for (i = 0; i < length; i += AES_BLOCKLEN)
|
||||
{
|
||||
XorWithIv(buf, Iv);
|
||||
Cipher((state_t*)buf, ctx->RoundKey);
|
||||
Iv = buf;
|
||||
buf += AES_BLOCKLEN;
|
||||
}
|
||||
/* store Iv in ctx for next call */
|
||||
memcpy(ctx->Iv, Iv, AES_BLOCKLEN);
|
||||
}
|
||||
|
||||
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length)
|
||||
{
|
||||
size_t i;
|
||||
uint8_t storeNextIv[AES_BLOCKLEN];
|
||||
for (i = 0; i < length; i += AES_BLOCKLEN)
|
||||
{
|
||||
memcpy(storeNextIv, buf, AES_BLOCKLEN);
|
||||
InvCipher((state_t*)buf, ctx->RoundKey);
|
||||
XorWithIv(buf, ctx->Iv);
|
||||
memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN);
|
||||
buf += AES_BLOCKLEN;
|
||||
}
|
||||
}
|
||||
53
nostr_core/crypto/nostr_aes.h
Normal file
53
nostr_core/crypto/nostr_aes.h
Normal file
@@ -0,0 +1,53 @@
|
||||
#ifndef _NOSTR_AES_H_
|
||||
#define _NOSTR_AES_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Configure for NIP-04 requirements: AES-256-CBC only
|
||||
#define CBC 1
|
||||
#define ECB 0
|
||||
#define CTR 0
|
||||
|
||||
// Configure for AES-256 (required by NIP-04)
|
||||
#define AES128 0
|
||||
#define AES192 0
|
||||
#define AES256 1
|
||||
|
||||
#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only
|
||||
|
||||
#if defined(AES256) && (AES256 == 1)
|
||||
#define AES_KEYLEN 32
|
||||
#define AES_keyExpSize 240
|
||||
#elif defined(AES192) && (AES192 == 1)
|
||||
#define AES_KEYLEN 24
|
||||
#define AES_keyExpSize 208
|
||||
#else
|
||||
#define AES_KEYLEN 16 // Key length in bytes
|
||||
#define AES_keyExpSize 176
|
||||
#endif
|
||||
|
||||
struct AES_ctx
|
||||
{
|
||||
uint8_t RoundKey[AES_keyExpSize];
|
||||
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
|
||||
uint8_t Iv[AES_BLOCKLEN];
|
||||
#endif
|
||||
};
|
||||
|
||||
void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key);
|
||||
#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1))
|
||||
void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
|
||||
void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv);
|
||||
#endif
|
||||
|
||||
#if defined(CBC) && (CBC == 1)
|
||||
// buffer size MUST be multiple of AES_BLOCKLEN;
|
||||
// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
|
||||
// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv()
|
||||
// no IV should ever be reused with the same key
|
||||
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
|
||||
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
|
||||
#endif // #if defined(CBC) && (CBC == 1)
|
||||
|
||||
#endif // _NOSTR_AES_H_
|
||||
163
nostr_core/crypto/nostr_chacha20.c
Normal file
163
nostr_core/crypto/nostr_chacha20.c
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* nostr_chacha20.c - ChaCha20 stream cipher implementation
|
||||
*
|
||||
* Implementation based on RFC 8439 "ChaCha20 and Poly1305 for IETF Protocols"
|
||||
*
|
||||
* This implementation is adapted from the RFC 8439 reference specification.
|
||||
* It prioritizes correctness and clarity over performance optimization.
|
||||
*/
|
||||
|
||||
#include "nostr_chacha20.h"
|
||||
#include <string.h>
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* UTILITY MACROS AND FUNCTIONS
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
/* Left rotate a 32-bit value by n bits */
|
||||
#define ROTLEFT(a, b) (((a) << (b)) | ((a) >> (32 - (b))))
|
||||
|
||||
/* Convert 4 bytes to 32-bit little-endian */
|
||||
static uint32_t bytes_to_u32_le(const uint8_t *bytes) {
|
||||
return ((uint32_t)bytes[0]) |
|
||||
((uint32_t)bytes[1] << 8) |
|
||||
((uint32_t)bytes[2] << 16) |
|
||||
((uint32_t)bytes[3] << 24);
|
||||
}
|
||||
|
||||
/* Convert 32-bit to 4 bytes little-endian */
|
||||
static void u32_to_bytes_le(uint32_t val, uint8_t *bytes) {
|
||||
bytes[0] = (uint8_t)(val & 0xff);
|
||||
bytes[1] = (uint8_t)((val >> 8) & 0xff);
|
||||
bytes[2] = (uint8_t)((val >> 16) & 0xff);
|
||||
bytes[3] = (uint8_t)((val >> 24) & 0xff);
|
||||
}
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* CHACHA20 CORE FUNCTIONS
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
void chacha20_quarter_round(uint32_t state[16], int a, int b, int c, int d) {
|
||||
state[a] += state[b];
|
||||
state[d] ^= state[a];
|
||||
state[d] = ROTLEFT(state[d], 16);
|
||||
|
||||
state[c] += state[d];
|
||||
state[b] ^= state[c];
|
||||
state[b] = ROTLEFT(state[b], 12);
|
||||
|
||||
state[a] += state[b];
|
||||
state[d] ^= state[a];
|
||||
state[d] = ROTLEFT(state[d], 8);
|
||||
|
||||
state[c] += state[d];
|
||||
state[b] ^= state[c];
|
||||
state[b] = ROTLEFT(state[b], 7);
|
||||
}
|
||||
|
||||
void chacha20_init_state(uint32_t state[16], const uint8_t key[32],
|
||||
uint32_t counter, const uint8_t nonce[12]) {
|
||||
/* ChaCha20 constants "expand 32-byte k" */
|
||||
state[0] = 0x61707865;
|
||||
state[1] = 0x3320646e;
|
||||
state[2] = 0x79622d32;
|
||||
state[3] = 0x6b206574;
|
||||
|
||||
/* Key (8 words) */
|
||||
state[4] = bytes_to_u32_le(key + 0);
|
||||
state[5] = bytes_to_u32_le(key + 4);
|
||||
state[6] = bytes_to_u32_le(key + 8);
|
||||
state[7] = bytes_to_u32_le(key + 12);
|
||||
state[8] = bytes_to_u32_le(key + 16);
|
||||
state[9] = bytes_to_u32_le(key + 20);
|
||||
state[10] = bytes_to_u32_le(key + 24);
|
||||
state[11] = bytes_to_u32_le(key + 28);
|
||||
|
||||
/* Counter (1 word) */
|
||||
state[12] = counter;
|
||||
|
||||
/* Nonce (3 words) */
|
||||
state[13] = bytes_to_u32_le(nonce + 0);
|
||||
state[14] = bytes_to_u32_le(nonce + 4);
|
||||
state[15] = bytes_to_u32_le(nonce + 8);
|
||||
}
|
||||
|
||||
void chacha20_serialize_state(const uint32_t state[16], uint8_t output[64]) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
u32_to_bytes_le(state[i], output + (i * 4));
|
||||
}
|
||||
}
|
||||
|
||||
int chacha20_block(const uint8_t key[32], uint32_t counter,
|
||||
const uint8_t nonce[12], uint8_t output[64]) {
|
||||
uint32_t state[16];
|
||||
uint32_t initial_state[16];
|
||||
|
||||
/* Initialize state */
|
||||
chacha20_init_state(state, key, counter, nonce);
|
||||
|
||||
/* Save initial state for later addition */
|
||||
memcpy(initial_state, state, sizeof(initial_state));
|
||||
|
||||
/* Perform 20 rounds (10 iterations of the 8 quarter rounds) */
|
||||
for (int i = 0; i < 10; i++) {
|
||||
/* Column rounds */
|
||||
chacha20_quarter_round(state, 0, 4, 8, 12);
|
||||
chacha20_quarter_round(state, 1, 5, 9, 13);
|
||||
chacha20_quarter_round(state, 2, 6, 10, 14);
|
||||
chacha20_quarter_round(state, 3, 7, 11, 15);
|
||||
|
||||
/* Diagonal rounds */
|
||||
chacha20_quarter_round(state, 0, 5, 10, 15);
|
||||
chacha20_quarter_round(state, 1, 6, 11, 12);
|
||||
chacha20_quarter_round(state, 2, 7, 8, 13);
|
||||
chacha20_quarter_round(state, 3, 4, 9, 14);
|
||||
}
|
||||
|
||||
/* Add initial state back (prevents slide attacks) */
|
||||
for (int i = 0; i < 16; i++) {
|
||||
state[i] += initial_state[i];
|
||||
}
|
||||
|
||||
/* Serialize to output bytes */
|
||||
chacha20_serialize_state(state, output);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int chacha20_encrypt(const uint8_t key[32], uint32_t counter,
|
||||
const uint8_t nonce[12], const uint8_t* input,
|
||||
uint8_t* output, size_t length) {
|
||||
uint8_t keystream[CHACHA20_BLOCK_SIZE];
|
||||
size_t offset = 0;
|
||||
|
||||
while (length > 0) {
|
||||
/* Generate keystream block */
|
||||
int ret = chacha20_block(key, counter, nonce, keystream);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* XOR with input to produce output */
|
||||
size_t block_len = (length < CHACHA20_BLOCK_SIZE) ? length : CHACHA20_BLOCK_SIZE;
|
||||
for (size_t i = 0; i < block_len; i++) {
|
||||
output[offset + i] = input[offset + i] ^ keystream[i];
|
||||
}
|
||||
|
||||
/* Move to next block */
|
||||
offset += block_len;
|
||||
length -= block_len;
|
||||
counter++;
|
||||
|
||||
/* Check for counter overflow */
|
||||
if (counter == 0) {
|
||||
return -1; /* Counter wrapped around */
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
115
nostr_core/crypto/nostr_chacha20.h
Normal file
115
nostr_core/crypto/nostr_chacha20.h
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* nostr_chacha20.h - ChaCha20 stream cipher implementation
|
||||
*
|
||||
* Implementation based on RFC 8439 "ChaCha20 and Poly1305 for IETF Protocols"
|
||||
*
|
||||
* This is a small, portable implementation for NIP-44 support in the NOSTR library.
|
||||
* The implementation prioritizes correctness and simplicity over performance.
|
||||
*/
|
||||
|
||||
#ifndef NOSTR_CHACHA20_H
|
||||
#define NOSTR_CHACHA20_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* CONSTANTS AND DEFINITIONS
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
#define CHACHA20_KEY_SIZE 32 /* 256 bits */
|
||||
#define CHACHA20_NONCE_SIZE 12 /* 96 bits */
|
||||
#define CHACHA20_BLOCK_SIZE 64 /* 512 bits */
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* CORE CHACHA20 FUNCTIONS
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* ChaCha20 quarter round operation
|
||||
*
|
||||
* Operates on four 32-bit words performing the core ChaCha20 quarter round:
|
||||
* a += b; d ^= a; d <<<= 16;
|
||||
* c += d; b ^= c; b <<<= 12;
|
||||
* a += b; d ^= a; d <<<= 8;
|
||||
* c += d; b ^= c; b <<<= 7;
|
||||
*
|
||||
* @param state[in,out] ChaCha state as 16 32-bit words
|
||||
* @param a, b, c, d Indices into state array for quarter round
|
||||
*/
|
||||
void chacha20_quarter_round(uint32_t state[16], int a, int b, int c, int d);
|
||||
|
||||
/**
|
||||
* ChaCha20 block function
|
||||
*
|
||||
* Transforms a 64-byte input block using ChaCha20 algorithm with 20 rounds.
|
||||
*
|
||||
* @param key[in] 32-byte key
|
||||
* @param counter[in] 32-bit block counter
|
||||
* @param nonce[in] 12-byte nonce
|
||||
* @param output[out] 64-byte output buffer
|
||||
* @return 0 on success, negative on error
|
||||
*/
|
||||
int chacha20_block(const uint8_t key[32], uint32_t counter,
|
||||
const uint8_t nonce[12], uint8_t output[64]);
|
||||
|
||||
/**
|
||||
* ChaCha20 encryption/decryption
|
||||
*
|
||||
* Encrypts or decrypts data using ChaCha20 stream cipher.
|
||||
* Since ChaCha20 is a stream cipher, encryption and decryption are the same operation.
|
||||
*
|
||||
* @param key[in] 32-byte key
|
||||
* @param counter[in] Initial 32-bit counter value
|
||||
* @param nonce[in] 12-byte nonce
|
||||
* @param input[in] Input data to encrypt/decrypt
|
||||
* @param output[out] Output buffer (can be same as input)
|
||||
* @param length[in] Length of input data in bytes
|
||||
* @return 0 on success, negative on error
|
||||
*/
|
||||
int chacha20_encrypt(const uint8_t key[32], uint32_t counter,
|
||||
const uint8_t nonce[12], const uint8_t* input,
|
||||
uint8_t* output, size_t length);
|
||||
|
||||
/*
|
||||
* ============================================================================
|
||||
* UTILITY FUNCTIONS
|
||||
* ============================================================================
|
||||
*/
|
||||
|
||||
/**
|
||||
* Initialize ChaCha20 state matrix
|
||||
*
|
||||
* Sets up the initial 16-word state matrix with constants, key, counter, and nonce.
|
||||
*
|
||||
* @param state[out] 16-word state array to initialize
|
||||
* @param key[in] 32-byte key
|
||||
* @param counter[in] 32-bit block counter
|
||||
* @param nonce[in] 12-byte nonce
|
||||
*/
|
||||
void chacha20_init_state(uint32_t state[16], const uint8_t key[32],
|
||||
uint32_t counter, const uint8_t nonce[12]);
|
||||
|
||||
/**
|
||||
* Serialize ChaCha20 state to bytes
|
||||
*
|
||||
* Converts 16 32-bit words to 64 bytes in little-endian format.
|
||||
*
|
||||
* @param state[in] 16-word state array
|
||||
* @param output[out] 64-byte output buffer
|
||||
*/
|
||||
void chacha20_serialize_state(const uint32_t state[16], uint8_t output[64]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NOSTR_CHACHA20_H */
|
||||
905
nostr_core/crypto/nostr_core.h.old
Normal file
905
nostr_core/crypto/nostr_core.h.old
Normal file
@@ -0,0 +1,905 @@
|
||||
/*
|
||||
* NOSTR Core Library
|
||||
*
|
||||
* A C library for NOSTR protocol implementation
|
||||
* Self-contained crypto implementation (no external crypto dependencies)
|
||||
*
|
||||
* Features:
|
||||
* - BIP39 mnemonic generation and validation
|
||||
* - BIP32 hierarchical deterministic key derivation (NIP-06 compliant)
|
||||
* - NOSTR key pair generation and management
|
||||
* - Event creation, signing, and serialization
|
||||
* - Relay communication (websocket-based)
|
||||
* - Identity management and persistence
|
||||
*/
|
||||
|
||||
#ifndef NOSTR_CORE_H
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
|
||||
// Forward declare cJSON to avoid requiring cJSON.h in public header
|
||||
typedef struct cJSON cJSON;
|
||||
|
||||
// Return codes
|
||||
#define NOSTR_SUCCESS 0
|
||||
#define NOSTR_ERROR_INVALID_INPUT -1
|
||||
#define NOSTR_ERROR_CRYPTO_FAILED -2
|
||||
#define NOSTR_ERROR_MEMORY_FAILED -3
|
||||
#define NOSTR_ERROR_IO_FAILED -4
|
||||
#define NOSTR_ERROR_NETWORK_FAILED -5
|
||||
#define NOSTR_ERROR_NIP04_INVALID_FORMAT -10
|
||||
#define NOSTR_ERROR_NIP04_DECRYPT_FAILED -11
|
||||
#define NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL -12
|
||||
#define NOSTR_ERROR_NIP44_INVALID_FORMAT -13
|
||||
#define NOSTR_ERROR_NIP44_DECRYPT_FAILED -14
|
||||
#define NOSTR_ERROR_NIP44_BUFFER_TOO_SMALL -15
|
||||
#define NOSTR_ERROR_NIP05_INVALID_IDENTIFIER -16
|
||||
#define NOSTR_ERROR_NIP05_HTTP_FAILED -17
|
||||
#define NOSTR_ERROR_NIP05_JSON_PARSE_FAILED -18
|
||||
#define NOSTR_ERROR_NIP05_NAME_NOT_FOUND -19
|
||||
#define NOSTR_ERROR_NIP05_PUBKEY_MISMATCH -20
|
||||
|
||||
// Debug control - uncomment to enable debug output
|
||||
// #define NOSTR_DEBUG_ENABLED
|
||||
|
||||
// Constants
|
||||
#define NOSTR_PRIVATE_KEY_SIZE 32
|
||||
#define NOSTR_PUBLIC_KEY_SIZE 32
|
||||
#define NOSTR_HEX_KEY_SIZE 65 // 64 + null terminator
|
||||
#define NOSTR_BECH32_KEY_SIZE 100
|
||||
#define NOSTR_MAX_CONTENT_SIZE 2048
|
||||
#define NOSTR_MAX_URL_SIZE 256
|
||||
|
||||
// NIP-04 Constants
|
||||
#define NOSTR_NIP04_MAX_PLAINTEXT_SIZE 16777216 // 16MB
|
||||
#define NOSTR_NIP04_MAX_ENCRYPTED_SIZE 22369621 // ~21.3MB (accounts for base64 overhead + IV)
|
||||
|
||||
// Input type detection
|
||||
typedef enum {
|
||||
NOSTR_INPUT_UNKNOWN = 0,
|
||||
NOSTR_INPUT_MNEMONIC,
|
||||
NOSTR_INPUT_NSEC_HEX,
|
||||
NOSTR_INPUT_NSEC_BECH32
|
||||
} nostr_input_type_t;
|
||||
|
||||
// Relay permissions
|
||||
typedef enum {
|
||||
NOSTR_RELAY_READ_WRITE = 0,
|
||||
NOSTR_RELAY_READ_ONLY,
|
||||
NOSTR_RELAY_WRITE_ONLY
|
||||
} nostr_relay_permission_t;
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// LIBRARY MAINTENANCE - KEEP THE SHELVES NICE AND ORGANIZED.
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Initialize the NOSTR core library (must be called before using other functions)
|
||||
*
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_init(void);
|
||||
|
||||
/**
|
||||
* Cleanup the NOSTR core library (call when done)
|
||||
*/
|
||||
void nostr_cleanup(void);
|
||||
|
||||
/**
|
||||
* Get human-readable error message for error code
|
||||
*
|
||||
* @param error_code Error code from other functions
|
||||
* @return Human-readable error string
|
||||
*/
|
||||
const char* nostr_strerror(int error_code);
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// GENERAL NOSTR UTILITIES
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Convert bytes to hexadecimal string
|
||||
*
|
||||
* @param bytes Input bytes
|
||||
* @param len Number of bytes
|
||||
* @param hex Output hex string (must be at least len*2+1 bytes)
|
||||
*/
|
||||
void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex);
|
||||
|
||||
/**
|
||||
* Convert hexadecimal string to bytes
|
||||
*
|
||||
* @param hex Input hex string
|
||||
* @param bytes Output bytes buffer
|
||||
* @param len Expected number of bytes
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t len);
|
||||
|
||||
/**
|
||||
* Generate public key from private key
|
||||
*
|
||||
* @param private_key Input private key (32 bytes)
|
||||
* @param public_key Output public key (32 bytes, x-only)
|
||||
* @return 0 on success, non-zero on failure
|
||||
*/
|
||||
int nostr_ec_public_key_from_private_key(const unsigned char* private_key, unsigned char* public_key);
|
||||
|
||||
/**
|
||||
* Sign a hash using BIP-340 Schnorr signatures (NOSTR standard)
|
||||
*
|
||||
* @param private_key Input private key (32 bytes)
|
||||
* @param hash Input hash to sign (32 bytes)
|
||||
* @param signature Output signature (64 bytes)
|
||||
* @return 0 on success, non-zero on failure
|
||||
*/
|
||||
int nostr_schnorr_sign(const unsigned char* private_key, const unsigned char* hash, unsigned char* signature);
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-01: BASIC PROTOCOL FLOW
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Create and sign a NOSTR event
|
||||
*
|
||||
* @param kind Event kind (0=profile, 1=text, 3=contacts, 10002=relays, etc.)
|
||||
* @param content Event content string
|
||||
* @param tags cJSON array of tags (NULL for empty tags)
|
||||
* @param private_key Private key for signing (32 bytes)
|
||||
* @param timestamp Event timestamp (0 for current time)
|
||||
* @return cJSON event object (caller must free), NULL on failure
|
||||
*/
|
||||
cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags, const unsigned char* private_key, time_t timestamp);
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-05: MAPPING NOSTR KEYS TO DNS-BASED INTERNET IDENTIFIERS
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Verify a NIP-05 identifier against a public key
|
||||
* Checks if the given identifier (e.g., "bob@example.com") maps to the provided pubkey
|
||||
*
|
||||
* @param nip05_identifier Internet identifier (e.g., "bob@example.com")
|
||||
* @param pubkey_hex Public key in hex format to verify against
|
||||
* @param relays OUTPUT: Array of relay URLs (caller must free each string and array), NULL if not needed
|
||||
* @param relay_count OUTPUT: Number of relay URLs returned, NULL if not needed
|
||||
* @param timeout_seconds HTTP timeout in seconds (0 for default 10 seconds)
|
||||
* @return NOSTR_SUCCESS if verified, NOSTR_ERROR_* on failure
|
||||
*/
|
||||
int nostr_nip05_verify(const char* nip05_identifier, const char* pubkey_hex,
|
||||
char*** relays, int* relay_count, int timeout_seconds);
|
||||
|
||||
/**
|
||||
* Lookup a public key from a NIP-05 identifier
|
||||
* Finds the public key associated with the given identifier (e.g., "bob@example.com")
|
||||
*
|
||||
* @param nip05_identifier Internet identifier (e.g., "bob@example.com")
|
||||
* @param pubkey_hex_out OUTPUT: Public key in hex format (65 bytes including null terminator)
|
||||
* @param relays OUTPUT: Array of relay URLs (caller must free each string and array), NULL if not needed
|
||||
* @param relay_count OUTPUT: Number of relay URLs returned, NULL if not needed
|
||||
* @param timeout_seconds HTTP timeout in seconds (0 for default 10 seconds)
|
||||
* @return NOSTR_SUCCESS if found, NOSTR_ERROR_* on failure
|
||||
*/
|
||||
int nostr_nip05_lookup(const char* nip05_identifier, char* pubkey_hex_out,
|
||||
char*** relays, int* relay_count, int timeout_seconds);
|
||||
|
||||
/**
|
||||
* Parse a .well-known/nostr.json response and extract pubkey and relays for a specific name
|
||||
*
|
||||
* @param json_response JSON string from .well-known/nostr.json endpoint
|
||||
* @param local_part Local part of identifier (e.g., "bob" from "bob@example.com")
|
||||
* @param pubkey_hex_out OUTPUT: Public key in hex format (65 bytes including null terminator)
|
||||
* @param relays OUTPUT: Array of relay URLs (caller must free each string and array), NULL if not needed
|
||||
* @param relay_count OUTPUT: Number of relay URLs returned, NULL if not needed
|
||||
* @return NOSTR_SUCCESS if found, NOSTR_ERROR_* on failure
|
||||
*/
|
||||
int nostr_nip05_parse_well_known(const char* json_response, const char* local_part,
|
||||
char* pubkey_hex_out, char*** relays, int* relay_count);
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-11: RELAY INFORMATION DOCUMENT
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// NIP-11 data structures
|
||||
typedef struct {
|
||||
char* name;
|
||||
char* description;
|
||||
char* pubkey;
|
||||
char* contact;
|
||||
int* supported_nips;
|
||||
size_t supported_nips_count;
|
||||
char* software;
|
||||
char* version;
|
||||
} nostr_relay_basic_info_t;
|
||||
|
||||
typedef struct {
|
||||
int max_message_length;
|
||||
int max_subscriptions;
|
||||
int max_filters;
|
||||
int max_limit;
|
||||
int max_subid_length;
|
||||
int min_prefix;
|
||||
int max_event_tags;
|
||||
int max_content_length;
|
||||
int min_pow_difficulty;
|
||||
int auth_required;
|
||||
int payment_required;
|
||||
long created_at_lower_limit;
|
||||
long created_at_upper_limit;
|
||||
int restricted_writes;
|
||||
} nostr_relay_limitations_t;
|
||||
|
||||
typedef struct {
|
||||
char** relay_countries;
|
||||
size_t relay_countries_count;
|
||||
} nostr_relay_content_limitations_t;
|
||||
|
||||
typedef struct {
|
||||
char** language_tags;
|
||||
size_t language_tags_count;
|
||||
char** tags;
|
||||
size_t tags_count;
|
||||
char* posting_policy;
|
||||
} nostr_relay_community_preferences_t;
|
||||
|
||||
typedef struct {
|
||||
char* icon;
|
||||
} nostr_relay_icon_t;
|
||||
|
||||
typedef struct {
|
||||
// Basic information (always present)
|
||||
nostr_relay_basic_info_t basic;
|
||||
|
||||
// Optional sections (check has_* flags)
|
||||
int has_limitations;
|
||||
nostr_relay_limitations_t limitations;
|
||||
|
||||
int has_content_limitations;
|
||||
nostr_relay_content_limitations_t content_limitations;
|
||||
|
||||
int has_community_preferences;
|
||||
nostr_relay_community_preferences_t community_preferences;
|
||||
|
||||
int has_icon;
|
||||
nostr_relay_icon_t icon;
|
||||
} nostr_relay_info_t;
|
||||
|
||||
/**
|
||||
* Fetch relay information document from a relay URL
|
||||
* Converts WebSocket URLs to HTTP and retrieves NIP-11 document
|
||||
*
|
||||
* @param relay_url Relay URL (ws://, wss://, http://, or https://)
|
||||
* @param info_out OUTPUT: Pointer to relay info structure (caller must free with nostr_nip11_relay_info_free)
|
||||
* @param timeout_seconds HTTP timeout in seconds (0 for default 10 seconds)
|
||||
* @return NOSTR_SUCCESS if retrieved, NOSTR_ERROR_* on failure
|
||||
*/
|
||||
int nostr_nip11_fetch_relay_info(const char* relay_url, nostr_relay_info_t** info_out, int timeout_seconds);
|
||||
|
||||
/**
|
||||
* Free relay information structure
|
||||
*
|
||||
* @param info Relay info structure to free (safe to pass NULL)
|
||||
*/
|
||||
void nostr_nip11_relay_info_free(nostr_relay_info_t* info);
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-04: ENCRYPTED DIRECT MESSAGES
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Encrypt a message using NIP-04 (ECDH + AES-CBC + Base64)
|
||||
*
|
||||
* @param sender_private_key Sender's 32-byte private key
|
||||
* @param recipient_public_key Recipient's 32-byte public key (x-only)
|
||||
* @param plaintext Message to encrypt
|
||||
* @param output Buffer for encrypted result (recommend NOSTR_NIP04_MAX_ENCRYPTED_SIZE)
|
||||
* @param output_size Size of output buffer
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
const unsigned char* recipient_public_key,
|
||||
const char* plaintext,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
/**
|
||||
* Decrypt a NIP-04 encrypted message
|
||||
*
|
||||
* @param recipient_private_key Recipient's 32-byte private key
|
||||
* @param sender_public_key Sender's 32-byte public key (x-only)
|
||||
* @param encrypted_data Encrypted message in format "ciphertext?iv=iv"
|
||||
* @param output Buffer for decrypted plaintext (recommend NOSTR_NIP04_MAX_PLAINTEXT_SIZE)
|
||||
* @param output_size Size of output buffer
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip04_decrypt(const unsigned char* recipient_private_key,
|
||||
const unsigned char* sender_public_key,
|
||||
const char* encrypted_data,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-44: VERSIONED ENCRYPTED DIRECT MESSAGES
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Encrypt a message using NIP-44 v2 (ECDH + ChaCha20 + HMAC)
|
||||
*
|
||||
* @param sender_private_key Sender's 32-byte private key
|
||||
* @param recipient_public_key Recipient's 32-byte public key (x-only)
|
||||
* @param plaintext Message to encrypt
|
||||
* @param output Buffer for encrypted result (recommend NOSTR_NIP44_MAX_PLAINTEXT_SIZE * 2)
|
||||
* @param output_size Size of output buffer
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
|
||||
const unsigned char* recipient_public_key,
|
||||
const char* plaintext,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
/**
|
||||
* Decrypt a NIP-44 encrypted message
|
||||
*
|
||||
* @param recipient_private_key Recipient's 32-byte private key
|
||||
* @param sender_public_key Sender's 32-byte public key (x-only)
|
||||
* @param encrypted_data Base64-encoded encrypted message
|
||||
* @param output Buffer for decrypted plaintext
|
||||
* @param output_size Size of output buffer
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
||||
const unsigned char* sender_public_key,
|
||||
const char* encrypted_data,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-06: KEY DERIVATION FROM MNEMONIC
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Generate a random NOSTR keypair using cryptographically secure entropy
|
||||
*
|
||||
* @param private_key Output buffer for private key (32 bytes)
|
||||
* @param public_key Output buffer for public key (32 bytes, x-only)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_generate_keypair(unsigned char* private_key, unsigned char* public_key);
|
||||
|
||||
|
||||
/**
|
||||
* Generate a BIP39 mnemonic phrase and derive NOSTR keys
|
||||
*
|
||||
* @param mnemonic Output buffer for mnemonic (at least 256 bytes recommended)
|
||||
* @param mnemonic_size Size of mnemonic buffer
|
||||
* @param account Account number for key derivation (default: 0)
|
||||
* @param private_key Output buffer for private key (32 bytes)
|
||||
* @param public_key Output buffer for public key (32 bytes)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_generate_mnemonic_and_keys(char* mnemonic, size_t mnemonic_size,
|
||||
int account, unsigned char* private_key,
|
||||
unsigned char* public_key);
|
||||
|
||||
|
||||
/**
|
||||
* Derive NOSTR keys from existing BIP39 mnemonic (NIP-06 compliant)
|
||||
*
|
||||
* @param mnemonic BIP39 mnemonic phrase
|
||||
* @param account Account number for derivation path m/44'/1237'/account'/0/0
|
||||
* @param private_key Output buffer for private key (32 bytes)
|
||||
* @param public_key Output buffer for public key (32 bytes)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_derive_keys_from_mnemonic(const char* mnemonic, int account,
|
||||
unsigned char* private_key, unsigned char* public_key);
|
||||
|
||||
|
||||
/**
|
||||
* Convert NOSTR key to bech32 format (nsec/npub)
|
||||
*
|
||||
* @param key Key data (32 bytes)
|
||||
* @param hrp Human readable part ("nsec" or "npub")
|
||||
* @param output Output buffer (at least 100 bytes recommended)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_key_to_bech32(const unsigned char* key, const char* hrp, char* output);
|
||||
|
||||
|
||||
/**
|
||||
* Detect the type of input string (mnemonic, hex nsec, bech32 nsec)
|
||||
*
|
||||
* @param input Input string to analyze
|
||||
* @return Input type enum
|
||||
*/
|
||||
nostr_input_type_t nostr_detect_input_type(const char* input);
|
||||
|
||||
|
||||
/**
|
||||
* Validate and decode an nsec (hex or bech32) to private key
|
||||
*
|
||||
* @param input Input nsec string
|
||||
* @param private_key Output buffer for private key (32 bytes)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_decode_nsec(const char* input, unsigned char* private_key);
|
||||
|
||||
/**
|
||||
* Validate and decode an npub (hex or bech32) to private key
|
||||
*
|
||||
* @param input Input nsec string
|
||||
* @param private_key Output buffer for private key (32 bytes)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_decode_npub(const char* input, unsigned char* private_key);
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// NIP-13: PROOF OF WORK
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Add NIP-13 Proof of Work to an existing event
|
||||
*
|
||||
* @param event cJSON event object to add PoW to
|
||||
* @param private_key Private key for re-signing the event during mining
|
||||
* @param target_difficulty Target number of leading zero bits (default: 4 if 0)
|
||||
* @param max_attempts Maximum number of mining attempts (default: 10,000,000 if <= 0)
|
||||
* @param progress_report_interval How often to call progress callback (default: 10,000 if <= 0)
|
||||
* @param timestamp_update_interval How often to update timestamp (default: 10,000 if <= 0)
|
||||
* @param progress_callback Optional callback for progress updates (current_difficulty, nonce, user_data)
|
||||
* @param user_data User data passed to progress callback
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_add_proof_of_work(cJSON* event, const unsigned char* private_key,
|
||||
int target_difficulty, int max_attempts,
|
||||
int progress_report_interval, int timestamp_update_interval,
|
||||
void (*progress_callback)(int current_difficulty, uint64_t nonce, void* user_data),
|
||||
void* user_data);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// RELAYS - SYNCHRONOUS MULTI-RELAY QUERIES AND PUBLISHING
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Query a relay for a specific event
|
||||
*
|
||||
* @param relay_url Relay WebSocket URL (ws:// or wss://)
|
||||
* @param pubkey_hex Author's public key in hex format
|
||||
* @param kind Event kind to search for
|
||||
* @return cJSON event object (caller must free), NULL if not found/error
|
||||
*/
|
||||
cJSON* nostr_query_relay_for_event(const char* relay_url, const char* pubkey_hex, int kind);
|
||||
|
||||
|
||||
// Query mode enum
|
||||
typedef enum {
|
||||
RELAY_QUERY_FIRST_RESULT, // Return as soon as first event is found
|
||||
RELAY_QUERY_MOST_RECENT, // Wait for all relays, return most recent event
|
||||
RELAY_QUERY_ALL_RESULTS // Wait for all relays, return all unique events
|
||||
} relay_query_mode_t;
|
||||
|
||||
// Progress callback type for relay queries
|
||||
typedef void (*relay_progress_callback_t)(
|
||||
const char* relay_url, // Which relay is reporting (NULL for summary)
|
||||
const char* status, // Status: "connecting", "subscribed", "event_found", "eose", "complete", "timeout", "error", "first_result", "all_complete"
|
||||
const char* event_id, // Event ID when applicable (NULL otherwise)
|
||||
int events_received, // Number of events from this relay
|
||||
int total_relays, // Total number of relays
|
||||
int completed_relays, // Number of relays finished
|
||||
void* user_data // User data pointer
|
||||
);
|
||||
|
||||
/**
|
||||
* Query multiple relays synchronously with progress callbacks
|
||||
*
|
||||
* @param relay_urls Array of relay WebSocket URLs
|
||||
* @param relay_count Number of relays in array
|
||||
* @param filter cJSON filter object for query
|
||||
* @param mode Query mode (FIRST_RESULT, MOST_RECENT, or ALL_RESULTS)
|
||||
* @param result_count OUTPUT: number of events returned
|
||||
* @param relay_timeout_seconds Timeout per relay in seconds (default: 2 if <= 0)
|
||||
* @param callback Progress callback function (can be NULL)
|
||||
* @param user_data User data passed to callback
|
||||
* @return Array of cJSON events (caller must free each event and array), NULL on failure
|
||||
*/
|
||||
cJSON** synchronous_query_relays_with_progress(
|
||||
const char** relay_urls,
|
||||
int relay_count,
|
||||
cJSON* filter,
|
||||
relay_query_mode_t mode,
|
||||
int* result_count,
|
||||
int relay_timeout_seconds,
|
||||
relay_progress_callback_t callback,
|
||||
void* user_data
|
||||
);
|
||||
|
||||
// Publish result enum
|
||||
typedef enum {
|
||||
PUBLISH_SUCCESS, // Event accepted by relay (received OK with true)
|
||||
PUBLISH_REJECTED, // Event rejected by relay (received OK with false)
|
||||
PUBLISH_TIMEOUT, // No response from relay within timeout
|
||||
PUBLISH_ERROR // Connection error or other failure
|
||||
} publish_result_t;
|
||||
|
||||
// Progress callback type for publishing
|
||||
typedef void (*publish_progress_callback_t)(
|
||||
const char* relay_url, // Which relay is reporting
|
||||
const char* status, // Status: "connecting", "publishing", "accepted", "rejected", "timeout", "error"
|
||||
const char* message, // OK message from relay (for rejected events)
|
||||
int successful_publishes, // Count of successful publishes so far
|
||||
int total_relays, // Total number of relays
|
||||
int completed_relays, // Number of relays finished
|
||||
void* user_data // User data pointer
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish event to multiple relays synchronously with progress callbacks
|
||||
*
|
||||
* @param relay_urls Array of relay WebSocket URLs
|
||||
* @param relay_count Number of relays in array
|
||||
* @param event cJSON event object to publish
|
||||
* @param success_count OUTPUT: number of successful publishes
|
||||
* @param relay_timeout_seconds Timeout per relay in seconds (default: 5 if <= 0)
|
||||
* @param callback Progress callback function (can be NULL)
|
||||
* @param user_data User data passed to callback
|
||||
* @return Array of publish_result_t (caller must free), NULL on failure
|
||||
*/
|
||||
publish_result_t* synchronous_publish_event_with_progress(
|
||||
const char** relay_urls,
|
||||
int relay_count,
|
||||
cJSON* event,
|
||||
int* success_count,
|
||||
int relay_timeout_seconds,
|
||||
publish_progress_callback_t callback,
|
||||
void* user_data
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// RELAYS - ASYNCHRONOUS RELAY POOLS
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Forward declarations for relay pool types
|
||||
typedef struct nostr_relay_pool nostr_relay_pool_t;
|
||||
typedef struct nostr_pool_subscription nostr_pool_subscription_t;
|
||||
|
||||
// Pool connection status
|
||||
typedef enum {
|
||||
NOSTR_POOL_RELAY_DISCONNECTED = 0,
|
||||
NOSTR_POOL_RELAY_CONNECTING = 1,
|
||||
NOSTR_POOL_RELAY_CONNECTED = 2,
|
||||
NOSTR_POOL_RELAY_ERROR = -1
|
||||
} nostr_pool_relay_status_t;
|
||||
|
||||
// Relay statistics structure
|
||||
typedef struct {
|
||||
// Event counters
|
||||
int events_received;
|
||||
int events_published;
|
||||
int events_published_ok;
|
||||
int events_published_failed;
|
||||
|
||||
// Connection stats
|
||||
int connection_attempts;
|
||||
int connection_failures;
|
||||
time_t connection_uptime_start;
|
||||
time_t last_event_time;
|
||||
|
||||
// Latency measurements (milliseconds)
|
||||
// NOTE: ping_latency_* values will be 0.0/-1.0 until PONG response handling is fixed
|
||||
double ping_latency_current;
|
||||
double ping_latency_avg;
|
||||
double ping_latency_min;
|
||||
double ping_latency_max;
|
||||
double publish_latency_avg; // EVENT->OK response time
|
||||
double query_latency_avg; // REQ->first EVENT response time
|
||||
double query_latency_min; // Min query latency
|
||||
double query_latency_max; // Max query latency
|
||||
|
||||
// Sample counts for averaging
|
||||
int ping_samples;
|
||||
int publish_samples;
|
||||
int query_samples;
|
||||
} nostr_relay_stats_t;
|
||||
|
||||
/**
|
||||
* Create a new relay pool for managing multiple relay connections
|
||||
*
|
||||
* @return New relay pool instance (caller must destroy), NULL on failure
|
||||
*/
|
||||
nostr_relay_pool_t* nostr_relay_pool_create(void);
|
||||
|
||||
/**
|
||||
* Add a relay to the pool
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay WebSocket URL (ws:// or wss://)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_relay_pool_add_relay(nostr_relay_pool_t* pool, const char* relay_url);
|
||||
|
||||
/**
|
||||
* Remove a relay from the pool
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to remove
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_relay_pool_remove_relay(nostr_relay_pool_t* pool, const char* relay_url);
|
||||
|
||||
/**
|
||||
* Destroy relay pool and cleanup all connections
|
||||
*
|
||||
* @param pool Relay pool instance to destroy
|
||||
*/
|
||||
void nostr_relay_pool_destroy(nostr_relay_pool_t* pool);
|
||||
|
||||
/**
|
||||
* Subscribe to events from multiple relays with event deduplication
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_urls Array of relay URLs to subscribe to
|
||||
* @param relay_count Number of relays in array
|
||||
* @param filter cJSON filter object for subscription
|
||||
* @param on_event Callback for received events (event, relay_url, user_data)
|
||||
* @param on_eose Callback when all relays have sent EOSE (user_data)
|
||||
* @param user_data User data passed to callbacks
|
||||
* @return Subscription handle (caller must close), NULL on failure
|
||||
*/
|
||||
nostr_pool_subscription_t* nostr_relay_pool_subscribe(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char** relay_urls,
|
||||
int relay_count,
|
||||
cJSON* filter,
|
||||
void (*on_event)(cJSON* event, const char* relay_url, void* user_data),
|
||||
void (*on_eose)(void* user_data),
|
||||
void* user_data
|
||||
);
|
||||
|
||||
/**
|
||||
* Close a pool subscription
|
||||
*
|
||||
* @param subscription Subscription to close
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_pool_subscription_close(nostr_pool_subscription_t* subscription);
|
||||
|
||||
/**
|
||||
* Query multiple relays synchronously and return all matching events
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_urls Array of relay URLs to query
|
||||
* @param relay_count Number of relays in array
|
||||
* @param filter cJSON filter object for query
|
||||
* @param event_count Output: number of events returned
|
||||
* @param timeout_ms Timeout in milliseconds
|
||||
* @return Array of cJSON events (caller must free), NULL on failure/timeout
|
||||
*/
|
||||
cJSON** nostr_relay_pool_query_sync(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char** relay_urls,
|
||||
int relay_count,
|
||||
cJSON* filter,
|
||||
int* event_count,
|
||||
int timeout_ms
|
||||
);
|
||||
|
||||
/**
|
||||
* Get a single event from multiple relays (returns the most recent one)
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_urls Array of relay URLs to query
|
||||
* @param relay_count Number of relays in array
|
||||
* @param filter cJSON filter object for query
|
||||
* @param timeout_ms Timeout in milliseconds
|
||||
* @return cJSON event (caller must free), NULL if not found/timeout
|
||||
*/
|
||||
cJSON* nostr_relay_pool_get_event(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char** relay_urls,
|
||||
int relay_count,
|
||||
cJSON* filter,
|
||||
int timeout_ms
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish an event to multiple relays
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_urls Array of relay URLs to publish to
|
||||
* @param relay_count Number of relays in array
|
||||
* @param event cJSON event to publish
|
||||
* @return Number of successful publishes, negative on error
|
||||
*/
|
||||
int nostr_relay_pool_publish(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char** relay_urls,
|
||||
int relay_count,
|
||||
cJSON* event
|
||||
);
|
||||
|
||||
/**
|
||||
* Get connection status for a relay in the pool
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to check
|
||||
* @return Connection status enum
|
||||
*/
|
||||
nostr_pool_relay_status_t nostr_relay_pool_get_relay_status(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url
|
||||
);
|
||||
|
||||
/**
|
||||
* Get list of all relays in pool with their status
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_urls Output: array of relay URL strings (caller must free)
|
||||
* @param statuses Output: array of status values (caller must free)
|
||||
* @return Number of relays, negative on error
|
||||
*/
|
||||
int nostr_relay_pool_list_relays(
|
||||
nostr_relay_pool_t* pool,
|
||||
char*** relay_urls,
|
||||
nostr_pool_relay_status_t** statuses
|
||||
);
|
||||
|
||||
/**
|
||||
* Run continuous event processing for active subscriptions (blocking)
|
||||
* Processes incoming events and calls subscription callbacks until timeout or stopped
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param timeout_ms Timeout in milliseconds (0 = no timeout, runs indefinitely)
|
||||
* @return Total number of events processed, negative on error
|
||||
*/
|
||||
int nostr_relay_pool_run(nostr_relay_pool_t* pool, int timeout_ms);
|
||||
|
||||
/**
|
||||
* Process events for active subscriptions (non-blocking, single pass)
|
||||
* Processes available events and returns immediately
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param timeout_ms Maximum time to spend processing in milliseconds
|
||||
* @return Number of events processed in this call, negative on error
|
||||
*/
|
||||
int nostr_relay_pool_poll(nostr_relay_pool_t* pool, int timeout_ms);
|
||||
|
||||
// =============================================================================
|
||||
// RELAY POOL STATISTICS AND LATENCY
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Get statistics for a specific relay in the pool
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to get statistics for
|
||||
* @return Pointer to statistics structure (owned by pool), NULL if relay not found
|
||||
*/
|
||||
const nostr_relay_stats_t* nostr_relay_pool_get_relay_stats(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url
|
||||
);
|
||||
|
||||
/**
|
||||
* Reset statistics for a specific relay
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to reset statistics for
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_relay_pool_reset_relay_stats(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url
|
||||
);
|
||||
|
||||
/**
|
||||
* Get current ping latency for a relay (most recent ping result)
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to check
|
||||
* @return Ping latency in milliseconds, -1.0 if no ping data available
|
||||
*/
|
||||
double nostr_relay_pool_get_relay_ping_latency(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url
|
||||
);
|
||||
|
||||
/**
|
||||
* Get average query latency for a relay (REQ->first EVENT response time)
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to check
|
||||
* @return Average query latency in milliseconds, -1.0 if no data available
|
||||
*/
|
||||
double nostr_relay_pool_get_relay_query_latency(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url
|
||||
);
|
||||
|
||||
/**
|
||||
* Manually trigger ping measurement for a relay (asynchronous)
|
||||
*
|
||||
* NOTE: PING frames are sent correctly, but PONG response handling needs debugging.
|
||||
* Currently times out waiting for PONG responses. Future fix needed.
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to ping
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_relay_pool_ping_relay(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url
|
||||
);
|
||||
|
||||
/**
|
||||
* Manually trigger ping measurement for a relay and wait for response (synchronous)
|
||||
*
|
||||
* NOTE: PING frames are sent correctly, but PONG response handling needs debugging.
|
||||
* Currently times out waiting for PONG responses. Future fix needed.
|
||||
*
|
||||
* @param pool Relay pool instance
|
||||
* @param relay_url Relay URL to ping
|
||||
* @param timeout_seconds Timeout in seconds (0 for default 5 seconds)
|
||||
* @return NOSTR_SUCCESS on success, error code on failure
|
||||
*/
|
||||
int nostr_relay_pool_ping_relay_sync(
|
||||
nostr_relay_pool_t* pool,
|
||||
const char* relay_url,
|
||||
int timeout_seconds
|
||||
);
|
||||
|
||||
#endif // NOSTR_CORE_H
|
||||
2143
nostr_core/crypto/nostr_crypto.c
Normal file
2143
nostr_core/crypto/nostr_crypto.c
Normal file
File diff suppressed because it is too large
Load Diff
186
nostr_core/crypto/nostr_crypto.h
Normal file
186
nostr_core/crypto/nostr_crypto.h
Normal file
@@ -0,0 +1,186 @@
|
||||
/*
|
||||
* NOSTR Crypto - Self-contained cryptographic functions
|
||||
*
|
||||
* Embedded implementations of crypto primitives needed for NOSTR
|
||||
* No external dependencies except standard C library
|
||||
*/
|
||||
|
||||
#ifndef NOSTR_CRYPTO_H
|
||||
#define NOSTR_CRYPTO_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// =============================================================================
|
||||
// CORE CRYPTO FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
// Initialize crypto subsystem
|
||||
int nostr_crypto_init(void);
|
||||
|
||||
// Cleanup crypto subsystem
|
||||
void nostr_crypto_cleanup(void);
|
||||
|
||||
// SHA-256 hash function
|
||||
int nostr_sha256(const unsigned char* data, size_t len, unsigned char* hash);
|
||||
|
||||
// HMAC-SHA256
|
||||
int nostr_hmac_sha256(const unsigned char* key, size_t key_len,
|
||||
const unsigned char* data, size_t data_len,
|
||||
unsigned char* output);
|
||||
|
||||
// HMAC-SHA512
|
||||
int nostr_hmac_sha512(const unsigned char* key, size_t key_len,
|
||||
const unsigned char* data, size_t data_len,
|
||||
unsigned char* output);
|
||||
|
||||
// PBKDF2 with HMAC-SHA512
|
||||
int nostr_pbkdf2_hmac_sha512(const unsigned char* password, size_t password_len,
|
||||
const unsigned char* salt, size_t salt_len,
|
||||
int iterations,
|
||||
unsigned char* output, size_t output_len);
|
||||
|
||||
// SHA-512 implementation (for testing)
|
||||
int nostr_sha512(const unsigned char* data, size_t len, unsigned char* hash);
|
||||
|
||||
// =============================================================================
|
||||
// SECP256K1 ELLIPTIC CURVE FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
// Verify private key is valid
|
||||
int nostr_ec_private_key_verify(const unsigned char* private_key);
|
||||
|
||||
// Generate public key from private key
|
||||
int nostr_ec_public_key_from_private_key(const unsigned char* private_key,
|
||||
unsigned char* public_key);
|
||||
|
||||
// Sign data with ECDSA
|
||||
int nostr_ec_sign(const unsigned char* private_key,
|
||||
const unsigned char* hash,
|
||||
unsigned char* signature);
|
||||
|
||||
// RFC 6979 deterministic nonce generation
|
||||
int nostr_rfc6979_generate_k(const unsigned char* private_key,
|
||||
const unsigned char* message_hash,
|
||||
unsigned char* k_out);
|
||||
|
||||
// =============================================================================
|
||||
// HKDF KEY DERIVATION FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
// HKDF Extract step
|
||||
int nostr_hkdf_extract(const unsigned char* salt, size_t salt_len,
|
||||
const unsigned char* ikm, size_t ikm_len,
|
||||
unsigned char* prk);
|
||||
|
||||
// HKDF Expand step
|
||||
int nostr_hkdf_expand(const unsigned char* prk, size_t prk_len,
|
||||
const unsigned char* info, size_t info_len,
|
||||
unsigned char* okm, size_t okm_len);
|
||||
|
||||
// HKDF (Extract + Expand)
|
||||
int nostr_hkdf(const unsigned char* salt, size_t salt_len,
|
||||
const unsigned char* ikm, size_t ikm_len,
|
||||
const unsigned char* info, size_t info_len,
|
||||
unsigned char* okm, size_t okm_len);
|
||||
|
||||
// ECDH shared secret computation (for debugging)
|
||||
int ecdh_shared_secret(const unsigned char* private_key,
|
||||
const unsigned char* public_key_x,
|
||||
unsigned char* shared_secret);
|
||||
|
||||
// Base64 encoding function (for debugging)
|
||||
size_t base64_encode(const unsigned char* data, size_t len, char* output, size_t output_size);
|
||||
|
||||
// =============================================================================
|
||||
// NIP-04 AND NIP-44 ENCRYPTION FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
// Note: NOSTR_NIP04_MAX_PLAINTEXT_SIZE already defined in nostr_core.h
|
||||
#define NOSTR_NIP44_MAX_PLAINTEXT_SIZE 65536
|
||||
|
||||
// NIP-04 encryption (AES-256-CBC)
|
||||
int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
const unsigned char* recipient_public_key,
|
||||
const char* plaintext,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
// NIP-04 decryption
|
||||
int nostr_nip04_decrypt(const unsigned char* recipient_private_key,
|
||||
const unsigned char* sender_public_key,
|
||||
const char* encrypted_data,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
// NIP-44 encryption (ChaCha20-Poly1305)
|
||||
int nostr_nip44_encrypt(const unsigned char* sender_private_key,
|
||||
const unsigned char* recipient_public_key,
|
||||
const char* plaintext,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
// NIP-44 encryption with fixed nonce (for testing)
|
||||
int nostr_nip44_encrypt_with_nonce(const unsigned char* sender_private_key,
|
||||
const unsigned char* recipient_public_key,
|
||||
const char* plaintext,
|
||||
const unsigned char* nonce,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
// NIP-44 decryption
|
||||
int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
||||
const unsigned char* sender_public_key,
|
||||
const char* encrypted_data,
|
||||
char* output,
|
||||
size_t output_size);
|
||||
|
||||
// =============================================================================
|
||||
// BIP39 MNEMONIC FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
// Generate mnemonic from entropy
|
||||
int nostr_bip39_mnemonic_from_bytes(const unsigned char* entropy, size_t entropy_len,
|
||||
char* mnemonic);
|
||||
|
||||
// Validate mnemonic
|
||||
int nostr_bip39_mnemonic_validate(const char* mnemonic);
|
||||
|
||||
// Convert mnemonic to seed
|
||||
int nostr_bip39_mnemonic_to_seed(const char* mnemonic, const char* passphrase,
|
||||
unsigned char* seed, size_t seed_len);
|
||||
|
||||
// =============================================================================
|
||||
// BIP32 HD WALLET FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
typedef struct {
|
||||
unsigned char private_key[32];
|
||||
unsigned char public_key[33];
|
||||
unsigned char chain_code[32];
|
||||
uint32_t depth;
|
||||
uint32_t parent_fingerprint;
|
||||
uint32_t child_number;
|
||||
} nostr_hd_key_t;
|
||||
|
||||
// Create master key from seed
|
||||
int nostr_bip32_key_from_seed(const unsigned char* seed, size_t seed_len,
|
||||
nostr_hd_key_t* master_key);
|
||||
|
||||
// Derive child key from parent
|
||||
int nostr_bip32_derive_child(const nostr_hd_key_t* parent_key, uint32_t child_number,
|
||||
nostr_hd_key_t* child_key);
|
||||
|
||||
// Derive key from path
|
||||
int nostr_bip32_derive_path(const nostr_hd_key_t* master_key, const uint32_t* path,
|
||||
size_t path_len, nostr_hd_key_t* derived_key);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // NOSTR_CRYPTO_H
|
||||
238
nostr_core/crypto/nostr_secp256k1.c
Normal file
238
nostr_core/crypto/nostr_secp256k1.c
Normal file
@@ -0,0 +1,238 @@
|
||||
#include "nostr_secp256k1.h"
|
||||
#include <secp256k1.h>
|
||||
#include <secp256k1_schnorrsig.h>
|
||||
#include <secp256k1_ecdh.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Global context for secp256k1 operations
|
||||
static secp256k1_context* g_ctx = NULL;
|
||||
|
||||
int nostr_secp256k1_context_create(void) {
|
||||
if (g_ctx != NULL) {
|
||||
return 1; // Already initialized
|
||||
}
|
||||
|
||||
g_ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY);
|
||||
if (g_ctx == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Add some randomization to the context for better security
|
||||
unsigned char randomize[32];
|
||||
// In a real implementation, you'd want better randomness
|
||||
// For now, just use a simple pattern
|
||||
for (int i = 0; i < 32; i++) {
|
||||
randomize[i] = (unsigned char)(i * 7 + 13);
|
||||
}
|
||||
|
||||
if (!secp256k1_context_randomize(g_ctx, randomize)) {
|
||||
secp256k1_context_destroy(g_ctx);
|
||||
g_ctx = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void nostr_secp256k1_context_destroy(void) {
|
||||
if (g_ctx != NULL) {
|
||||
secp256k1_context_destroy(g_ctx);
|
||||
g_ctx = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_seckey_verify(const unsigned char *seckey) {
|
||||
if (g_ctx == NULL || seckey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return secp256k1_ec_seckey_verify(g_ctx, seckey);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_pubkey_create(nostr_secp256k1_pubkey *pubkey, const unsigned char *seckey) {
|
||||
if (g_ctx == NULL || pubkey == NULL || seckey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_pubkey internal_pubkey;
|
||||
if (!secp256k1_ec_pubkey_create(g_ctx, &internal_pubkey, seckey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the internal representation to our wrapper
|
||||
memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_keypair_create(nostr_secp256k1_keypair *keypair, const unsigned char *seckey) {
|
||||
if (g_ctx == NULL || keypair == NULL || seckey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_keypair internal_keypair;
|
||||
if (!secp256k1_keypair_create(g_ctx, &internal_keypair, seckey)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the internal representation to our wrapper
|
||||
memcpy(keypair->data, &internal_keypair, sizeof(secp256k1_keypair));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_keypair_xonly_pub(nostr_secp256k1_xonly_pubkey *pubkey, const nostr_secp256k1_keypair *keypair) {
|
||||
if (g_ctx == NULL || pubkey == NULL || keypair == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_keypair internal_keypair;
|
||||
secp256k1_xonly_pubkey internal_xonly;
|
||||
|
||||
// Copy from our wrapper to internal representation
|
||||
memcpy(&internal_keypair, keypair->data, sizeof(secp256k1_keypair));
|
||||
|
||||
if (!secp256k1_keypair_xonly_pub(g_ctx, &internal_xonly, NULL, &internal_keypair)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the internal representation to our wrapper
|
||||
memcpy(pubkey->data, &internal_xonly, sizeof(secp256k1_xonly_pubkey));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_xonly_pubkey_parse(nostr_secp256k1_xonly_pubkey *pubkey, const unsigned char *input32) {
|
||||
if (g_ctx == NULL || pubkey == NULL || input32 == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_xonly_pubkey internal_xonly;
|
||||
if (!secp256k1_xonly_pubkey_parse(g_ctx, &internal_xonly, input32)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the internal representation to our wrapper
|
||||
memcpy(pubkey->data, &internal_xonly, sizeof(secp256k1_xonly_pubkey));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_xonly_pubkey_serialize(unsigned char *output32, const nostr_secp256k1_xonly_pubkey *pubkey) {
|
||||
if (g_ctx == NULL || output32 == NULL || pubkey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_xonly_pubkey internal_xonly;
|
||||
|
||||
// Copy from our wrapper to internal representation
|
||||
memcpy(&internal_xonly, pubkey->data, sizeof(secp256k1_xonly_pubkey));
|
||||
|
||||
return secp256k1_xonly_pubkey_serialize(g_ctx, output32, &internal_xonly);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_schnorrsig_sign32(unsigned char *sig64, const unsigned char *msghash32, const nostr_secp256k1_keypair *keypair, const unsigned char *aux_rand32) {
|
||||
if (g_ctx == NULL || sig64 == NULL || msghash32 == NULL || keypair == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_keypair internal_keypair;
|
||||
|
||||
// Copy from our wrapper to internal representation
|
||||
memcpy(&internal_keypair, keypair->data, sizeof(secp256k1_keypair));
|
||||
|
||||
return secp256k1_schnorrsig_sign32(g_ctx, sig64, msghash32, &internal_keypair, aux_rand32);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_schnorrsig_verify(const unsigned char *sig64, const unsigned char *msghash32, const nostr_secp256k1_xonly_pubkey *pubkey) {
|
||||
if (g_ctx == NULL || sig64 == NULL || msghash32 == NULL || pubkey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_xonly_pubkey internal_xonly;
|
||||
|
||||
// Copy from our wrapper to internal representation
|
||||
memcpy(&internal_xonly, pubkey->data, sizeof(secp256k1_xonly_pubkey));
|
||||
|
||||
return secp256k1_schnorrsig_verify(g_ctx, sig64, msghash32, 32, &internal_xonly);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_pubkey_serialize_compressed(unsigned char *output, const nostr_secp256k1_pubkey *pubkey) {
|
||||
if (g_ctx == NULL || output == NULL || pubkey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_pubkey internal_pubkey;
|
||||
size_t outputlen = 33;
|
||||
|
||||
// Copy from our wrapper to internal representation
|
||||
memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
|
||||
|
||||
return secp256k1_ec_pubkey_serialize(g_ctx, output, &outputlen, &internal_pubkey, SECP256K1_EC_COMPRESSED);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_seckey_tweak_add(unsigned char *seckey, const unsigned char *tweak) {
|
||||
if (g_ctx == NULL || seckey == NULL || tweak == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return secp256k1_ec_seckey_tweak_add(g_ctx, seckey, tweak);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ec_pubkey_parse(nostr_secp256k1_pubkey *pubkey, const unsigned char *input, size_t inputlen) {
|
||||
if (g_ctx == NULL || pubkey == NULL || input == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_pubkey internal_pubkey;
|
||||
if (!secp256k1_ec_pubkey_parse(g_ctx, &internal_pubkey, input, inputlen)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Copy the internal representation to our wrapper
|
||||
memcpy(pubkey->data, &internal_pubkey, sizeof(secp256k1_pubkey));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nostr_secp256k1_ecdh(unsigned char *result, const nostr_secp256k1_pubkey *pubkey, const unsigned char *seckey, void *hashfp, void *data) {
|
||||
if (g_ctx == NULL || result == NULL || pubkey == NULL || seckey == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
secp256k1_pubkey internal_pubkey;
|
||||
|
||||
// Copy from our wrapper to internal representation
|
||||
memcpy(&internal_pubkey, pubkey->data, sizeof(secp256k1_pubkey));
|
||||
|
||||
return secp256k1_ecdh(g_ctx, result, &internal_pubkey, seckey, hashfp, data);
|
||||
}
|
||||
|
||||
int nostr_secp256k1_get_random_bytes(unsigned char *buf, size_t len) {
|
||||
if (buf == NULL || len == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Try to use /dev/urandom for good randomness
|
||||
int fd = open("/dev/urandom", O_RDONLY);
|
||||
if (fd >= 0) {
|
||||
ssize_t result = read(fd, buf, len);
|
||||
close(fd);
|
||||
if (result == (ssize_t)len) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to a simple PRNG (not cryptographically secure, but better than nothing)
|
||||
// In a real implementation, you'd want to use a proper CSPRNG
|
||||
static unsigned long seed = 1;
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
seed = seed * 1103515245 + 12345;
|
||||
buf[i] = (unsigned char)(seed >> 16);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
141
nostr_core/crypto/nostr_secp256k1.h
Normal file
141
nostr_core/crypto/nostr_secp256k1.h
Normal file
@@ -0,0 +1,141 @@
|
||||
#ifndef NOSTR_SECP256K1_H
|
||||
#define NOSTR_SECP256K1_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
/** Opaque data structure that holds a parsed and valid public key.
|
||||
* Guaranteed to be 64 bytes in size, and can be safely copied/moved.
|
||||
*/
|
||||
typedef struct nostr_secp256k1_pubkey {
|
||||
unsigned char data[64];
|
||||
} nostr_secp256k1_pubkey;
|
||||
|
||||
/** Opaque data structure that holds a parsed keypair.
|
||||
* Guaranteed to be 96 bytes in size, and can be safely copied/moved.
|
||||
*/
|
||||
typedef struct nostr_secp256k1_keypair {
|
||||
unsigned char data[96];
|
||||
} nostr_secp256k1_keypair;
|
||||
|
||||
/** Opaque data structure that holds a parsed x-only public key.
|
||||
* Guaranteed to be 64 bytes in size, and can be safely copied/moved.
|
||||
*/
|
||||
typedef struct nostr_secp256k1_xonly_pubkey {
|
||||
unsigned char data[64];
|
||||
} nostr_secp256k1_xonly_pubkey;
|
||||
|
||||
/** Initialize the secp256k1 library. Must be called before any other functions.
|
||||
* Returns: 1 on success, 0 on failure.
|
||||
*/
|
||||
int nostr_secp256k1_context_create(void);
|
||||
|
||||
/** Clean up the secp256k1 library resources.
|
||||
*/
|
||||
void nostr_secp256k1_context_destroy(void);
|
||||
|
||||
/** Verify an elliptic curve secret key.
|
||||
* Returns: 1: secret key is valid, 0: secret key is invalid
|
||||
* In: seckey: pointer to a 32-byte secret key.
|
||||
*/
|
||||
int nostr_secp256k1_ec_seckey_verify(const unsigned char *seckey);
|
||||
|
||||
/** Compute the public key for a secret key.
|
||||
* Returns: 1: secret was valid, public key stored. 0: secret was invalid.
|
||||
* Out: pubkey: pointer to the created public key.
|
||||
* In: seckey: pointer to a 32-byte secret key.
|
||||
*/
|
||||
int nostr_secp256k1_ec_pubkey_create(nostr_secp256k1_pubkey *pubkey, const unsigned char *seckey);
|
||||
|
||||
/** Create a keypair from a secret key.
|
||||
* Returns: 1: keypair created, 0: secret key invalid.
|
||||
* Out: keypair: pointer to the created keypair.
|
||||
* In: seckey: pointer to a 32-byte secret key.
|
||||
*/
|
||||
int nostr_secp256k1_keypair_create(nostr_secp256k1_keypair *keypair, const unsigned char *seckey);
|
||||
|
||||
/** Get the x-only public key from a keypair.
|
||||
* Returns: 1 always.
|
||||
* Out: pubkey: pointer to storage for the x-only public key.
|
||||
* In: keypair: pointer to a keypair.
|
||||
*/
|
||||
int nostr_secp256k1_keypair_xonly_pub(nostr_secp256k1_xonly_pubkey *pubkey, const nostr_secp256k1_keypair *keypair);
|
||||
|
||||
/** Parse an x-only public key from bytes.
|
||||
* Returns: 1: public key parsed, 0: invalid public key.
|
||||
* Out: pubkey: pointer to the created x-only public key.
|
||||
* In: input32: pointer to a 32-byte x-only public key.
|
||||
*/
|
||||
int nostr_secp256k1_xonly_pubkey_parse(nostr_secp256k1_xonly_pubkey *pubkey, const unsigned char *input32);
|
||||
|
||||
/** Serialize an x-only public key to bytes.
|
||||
* Returns: 1 always.
|
||||
* Out: output32: pointer to a 32-byte array to store the serialized key.
|
||||
* In: pubkey: pointer to an x-only public key.
|
||||
*/
|
||||
int nostr_secp256k1_xonly_pubkey_serialize(unsigned char *output32, const nostr_secp256k1_xonly_pubkey *pubkey);
|
||||
|
||||
/** Create a Schnorr signature.
|
||||
* Returns: 1: signature created, 0: nonce generation failed or secret key invalid.
|
||||
* Out: sig64: pointer to a 64-byte array where the signature will be placed.
|
||||
* In: msghash32: the 32-byte message hash being signed.
|
||||
* keypair: pointer to an initialized keypair.
|
||||
* aux_rand32: pointer to 32 bytes of auxiliary randomness (can be NULL).
|
||||
*/
|
||||
int nostr_secp256k1_schnorrsig_sign32(unsigned char *sig64, const unsigned char *msghash32, const nostr_secp256k1_keypair *keypair, const unsigned char *aux_rand32);
|
||||
|
||||
/** Verify a Schnorr signature.
|
||||
* Returns: 1: correct signature, 0: incorrect signature
|
||||
* In: sig64: pointer to the 64-byte signature being verified.
|
||||
* msghash32: the 32-byte message hash being verified.
|
||||
* pubkey: pointer to an x-only public key to verify with.
|
||||
*/
|
||||
int nostr_secp256k1_schnorrsig_verify(const unsigned char *sig64, const unsigned char *msghash32, const nostr_secp256k1_xonly_pubkey *pubkey);
|
||||
|
||||
/** Serialize a pubkey object into a serialized byte sequence.
|
||||
* Returns: 1 always.
|
||||
* Out: output: pointer to a 33-byte array to place the serialized key in.
|
||||
* In: pubkey: pointer to a secp256k1_pubkey containing an initialized public key.
|
||||
*
|
||||
* The output will be a 33-byte compressed public key (0x02 or 0x03 prefix + 32 bytes x coordinate).
|
||||
*/
|
||||
int nostr_secp256k1_ec_pubkey_serialize_compressed(unsigned char *output, const nostr_secp256k1_pubkey *pubkey);
|
||||
|
||||
/** Tweak a secret key by adding a 32-byte tweak to it.
|
||||
* Returns: 1: seckey was valid, 0: seckey invalid or resulting key invalid
|
||||
* In/Out: seckey: pointer to a 32-byte secret key. Will be modified in-place.
|
||||
* In: tweak: pointer to a 32-byte tweak.
|
||||
*/
|
||||
int nostr_secp256k1_ec_seckey_tweak_add(unsigned char *seckey, const unsigned char *tweak);
|
||||
|
||||
/** Parse a variable-length public key into the pubkey object.
|
||||
* Returns: 1: public key parsed, 0: invalid public key.
|
||||
* Out: pubkey: pointer to the created public key.
|
||||
* In: input: pointer to a serialized public key
|
||||
* inputlen: length of the array pointed to by input
|
||||
*/
|
||||
int nostr_secp256k1_ec_pubkey_parse(nostr_secp256k1_pubkey *pubkey, const unsigned char *input, size_t inputlen);
|
||||
|
||||
/** Compute an EC Diffie-Hellman secret in constant time.
|
||||
* Returns: 1: exponentiation was successful, 0: scalar was invalid (zero or overflow)
|
||||
* Out: result: a 32-byte array which will be populated by an ECDH secret computed from point and scalar
|
||||
* In: pubkey: a pointer to a secp256k1_pubkey containing an initialized public key
|
||||
* seckey: a 32-byte scalar with which to multiply the point
|
||||
*/
|
||||
int nostr_secp256k1_ecdh(unsigned char *result, const nostr_secp256k1_pubkey *pubkey, const unsigned char *seckey, void *hashfp, void *data);
|
||||
|
||||
/** Generate cryptographically secure random bytes.
|
||||
* Returns: 1: success, 0: failure
|
||||
* Out: buf: buffer to fill with random bytes
|
||||
* In: len: number of bytes to generate
|
||||
*/
|
||||
int nostr_secp256k1_get_random_bytes(unsigned char *buf, size_t len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NOSTR_SECP256K1_H */
|
||||
Reference in New Issue
Block a user