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
1 change: 1 addition & 0 deletions content/_meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default {
nips: 'NIP Support',
plaza: 'Plaza',
notary: 'Notary',
deed: 'deed',
performance: 'Performance',
roadmap: 'Roadmap',
}
64 changes: 64 additions & 0 deletions content/deed.mdx
Original file line number Diff line number Diff line change
@@ -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 <command>` 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).
5 changes: 5 additions & 0 deletions content/ecosystem.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions content/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ title: The Nostr protocol, natively in Zig
<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>
<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>
<a className="zn-applink" href="/deed">Learn more →</a>
</div>
<div className="zn-card">
<div className="zn-app-head">
<h3>Private messages</h3>
Expand Down
48 changes: 47 additions & 1 deletion content/performance.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Loading