diff --git a/content/_meta.js b/content/_meta.js index 61f7a6a..83b5a61 100644 --- a/content/_meta.js +++ b/content/_meta.js @@ -17,6 +17,7 @@ export default { nips: 'NIP Support', plaza: 'Plaza', notary: 'Notary', + deed: 'deed', performance: 'Performance', roadmap: 'Roadmap', } diff --git a/content/deed.mdx b/content/deed.mdx new file mode 100644 index 0000000..17f8b34 --- /dev/null +++ b/content/deed.mdx @@ -0,0 +1,64 @@ +# 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. + +A deed is two things at once: a signed instrument, and a thing done. So is a nostr event. + +## Install + +```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. + +## What it does + +| Verb | | +| --- | --- | +| `key` | make a key, or derive the public one from it | +| `event` | build an event and sign it | +| `decode` | turn a NIP-19 code into the fields it carries | +| `encode` | build a NIP-19 code out of its parts | +| `encrypt` | encrypt a message to someone, with NIP-44 | +| `decrypt` | decrypt a NIP-44 payload from someone | +| `verify` | check that events are correctly signed | +| `req` | build a subscription, and run it | +| `fetch` | get the events a code names | +| `publish` | offer signed events to relays, and print the ones they accepted | + +Every verb that does not need a socket works without one, so what comes out can be read before any of it leaves the machine. `deed help ` explains any of them. + +## The verbs compose + +Each verb takes its inputs as arguments and, given none, reads them as newline-delimited records on standard input, writing one result per line: + +```sh +export NOSTR_SECRET_KEY=$(deed key generate) + +deed event -c "hello" | deed verify +cat drafts.jsonl | deed event - | deed publish wss://relay.example > sent.jsonl +``` + +`publish` prints each event a relay accepted, once the relays have answered or the deadline has passed, so what it writes out is what was published. Every relay's answer goes to standard error with the event's id on it, and the exit code is 0 only when every event was accepted by at least one relay. + +## It keeps what it fetches + +```sh +deed req -k 1 -l 50 --store ~/.deed/db wss://relay.example # once, over the network +deed req -k 1 -l 50 --store ~/.deed/db --local # again, dialling nothing +``` + +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. + +## How fast it is + +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). + +## 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). diff --git a/content/ecosystem.mdx b/content/ecosystem.mdx index 8ce3095..efeb26e 100644 --- a/content/ecosystem.mdx +++ b/content/ecosystem.mdx @@ -36,6 +36,11 @@ rendered from disk and reconciled in the background. Your key lives in Notary, a 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. +→ [`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 diff --git a/content/index.mdx b/content/index.mdx index 19b7df5..162e4a2 100644 --- a/content/index.mdx +++ b/content/index.mdx @@ -98,6 +98,14 @@ title: The Nostr protocol, natively in Zig

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.

Learn more → +
+
+

deed

+ Shipped +
+

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.

+ Learn more → +

Private messages

diff --git a/content/performance.mdx b/content/performance.mdx index e3d6059..42330e0 100644 --- a/content/performance.mdx +++ b/content/performance.mdx @@ -23,7 +23,7 @@ much history sits behind it. That's the **bounded query planner**: it walks the indexes newest-first and stops at `limit`, so latency tracks the page size you ask for, not the size of the store. -## Reproduce it +## Reproduce the library numbers ```sh git clone https://github.com/zig-nostr/nostr @@ -35,3 +35,49 @@ The benchmark source is [`src/bench.zig`](https://github.com/zig-nostr/nostr/blob/main/src/bench.zig). Absolute numbers vary with hardware; the *shape*, flat feed latency as the store grows, is the point. + +## deed + +What the [command line](/deed) costs to run, measured on an Apple M2 Pro with the deed 0.3.1 binary from the release page (`ReleaseSafe`, stripped). Each number is the best of several runs, and every one is the whole command, starting the process included. + +| Size | Binary | Download | +|----|----|----| +| macOS, Apple Silicon | 2.3 MB | 1.6 MB | +| macOS, Intel | 2.4 MB | 1.7 MB | +| Linux, x86_64 | 2.7 MB | 1.8 MB | +| Linux, aarch64 | 2.4 MB | 1.8 MB | + +| Command | | +|----|----| +| starting any process at all, for scale | 1.57 ms | +| `deed version` | 2.34 ms | +| `deed event`, signing one | 2.46 ms | +| `deed verify`, one | 3.05 ms | +| peak memory, a one-shot command | 1.7 MB | + +| Streams of 10,000 through stdin | | +|----|----| +| signing | 30,986 per second | +| verifying | 32,523 per second | +| decoding npubs | 568,831 per second | +| peak memory, verifying | 3.9 MB | + +| Local store, 100,000 events from 1,000 authors | | +|----|----| +| storing them from a loopback relay, each signature checked | 17,379 per second | +| one event by id | 3.25 ms | +| one profile | 3.21 ms | +| one author's latest 50 notes | 3.50 ms | +| the latest 500 notes | 5.03 ms | +| every note, 98,000 of them | 286 ms | + +A lookup costs about a millisecond more than `deed version` does, starting deed and opening the store included. The store's own share is the fraction of a millisecond the library numbers at the top of this page show. + +```sh +git clone https://github.com/zig-nostr/deed +cd deed +zig build -Doptimize=ReleaseSafe -Dstrip=true +python3 bench/run.py zig-out/bin/deed +``` + +It needs python3 and websocat, and talks only to a relay it runs on 127.0.0.1. The script is [`bench/run.py`](https://github.com/zig-nostr/deed/blob/main/bench/run.py), and [`BENCHMARKS.md`](https://github.com/zig-nostr/deed/blob/main/BENCHMARKS.md) has every number it prints.