From 33129d82fdce8cff280bc0b5ba7ed5e49531606d Mon Sep 17 00:00:00 2001 From: Laan Tungir Date: Tue, 2 Sep 2025 12:36:52 -0400 Subject: [PATCH] remove exposed .h crypto headers --- .gitignore | 2 +- README.md | 148 ++++++-- nostr_core/crypto/nostr_aes.c | 67 +++- nostr_core/crypto/nostr_aes.h | 53 --- nostr_core/crypto/nostr_chacha20.c | 38 ++- nostr_core/crypto/nostr_chacha20.h | 115 ------- nostr_core/crypto/nostr_secp256k1.c | 28 +- nostr_core/crypto/nostr_secp256k1.h | 141 -------- nostr_core/nip001.c | 13 +- nostr_core/nip004.c | 17 +- nostr_core/nip044.c | 25 +- nostr_core/nostr_core.h | 100 +++++- nostr_core/utils.c | 145 +++++++- nostr_core/utils.h | 24 ++ tests/chacha20_test.c | 10 +- tests/debug.log | 8 + tests/{nip01_validation_test => nip01_test} | Bin 156784 -> 156776 bytes .../{nip01_validation_test.c => nip01_test.c} | 0 tests/nip04_comparison_test | Bin 123096 -> 0 bytes tests/nip04_comparison_test.c | 92 ----- tests/old/relay_pool_test.c | 318 ------------------ tests/old/test_vectors_display.c | 101 ------ 22 files changed, 562 insertions(+), 883 deletions(-) delete mode 100644 nostr_core/crypto/nostr_aes.h delete mode 100644 nostr_core/crypto/nostr_chacha20.h delete mode 100644 nostr_core/crypto/nostr_secp256k1.h rename tests/{nip01_validation_test => nip01_test} (91%) rename tests/{nip01_validation_test.c => nip01_test.c} (100%) delete mode 100755 tests/nip04_comparison_test delete mode 100644 tests/nip04_comparison_test.c delete mode 100644 tests/old/relay_pool_test.c delete mode 100644 tests/old/test_vectors_display.c diff --git a/.gitignore b/.gitignore index f50c64c7..5257b230 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ nips/ node_modules/ nostr-tools/ tiny-AES-c/ - +blossom/ Trash/debug_tests/ diff --git a/README.md b/README.md index 63036747..29d3f823 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,118 @@ # NOSTR Core Library -A comprehensive, production-ready C library for NOSTR protocol implementation with OpenSSL-based cryptography and extensive protocol support. +A C library for NOSTR protocol implementation. Work in progress. -[![Version](https://img.shields.io/badge/version-0.1.20-blue.svg)](VERSION) +[![Version](https://img.shields.io/badge/version-0.2.1-blue.svg)](VERSION) [![License](https://img.shields.io/badge/license-MIT-green.svg)](#license) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](#building) -## ๐Ÿš€ Features -### Core Protocol Support -- **NIP-01**: Basic protocol flow - event creation, signing, and validation -- **NIP-04**: Encrypted direct messages (ECDH + AES-CBC + Base64) -- **NIP-05**: DNS-based internet identifier verification -- **NIP-06**: Key derivation from mnemonic (BIP39/BIP32 compliant) -- **NIP-11**: Relay information documents -- **NIP-13**: Proof of Work for events -- **NIP-44**: Versioned encrypted direct messages (ECDH + ChaCha20 + HMAC) +## ๐Ÿ“‹ NIP Implementation Status -### Cryptographic Features -- **OpenSSL-Based**: Production-grade cryptography with OpenSSL backend -- **Secp256k1**: Complete elliptic curve implementation bundled -- **BIP39**: Mnemonic phrase generation and validation -- **BIP32**: Hierarchical deterministic key derivation -- **ChaCha20**: Stream cipher for NIP-44 encryption -- **AES-CBC**: Block cipher for NIP-04 encryption -- **Schnorr Signatures**: BIP-340 compliant signing and verification +### Core Protocol NIPs +- [x] [NIP-01](nips/01.md) - Basic protocol flow - event creation, signing, and validation +- [ ] [NIP-02](nips/02.md) - Contact List and Petnames +- [ ] [NIP-03](nips/03.md) - OpenTimestamps Attestations for Events +- [x] [NIP-04](nips/04.md) - Encrypted Direct Messages (legacy) +- [x] [NIP-05](nips/05.md) - Mapping Nostr keys to DNS-based internet identifiers +- [x] [NIP-06](nips/06.md) - Basic key derivation from mnemonic seed phrase +- [ ] [NIP-07](nips/07.md) - `window.nostr` capability for web browsers +- [ ] [NIP-08](nips/08.md) - Handling Mentions +- [ ] [NIP-09](nips/09.md) - Event Deletion +- [ ] [NIP-10](nips/10.md) - Conventions for clients' use of `e` and `p` tags in text events +- [x] [NIP-11](nips/11.md) - Relay Information Document +- [ ] [NIP-12](nips/12.md) - Generic Tag Queries +- [x] [NIP-13](nips/13.md) - Proof of Work +- [ ] [NIP-14](nips/14.md) - Subject tag in text events +- [ ] [NIP-15](nips/15.md) - Nostr Marketplace (for resilient marketplaces) +- [ ] [NIP-16](nips/16.md) - Event Treatment +- [ ] [NIP-17](nips/17.md) - Private Direct Messages +- [ ] [NIP-18](nips/18.md) - Reposts +- [x] [NIP-19](nips/19.md) - bech32-encoded entities +- [ ] [NIP-20](nips/20.md) - Command Results +- [ ] [NIP-21](nips/21.md) - `nostr:` URI scheme +- [ ] [NIP-22](nips/22.md) - Event `created_at` Limits +- [ ] [NIP-23](nips/23.md) - Long-form Content +- [ ] [NIP-24](nips/24.md) - Extra metadata fields and tags +- [ ] [NIP-25](nips/25.md) - Reactions +- [ ] [NIP-26](nips/26.md) - Delegated Event Signing +- [ ] [NIP-27](nips/27.md) - Text Note References +- [ ] [NIP-28](nips/28.md) - Public Chat +- [ ] [NIP-29](nips/29.md) - Relay-based Groups +- [ ] [NIP-30](nips/30.md) - Custom Emoji +- [ ] [NIP-31](nips/31.md) - Dealing with Unknown Events +- [ ] [NIP-32](nips/32.md) - Labeling +- [ ] [NIP-33](nips/33.md) - Parameterized Replaceable Events +- [ ] [NIP-34](nips/34.md) - `git` stuff +- [ ] [NIP-35](nips/35.md) - Torrents +- [ ] [NIP-36](nips/36.md) - Sensitive Content / Content Warning +- [ ] [NIP-37](nips/37.md) - Draft Events +- [ ] [NIP-38](nips/38.md) - User Statuses +- [ ] [NIP-39](nips/39.md) - External Identities in Profiles +- [ ] [NIP-40](nips/40.md) - Expiration Timestamp +- [ ] [NIP-42](nips/42.md) - Authentication of clients to relays +- [x] [NIP-44](nips/44.md) - Versioned Encryption +- [ ] [NIP-45](nips/45.md) - Counting results +- [ ] [NIP-46](nips/46.md) - Nostr Connect +- [ ] [NIP-47](nips/47.md) - Wallet Connect +- [ ] [NIP-48](nips/48.md) - Proxy Tags +- [ ] [NIP-49](nips/49.md) - Private Key Encryption +- [ ] [NIP-50](nips/50.md) - Search Capability +- [ ] [NIP-51](nips/51.md) - Lists +- [ ] [NIP-52](nips/52.md) - Calendar Events +- [ ] [NIP-53](nips/53.md) - Live Activities +- [ ] [NIP-54](nips/54.md) - Wiki +- [ ] [NIP-55](nips/55.md) - Android Signer Application +- [ ] [NIP-56](nips/56.md) - Reporting +- [ ] [NIP-57](nips/57.md) - Lightning Zaps +- [ ] [NIP-58](nips/58.md) - Badges +- [ ] [NIP-59](nips/59.md) - Gift Wrap +- [ ] [NIP-60](nips/60.md) - Cashu Wallet +- [ ] [NIP-61](nips/61.md) - Nutzaps +- [ ] [NIP-62](nips/62.md) - Log events +- [ ] [NIP-64](nips/64.md) - Chess (PGN) +- [ ] [NIP-65](nips/65.md) - Relay List Metadata +- [ ] [NIP-66](nips/66.md) - Relay Monitor +- [ ] [NIP-68](nips/68.md) - Web badges +- [ ] [NIP-69](nips/69.md) - Peer-to-peer Order events +- [ ] [NIP-70](nips/70.md) - Protected Events +- [ ] [NIP-71](nips/71.md) - Video Events +- [ ] [NIP-72](nips/72.md) - Moderated Communities +- [ ] [NIP-73](nips/73.md) - External Content IDs +- [ ] [NIP-75](nips/75.md) - Zap Goals +- [ ] [NIP-77](nips/77.md) - Arbitrary custom app data +- [ ] [NIP-78](nips/78.md) - Application-specific data +- [ ] [NIP-84](nips/84.md) - Highlights +- [ ] [NIP-86](nips/86.md) - Relay Management API +- [ ] [NIP-87](nips/87.md) - Relay List Recommendations +- [ ] [NIP-88](nips/88.md) - Stella: A Stellar Relay +- [ ] [NIP-89](nips/89.md) - Recommended Application Handlers +- [ ] [NIP-90](nips/90.md) - Data Vending Machines +- [ ] [NIP-92](nips/92.md) - Media Attachments +- [ ] [NIP-94](nips/94.md) - File Metadata +- [ ] [NIP-96](nips/96.md) - HTTP File Storage Integration +- [ ] [NIP-98](nips/98.md) - HTTP Auth +- [ ] [NIP-99](nips/99.md) - Classified Listings -### Networking & Relay Support -- **Multi-Relay Queries**: Synchronous querying with progress callbacks -- **Relay Pools**: Asynchronous connection management with statistics -- **OpenSSL WebSocket Communication**: Full relay protocol support with TLS -- **NIP-05 Identifier Verification**: DNS-based identity resolution -- **NIP-11 Relay Information**: Automatic relay capability discovery -- **Event Deduplication**: Automatic handling of duplicate events across relays -- **Connection Management**: Automatic reconnection and error handling +**Legend:** โœ… Fully Implemented | โš ๏ธ Partial Implementation | โŒ Not Implemented -### Developer Experience -- **System Dependencies**: Uses system-installed OpenSSL, curl, and secp256k1 libraries -- **Thread-Safe**: Core cryptographic functions are stateless -- **Cross-Platform**: Builds on Linux, macOS, Windows -- **Comprehensive Examples**: Ready-to-run demonstration programs -- **Automatic Versioning**: Git-tag based version management +**Implementation Summary:** 8 of 96+ NIPs fully implemented (8.3%) + +## ๐Ÿ“ฆ Blossom Protocol Support (BUD Implementation Status) + +### Blossom Upgrade Documents (Client-Side Implementation) +- [ ] [BUD-01](blossom/buds/01.md) - Blob Retrieval - GET/HEAD endpoints for blob download +- [ ] [BUD-02](blossom/buds/02.md) - Upload/Management - Blob upload, listing, and deletion +- [ ] [BUD-03](blossom/buds/03.md) - User Server Lists - Managing preferred Blossom servers (kind 10063) +- [ ] [BUD-04](blossom/buds/04.md) - Mirroring - Copying blobs between servers +- [ ] [BUD-05](blossom/buds/05.md) - Media Optimization - Trusted server processing for media +- [ ] [BUD-06](blossom/buds/06.md) - Upload Requirements - Pre-upload validation with HEAD requests +- [ ] [BUD-07](blossom/buds/07.md) - Paid Operations - Payment handling (Cashu/Lightning) +- [ ] [BUD-08](blossom/buds/08.md) - NIP-94 Metadata - File metadata tag processing +- [ ] [BUD-09](blossom/buds/09.md) - Blob Reporting - Content moderation and reporting (NIP-56) + +**Implementation Summary:** 0 of 9 client-relevant BUDs implemented (0%) +**Protocol Overview:** [Blossom README](blossom/README.md) ## ๐Ÿ“ฆ Quick Start @@ -434,7 +506,7 @@ make arm64 ## ๐Ÿ“ˆ Version History -Current version: **0.1.20** +Current version: **0.2.1** The library uses automatic semantic versioning based on Git tags. Each build increments the patch version automatically. @@ -442,13 +514,15 @@ The library uses automatic semantic versioning based on Git tags. Each build inc - **OpenSSL Migration**: Transitioned from mbedTLS to OpenSSL for improved compatibility - **NIP-05 Support**: DNS-based internet identifier verification - **NIP-11 Support**: Relay information document fetching and parsing +- **NIP-19 Support**: Bech32-encoded entities (nsec/npub) - **Enhanced WebSocket**: OpenSSL-based TLS WebSocket communication -- **Production Ready**: Comprehensive test suite and error handling +- **Comprehensive Testing**: Extensive test suite and error handling **Version Timeline:** +- `v0.2.x` - Current development releases with enhanced NIP support - `v0.1.x` - Initial development releases - Focus on core protocol implementation and OpenSSL-based crypto -- Full NIP-01, NIP-04, NIP-05, NIP-06, NIP-11, NIP-13, NIP-44 support +- Full NIP-01, NIP-04, NIP-05, NIP-06, NIP-11, NIP-13, NIP-19, NIP-44 support ## ๐Ÿ› Troubleshooting @@ -496,4 +570,4 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file **Built with โค๏ธ for the decentralized web** -*OpenSSL-based โ€ข Minimal dependencies โ€ข Production ready* +*OpenSSL-based โ€ข Minimal dependencies โ€ข Work in progress* diff --git a/nostr_core/crypto/nostr_aes.c b/nostr_core/crypto/nostr_aes.c index d9972390..6b6e921b 100644 --- a/nostr_core/crypto/nostr_aes.c +++ b/nostr_core/crypto/nostr_aes.c @@ -1,15 +1,76 @@ /* * 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 +#include #include // CBC mode, for memset -#include "nostr_aes.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 +}; + +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + +// Function prototypes (internal use only) +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key); +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey); +static void SubBytes(state_t* state); +static void ShiftRows(state_t* state); +static uint8_t xtime(uint8_t x); +static void MixColumns(state_t* state); +static void InvMixColumns(state_t* state); +static void InvSubBytes(state_t* state); +static void InvShiftRows(state_t* state); +static void Cipher(state_t* state, const uint8_t* RoundKey); +static void InvCipher(state_t* state, const uint8_t* RoundKey); +static void XorWithIv(uint8_t* buf, const uint8_t* Iv); + +// Public functions (used by NIP-04 implementation) +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) +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 // The number of columns comprising a state in AES. This is a constant in AES. Value=4 #define Nb 4 diff --git a/nostr_core/crypto/nostr_aes.h b/nostr_core/crypto/nostr_aes.h deleted file mode 100644 index 3b0aad18..00000000 --- a/nostr_core/crypto/nostr_aes.h +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef _NOSTR_AES_H_ -#define _NOSTR_AES_H_ - -#include -#include - -// 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_ diff --git a/nostr_core/crypto/nostr_chacha20.c b/nostr_core/crypto/nostr_chacha20.c index bb95d377..5814171c 100644 --- a/nostr_core/crypto/nostr_chacha20.c +++ b/nostr_core/crypto/nostr_chacha20.c @@ -1,15 +1,47 @@ /* * 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 +#include #include +/* + * ============================================================================ + * CONSTANTS AND DEFINITIONS + * ============================================================================ + */ + +#define CHACHA20_KEY_SIZE 32 /* 256 bits */ +#define CHACHA20_NONCE_SIZE 12 /* 96 bits */ +#define CHACHA20_BLOCK_SIZE 64 /* 512 bits */ + +/* + * ============================================================================ + * FUNCTION PROTOTYPES (INTERNAL USE ONLY) + * ============================================================================ + */ + +// Internal utility functions +static uint32_t bytes_to_u32_le(const uint8_t *bytes); +static void u32_to_bytes_le(uint32_t val, uint8_t *bytes); + +// Public functions (used by NIP-44 implementation) +void chacha20_quarter_round(uint32_t state[16], int a, int b, int c, int d); +int chacha20_block(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], uint8_t output[64]); +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); +void chacha20_init_state(uint32_t state[16], const uint8_t key[32], + uint32_t counter, const uint8_t nonce[12]); +void chacha20_serialize_state(const uint32_t state[16], uint8_t output[64]); + /* * ============================================================================ * UTILITY MACROS AND FUNCTIONS diff --git a/nostr_core/crypto/nostr_chacha20.h b/nostr_core/crypto/nostr_chacha20.h deleted file mode 100644 index 93a2f35c..00000000 --- a/nostr_core/crypto/nostr_chacha20.h +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 -#include - -#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 */ diff --git a/nostr_core/crypto/nostr_secp256k1.c b/nostr_core/crypto/nostr_secp256k1.c index 8e2d1829..5556b98f 100644 --- a/nostr_core/crypto/nostr_secp256k1.c +++ b/nostr_core/crypto/nostr_secp256k1.c @@ -1,4 +1,3 @@ -#include "nostr_secp256k1.h" #include #include #include @@ -6,6 +5,33 @@ #include #include #include +#include + +/* + * PRIVATE INTERNAL FUNCTIONS - NOT EXPORTED + * These functions are for internal library use only. + */ + +/** 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; // Global context for secp256k1 operations static secp256k1_context* g_ctx = NULL; diff --git a/nostr_core/crypto/nostr_secp256k1.h b/nostr_core/crypto/nostr_secp256k1.h deleted file mode 100644 index e5e3a46a..00000000 --- a/nostr_core/crypto/nostr_secp256k1.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef NOSTR_SECP256K1_H -#define NOSTR_SECP256K1_H - -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/** 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 */ diff --git a/nostr_core/nip001.c b/nostr_core/nip001.c index 77402be9..69aa77f2 100644 --- a/nostr_core/nip001.c +++ b/nostr_core/nip001.c @@ -6,7 +6,6 @@ #include "nip001.h" #include "utils.h" -#include "crypto/nostr_secp256k1.h" #include "../cjson/cJSON.h" #include #include @@ -14,9 +13,21 @@ #include #include "../nostr_core/nostr_common.h" +// Forward declarations for crypto functions (private API) +// These functions are implemented in crypto/ but not exposed through public headers +typedef struct { + unsigned char data[64]; +} nostr_secp256k1_xonly_pubkey; + +int nostr_secp256k1_xonly_pubkey_parse(nostr_secp256k1_xonly_pubkey* pubkey, const unsigned char* input32); +int nostr_secp256k1_schnorrsig_verify(const unsigned char* sig64, const unsigned char* msg32, const nostr_secp256k1_xonly_pubkey* pubkey); + // Declare utility functions void nostr_bytes_to_hex(const unsigned char* bytes, size_t len, char* hex); int nostr_hex_to_bytes(const char* hex, unsigned char* bytes, size_t len); +int nostr_sha256(const unsigned char* data, size_t len, unsigned char* hash); +int nostr_ec_public_key_from_private_key(const unsigned char* private_key, unsigned char* public_key); +int nostr_ec_sign(const unsigned char* private_key, const unsigned char* hash, unsigned char* signature); /** * Create and sign a NOSTR event diff --git a/nostr_core/nip004.c b/nostr_core/nip004.c index 46144b7f..183f945e 100644 --- a/nostr_core/nip004.c +++ b/nostr_core/nip004.c @@ -6,13 +6,24 @@ #include "nip004.h" #include "utils.h" #include "nostr_common.h" -#include "crypto/nostr_secp256k1.h" #include #include #include -// Include our AES implementation -#include "crypto/nostr_aes.h" +// Forward declarations for crypto functions (private API) +// These functions are implemented in crypto/ but not exposed through public headers +int ecdh_shared_secret(const unsigned char* private_key, const unsigned char* public_key, unsigned char* shared_secret); +int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len); + +// AES context and functions for NIP-04 encryption +struct AES_ctx { + unsigned char RoundKey[240]; // AES-256 key expansion + unsigned char Iv[16]; // Initialization vector +}; + +void AES_init_ctx_iv(struct AES_ctx* ctx, const unsigned char* key, const unsigned char* iv); +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, unsigned char* buf, size_t length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, unsigned char* buf, size_t length); // Forward declarations for internal functions static int aes_cbc_encrypt(const unsigned char* key, const unsigned char* iv, diff --git a/nostr_core/nip044.c b/nostr_core/nip044.c index 3bf2a7aa..9051dfb7 100644 --- a/nostr_core/nip044.c +++ b/nostr_core/nip044.c @@ -9,10 +9,29 @@ #include #include #include -#include "./crypto/nostr_secp256k1.h" -// Include our ChaCha20 implementation -#include "crypto/nostr_chacha20.h" +// Forward declarations for crypto functions (private API) +// These functions are implemented in crypto/ but not exposed through public headers +int ecdh_shared_secret(const unsigned char* private_key, const unsigned char* public_key, unsigned char* shared_secret); +int nostr_secp256k1_get_random_bytes(unsigned char* buf, size_t len); + +// ChaCha20 functions for NIP-44 encryption +int chacha20_encrypt(const unsigned char key[32], unsigned int counter, + const unsigned char nonce[12], const unsigned char* input, + unsigned char* output, size_t length); + +// HKDF functions for NIP-44 key derivation +int nostr_hkdf_extract(const unsigned char* salt, size_t salt_len, + const unsigned char* ikm, size_t ikm_len, + unsigned char* prk); +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); + +// HMAC-SHA256 function for NIP-44 authentication +int nostr_hmac_sha256(const unsigned char* key, size_t key_len, + const unsigned char* data, size_t data_len, + unsigned char* hmac); // Forward declarations for internal functions static size_t calc_padded_len(size_t unpadded_len); diff --git a/nostr_core/nostr_core.h b/nostr_core/nostr_core.h index ff42acaa..e5f2219f 100644 --- a/nostr_core/nostr_core.h +++ b/nostr_core/nostr_core.h @@ -1,11 +1,103 @@ #ifndef NOSTR_CORE_H #define NOSTR_CORE_H -/** - * NOSTR Core Library - Unified Header File +/* + * NOSTR Core Library - Complete API Reference * - * This file provides a single include point for all NOSTR Core functionality. - * Include this file to access all available NIPs and core functions. + * This header includes ALL library functionality. For modular includes, + * use individual headers instead. + * + * ============================================================================ + * QUICK FUNCTION REFERENCE - Find what you need fast! + * ============================================================================ + * + * EVENT OPERATIONS (NIP-01): + * - nostr_create_and_sign_event() -> Create and sign new Nostr events + * - nostr_validate_event() -> Validate complete Nostr event + * - nostr_validate_event_structure() -> Check event structure only + * - nostr_verify_event_signature() -> Verify cryptographic signature + * + * CRYPTOGRAPHIC HASHING: + * - nostr_sha256() -> Single-call SHA-256 hash + * - nostr_sha256_init() -> Initialize streaming SHA-256 context + * - nostr_sha256_update() -> Process data chunks incrementally + * - nostr_sha256_final() -> Finalize hash and clear context + * - nostr_sha256_file_stream() -> Hash large files efficiently (NEW!) + * - nostr_sha512() -> SHA-512 hash function + * - nostr_hmac_sha256() -> HMAC with SHA-256 + * - nostr_hmac_sha512() -> HMAC with SHA-512 + * + * DIGITAL SIGNATURES & KEYS: + * - nostr_schnorr_sign() -> Create Schnorr signatures + * - nostr_ec_public_key_from_private_key() -> Generate public keys + * - nostr_ec_private_key_verify() -> Validate private key format + * - nostr_ec_sign() -> ECDSA signature creation + * - nostr_rfc6979_generate_k() -> Deterministic nonce generation + * + * NIP-04 ENCRYPTION (Legacy): + * - nostr_nip04_encrypt() -> Encrypt messages (AES-256-CBC) + * - nostr_nip04_decrypt() -> Decrypt messages (AES-256-CBC) + * + * NIP-44 ENCRYPTION (Modern - Recommended): + * - nostr_nip44_encrypt() -> Encrypt with ChaCha20 + HMAC + * - nostr_nip44_encrypt_with_nonce() -> Encrypt with specific nonce (testing) + * - nostr_nip44_decrypt() -> Decrypt ChaCha20 + HMAC messages + * + * BIP39 MNEMONICS: + * - nostr_bip39_mnemonic_from_bytes() -> Generate mnemonic from entropy + * - nostr_bip39_mnemonic_validate() -> Validate mnemonic phrase + * - nostr_bip39_mnemonic_to_seed() -> Convert mnemonic to seed + * + * BIP32 HD WALLETS: + * - nostr_bip32_key_from_seed() -> Create master key from seed + * - nostr_bip32_derive_child() -> Derive child key from parent + * - nostr_bip32_derive_path() -> Derive keys from derivation path + * + * KEY DERIVATION: + * - nostr_hkdf() -> HKDF key derivation (full) + * - nostr_hkdf_extract() -> HKDF extract step only + * - nostr_hkdf_expand() -> HKDF expand step only + * - nostr_pbkdf2_hmac_sha512() -> PBKDF2 with HMAC-SHA512 + * - ecdh_shared_secret() -> ECDH shared secret computation + * + * UTILITIES & ENCODING: + * - nostr_bytes_to_hex() -> Convert bytes to hex string + * - nostr_hex_to_bytes() -> Convert hex string to bytes + * - base64_encode() -> Base64 encoding + * - base64_decode() -> Base64 decoding + * + * SYSTEM FUNCTIONS: + * - nostr_crypto_init() -> Initialize crypto subsystem + * - nostr_crypto_cleanup() -> Cleanup crypto subsystem + * + * ============================================================================ + * USAGE EXAMPLES: + * ============================================================================ + * + * Basic Event Creation: + * cJSON* event = nostr_create_and_sign_event(1, "Hello Nostr!", NULL, private_key, time(NULL)); + * if (nostr_validate_event(event) == NOSTR_SUCCESS) { ... } + * + * Streaming SHA-256 (for large files): + * nostr_sha256_ctx_t ctx; + * nostr_sha256_init(&ctx); + * // Process data in chunks... + * nostr_sha256_update(&ctx, data, data_size); + * nostr_sha256_final(&ctx, hash_output); + * + * File Hashing: + * unsigned char file_hash[32]; + * nostr_sha256_file_stream("large_video.mp4", file_hash); + * + * Modern Encryption (NIP-44): + * nostr_nip44_encrypt(sender_key, recipient_pubkey, "secret message", output, sizeof(output)); + * + * HD Wallet Derivation: + * nostr_bip32_key_from_seed(seed, 64, &master_key); + * uint32_t path[] = {44, 1237, 0, 0, 0}; // m/44'/1237'/0'/0/0 + * nostr_bip32_derive_path(&master_key, path, 5, &derived_key); + * + * ============================================================================ */ #ifdef __cplusplus diff --git a/nostr_core/utils.c b/nostr_core/utils.c index 3069c003..06545930 100644 --- a/nostr_core/utils.c +++ b/nostr_core/utils.c @@ -9,14 +9,28 @@ #include #include -// Include our secp256k1 wrapper for elliptic curve operations -#include "crypto/nostr_secp256k1.h" +// Forward declarations for crypto functions (private API) +// These functions are implemented in crypto/ but not exposed through public headers -// Include our self-contained AES implementation for NIP-04 -#include "crypto/nostr_aes.h" +// secp256k1 functions +typedef struct { + unsigned char data[64]; +} nostr_secp256k1_pubkey; -// Include our ChaCha20 implementation for NIP-44 -#include "crypto/nostr_chacha20.h" +typedef struct { + unsigned char data[96]; +} nostr_secp256k1_keypair; + +int nostr_secp256k1_context_create(void); +void nostr_secp256k1_context_destroy(void); +int nostr_secp256k1_ec_pubkey_parse(nostr_secp256k1_pubkey* pubkey, const unsigned char* input, size_t inputlen); +int nostr_secp256k1_ecdh(unsigned char* output, const nostr_secp256k1_pubkey* pubkey, const unsigned char* privkey, void* hashfp, void* data); +int nostr_secp256k1_ec_seckey_verify(const unsigned char* seckey); +int nostr_secp256k1_ec_pubkey_create(nostr_secp256k1_pubkey* pubkey, const unsigned char* privkey); +int nostr_secp256k1_ec_pubkey_serialize_compressed(unsigned char* output, const nostr_secp256k1_pubkey* pubkey); +int nostr_secp256k1_keypair_create(nostr_secp256k1_keypair* keypair, const unsigned char* privkey); +int nostr_secp256k1_schnorrsig_sign32(unsigned char* sig, const unsigned char* msg32, const nostr_secp256k1_keypair* keypair, const unsigned char* aux_rand32); +int nostr_secp256k1_ec_seckey_tweak_add(unsigned char* seckey, const unsigned char* tweak); // ============================================================================= @@ -328,6 +342,125 @@ int nostr_sha256(const unsigned char* data, size_t len, unsigned char* hash) { return 0; } +// ============================================================================= +// STREAMING SHA-256 IMPLEMENTATION +// ============================================================================= + +int nostr_sha256_init(nostr_sha256_ctx_t* ctx) { + if (!ctx) return -1; + + // Initialize SHA-256 state + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; + + // Initialize counters and buffer + ctx->bitlen = 0; + ctx->buflen = 0; + + return 0; +} + +int nostr_sha256_update(nostr_sha256_ctx_t* ctx, const unsigned char* data, size_t len) { + if (!ctx || !data) return -1; + + for (size_t i = 0; i < len; i++) { + ctx->buffer[ctx->buflen] = data[i]; + ctx->buflen++; + + // Process complete blocks + if (ctx->buflen == 64) { + sha256_transform(ctx->state, ctx->buffer); + ctx->bitlen += 512; // 64 bytes * 8 bits + ctx->buflen = 0; + } + } + + return 0; +} + +int nostr_sha256_final(nostr_sha256_ctx_t* ctx, unsigned char* hash) { + if (!ctx || !hash) return -1; + + // Calculate final bit length + uint64_t final_bitlen = ctx->bitlen + (ctx->buflen * 8); + + // Pad the message + ctx->buffer[ctx->buflen] = 0x80; + ctx->buflen++; + + // If not enough space for length, pad and process another block + if (ctx->buflen > 56) { + while (ctx->buflen < 64) { + ctx->buffer[ctx->buflen] = 0x00; + ctx->buflen++; + } + sha256_transform(ctx->state, ctx->buffer); + ctx->buflen = 0; + } + + // Pad with zeros up to 56 bytes + while (ctx->buflen < 56) { + ctx->buffer[ctx->buflen] = 0x00; + ctx->buflen++; + } + + // Append length as big-endian 64-bit integer + for (int i = 0; i < 8; i++) { + ctx->buffer[56 + i] = (final_bitlen >> (56 - i * 8)) & 0xff; + } + + // Process final block + sha256_transform(ctx->state, ctx->buffer); + + // Convert state to output bytes + for (int i = 0; i < 8; i++) { + hash[i * 4] = (ctx->state[i] >> 24) & 0xff; + hash[i * 4 + 1] = (ctx->state[i] >> 16) & 0xff; + hash[i * 4 + 2] = (ctx->state[i] >> 8) & 0xff; + hash[i * 4 + 3] = ctx->state[i] & 0xff; + } + + // Clear sensitive data + memory_clear(ctx, sizeof(nostr_sha256_ctx_t)); + + return 0; +} + +int nostr_sha256_file_stream(const char* filename, unsigned char* hash) { + if (!filename || !hash) return -1; + + FILE* file = fopen(filename, "rb"); + if (!file) return -1; + + nostr_sha256_ctx_t ctx; + if (nostr_sha256_init(&ctx) != 0) { + fclose(file); + return -1; + } + + // Process file in 4KB chunks for memory efficiency + unsigned char buffer[4096]; + size_t bytes_read; + + while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { + if (nostr_sha256_update(&ctx, buffer, bytes_read) != 0) { + fclose(file); + return -1; + } + } + + fclose(file); + + // Finalize and return result + return nostr_sha256_final(&ctx, hash); +} + // ============================================================================= // HMAC IMPLEMENTATION // ============================================================================= diff --git a/nostr_core/utils.h b/nostr_core/utils.h index dae184a3..a1b927b8 100644 --- a/nostr_core/utils.h +++ b/nostr_core/utils.h @@ -45,6 +45,30 @@ void nostr_crypto_cleanup(void); // SHA-256 hash function int nostr_sha256(const unsigned char *data, size_t len, unsigned char *hash); +// ============================================================================= +// STREAMING SHA-256 FUNCTIONS +// ============================================================================= + +// SHA-256 streaming context +typedef struct { + uint32_t state[8]; // Current hash state + unsigned char buffer[64]; // Input buffer for incomplete blocks + uint64_t bitlen; // Total bits processed + size_t buflen; // Current buffer length +} nostr_sha256_ctx_t; + +// Initialize SHA-256 streaming context +int nostr_sha256_init(nostr_sha256_ctx_t* ctx); + +// Update SHA-256 context with new data +int nostr_sha256_update(nostr_sha256_ctx_t* ctx, const unsigned char* data, size_t len); + +// Finalize SHA-256 and output hash +int nostr_sha256_final(nostr_sha256_ctx_t* ctx, unsigned char* hash); + +// Stream SHA-256 hash of a file +int nostr_sha256_file_stream(const char* filename, 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, diff --git a/tests/chacha20_test.c b/tests/chacha20_test.c index 11e250de..4e57eafa 100644 --- a/tests/chacha20_test.c +++ b/tests/chacha20_test.c @@ -9,7 +9,15 @@ #include #include #include -#include "../nostr_core/crypto/nostr_chacha20.h" + +// Forward declarations for ChaCha20 functions (private API) +// These functions are implemented in crypto/ but not exposed through public headers +void chacha20_quarter_round(uint32_t state[16], int a, int b, int c, int d); +int chacha20_block(const uint8_t key[32], uint32_t counter, + const uint8_t nonce[12], uint8_t output[64]); +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); // Helper function to convert hex string to bytes static int hex_to_bytes(const char* hex, uint8_t* bytes, size_t len) { diff --git a/tests/debug.log b/tests/debug.log index 1264c80d..b46110ea 100644 --- a/tests/debug.log +++ b/tests/debug.log @@ -6,3 +6,11 @@ }] [10:24:53.944] RECV nostr.mom:443: ["EVENT","sync_0_1755354293",{"content":"GM๐Ÿซก","created_at":1755354265,"id":"3e7c67349dd3e1ccaaf4dcd6f5987451d63561b14cdff6c6e68cc5448ec5acaf","kind":1,"pubkey":"ff2f4cd786e42b4323749c91517ec7baf22dfd035b7a101bea83b6e2bcbacd15","sig":"2278afa7b7f42b68f8b3f393bb72b6af4b2a34b77008e109232e24bcfe8a3d1ce917187ef1ca68f4a69e52c2067c14da03ed63e31e4137b1175f8ee1a08b7c21","tags":[["e","45983e18b7c0f28933ecd1c4ead88eb5561caa465a5bc939914b64fa455aefc3","wss://relay.primal.net","root"],["p","db625e7637543ca7d7be65025834db318a0c7b75b0e23d4fb9e39229f5ba6fa7","","mention"]]}] [10:24:53.944] SEND nostr.mom:443: ["CLOSE", "sync_0_1755354293"] + +=== NOSTR WebSocket Debug Log Started === +[12:34:34.841] SEND nostr.mom:443: ["REQ", "sync_0_1756830874", { + "kinds": [1], + "limit": 1 + }] +[12:34:34.997] RECV nostr.mom:443: ["EVENT","sync_0_1756830874",{"content":"It's a lot of work. ๐Ÿซ‚๐Ÿซ‚๐Ÿซ‚","created_at":1756830871,"id":"dd1db1b8e25c1278b6dc47b2a05633854a1afb936ea035a06182f4e0683a732e","kind":1,"pubkey":"3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24","sig":"33ef0e09477fc978fccfe57d40856952af01b7e413a6174cb99e1d39e0639ba75d5c300f74bd2945c48275debb53f80e7f6a5cc91a4511323b756f5e09707969","tags":[["alt","A short note: It's a lot of work. ๐Ÿซ‚๐Ÿซ‚๐Ÿซ‚"],["e","33597a2e46cffec3da6500c5ddc3cfcf8248e065377e9a5abea23cf633a7c134","wss://nos.lol/","root","91c9a5e1a9744114c6fe2d61ae4de82629eaaa0fb52f48288093c7e7e036f832"],["p","91c9a5e1a9744114c6fe2d61ae4de82629eaaa0fb52f48288093c7e7e036f832","wss://nos.lol/"]]}] +[12:34:34.998] SEND nostr.mom:443: ["CLOSE", "sync_0_1756830874"] diff --git a/tests/nip01_validation_test b/tests/nip01_test similarity index 91% rename from tests/nip01_validation_test rename to tests/nip01_test index e8234bf06f8d08d9d0742cbeb1efc924c5c977df..8fac13800ac5b1033bad61feec63c5f26f0dc490 100755 GIT binary patch delta 3559 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 diff --git a/tests/nip01_validation_test.c b/tests/nip01_test.c similarity index 100% rename from tests/nip01_validation_test.c rename to tests/nip01_test.c diff --git a/tests/nip04_comparison_test b/tests/nip04_comparison_test deleted file mode 100755 index c7d44f5f0d6dc208061c03c8922ed530136a4a90..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123096 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 -#include -#include -#include "../nostr_core/nip004.h" -#include "../nostr_core/nostr_common.h" -#include "../nostr_core/utils.h" - -int main(void) { - printf("=== NIP-04 DEBUG COMPARISON (C) ===\n"); - - // Initialize NOSTR library - REQUIRED for secp256k1 operations - if (nostr_init() != NOSTR_SUCCESS) { - printf("โŒ Failed to initialize NOSTR library\n"); - return 1; - } - printf("โœ“ NOSTR library initialized successfully\n"); - - // Test vectors matching JavaScript - const char* sk1_hex = "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe"; - const char* pk1_hex = "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1"; - const char* sk2_hex = "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220"; - const char* pk2_hex = "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3"; - const char* plaintext = "nanana"; - const char* expectedCiphertext = "d6Joav5EciPI9hdHw31vmQ==?iv=fWs5rfv2+532arG/k83kcA=="; - - // Convert hex keys to bytes - unsigned char sk1[32], pk1[32], sk2[32], pk2[32]; - nostr_hex_to_bytes(sk1_hex, sk1, 32); - nostr_hex_to_bytes(pk1_hex, pk1, 32); - nostr_hex_to_bytes(sk2_hex, sk2, 32); - nostr_hex_to_bytes(pk2_hex, pk2, 32); - - // Print keys for comparison - printf("[C] Private Key sk1: %s\n", sk1_hex); - printf("[C] Public Key pk2: %s\n", pk2_hex); - - // Allocate output buffer for encryption - char* encrypted = malloc(NOSTR_NIP04_MAX_ENCRYPTED_SIZE); - if (!encrypted) { - printf("Memory allocation failed\n"); - return 1; - } - - printf("\n--- ENCRYPTION TEST ---\n"); - printf("[C] Encrypting \"%s\" using sk1 -> pk2\n", plaintext); - - // Call the encryption function - int result = nostr_nip04_encrypt(sk1, pk2, plaintext, encrypted, NOSTR_NIP04_MAX_ENCRYPTED_SIZE); - - if (result == NOSTR_SUCCESS) { - printf("[C] Encrypted Result: %s\n", encrypted); - } else { - printf("Encryption Error: %s\n", nostr_strerror(result)); - free(encrypted); - return 1; - } - - printf("\n--- DECRYPTION TEST ---\n"); - printf("[C] Decrypting \"%s\" using sk2 + pk1\n", expectedCiphertext); - printf("[C] Private Key sk2: %s\n", sk2_hex); - printf("[C] Public Key pk1: %s\n", pk1_hex); - - // Allocate output buffer for decryption - char* decrypted = malloc(1000); - if (!decrypted) { - printf("Memory allocation failed\n"); - free(encrypted); - return 1; - } - - // Call the decryption function - result = nostr_nip04_decrypt(sk2, pk1, expectedCiphertext, decrypted, 1000); - - if (result == NOSTR_SUCCESS) { - printf("[C] UTF-8 Decoded: \"%s\"\n", decrypted); - - printf("\n--- RESULTS ---\n"); - printf("Encryption Success: Generated ciphertext\n"); - printf("Decryption Success: %s\n", strcmp(decrypted, plaintext) == 0 ? "true" : "false"); - printf("Expected: \"%s\"\n", plaintext); - printf("Got: \"%s\"\n", decrypted); - } else { - printf("Decryption Error: %s\n", nostr_strerror(result)); - } - - free(encrypted); - free(decrypted); - - // Cleanup NOSTR library - nostr_cleanup(); - return 0; -} diff --git a/tests/old/relay_pool_test.c b/tests/old/relay_pool_test.c deleted file mode 100644 index ffa988da..00000000 --- a/tests/old/relay_pool_test.c +++ /dev/null @@ -1,318 +0,0 @@ -/* - * NOSTR Relay Pool Test Program (READ-ONLY) - * - * Tests the relay pool event processing functionality by: - * - Creating a pool with hardcoded relays - * - Subscribing to kind 1 events (text notes) from other users - * - Using the new event processing functions - * - Displaying raw data output without interpretation - * - * IMPORTANT: This test is READ-ONLY and never publishes events. - * It only sends REQ (subscription) messages and receives EVENT responses. - * Any test events seen in output are from other users or previous test runs. - */ - -#include -#include -#include -#include -#include -#include -#include "../nostr_core/nostr_core.h" -#include "../cjson/cJSON.h" - -// Global variables for clean shutdown -static volatile int keep_running = 1; -static nostr_relay_pool_t* g_pool = NULL; -static nostr_pool_subscription_t* g_subscription = NULL; - -// Statistics tracking -static int events_received = 0; -static int events_per_relay[3] = {0, 0, 0}; // Track events per relay -static const char* relay_urls[] = { - "wss://relay.laantungir.net", - "ws://127.0.0.1:7777", - "wss://nostr.mom" -}; -static const int relay_count = 3; - -// Signal handler for clean shutdown -void signal_handler(int sig) { - (void)sig; // Unused parameter - printf("\n๐Ÿ›‘ Received shutdown signal, cleaning up...\n"); - keep_running = 0; -} - -// Event callback - called when events are received -void on_event_received(cJSON* event, const char* relay_url, void* user_data) { - (void)user_data; // Unused parameter - - events_received++; - - // Track events per relay - for (int i = 0; i < relay_count; i++) { - if (strcmp(relay_url, relay_urls[i]) == 0) { - events_per_relay[i]++; - break; - } - } - - // Print raw event data - char* event_json = cJSON_Print(event); - if (event_json) { - printf("\n๐Ÿ“จ EVENT from %s:\n", relay_url); - printf("Raw JSON: %s\n", event_json); - printf("---\n"); - free(event_json); - } - - // Also extract and display key fields for readability - cJSON* id = cJSON_GetObjectItem(event, "id"); - cJSON* pubkey = cJSON_GetObjectItem(event, "pubkey"); - cJSON* created_at = cJSON_GetObjectItem(event, "created_at"); - cJSON* content = cJSON_GetObjectItem(event, "content"); - - printf("๐Ÿ“„ Parsed fields:\n"); - if (id && cJSON_IsString(id)) { - printf(" ID: %s\n", cJSON_GetStringValue(id)); - } - if (pubkey && cJSON_IsString(pubkey)) { - printf(" Author: %s\n", cJSON_GetStringValue(pubkey)); - } - if (created_at && cJSON_IsNumber(created_at)) { - time_t timestamp = (time_t)cJSON_GetNumberValue(created_at); - printf(" Created: %s", ctime(×tamp)); - } - if (content && cJSON_IsString(content)) { - const char* text = cJSON_GetStringValue(content); - printf(" Content: %.100s%s\n", text, strlen(text) > 100 ? "..." : ""); - } - printf("===============================\n"); -} - -// EOSE callback - called when all relays have sent "End of Stored Events" -void on_eose_received(void* user_data) { - (void)user_data; // Unused parameter - printf("โœ… EOSE: All relays have finished sending stored events\n"); -} - -// Display relay status -void display_relay_status() { - char** urls; - nostr_pool_relay_status_t* statuses; - - int count = nostr_relay_pool_list_relays(g_pool, &urls, &statuses); - if (count > 0) { - printf("\n๐Ÿ”— RELAY STATUS:\n"); - for (int i = 0; i < count; i++) { - const char* status_icon; - const char* status_text; - - switch (statuses[i]) { - case NOSTR_POOL_RELAY_CONNECTED: - status_icon = "๐ŸŸข"; - status_text = "Connected"; - break; - case NOSTR_POOL_RELAY_CONNECTING: - status_icon = "๐ŸŸก"; - status_text = "Connecting..."; - break; - case NOSTR_POOL_RELAY_DISCONNECTED: - status_icon = "๐Ÿ”ด"; - status_text = "Disconnected"; - break; - case NOSTR_POOL_RELAY_ERROR: - status_icon = "โŒ"; - status_text = "Error"; - break; - default: - status_icon = "โ“"; - status_text = "Unknown"; - break; - } - - // Get publish and query latency statistics - double query_latency = nostr_relay_pool_get_relay_query_latency(g_pool, urls[i]); - const nostr_relay_stats_t* stats = nostr_relay_pool_get_relay_stats(g_pool, urls[i]); - - // Get events count from relay statistics (more accurate) - int relay_events = 0; - if (stats) { - relay_events = stats->events_received; - } else { - // Fallback to local counter - for (int j = 0; j < relay_count; j++) { - if (strcmp(urls[i], relay_urls[j]) == 0) { - relay_events = events_per_relay[j]; - break; - } - } - } - - // Display status with latency information - if (query_latency >= 0.0) { - printf(" %s %-25s %s (query: %.0fms, events: %d)\n", - status_icon, urls[i], status_text, query_latency, relay_events); - } else { - printf(" %s %-25s %s (query: ---, events: %d)\n", - status_icon, urls[i], status_text, relay_events); - } - - // Show additional latency statistics if available - if (stats) { - if (stats->publish_samples > 0) { - printf(" ๐Ÿ“Š Publish latency: avg=%.0fms (%d samples)\n", - stats->publish_latency_avg, stats->publish_samples); - } - if (stats->query_samples > 0) { - printf(" ๐Ÿ“Š Query latency: avg=%.0fms (%d samples)\n", - stats->query_latency_avg, stats->query_samples); - } - if (stats->events_published > 0) { - printf(" ๐Ÿ“ค Published: %d events (%d OK, %d failed)\n", - stats->events_published, stats->events_published_ok, - stats->events_published_failed); - } - } - - free(urls[i]); - } - free(urls); - free(statuses); - - printf("๐Ÿ“Š Total events received: %d\n", events_received); - } -} - -int main() { - printf("๐Ÿš€ NOSTR Relay Pool Test Program\n"); - printf("=================================\n"); - printf("Testing relays:\n"); - for (int i = 0; i < relay_count; i++) { - printf(" - %s\n", relay_urls[i]); - } - printf("\n"); - - // Set up signal handler for clean shutdown - signal(SIGINT, signal_handler); - signal(SIGTERM, signal_handler); - - // Initialize NOSTR core library - printf("๐Ÿ”ง Initializing NOSTR core library...\n"); - if (nostr_init() != NOSTR_SUCCESS) { - fprintf(stderr, "โŒ Failed to initialize NOSTR core library\n"); - return 1; - } - - // Create relay pool - printf("๐ŸŠ Creating relay pool...\n"); - g_pool = nostr_relay_pool_create(); - if (!g_pool) { - fprintf(stderr, "โŒ Failed to create relay pool\n"); - nostr_cleanup(); - return 1; - } - - // Add relays to pool - printf("โž• Adding relays to pool...\n"); - for (int i = 0; i < relay_count; i++) { - printf(" Adding: %s\n", relay_urls[i]); - int result = nostr_relay_pool_add_relay(g_pool, relay_urls[i]); - if (result != NOSTR_SUCCESS) { - printf(" โš ๏ธ Warning: Failed to add relay %s (error: %s)\n", - relay_urls[i], nostr_strerror(result)); - } - } - - // Create filter for kind 1 events (text notes) - printf("๐Ÿ” Creating subscription filter for kind 1 events...\n"); - cJSON* filter = cJSON_CreateObject(); - if (!filter) { - fprintf(stderr, "โŒ Failed to create filter\n"); - nostr_relay_pool_destroy(g_pool); - nostr_cleanup(); - return 1; - } - - // Add kinds array with kind 1 (text notes) - cJSON* kinds = cJSON_CreateArray(); - cJSON_AddItemToArray(kinds, cJSON_CreateNumber(1)); - cJSON_AddItemToObject(filter, "kinds", kinds); - - // Limit to recent events to avoid flooding - cJSON_AddNumberToObject(filter, "limit", 1); - - // Subscribe to events from all relays - printf("๐Ÿ“ก Subscribing to events from all relays...\n"); - g_subscription = nostr_relay_pool_subscribe( - g_pool, - relay_urls, - relay_count, - filter, - on_event_received, - on_eose_received, - NULL - ); - - if (!g_subscription) { - fprintf(stderr, "โŒ Failed to create subscription\n"); - cJSON_Delete(filter); - nostr_relay_pool_destroy(g_pool); - nostr_cleanup(); - return 1; - } - - printf("โœ… Subscription created successfully!\n"); - printf("โฑ๏ธ Starting event processing...\n"); - printf(" (Press Ctrl+C to stop)\n\n"); - - // Display initial status - display_relay_status(); - - printf("๏ฟฝ Starting continuous monitoring...\n\n"); - - // Run event processing loop - time_t last_status_update = time(NULL); - - while (keep_running) { - // Process events for 1 second - int events_processed = nostr_relay_pool_run(g_pool, 1000); - - // Display status every 5 seconds - if (time(NULL) - last_status_update >= 5) { - display_relay_status(); - last_status_update = time(NULL); - } - - // Small status indicator - if (events_processed > 0) { - printf("."); - fflush(stdout); - } - } - - printf("\n\n๐Ÿ Test completed!\n"); - - // Final status display - display_relay_status(); - - // Cleanup - printf("๐Ÿงน Cleaning up...\n"); - if (g_subscription) { - nostr_pool_subscription_close(g_subscription); - } - if (g_pool) { - nostr_relay_pool_destroy(g_pool); - } - cJSON_Delete(filter); - nostr_cleanup(); - - printf("โœ… Test program finished successfully!\n"); - printf("๐Ÿ“ˆ Final stats:\n"); - printf(" Total events: %d\n", events_received); - for (int i = 0; i < relay_count; i++) { - printf(" %s: %d events\n", relay_urls[i], events_per_relay[i]); - } - - return 0; -} diff --git a/tests/old/test_vectors_display.c b/tests/old/test_vectors_display.c deleted file mode 100644 index c9b22352..00000000 --- a/tests/old/test_vectors_display.c +++ /dev/null @@ -1,101 +0,0 @@ -/* - * NIP-04 Test Vectors Display - All 6 Test Vectors - * Shows complete test vector integration even if runtime testing has issues - */ - -#include -#include -#include - -void display_test_vector(int num, const char* description, const char* sk1, const char* pk1, - const char* sk2, const char* pk2, const char* plaintext, const char* expected) { - printf("=== TEST VECTOR %d: %s ===\n", num, description); - printf("SK1 (Alice): %s\n", sk1); - printf("PK1 (Alice): %s\n", pk1); - printf("SK2 (Bob): %s\n", sk2); - printf("PK2 (Bob): %s\n", pk2); - printf("Plaintext: \"%s\"\n", plaintext); - - if (strlen(expected) > 80) { - char truncated[81]; - strncpy(truncated, expected, 80); - truncated[80] = '\0'; - printf("Expected: %s...\n", truncated); - } else { - printf("Expected: %s\n", expected); - } - printf("\n"); -} - -int main(void) { - printf("=== NIP-04 Test Vector Collection ===\n"); - printf("Complete integration of 6 test vectors (3 original + 3 from nostr-tools)\n\n"); - - // Original Test Vectors (1-3) - display_test_vector(1, "Basic NIP-04 Encryption", - "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe", - "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1", - "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220", - "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3", - "nanana", - "zJxfaJ32rN5Dg1ODjOlEew==?iv=EV5bUjcc4OX2Km/zPp4ndQ=="); - - display_test_vector(2, "Large Payload Test (800 characters)", - "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe", - "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1", - "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220", - "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3", - "800 'z' characters", - "6f8dMstm+udOu7yipSn33orTmwQpWbtfuY95NH+eTU1kArysWJIDkYgI2D25EAGIDJsNd45jOJ2NbVOhFiL3ZP/NWsTwXokk34iyHyA/lkjzugQ1bHXoMD1fP/Ay4hB4al1NHb8HXHKZaxPrErwdRDb8qa/I6dXb/1xxyVvNQBHHvmsM5yIFaPwnCN1DZqXf2KbTA/Ekz7Hy+7R+Sy3TXLQDFpWYqykppkXc7Fs0qSuPRyxz5+anuN0dxZa9GTwTEnBrZPbthKkNRrvZMdTGJ6WumOh9aUq8OJJWy9aOgsXvs7qjN1UqcCqQqYaVnEOhCaqWNDsVtsFrVDj+SaLIBvCiomwF4C4nIgngJ5I69tx0UNI0q+ZnvOGQZ7m1PpW2NYP7Yw43HJNdeUEQAmdCPnh/PJwzLTnIxHmQU7n7SPlMdV0SFa6H8y2HHvex697GAkyE5t8c2uO24OnqIwF1tR3blIqXzTSRl0GA6QvrSj2p4UtnWjvF7xT7RiIEyTtgU/AsihTrXyXzWWZaIBJogpgw6erlZqWjCH7sZy/WoGYEiblobOAqMYxax6vRbeuGtoYksr/myX+x9rfLrYuoDRTw4woXOLmMrrj+Mf0TbAgc3SjdkqdsPU1553rlSqIEZXuFgoWmxvVQDtekgTYyS97G81TDSK9nTJT5ilku8NVq2LgtBXGwsNIw/xekcOUzJke3kpnFPutNaexR1VF3ohIuqRKYRGcd8ADJP2lfwMcaGRiplAmFoaVS1YUhQwYFNq9rMLf7YauRGV4BJg/t9srdGxf5RoKCvRo+XM/nLxxysTR9MVaEP/3lDqjwChMxs+eWfLHE5vRWV8hUEqdrWNZV29gsx5nQpzJ4PARGZVu310pQzc6JAlc2XAhhFk6RamkYJnmCSMnb/RblzIATBi2kNrCVAlaXIon188inB62rEpZGPkRIP7PUfu27S/elLQHBHeGDsxOXsBRo1gl3te+raoBHsxo6zvRnYbwdAQa5taDE63eh+fT6kFI+xYmXNAQkU8Dp0MVhEh4JQI06Ni/AKrvYpC95TXXIphZcF+/Pv/vaGkhG2X9S3uhugwWK?iv=2vWkOQQi0WynNJz/aZ4k2g=="); - - display_test_vector(3, "Bidirectional Communication", - "91ba716fa9e7ea2fcbad360cf4f8e0d312f73984da63d90f524ad61a6a1e7dbe", - "b38ce15d3d9874ee710dfabb7ff9801b1e0e20aace6e9a1a05fa7482a04387d1", - "96f6fa197aa07477ab88f6981118466ae3a982faab8ad5db9d5426870c73d220", - "dcb33a629560280a0ee3b6b99b68c044fe8914ad8a984001ebf6099a9b474dc3", - "Hello Bob, this is Alice! / Hi Alice, Bob here. Message received!", - "Various encrypted messages"); - - printf("--- Generated with nostr-tools (Random Keys) ---\n\n"); - - // New Test Vectors Generated with nostr-tools (4-6) - display_test_vector(4, "Random Keys - Hello, NOSTR!", - "5c5ea5ec3a804533ba8a21ba3dd981fc55a84e854dde53869b3f812ccd788200", - "0988b20763d3f8bc06e88722f2aa6b3caed3cc510e93287e1ee3f70ed22f54d2", - "8e94e91ea679509ec1f5da2be87352ea78acde2b69563c23a41b7f07c0891bc3", - "13747a8025c1196da3e67ecf941aa889c5c4ec6773e7f325f3f8d2435c4603c6", - "Hello, NOSTR!", - "+bqZAkfv/tI4h0XcvB9Baw==?iv=Om7m3at5zjJjxyAQbFY2IQ=="); - - display_test_vector(5, "Long Message with Emoji", - "51099e755aaab7e8ee1850b683b673c11d09799e85a630e951eb3c92fab4aed3", - "c5fb1cad7b11e3cf7f31d5bf47aaf3398a4803ea786eedfd674f55fa55dcb649", - "41f2788d00bd362ac3c7c784ee46e35b99765a086514ee69cb15de38c072309a", - "ba6773cf6a9b11476f692d4681a2f1e3015d1ee4a8d7c9d0364bed120f225079", - "This is a longer message to test encryption with more content. ๐Ÿš€", - "3H9WEg9WjjN3r6ZymJt1R4ly3GlzhRR93FaSTGHLeM4oSS3eOnJtdXcO4ftgICMHRYM14WAmDDE9c12V8jhzua8GpnXKIVsNbY+oPF2yRwI=?iv=ztEGlo35pqJKrwZ2ZipsWg=="); - - display_test_vector(6, "Short Message", - "42c450eaebaee5ad94b602fc9054cde48f66d68c236b547aafee0ff319377290", - "a03f543eeb6c3f1c626181730751c39fd4f9f10455756d99ea855da97cf5076b", - "72f424c96239d271549c648d16635b5603ef32cdcbbff41058d14187b98f30cc", - "1c74b7a1d09ebeaf994a93a859682019930ad4f0f8ac7e65caacbbf4985042e8", - "Short", - "UIN92yHtAfX0vOTmn8VTtg==?iv=ou0QFU5UJUI6W4fUlkiElg=="); - - printf("=== SUMMARY ===\n"); - printf("โœ… Successfully generated 3 additional test vectors using nostr-tools\n"); - printf("โœ… All test vectors use genuine random nsec keys from the JavaScript ecosystem\n"); - printf("โœ… Test coverage includes: short, medium, long, Unicode, and emoji messages\n"); - printf("โœ… Enhanced from 3 to 6 comprehensive test vectors\n"); - printf("โœ… Ready for integration testing once library stability issues are resolved\n"); - printf("\n"); - printf("Files created:\n"); - printf("- test_vector_generator/generate_vectors.js (Vector generation script)\n"); - printf("- tests/nip04_test.c (Enhanced with 6 test vectors)\n"); - printf("- package.json (Node.js dependencies)\n"); - printf("\n"); - printf("๐ŸŽฏ Mission accomplished - Enhanced NIP-04 test coverage with nostr-tools vectors!\n"); - - return 0; -}