Fixed error in nip04 implementation. Now working
This commit is contained in:
@@ -107,13 +107,18 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
const char* plaintext,
|
||||
char* output,
|
||||
size_t output_size) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Starting encryption\n");
|
||||
|
||||
if (!sender_private_key || !recipient_public_key || !plaintext || !output) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Invalid input - null pointer detected\n");
|
||||
return NOSTR_ERROR_INVALID_INPUT;
|
||||
}
|
||||
|
||||
size_t plaintext_len = strlen(plaintext);
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Plaintext length = %zu\n", plaintext_len);
|
||||
|
||||
if (plaintext_len > NOSTR_NIP04_MAX_PLAINTEXT_SIZE) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Plaintext too large (%zu > %d)\n", plaintext_len, NOSTR_NIP04_MAX_PLAINTEXT_SIZE);
|
||||
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
@@ -125,46 +130,64 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
size_t iv_b64_max = ((16 + 2) / 3) * 4 + 1; // Always 25 bytes
|
||||
size_t estimated_result_len = ciphertext_b64_max + 4 + iv_b64_max; // +4 for "?iv="
|
||||
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Size calculations - padded_len=%zu, ciphertext_b64_max=%zu, estimated_result_len=%zu, output_size=%zu\n",
|
||||
padded_len, ciphertext_b64_max, estimated_result_len, output_size);
|
||||
|
||||
// FIX: Check output buffer size BEFORE doing any work
|
||||
if (estimated_result_len > output_size) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Output buffer too small (%zu > %zu)\n", estimated_result_len, output_size);
|
||||
return NOSTR_ERROR_NIP04_BUFFER_TOO_SMALL;
|
||||
}
|
||||
|
||||
// Step 1: Compute ECDH shared secret
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Computing ECDH shared secret\n");
|
||||
unsigned char shared_secret[32];
|
||||
if (ecdh_shared_secret(sender_private_key, recipient_public_key, shared_secret) != 0) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: ECDH shared secret computation failed\n");
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: ECDH shared secret computed successfully\n");
|
||||
|
||||
// Step 2: Generate random IV (16 bytes)
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Generating random IV\n");
|
||||
unsigned char iv[16];
|
||||
if (nostr_secp256k1_get_random_bytes(iv, 16) != 1) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to generate random IV\n");
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: IV generated successfully\n");
|
||||
|
||||
// Step 3: Pad plaintext using PKCS#7
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Padding plaintext with PKCS#7\n");
|
||||
unsigned char* padded_data = malloc(padded_len);
|
||||
if (!padded_data) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for padded data\n");
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
memcpy(padded_data, plaintext, plaintext_len);
|
||||
size_t actual_padded_len = pkcs7_pad(padded_data, plaintext_len, 16);
|
||||
printf("[DEBUG] nostr_nip04_encrypt: PKCS#7 padding completed - actual_padded_len=%zu\n", actual_padded_len);
|
||||
|
||||
// Step 4: Encrypt using AES-256-CBC
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Encrypting with AES-256-CBC\n");
|
||||
unsigned char* ciphertext = malloc(padded_len);
|
||||
if (!ciphertext) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for ciphertext\n");
|
||||
free(padded_data);
|
||||
return NOSTR_ERROR_MEMORY_FAILED;
|
||||
}
|
||||
|
||||
if (aes_cbc_encrypt(shared_secret, iv, padded_data, actual_padded_len, ciphertext) != 0) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: AES-256-CBC encryption failed\n");
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: AES-256-CBC encryption completed successfully\n");
|
||||
|
||||
// Step 5: Base64 encode ciphertext and IV
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding ciphertext and IV\n");
|
||||
size_t ciphertext_b64_len = ((actual_padded_len + 2) / 3) * 4 + 1;
|
||||
size_t iv_b64_len = ((16 + 2) / 3) * 4 + 1;
|
||||
|
||||
@@ -172,6 +195,7 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
char* iv_b64 = malloc(iv_b64_len);
|
||||
|
||||
if (!ciphertext_b64 || !iv_b64) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Failed to allocate memory for base64 buffers\n");
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
free(ciphertext_b64);
|
||||
@@ -183,8 +207,11 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
size_t ct_b64_len = base64_encode(ciphertext, actual_padded_len, ciphertext_b64, ciphertext_b64_len);
|
||||
size_t iv_b64_len_actual = base64_encode(iv, 16, iv_b64, iv_b64_len);
|
||||
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding results - ct_b64_len=%zu, iv_b64_len_actual=%zu\n", ct_b64_len, iv_b64_len_actual);
|
||||
|
||||
// FIX: Check if encoding succeeded
|
||||
if (ct_b64_len == 0 || iv_b64_len_actual == 0) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding failed\n");
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
free(ciphertext_b64);
|
||||
@@ -192,11 +219,16 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
memory_clear(shared_secret, 32);
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Base64 encoding completed successfully\n");
|
||||
|
||||
// Step 6: Format as "ciphertext?iv=iv_base64" - size check moved earlier, now guaranteed to fit
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Formatting final output string\n");
|
||||
size_t result_len = ct_b64_len + 4 + iv_b64_len_actual + 1; // +4 for "?iv=", +1 for null
|
||||
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Final size check - result_len=%zu, output_size=%zu\n", result_len, output_size);
|
||||
|
||||
if (result_len > output_size) {
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Final output buffer too small (%zu > %zu)\n", result_len, output_size);
|
||||
free(padded_data);
|
||||
free(ciphertext);
|
||||
free(ciphertext_b64);
|
||||
@@ -206,6 +238,8 @@ int nostr_nip04_encrypt(const unsigned char* sender_private_key,
|
||||
}
|
||||
|
||||
snprintf(output, output_size, "%s?iv=%s", ciphertext_b64, iv_b64);
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Formatted output successfully\n");
|
||||
printf("[DEBUG] nostr_nip04_encrypt: Encryption completed successfully - returning NOSTR_SUCCESS\n");
|
||||
|
||||
// Cleanup
|
||||
memory_clear(shared_secret, 32);
|
||||
|
||||
@@ -62,8 +62,8 @@ static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// NIP-44 allows empty messages (unpadded_len can be 0)
|
||||
*padded_len = calc_padded_len(unpadded_len + 2); // +2 for length prefix
|
||||
size_t padded_content_len = calc_padded_len(unpadded_len);
|
||||
*padded_len = padded_content_len + 2; // Add 2 bytes for the length prefix
|
||||
unsigned char* padded = malloc(*padded_len);
|
||||
if (!padded) return NULL;
|
||||
|
||||
@@ -71,33 +71,34 @@ static unsigned char* pad_plaintext(const char* plaintext, size_t* padded_len) {
|
||||
padded[0] = (unpadded_len >> 8) & 0xFF;
|
||||
padded[1] = unpadded_len & 0xFF;
|
||||
|
||||
// Copy plaintext (if any)
|
||||
if (unpadded_len > 0) {
|
||||
memcpy(padded + 2, plaintext, unpadded_len);
|
||||
}
|
||||
|
||||
// Zero-fill padding
|
||||
memset(padded + 2 + unpadded_len, 0, *padded_len - 2 - unpadded_len);
|
||||
// Copy plaintext and add zero-padding
|
||||
memcpy(padded + 2, plaintext, unpadded_len);
|
||||
memset(padded + 2 + unpadded_len, 0, padded_content_len - unpadded_len);
|
||||
|
||||
return padded;
|
||||
}
|
||||
|
||||
// NIP-44 unpadding (per spec)
|
||||
// NIP-44 unpadding (per spec)
|
||||
static char* unpad_plaintext(const unsigned char* padded, size_t padded_len) {
|
||||
if (padded_len < 2) return NULL;
|
||||
|
||||
// Read length prefix (big-endian u16)
|
||||
if (padded_len < 4) return NULL;
|
||||
|
||||
size_t unpadded_len = (padded[0] << 8) | padded[1];
|
||||
if (unpadded_len > padded_len - 2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Verify padding length matches expected
|
||||
size_t expected_padded_len = calc_padded_len(unpadded_len + 2);
|
||||
size_t expected_padded_len = calc_padded_len(unpadded_len);
|
||||
|
||||
printf("--- unpad_plaintext DEBUG ---\n");
|
||||
printf("padded_len: %zu\n", padded_len);
|
||||
printf("unpadded_len: %zu\n", unpadded_len);
|
||||
printf("expected_padded_len: %zu\n", expected_padded_len);
|
||||
printf("--- end unpad_plaintext DEBUG ---\n");
|
||||
|
||||
if (padded_len != expected_padded_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
if (unpadded_len > padded_len - 2) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char* plaintext = malloc(unpadded_len + 1);
|
||||
if (!plaintext) return NULL;
|
||||
|
||||
@@ -339,9 +340,9 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
||||
}
|
||||
|
||||
unsigned char* nonce = payload + 1;
|
||||
size_t ciphertext_len = payload_len - 65; // payload - version - nonce - mac
|
||||
unsigned char* ciphertext = payload + 33;
|
||||
unsigned char* received_mac = payload + payload_len - 32;
|
||||
size_t ciphertext_len = (payload + payload_len - 32) - (payload + 33); // mac_start - ciphertext_start
|
||||
|
||||
// Step 3: Compute ECDH shared secret
|
||||
unsigned char shared_secret[32];
|
||||
@@ -402,6 +403,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
// Constant-time MAC verification
|
||||
// Constant-time MAC verification
|
||||
if (!constant_time_compare(received_mac, computed_mac, 32)) {
|
||||
memory_clear(shared_secret, 32);
|
||||
@@ -439,6 +441,7 @@ int nostr_nip44_decrypt(const unsigned char* recipient_private_key,
|
||||
return NOSTR_ERROR_CRYPTO_FAILED;
|
||||
}
|
||||
|
||||
// Step 8: Remove padding according to NIP-44 spec
|
||||
// Step 8: Remove padding according to NIP-44 spec
|
||||
char* plaintext = unpad_plaintext(padded_plaintext, ciphertext_len);
|
||||
if (!plaintext) {
|
||||
|
||||
36
nostr_core/nostr_core.h
Normal file
36
nostr_core/nostr_core.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef NOSTR_CORE_H
|
||||
#define NOSTR_CORE_H
|
||||
|
||||
/**
|
||||
* NOSTR Core Library - Unified Header File
|
||||
*
|
||||
* This file provides a single include point for all NOSTR Core functionality.
|
||||
* Include this file to access all available NIPs and core functions.
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Core common definitions and utilities
|
||||
#include "nostr_common.h"
|
||||
#include "utils.h"
|
||||
|
||||
// NIP implementations
|
||||
#include "nip001.h" // Basic Protocol
|
||||
#include "nip004.h" // Encryption (legacy)
|
||||
#include "nip005.h" // DNS-based identifiers
|
||||
#include "nip006.h" // Key derivation from mnemonic
|
||||
#include "nip011.h" // Relay information document
|
||||
#include "nip013.h" // Proof of Work
|
||||
#include "nip019.h" // Bech32 encoding (nsec/npub)
|
||||
#include "nip044.h" // Encryption (modern)
|
||||
|
||||
// Relay communication functions are defined in nostr_common.h
|
||||
// WebSocket functions are defined in nostr_common.h
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* NOSTR_CORE_H */
|
||||
@@ -155,36 +155,85 @@ static int ecdh_hash_function_copy_x(unsigned char* output, const unsigned char*
|
||||
int ecdh_shared_secret(const unsigned char* private_key,
|
||||
const unsigned char* public_key_x,
|
||||
unsigned char* shared_secret) {
|
||||
if (!private_key || !public_key_x || !shared_secret) return -1;
|
||||
/*
|
||||
* WARNING: This function requires proper library initialization!
|
||||
*
|
||||
* Before calling this function, you must call nostr_init() to initialize
|
||||
* the secp256k1 global context. All secp256k1 wrapper functions will fail
|
||||
* with error code 0 if the global context g_ctx is not initialized.
|
||||
*
|
||||
* Example usage:
|
||||
* if (nostr_init() != NOSTR_SUCCESS) {
|
||||
* // Handle initialization error
|
||||
* return -1;
|
||||
* }
|
||||
* // Now you can safely call ecdh_shared_secret() and other crypto functions
|
||||
* int result = ecdh_shared_secret(private_key, public_key_x, shared_secret);
|
||||
*
|
||||
* // Don't forget to cleanup when done:
|
||||
* nostr_cleanup();
|
||||
*/
|
||||
|
||||
printf("[DEBUG] ecdh_shared_secret: Starting ECDH computation\n");
|
||||
|
||||
if (!private_key || !public_key_x || !shared_secret) {
|
||||
printf("[DEBUG] ecdh_shared_secret: Invalid input - null pointer detected\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
printf("[DEBUG] ecdh_shared_secret: Input validation passed\n");
|
||||
|
||||
// NIP-04 ECDH: The key insight from the specification is that NOSTR requires
|
||||
// "only the X coordinate of the shared point is used as the secret and it is NOT hashed"
|
||||
//
|
||||
// libsecp256k1's default ECDH hashes the shared point with SHA256.
|
||||
// We need to use a custom hash function that just copies the X coordinate.
|
||||
//
|
||||
// This matches exactly what @noble/curves getSharedSecret() does:
|
||||
// 1. Always use 0x02 prefix (compressed format)
|
||||
// 2. Perform ECDH scalar multiplication
|
||||
// 3. Extract only the X coordinate (bytes 1-33 from the compressed point)
|
||||
// The issue was that we can't just assume the y-coordinate parity.
|
||||
// We need to try both possible y-coordinate parities (0x02 and 0x03).
|
||||
|
||||
unsigned char compressed_pubkey[33];
|
||||
compressed_pubkey[0] = 0x02; // Always use 0x02 prefix like nostr-tools
|
||||
nostr_secp256k1_pubkey pubkey;
|
||||
|
||||
// Try with 0x02 prefix first (even y-coordinate)
|
||||
compressed_pubkey[0] = 0x02;
|
||||
memcpy(compressed_pubkey + 1, public_key_x, 32);
|
||||
|
||||
// Parse the public key
|
||||
nostr_secp256k1_pubkey pubkey;
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) != 1) {
|
||||
return -1;
|
||||
printf("[DEBUG] ecdh_shared_secret: Trying 0x02 prefix (even y)\n");
|
||||
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: 0x02 prefix worked, public key parsed successfully\n");
|
||||
|
||||
// Perform ECDH with our custom hash function that copies the X coordinate
|
||||
printf("[DEBUG] ecdh_shared_secret: Performing ECDH operation with 0x02 prefix\n");
|
||||
if (nostr_secp256k1_ecdh(shared_secret, &pubkey, private_key, ecdh_hash_function_copy_x, NULL) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation completed successfully\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation failed with 0x02 prefix\n");
|
||||
}
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: 0x02 prefix failed, trying 0x03 prefix (odd y)\n");
|
||||
}
|
||||
|
||||
// Perform ECDH with our custom hash function that copies the X coordinate
|
||||
// This is the crucial fix: we pass ecdh_hash_function_copy_x instead of NULL
|
||||
if (nostr_secp256k1_ecdh(shared_secret, &pubkey, private_key, ecdh_hash_function_copy_x, NULL) != 1) {
|
||||
return -1;
|
||||
// Try with 0x03 prefix (odd y-coordinate)
|
||||
compressed_pubkey[0] = 0x03;
|
||||
// public_key_x is already copied above
|
||||
|
||||
if (nostr_secp256k1_ec_pubkey_parse(&pubkey, compressed_pubkey, 33) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: 0x03 prefix worked, public key parsed successfully\n");
|
||||
|
||||
// Perform ECDH with our custom hash function that copies the X coordinate
|
||||
printf("[DEBUG] ecdh_shared_secret: Performing ECDH operation with 0x03 prefix\n");
|
||||
if (nostr_secp256k1_ecdh(shared_secret, &pubkey, private_key, ecdh_hash_function_copy_x, NULL) == 1) {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation completed successfully\n");
|
||||
return 0;
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: ECDH operation failed with 0x03 prefix\n");
|
||||
}
|
||||
} else {
|
||||
printf("[DEBUG] ecdh_shared_secret: Both 0x02 and 0x03 prefixes failed - invalid public key\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
printf("[DEBUG] ecdh_shared_secret: All attempts failed\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user