Skip to Content
Getting Started

Getting Started

zig-nostr is a Nostr protocol library for Zig . It targets Zig 0.16 and is in active development (v0.14.9).

Install

Fetch the library into your project’s build.zig.zon:

zig fetch --save https://github.com/zig-nostr/nostr/archive/refs/tags/v0.14.9.tar.gz

Then wire the module in build.zig:

const nostr = b.dependency("nostr", .{ .target = target, .optimize = optimize }); your_module.addImport("nostr", nostr.module("nostr"));

The library vendors and compiles bitcoin-core’s libsecp256k1 and LMDB from source, so no system packages are required.

Platforms

The library builds for macOS, Linux and Windows, on x86_64 and aarch64, and CI builds it and runs the tests on all three systems. To check a Windows build from another system, zig build test -Dtarget=x86_64-windows-gnu compiles the test binary and then stops with an error, because a non-Windows host cannot run it. A store takes its full map size on disk on Windows (1 GiB by default) from the first open, where on macOS and Linux it grows with what is kept.

Quickstart: sign and verify an event

const std = @import("std"); const nostr = @import("nostr"); pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); // Time comes from the Io implementation in Zig 0.16, the same way sockets // and threads do. Threaded takes a threadsafe allocator, which the arena // above is not. var threaded = std.Io.Threaded.init(std.heap.page_allocator, .{}); defer threaded.deinit(); const io = threaded.io(); // A Signer wraps the libsecp256k1 context; deinit it when done. var signer = nostr.keys.Signer.init(); defer signer.deinit(); // Load a keypair from a 32-byte secret key. const secret: nostr.keys.SecretKey = your_secret_key; // 32 bytes const keypair = try signer.keyPairFromSecretKey(secret); // Build and sign a kind:1 text note. const created_at = std.Io.Timestamp.now(io, .real).toSeconds(); const note = try nostr.event.create( allocator, signer, keypair, created_at, 1, &.{}, "hello from zig-nostr", null, ); // Verify: recompute the canonical id, then check the Schnorr signature. std.debug.assert(try nostr.event.verify(allocator, signer, note)); // Serialize to the wire JSON form. const json = try nostr.event.toJson(allocator, note); std.debug.print("{s}\n", .{json}); }

What’s in the box

The nostr module re-exports focused namespaces:

  • keys: secp256k1 keys and BIP-340 Schnorr sign/verify
  • event: the NIP-01 event model, canonical id, JSON encode/decode
  • nip19 / bech32: npub/nsec/note/nprofile/nevent/naddr encoding
  • nip06 / bip39: mnemonic key derivation
  • nip49: encrypted key storage at rest (ncryptsec)
  • nip44: v2 payload encryption
  • nip46 / nip46_client: remote signing (“bunker”) protocol, the signer side and a client that connects to one
  • nip04: legacy encrypted messages, for older clients only
  • nip59: gift wrap and NIP-17 private messages
  • nip42: client authentication to relays
  • relay / websocket / message / filter: live transport & subscriptions, with NIP-50 search on the filter
  • nip65: relay lists and outbox routing
  • nip05 / nip11: DNS identifiers and relay information documents, parsed (the library does no HTTP)
  • nip13: proof of work, counting and mining
  • nip77: negentropy set reconciliation
  • nip98 / nip86 / blossom: HTTP authorization headers, the relay management API and Blossom media authorization and blob descriptors
  • liveness: when a relay connection has gone quiet, and what to do about it
  • store: the local-first, memory-mapped event store

See Architecture for how these fit together.

Build from source

git clone https://github.com/zig-nostr/nostr cd nostr zig build test # run the suite

To fuzz the parsers that read bytes from outside (frames, relay messages, events, NIP-19, NIP-44, NIP-46 and more) until you stop it, use zig build test --fuzz -Doptimize=ReleaseSafe.

Last updated on