# Architecture

`zig-nostr` is built in layers, each shipped and tested on its own. Everything
above the crypto core is written in plain Zig; the two C dependencies
(libsecp256k1 and LMDB) are compiled from pinned source.

## Library core

The credibility anchor. secp256k1 keys and BIP-340 Schnorr signatures come from
bitcoin-core's **libsecp256k1**, bound via `translate-c` and passing the full
official BIP-340 test-vector suite. On top of it:

- **NIP-01 event model**: canonical serialization, sha256 id computation, and
wire JSON encode/decode, verified against sha256 oracle vectors.
- **NIP-19 / NIP-21**: bech32 entity encoding (`npub`/`nsec`/`note` and the
TLV forms `nprofile`/`nevent`/`naddr`/`nrelay`) plus `nostr:` URIs.
- **NIP-06**: BIP-39 mnemonics and BIP-32 HD derivation, byte-for-byte against
the official vectors.
- **NIP-49**: private-key encryption at rest (`ncryptsec`): scrypt +
XChaCha20-Poly1305, with NFKC password normalization for cross-implementation
interop.

## Transport & outbox

- **RFC 6455 WebSocket** framing and handshake, and a stream-generic relay
connection state machine with NIP-01 subscriptions.
- A live **TCP/TLS dialer**. Hostnames resolve through libc `getaddrinfo` on macOS and Linux and through std's own resolver on Windows, and an IP literal is parsed first so it never reaches a resolver. `dialDiagnosed` is `dial` that also records the HTTP status the relay answered the upgrade with, so a relay that says 503 when busy, 401 or 429 can be told apart from one that is down.
- **NIP-50 search** on the filter: `Filter.search` is sent to relays as `"search"`, and the local store applies it as a case-insensitive substring of the content.
- **NIP-65 outbox model**: relay lists with read/write routing and *zero*
hardcoded relays, so events are published and fetched where they actually
live.

## Local-first store

A zero-copy, memory-mapped **LMDB** event store (`store.zig`):

- A compact binary event record with secondary indexes (author, kind,
`created_at`, tag).
- A **bounded, newest-first query planner**: it walks the indexes via reverse
cursors and a k-way merge that takes the newest candidate from a binary heap,
stopping at `limit`, so feed latency stays flat as the store grows (see
[Performance](/performance)).
- Validate-on-insert ingestion: dedup, replaceable / parameterized-replaceable
upserts, and NIP-09 deletions.
- A direct-message conversation index, local-first reconciliation, and a
size-cap cache.

## Signer protocol

The layer the native signer is built on:

- **NIP-44 v2** payload encryption (ChaCha20 + HMAC-SHA256 over a libsecp256k1
ECDH secret), verified against the official vectors.
- **NIP-46** remote signing, the request/response messages, the `kind:24133`
envelope, a transport-agnostic dispatcher behind an approval policy, and the
`bunker://` / `nostrconnect://` connection URIs.
- **NIP-42** client authentication, so a signer can serve over relays that gate
access behind auth.

- A **NIP-46 client** (`nip46_client`, experimental): the app side of a bunker
connection, over `bunker://` or `nostrconnect://`, with one reader per relay.
Its shape may still change.

The native signer built on this layer is **[Notary](/notary)**.

## Messages, sync and HTTP

Codecs and protocols the command line is built from. Where a spec needs HTTP,
the library builds and reads the messages and leaves the transport to the caller.

- **NIP-59 gift wrap and NIP-17 messages**: rumor, seal and wrap, with the sender
proven by the seal alone.
- **NIP-04** legacy messages, for older clients. It has no authentication.
- **NIP-77 negentropy**: protocol version 1, checked against transcripts recorded
from the reference implementation, with the `NEG-*` and NIP-45 `COUNT` messages
encoded and parsed.
- **NIP-13** proof of work: counting, and mining to a target.
- **NIP-05** identifiers and **NIP-11** relay documents, parsed.
- **NIP-98** HTTP authentication, the **NIP-86** relay management API, and
**Blossom** authorization and blob descriptors.

## Platforms

The library builds for macOS, Linux and Windows, on x86_64 and aarch64, and CI builds and tests it on all three. On Windows the dialer waits for write room with a poll request to the AFD driver, because std opens its sockets on that driver and never registers them with Winsock, and a key file takes its directory's default access list because there are no mode bits. macOS and Linux keep `getaddrinfo` and `poll`.

## How it is tested

Tests sit beside the code and `zig build test` runs them all. The cryptography is checked against the official vectors (the BIP-340 suite, the NIP-44 v2 vectors, and the NIP-19, NIP-49 and RFC 6455 worked examples). The transport is driven by a scripted in-memory relay, with a few tests on a real loopback socket for the parts a fake cannot show. The store runs on a real LMDB file in a temporary directory.

The parsers that read bytes someone else wrote have fuzz targets: WebSocket frames and handshake responses, relay messages, events, bech32 and NIP-19, NIP-44 payloads, NIP-46 messages and URIs, relay lists and URLs, mnemonics, `ncryptsec` files and filters. Each asserts that nothing crashes or leaks and, where one exists, an invariant, such as an event parsed, serialized and parsed again being the same event. `zig build test` runs each target over a seed corpus, and `zig build test --fuzz -Doptimize=ReleaseSafe` fuzzes them until stopped.

The repository's [`ARCHITECTURE.md`](https://github.com/zig-nostr/nostr/blob/main/ARCHITECTURE.md) goes through the modules file by file, how an event travels from a key to a relay to the store, and the threading and cancellation rules.
