A lightweight, portable C library for the Nostr protocol with native Lightning Network integration
A high-performance C implementation of the Nostr protocol built for efficiency and minimal dependencies. libnostr-c provides core event management, modern NIP-44 encryption powered by noscrypt, Lightning zaps, WebSocket relay communication, and complete relay-side protocol support. Designed for both client applications and relay implementations, with a focus on security, portability, and embedded systems.
// Client-side
nostr_init();
nostr_keypair_generate();
nostr_event_create();
nostr_event_sign();
nostr_nip44_encrypt();
nostr_relay_connect();
// Relay-side (for relay implementations)
nostr_client_msg_parse();
nostr_filter_matches();
nostr_event_validate_full();
nostr_deletion_authorized();
nostr_relay_msg_serialize();
nostr_relay_info_serialize();Building nostr applications in C requires reliable, efficient cryptographic operations and protocol handling. libnostr-c provides a complete implementation with modern NIP-44 encryption, Lightning integration, and minimal resource requirements. Built with security-first design principles and optimized for both desktop and embedded systems.
The following table lists supported platforms and cryptographic backends:
| Platform | Crypto Backend | Notes | CI Status |
|---|---|---|---|
| Linux | noscrypt, secp256k1 | GCC/Clang | ✅ Tested |
| macOS | noscrypt, secp256k1 | Clang | ✅ Tested |
| Windows | noscrypt, secp256k1 | MSVC | ✅ Tested |
| ESP-IDF | noscrypt, mbedtls | ESP32/S2/S3/C3/C6, v5.0+ | ✅ Tested, run under QEMU |
On ESP targets the RNG goes through a cached mbedTLS CTR-DRBG rather than
esp_fill_random(), so that a failed draw can be reported instead of silently
returning zeros. That costs stack in the task that happens to seed it.
Call nostr_random_bytes(), nostr_key_generate(), nostr_keypair_generate()
or any signing entry point from a task with at least 4 KB of stack.
(nostr_keypair_generate() delegates to nostr_key_generate(), so either
reaches the same draw.) The first call in the process seeds
the DRBG, and ctr_drbg_reseed_internal() puts a 384-byte
MBEDTLS_CTR_DRBG_MAX_SEED_INPUT buffer on the caller's stack
(mbedtls/library/ctr_drbg.c:452), plus the entropy gather. Roughly 500 bytes
transient that esp_fill_random() never needed. It is re-paid every
MBEDTLS_CTR_DRBG_RESEED_INTERVAL draws, which is 10000 by default, so a task
that seeds successfully once can still overflow much later.
Seeding lazily from whichever task draws first means the cost lands wherever
that happens to be. Calling nostr_init() early, from a task you control the
depth of, makes it predictable.
Fixed cost is small: 500 bytes of .bss for the two contexts, measured from a
built ESP32-S3 image (rng_entropy 420 B, rng_ctr_drbg 76 B,
rng_initialized 4 B). The entropy accumulator also mallocs its message-digest
context on first use and never frees it, which is one-time rather than a growing
leak.
This is measured, not estimated. Under QEMU on ESP32-S3:
| first caller | result |
|---|---|
| 2 KB task drawing from an already-seeded DRBG | passes |
| 2 KB task that seeds the DRBG itself | crashes, Guru Meditation Error: Core 1 panic'ed (LoadProhibited) |
| 3 KB task that seeds | passes |
| 4 KB task that seeds | passes |
8 KB task (app_main) seeding, then 2 KB tasks drawing |
passes |
The threshold is between 2 KB and 3 KB on this build. 4 KB is the recommendation because it leaves margin for your own frames on top of the seed, not because 3 KB was observed to fail.
So the hazard is specifically the first caller, which is why seeding from a task whose depth you control is worth doing deliberately.
test/esp-idf sets CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192, so CI seeds from
app_main with headroom and will not catch a consumer that seeds from a smaller
task.
Please use the following links to obtain packages and extended documentation.
Documentation
Examples
Performance Analysis
Prerequisites:
- CMake 3.16+, C99 compiler, OpenSSL
- secp256k1, libcjson, libwebsockets (see BUILDING.md)
git clone https://github.com/privkeyio/libnostr-c.git
cd libnostr-c
mkdir build && cd build
cmake ..
make -j$(nproc)
sudo make installFor detailed installation instructions, dependency management, and platform-specific setup, see docs/BUILDING.md.
Automated builds and releases are available through GitHub Actions CI/CD.
libnostr-c supports modular compilation - build only what you need:
- Core NIPs (01, 02, 05, 06, 10, 13, 18, 19, 21, 25, 57, 65) enabled by default
- Optional NIPs (04, 09, 11, 17, 26, 40, 44, 45, 46, 47, 49, 50, 51, 59) via feature flags
- NIP-11 relay information document for serving relay metadata
- Relay protocol support for building relay implementations
- Modern noscrypt backend provides NIP-44 encryption
- Fallback to secp256k1 for basic operations
- keep-esp32 - Air-gapped ESP32-S3 FROST threshold signing device with Nostr DKG coordination
- wisp-esp32 - Minimal ESP32-S3 Nostr relay with 21-day ephemeral storage
- whisper - Encrypted DM pipe for Nostr using NIP-17 + NIP-44, Unix-style CLI
- vain - High-performance vanity Nostr public key miner
The software in this repository is licensed under MIT. See the LICENSE file for details.
This project is powered by noscrypt for high-performance cryptographic operations and NIP-44 encryption.