Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/structured-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ const byPage = {
library,
],
'getting-started': [library],
deed: [app('deed', 'deed', 'deed', 'The nostr command line: keys, events, NIP-19, NIP-44, relay queries, publishing, and a local store that later runs read without a network.')],
plaza: [app('Plaza', 'plaza', 'plaza', 'A fast, local-first Nostr client for macOS and Linux, rendered from its own store and reconciled with relays in the background.', { applicationCategory: 'SocialNetworkingApplication' })],
deed: [app('deed', 'deed', 'deed', 'The nostr command line: keys, events, NIP-19, NIP-44, relay queries, publishing, and a local store that later runs read without a network.', { operatingSystem: 'macOS, Linux, Windows' })],
plaza: [app('Plaza', 'plaza', 'plaza', 'A fast, local-first Nostr client for macOS and Linux, rendered from its own store and reconciled with relays in the background. It reads long-form articles, searches for people by name and uploads pictures to Blossom servers.', { applicationCategory: 'SocialNetworkingApplication' })],
notary: [app('Notary', 'notary', 'notary', 'A native NIP-46 remote signer for macOS and Linux: the key stays in a local daemon on a machine you control.', { applicationCategory: 'SecurityApplication' })],
}

Expand Down
20 changes: 17 additions & 3 deletions content/architecture.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ official BIP-340 test-vector suite. On top of it:

- **RFC 6455 WebSocket** framing and handshake, and a stream-generic relay
connection state machine with NIP-01 subscriptions.
- A live **TCP/TLS dialer** that resolves hostnames through the system resolver.
- 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.
Expand All @@ -36,8 +37,9 @@ 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, stopping at `limit`, so feed latency stays flat as
the store grows (see [Performance](/performance)).
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
Expand All @@ -56,3 +58,15 @@ The layer the native signer is built on:
access behind auth.

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

## 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.
24 changes: 20 additions & 4 deletions content/deed.mdx
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
# deed: the nostr command line

**deed** is a command line for nostr, built on `zig-nostr`. It makes keys, builds and signs events, reads and writes NIP-19 codes, encrypts and decrypts with NIP-44, checks signatures, asks relays for events and publishes to them, and keeps what it fetches in a local store that later runs can read without a network. One binary of under 3 MB for macOS and Linux, Intel and ARM, statically linked on Linux.
**deed** is a command line for nostr, built on `zig-nostr`. It makes keys, builds and signs events, reads and writes NIP-19 codes, encrypts and decrypts with NIP-44, checks signatures, asks relays for events and publishes to them, and keeps what it fetches in a local store that later runs can read without a network. One binary of under 3 MB for macOS, Linux and Windows, Intel and ARM, statically linked on Linux.

A deed is two things at once: a signed instrument, and a thing done. So is a nostr event.

## Install

macOS and Linux:

```sh
curl -fsSL https://raw.githubusercontent.com/zig-nostr/deed/main/scripts/install.sh | bash
```

The installer works out which build the machine wants, checks the download against the SHA-256 published beside it, and installs into `~/.local/bin`, so nothing needs root. If the digest does not match, it installs nothing and says so.
Windows, in PowerShell:

```powershell
irm https://raw.githubusercontent.com/zig-nostr/deed/main/scripts/install.ps1 | iex
```

Each checks the download against the SHA-256 published beside it, and if the digest does not match it installs nothing and says so. On macOS and Linux deed goes into `~/.local/bin`. On Windows it goes into `%LOCALAPPDATA%\deed`, which is added to your `PATH`, so `deed version` works in a new terminal. Neither needs root or administrator rights.

`--version <tag>` installs a particular release and `--prefix <dir>` puts it somewhere else; on Windows they are `-Version` and `-Prefix` when the script runs as a file, and `$env:DEED_VERSION` and `$env:DEED_PREFIX` for the piped line. Windows on ARM gets the x86_64 build, which Windows 11 runs under emulation. The Windows binary is not code-signed, so Windows may show a warning the first time it runs.

## What it does

Expand Down Expand Up @@ -51,6 +61,10 @@ deed req -k 1 -l 50 --store ~/.deed/db --local # again, dialling n

The second command opens no socket, and the events it prints are the same events: what is stored is what was signed, so they still verify. Every event is checked before it is stored or printed, so a relay cannot put a forgery, or an event nobody asked for, into the store or the output.

