Nostr C Library
The Nostr C library provides an implementation of the Nostr protocol, including various NIPs (Nostr Improvement Proposals). This library aims to be highly portable, suitable for use in IoT environments, and provides bindings for integration with the GNOME desktop environment.
Features
Nostr event handling
JSON (de)serialization with optional NSON support
NIP implementations (e.g., NIP-04, NIP-05, NIP-13, NIP-19, NIP-29, NIP-31, NIP-34)
Optional memory management handled by the library
NIP-47 (Wallet Connect): canonical helpers for encrypt/decrypt supporting NIP-44 v2 (preferred) and NIP-04 fallback, with automatic key format handling (x-only and SEC1) and full tests/examples.
Security Hardening and Migration Notes
Canonical NIP-01 hashing/signing/verification
Event IDs and signatures now use the canonical preimage array
[0, pubkey, created_at, kind, tags, content]
and ignore any caller-providedid
when verifying. This prevents trusting untrustedid
fields and aligns with the NIP-01 spec. Existing APIs continue to work;nostr_event_check_signature()
always recomputes the hash.
NIP-04 AEAD migration (v2)
New default envelope format:
v=2:base64(nonce(12) || ciphertext || tag(16))
using AES-256-GCM.Keys are derived via HKDF-SHA256 with
info="NIP04"
for domain separation from the ECDH shared secret.Encryption now emits AEAD v2 only. Legacy AES-CBC
?iv=
is no longer produced by library APIs.Decrypt fallback remains: legacy AES-CBC
?iv=
content is still accepted to preserve interop with older peers.All decryption failures return a unified error string ("decrypt failed") to reduce side-channel leakage.
Optional hardening:
Build-time switch to disable legacy decrypt entirely: set
-DNIP04_STRICT_AEAD_ONLY=ON
when configuring CMake to reject any non-v=2:
envelopes at decrypt.
Migration guidance:
Prefer
nostr_nip04_encrypt
/nostr_nip04_encrypt_secure
andnostr_nip04_decrypt
/nostr_nip04_decrypt_secure
.The helper
nostr_nip04_shared_secret_hex
is deprecated and should not be used by new code; exposing raw ECDH shared secrets increases attack surface.Tests and examples have been updated to expect
v=2:
envelopes and avoid?iv=
parsing.
Relay ingress hardening
Adds an in-memory replay cache (TTL 15 minutes) keyed by canonical event
id
to avoid redundant storage/flood.Enforces timestamp skew limits (+10 minutes future, -24 hours past) on
created_at
.On startup, the relay prints a concise security posture banner, for example:
nostrc-relayd: security AEAD=v2 replayTTL=900s skew=+600/-86400
See tests/test_event_canonical.c
and tests/test_nip04_aead.c
for minimal validation of these behaviors.
Quick Start
Build the libraries and tests with CMake:
git clone https://github.com/chebizarro/nostrc.git
cd nostrc
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j
ctest --output-on-failure
Install system-wide (optional):
sudo make install
Link in your C project:
find_library(NOSTR_LIB libnostr REQUIRED)
find_library(NOSTR_JSON_LIB nostr_json REQUIRED)
find_library(NSYNC_LIB nsync REQUIRED)
find_package(OpenSSL REQUIRED)
pkg_check_modules(SECP256K1 REQUIRED libsecp256k1)
add_executable(my_app main.c)
target_link_libraries(my_app PRIVATE ${NOSTR_LIB} ${NOSTR_JSON_LIB} ${NSYNC_LIB} OpenSSL::SSL OpenSSL::Crypto ${SECP256K1_LIBRARIES})
Documentation
See
docs/LIBJSON.md
for libjson API, NIP-01 #tag mapping, robustness rules, and tests.See
docs/SHUTDOWN.md
for libnostr/libgo shutdown order, invariants, and troubleshooting.See
docs/NIP47.md
for NIP-47 (Wallet Connect) envelope helpers, negotiation, canonical crypto helpers (NIP-44 v2/NIP-04), accepted key formats (x-only/SEC1), sessions, GLib bindings, and examples.See
docs/NIP04_MIGRATION.md
for migrating to NIP-04 AEAD v2 envelopes and deprecation details.
Installation
Dependencies
C compiler (GCC/Clang)
CMake
libsecp256k1
libjansson (optional, for JSON parsing)
Building
git clone https://github.com/chebizarro/nostrc.git
cd nostrc
mkdir -p build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j
sudo make install
NIP Implementations
This library includes various Nostr Improvement Proposals (NIPs):
NIP-04: Encrypted Direct Messages
NIP-05: Mapping Nostr keys to DNS-based identifiers
NIP-13: Proof-of-Work
NIP-19: Bech32-encoded entities
NIP-29: Simple group management
NIP-31: Alternative content
NIP-34: GitHub-like repository management on Nostr
Contributing
Contributions are welcome! Please open issues or submit pull requests on GitHub.
Guidelines:
Fork and create a topic branch.
Add focused changes with tests in
tests/
orlibgo/tests/
.Update docs (README, ARCHITECTURE, API) when public APIs change.
Run
ctest
and ensure no regressions.Follow the style in
CODING_STANDARDS.md
.
Usage Examples
See examples/
for basic JSON integration and event serialization. For NIP-47 examples, see:
./build/nips/nip47/nwc_client_example
./build/nips/nip47/nwc_wallet_example
A minimal flow:
#include "nostr-event.h"
#include "keys.h"
int main(void) {
NostrEvent *ev = create_event();
// set fields on ev...
char *json = event_serialize(ev);
// use json...
free(json);
free_event(ev);
return 0;
}
Adding New NIPs
To add a new NIP:
Create a new folder in the
nips
directory.Implement the required functionality in C.
Update the headers and add test cases.
Ensure all tests pass and submit a pull request.
License
This project is licensed under the MIT License. See the LICENSE file for details.
Shutdown Quick Reference
For correct relay/connection/subscription teardown and to avoid hangs or use-after-free during shutdown, see:
docs/SHUTDOWN.md
Key points:
Cancel context, close relay queues, snapshot+null the connection.
Wait for relay workers to exit, then free
conn
channels.Finally call
connection_close(conn)
; it closes channels but does not free them.
The official Nostr NIPs are vendored as a git submodule under docs/nips
.
Update with
scripts/update_nips.sh
Keep the submodule pinned; bump deliberately in separate commits
Code under
nips/nipXX/
MUST reference the matchingdocs/nips/XX.md
Developer Notes (libgo)
Sanitizers (Debug):
# AddressSanitizer + UndefinedBehaviorSanitizer
cmake -S libgo -B build -DCMAKE_BUILD_TYPE=Debug -DGO_ENABLE_ASAN=ON -DGO_ENABLE_UBSAN=ON
cmake --build build -j && ctest --test-dir build --output-on-failure
# ThreadSanitizer
cmake -S libgo -B build_tsan -DCMAKE_BUILD_TYPE=Debug -DGO_ENABLE_TSAN=ON
cmake --build build_tsan -j && ctest --test-dir build_tsan --output-on-failure
Warnings:
cmake -S libgo -B build -DCMAKE_BUILD_TYPE=Debug -DGO_WARNINGS_AS_ERRORS=ON