A store is one file. `--store` creates it the first time, and the directories above it as `mkdir -p` would, and a leading `~` is your home directory even when it was quoted. `--local` only reads, so it opens a store that already exists and says "there is no store at" a path that has none, rather than leaving an empty one behind. When a store will not open, the message says why: a directory in the way, no permission, a read-only file system, a full disk, or a file that is not a store. On Windows the file takes 1 GiB of disk from the first run, because LMDB sizes it to its whole map up front there; on macOS and Linux it grows with what is kept.

NIP-19 codes are read the way a QR code carries them: `deed decode NPUB1...` and `NOSTR:NPUB1...` work as the lowercase forms do, and a code that mixes cases is refused.

## Judging what it fetches

deed's output is one verified event per line, which makes it a clean first stage for anything that classifies text. The [Jev example](https://github.com/zig-nostr/deed/tree/main/examples/jev) fetches the newest thousand notes from five relays, drops the app data some clients publish as notes, and has [Jev](https://docs.typesafe.ai), TypeSafe's classification model, answer three questions about each: what it is about, how much substance it carries, and whether it is spam. The thresholds that decide what to keep are plain constants in the script.
Expand All @@ -66,10 +80,12 @@ On one run, 629 of 1,000 notes were text worth judging, 157 of those were droppe

A one-shot command runs in about 2.3 ms and under 2 MB of memory. deed signs and verifies about 31,000 events a second each, stores 100,000 events from a relay at about 17,000 a second, and answers a lookup from that store in about 3 ms, start to finish. The full numbers, and how to reproduce them, are on the [performance page](/performance#deed).

## How it is built

[`ARCHITECTURE.md`](https://github.com/zig-nostr/deed/blob/main/ARCHITECTURE.md) explains how deed is put together: where each command lives, how a run flows, the store, the exit codes and how it is tested. CI builds deed and runs its tests on Linux, macOS and Windows, and on Windows it also makes a key, signs an event and verifies it through a pipe with the binary it built.

## What is missing

**Relay selection.** A code with no relay hints is not looked up yet: `deed fetch npub1...` asks you to name a relay rather than finding the author's relay list first.

**Windows.** deed does not build there yet.

Source, releases and the full reference live at [`zig-nostr/deed`](https://github.com/zig-nostr/deed).
13 changes: 7 additions & 6 deletions content/ecosystem.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ apps, so every layer benefits from the same well-tested foundation.

**[`zig-nostr/nostr`](https://github.com/zig-nostr/nostr)**: secp256k1 keys and
BIP-340 signing, the NIP-01 event model, relay transport with the outbox model, a
zero-copy local-first store, and the NIP-46 signer protocol. It's the credibility
zero-copy local-first store, and the NIP-46 signer protocol. It builds for macOS,
Linux and Windows, and its parsers are fuzzed. It's the credibility
anchor for everything else, and it keeps growing: more NIPs and capabilities land
with each milestone. See [NIP support](/nips) and the [architecture](/architecture).

Expand All @@ -26,26 +27,26 @@ and the reason the ecosystem exists.

A native NIP-46 signer for macOS and Linux: Zig, no Electron, no WebView. Your key lives in
a local daemon, nothing gets signed quietly, and your `nsec` never leaves it
unless you ask for it.
unless you ask for it. On macOS it waits in the menu bar and shows its window only when a request needs a decision.
→ [`zig-nostr/notary`](https://github.com/zig-nostr/notary) · [read more](/notary)

### Plaza: shipped

The flagship client: read without an account, post in four clicks, with the feed
rendered from disk and reconciled in the background. Your key lives in Notary, a separate
rendered from disk and reconciled in the background. It opens long-form articles, searches for people by name, adds pictures from a file and covers notes marked sensitive. Your key lives in Notary, a separate
process, or in your own signer.
→ [`zig-nostr/plaza`](https://github.com/zig-nostr/plaza) · [read more](/plaza)

### deed: shipped

The nostr command line: keys, events, NIP-19 codes, NIP-44, signature checks, relays, and a local store that later runs read without a network. One binary of under 3 MB for macOS and Linux.
The nostr command line: keys, events, NIP-19 codes, NIP-44, signature checks, relays, and a local store that later runs read without a network. One binary of under 3 MB for macOS, Linux and Windows.
→ [`zig-nostr/deed`](https://github.com/zig-nostr/deed) · [read more](/deed)

### On the roadmap

Both apps are downloadable and both keep growing. What comes next lands inside
them rather than as new apps: private messages, zaps and a wallet, groups, search
and media all belong to the client people already have.
them rather than as new apps: private messages, zaps and a wallet, groups, searching
notes and playing video all belong to the client people already have.

See the [roadmap](/roadmap) for the order, and for what is deliberately not being
built.
Expand Down
12 changes: 9 additions & 3 deletions content/getting-started.mdx
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Getting Started

`zig-nostr` is a Nostr protocol library for [Zig](https://ziglang.org). It
targets Zig **0.16** and is in **active development** (`v0.14.1`).
targets Zig **0.16** and is in **active development** (`v0.14.9`).

## Install

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

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

Then wire the module in `build.zig`:
Expand All @@ -21,6 +21,10 @@ 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

```zig
Expand Down Expand Up @@ -81,7 +85,7 @@ The `nostr` module re-exports focused namespaces:
- `nip44`: v2 payload encryption
- `nip46`: remote signing ("bunker") protocol
- `nip42`: client authentication to relays
- `relay` / `websocket` / `message` / `filter`: live transport & subscriptions
- `relay` / `websocket` / `message` / `filter`: live transport & subscriptions, with NIP-50 `search` on the filter
- `nip65`: relay lists and outbox routing
- `liveness`: when a relay connection has gone quiet, and what to do about it
- `store`: the local-first, memory-mapped event store
Expand All @@ -95,3 +99,5 @@ 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`.
10 changes: 5 additions & 5 deletions content/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ title: The Nostr protocol, natively in Zig
<div className="zn-card">
<div className="zn-icon">📦</div>
<h3>Core library</h3>
<p>The foundation, keys, events, transport, the outbox model, and a local-first store. Actively growing toward full Nostr coverage, milestone after milestone.</p>
<p>The foundation, keys, events, transport, the outbox model, and a local-first store. It builds on macOS, Linux and Windows, and it is actively growing toward full Nostr coverage, milestone after milestone.</p>
</div>
<div className="zn-card">
<div className="zn-icon">🧩</div>
Expand Down Expand Up @@ -87,23 +87,23 @@ title: The Nostr protocol, natively in Zig
<h3>Notary</h3>
<span className="zn-tag zn-tag-shipped">Shipped</span>
</div>
<p>A native NIP-46 signer for macOS and Linux: Zig, no Electron. Your nsec stays in a local daemon, and nothing gets signed quietly.</p>
<p>A native NIP-46 signer for macOS and Linux: Zig, no Electron. Your nsec stays in a local daemon, and nothing gets signed quietly. On macOS it waits in the menu bar until a request needs you.</p>
<a className="zn-applink" href="/notary">Learn more →</a>
</div>
<div className="zn-card">
<div className="zn-app-head">
<h3>Plaza</h3>
<span className="zn-tag zn-tag-shipped">Shipped</span>
</div>
<p>A fast, local-first client: read without an account, post in four clicks, with the feed rendered from disk. A downloadable app for macOS and Linux.</p>
<p>A fast, local-first client: read without an account, post in four clicks, with the feed rendered from disk. It reads long-form articles, finds people by name and adds pictures from a file. A downloadable app for macOS and Linux.</p>
<a className="zn-applink" href="/plaza">Learn more →</a>
</div>
<div className="zn-card">
<div className="zn-app-head">
<h3>deed</h3>
<span className="zn-tag zn-tag-shipped">Shipped</span>
</div>
<p>The nostr command line: sign, verify, encode, encrypt, query relays and publish, with a local store that answers without a network. One small binary for macOS and Linux.</p>
<p>The nostr command line: sign, verify, encode, encrypt, query relays and publish, with a local store that answers without a network. One small binary for macOS, Linux and Windows.</p>
<a className="zn-applink" href="/deed">Learn more →</a>
</div>
</div>
Expand All @@ -117,7 +117,7 @@ title: The Nostr protocol, natively in Zig
</p>

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

```zig
Expand Down
Loading
Loading