From 1132ffabb23ab659af43ba8dfe3618e4b5e691af Mon Sep 17 00:00:00 2001 From: alltheseas Date: Wed, 12 Nov 2025 06:31:15 -0600 Subject: [PATCH 1/4] doc: add repo getting-started guide --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 784399459..a0a03787d 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,34 @@ Check the example in the [`embedded/`](./crates/nostr/examples/embedded) directo Learn more about `rust-nostr` at . +## Getting started + +### Prerequisites + +- Rust **1.85.0** (automatically enforced through `rust-toolchain.toml`). Install it with `rustup` and make sure the `clippy`, `rustfmt`, and `rust-docs` components are available. +- [`just`](https://github.com/casey/just) for the repo’s helper recipes (optional but strongly encouraged). + +### Clone and check + +```bash +git clone https://github.com/rust-nostr/nostr.git +cd nostr +just check # fmt + lint + doc + workspace checks +cargo test # run the full workspace test suite +``` + +Run only the formatting and lint gate the CI expects with `just precommit`, or call any script directly from `contrib/scripts/`. + +### Examples and docs + +- High-level client examples live in `crates/nostr-sdk/examples/`; run one with `cargo run --example client --package nostr-sdk`. +- Low-level protocol examples live under `crates/nostr/examples/`. +- To browse the API documentation offline, run `cargo doc --workspace --no-deps --open`. This generates the same content that powers and complements the mdBook available at . + ## Supported NIPs +The table below lists which NIPs have implementations somewhere in this workspace. Some features are behind crate flags (for example `nostr-sdk --features nip44,nip57`) or only available when using a specific crate (e.g., signer integrations). ✅ means “implemented and tested behind the relevant feature flag”, ❌ means “not available anywhere in this repo yet”. + | Supported | NIP | |:---------:|-----------------------------------------------------------------------------------------------------------------| | ✅ | [01 - Basic protocol flow description](https://github.com/nostr-protocol/nips/blob/master/01.md) | From 843fefe22fbdeb81a828e66f31df5b87a0dca75b Mon Sep 17 00:00:00 2001 From: alltheseas Date: Wed, 12 Nov 2025 06:31:21 -0600 Subject: [PATCH 2/4] doc: expand crate usage guides --- crates/nostr-keyring/README.md | 34 ++++++++++++++++++++ crates/nostr-relay-builder/README.md | 46 +++++++++++++++++++++++---- crates/nostr-relay-pool/README.md | 36 +++++++++++++++++++++ gossip/nostr-gossip-memory/README.md | 33 +++++++++++++++++++ gossip/nostr-gossip/README.md | 45 ++++++++++++++++++++++++++ rfs/README.md | 7 ++++ rfs/nostr-http-file-storage/README.md | 34 ++++++++++++++++++-- 7 files changed, 226 insertions(+), 9 deletions(-) diff --git a/crates/nostr-keyring/README.md b/crates/nostr-keyring/README.md index 6c8f01823..ab6d63d09 100644 --- a/crates/nostr-keyring/README.md +++ b/crates/nostr-keyring/README.md @@ -1,5 +1,37 @@ # Nostr Keyring +Thin wrapper around the system keyring that stores `nostr::Keys` objects without forcing you to handle secret material manually. The crate keeps all serialization in-memory and relies on the OS-provided credential store (macOS Keychain, Windows Credential Manager, Secret Service, etc.). + +```rust +use nostr::prelude::*; +use nostr_keyring::NostrKeyring; + +fn main() -> Result<(), Box> { + let keyring = NostrKeyring::new("my-nostr-app"); + let keys = Keys::generate(); + + keyring.set("default", &keys)?; + let restored = keyring.get("default")?; + assert_eq!(keys.public_key(), restored.public_key()); + + Ok(()) +} +``` + +Enable the `async` feature to offload OS keyring access to a blocking thread pool when running inside async executors: + +```rust,no_run +use nostr::prelude::*; +use nostr_keyring::NostrKeyring; + +# #[tokio::main] +# async fn main() -> Result<(), Box> { +let keyring = NostrKeyring::new("bot"); +let keys = keyring.get_async("default").await?; +println!("Using {}", keys.public_key()); +# Ok(()) } +``` + ## Crate Feature Flags The following crate feature flags are available: @@ -8,6 +40,8 @@ The following crate feature flags are available: |---------|:-------:|-------------------------------------------| | `async` | No | Enable async APIs | +Install with `cargo add nostr-keyring --features async` to opt into the Tokio-friendly async helpers. + ## Changelog All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). diff --git a/crates/nostr-relay-builder/README.md b/crates/nostr-relay-builder/README.md index b9e20c723..9b04ef136 100644 --- a/crates/nostr-relay-builder/README.md +++ b/crates/nostr-relay-builder/README.md @@ -1,11 +1,45 @@ # Nostr Relay Builder -## Description - -Build your own custom nostr relay! - -This library contains all the stuff to easily build a nostr relay. -It also contains a ready-to-use `MockRelay` for unit tests. +`nostr-relay-builder` helps you stand up fully configurable relays (local or hidden-service) without re-implementing policies, storage, or protocol minutiae. The crate exposes two main entry points: + +- `LocalRelay` – run a fully fledged relay inside your process. +- `MockRelay` – deterministic relay for unit/integration tests. + +## Quick start + +```rust,no_run +use std::net::Ipv4Addr; + +use nostr::prelude::*; +use nostr_database::MemoryDatabase; +use nostr_relay_builder::{RelayBuilder, LocalRelay, RateLimit}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let relay = LocalRelay::new( + RelayBuilder::default() + .addr(Ipv4Addr::LOCALHOST.into()) + .port(7777) + .database(MemoryDatabase::default()) + .rate_limit(RateLimit { + max_reqs: 128, + notes_per_minute: 30, + }), + ); + + relay.run().await?; + println!("relay listening on {}", relay.url().await); + + Ok(()) +} +``` + +See the `local` and `mock` modules plus `examples/` for advanced policies such as: + +- Enforcing NIP-42 auth via `RelayBuilder::nip42`. +- Only accepting writes from a given pubkey (`RelayBuilderMode::PublicKey`). +- Plugging in your own `NostrDatabase` backend and rate limits. +- Injecting events from tests via `MockRelay::notify_event`. ## Changelog diff --git a/crates/nostr-relay-pool/README.md b/crates/nostr-relay-pool/README.md index 56adb0c77..0623c6eaf 100644 --- a/crates/nostr-relay-pool/README.md +++ b/crates/nostr-relay-pool/README.md @@ -1,5 +1,39 @@ # Nostr Relay Pool +Nostr Relay Pool is the low-level building block used by `nostr-sdk` to manage many relay connections in parallel. Use it when you need fine-grained control over relay policies, admission rules, or when embedding the gossip stack in your own executor. + +## Usage + +```rust,no_run +use nostr::prelude::*; +use nostr_relay_pool::{RelayOptions, RelayPool, RelayPoolNotification}; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let pool = RelayPool::builder().build(); + + // Add relays with custom options (timeouts, flags, etc.) + pool.add_relay("wss://relay.damus.io", RelayOptions::default()).await?; + pool.add_relay("wss://relay.primal.net", RelayOptions::default()).await?; + + // Fire up the background tasks and wait until we are connected + pool.connect().await; + pool.wait_for_connection(std::time::Duration::from_secs(5)).await; + + // Listen for broadcast notifications straight from the relays + let mut notifications = pool.notifications(); + while let Ok(notification) = notifications.recv().await { + if let RelayPoolNotification::Event { event, .. } = notification { + println!("Got event {} -> {}", event.author(), event.content()); + } + } + + Ok(()) +} +``` + +See `crates/nostr-relay-pool/examples/` for more involved setups that mix monitors, sync policies, and custom transports. + ## Crate Feature Flags The following crate feature flags are available: @@ -8,6 +42,8 @@ The following crate feature flags are available: |---------|:-------:|-------------------------------------------| | `tor` | No | Enable support for embedded tor client | +Enable the feature with `cargo add nostr-relay-pool --features tor` (native targets only). + ## Changelog All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). diff --git a/gossip/nostr-gossip-memory/README.md b/gossip/nostr-gossip-memory/README.md index 3b095ee7a..12a365c40 100644 --- a/gossip/nostr-gossip-memory/README.md +++ b/gossip/nostr-gossip-memory/README.md @@ -1,5 +1,38 @@ # Gossip in-memory storage +Reference `NostrGossip` implementation that stores relay metadata in an LRU cache. Ideal for bots or clients that want a drop-in gossip engine without running a database. + +```rust,no_run +use std::num::NonZeroUsize; + +use nostr::prelude::*; +use nostr_gossip::{BestRelaySelection, NostrGossip}; +use nostr_gossip_memory::NostrGossipMemory; + +# #[tokio::main] +async fn main() -> Result<(), Box> { + let gossip = NostrGossipMemory::bounded(NonZeroUsize::new(2048).unwrap()); + let relay = RelayUrl::parse("wss://relay.primal.net")?; + + // Every event coming from your relay pool should be forwarded here + let event = EventBuilder::text_note("demo note").sign_with_keys(&Keys::generate())?; + gossip.process(&event, Some(&relay)).await?; + + // Later on, ask for the best relays for a profile + let best = gossip + .get_best_relays( + &event.pubkey, + BestRelaySelection::PrivateMessage { limit: 2 }, + ) + .await?; + println!("DM relays -> {:?}", best); + + Ok(()) +} +``` + +Use `NostrGossipMemory::unbounded()` for testing or small bots, and `bounded(limit)` to cap memory usage in long-running clients. + ## Changelog All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). diff --git a/gossip/nostr-gossip/README.md b/gossip/nostr-gossip/README.md index 768f0cb5e..b914a5db7 100644 --- a/gossip/nostr-gossip/README.md +++ b/gossip/nostr-gossip/README.md @@ -1,5 +1,50 @@ # Nostr gossip traits +Core traits and utility types for tracking relay lists (NIP-65), inbox relays (NIP-17), and best-relay selection heuristics. Implement the `NostrGossip` trait to plug custom storage engines into `nostr-sdk` or run the provided in-memory store from `nostr-gossip-memory`. + +## Usage + +```rust,no_run +use std::num::NonZeroUsize; + +use nostr::prelude::*; +use nostr_gossip::{BestRelaySelection, GossipListKind, NostrGossip}; +use nostr_gossip_memory::NostrGossipMemory; + +# #[tokio::main] +async fn main() -> Result<(), Box> { + let gossip = NostrGossipMemory::bounded(NonZeroUsize::new(1024).unwrap()); + let relay = RelayUrl::parse("wss://relay.damus.io")?; + + // Feed the store with events as they arrive from your pool/client + let event = EventBuilder::text_note("hello").sign_with_keys(&Keys::generate())?; + gossip.process(&event, Some(&relay)).await?; + + // Check if we need to refresh metadata for a pubkey + if matches!( + gossip + .status(&event.pubkey, GossipListKind::Nip65) + .await?, + nostr_gossip::GossipPublicKeyStatus::Outdated { .. } + ) { + // trigger a sync + } + + // Ask for the best relays to read from + let relays = gossip + .get_best_relays( + &event.pubkey, + BestRelaySelection::Read { limit: 2 }, + ) + .await?; + println!("{} prefers {:?}", event.pubkey, relays); + + Ok(()) +} +``` + +The crate also exposes flags for scoring relays (`GossipFlags`) and helper enums for targeting read/write/private message selections. + ## Changelog All notable changes to this library are documented in the [CHANGELOG.md](CHANGELOG.md). diff --git a/rfs/README.md b/rfs/README.md index 87ebea1ce..115780242 100644 --- a/rfs/README.md +++ b/rfs/README.md @@ -1 +1,8 @@ # Remote File Storage implementations + +This workspace folder hosts the crates that implement [NIP-96](https://github.com/nostr-protocol/nips/blob/master/96.md) compatible uploads and the experimental [Blossom](https://github.com/hzrd149/blossom) protocol support. + +- [`nostr-http-file-storage`](./nostr-http-file-storage) – async HTTP client that knows how to discover `nip96.json`, sign upload requests, and return permanent download URLs. +- [`nostr-blossom`](./nostr-blossom) – builder blocks for the Blossom protocol (basic client support today). + +Each crate can be used stand-alone; nothing in here is pulled in automatically by `nostr` or `nostr-sdk`. Enable whichever fits your application via Cargo features. diff --git a/rfs/nostr-http-file-storage/README.md b/rfs/nostr-http-file-storage/README.md index 90af3fce0..585d8dae4 100644 --- a/rfs/nostr-http-file-storage/README.md +++ b/rfs/nostr-http-file-storage/README.md @@ -1,8 +1,36 @@ # Nostr HTTP File Storage client (NIP-96) -## Description - -Nostr HTTP File Storage client ([NIP-96](https://github.com/nostr-protocol/nips/blob/master/96.md)). +Async client for [NIP-96](https://github.com/nostr-protocol/nips/blob/master/96.md) servers. Handles discovery of `nip96.json`, authenticated uploads, and returns the download URL you can embed inside events. + +```rust,no_run +use nostr::prelude::*; +use nostr_http_file_storage::NostrHttpFileStorageClient; + +# #[tokio::main] +async fn main() -> Result<(), Box> { + let client = NostrHttpFileStorageClient::new(); + let server = Url::parse("https://files.example.com")?; + + // Fetch nip96.json to learn limits and auth requirements + let config = client.get_server_config(&server).await?; + + // Any `NostrSigner` works; `Keys` implements it out of the box + let signer = Keys::generate(); + let download_url = client + .upload( + &signer, + &config, + b"hello nostr".to_vec(), + Some("text/plain"), + ) + .await?; + + println!("File available at {download_url}"); + Ok(()) +} +``` + +Use `NostrHttpFileStorageClient::builder()` when you need granular control over timeouts or a SOCKS5 proxy (enable the `socks` feature on native targets). ## Changelog From 0e2f343dafbea3f7a634f252d53b21f2114c917b Mon Sep 17 00:00:00 2001 From: alltheseas Date: Wed, 12 Nov 2025 06:31:29 -0600 Subject: [PATCH 3/4] doc: clarify contributor and security policies --- CONTRIBUTING.md | 18 ++++++++++++++++-- SECURITY.md | 7 ++++++- contrib/README.md | 26 ++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f097c06e6..e4de1afed 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,7 +11,7 @@ This project follows the rust-nostr organization guidelines: https://github.com/ The commit **must** be formatted as follows: ``` -: +: ``` @@ -24,7 +24,7 @@ If applicable, link the `issue`/`PR` to be closed with: The `context` **must be**: - `nostr` for changes to the `nostr` crate -- `sdk`, `cli`, `relay-pool`, `connect`, `nwc` and so on for the others crates (remote the `nostr-` prefix) +- `sdk`, `cli`, `relay-pool`, `connect`, `nwc` and so on for the others crates (remove the `nostr-` prefix) - `test` for changes to the unit tests - `doc` for changes to the documentation - `contrib` for changes to the scripts and tools @@ -60,3 +60,17 @@ Closes https://.com/rust-nostr/nostr/issue/2222 Install https://github.com/casey/just and use `just precommit` or `just check` to format and check the code before committing. The CI also enforces this. + +## Local development workflow + +1. Install the workspace toolchain (`rustup show 1.85.0`) and `just`. +2. Run `just check` early—this executes formatting, clippy, doctests, and `cargo check` for every crate. +3. Use `cargo test -p ` while iterating, then `cargo test --workspace` before opening a PR. +4. Keep feature-gated code tested by passing `--all-features` (or at least the features you touched). +5. When documentation changes reference code, build the docs locally with `cargo doc --workspace --no-deps`. + +## Documentation contributions + +- Edit Markdown in `README.md`, crate-level READMEs under `crates/*/README.md`, or the book at . +- Prefer short runnable examples; keep them in sync with the APIs by compiling them locally (use `cargo test --doc`). +- If you add scripts or tooling, document them under `contrib/README.md` so future contributors can discover them. diff --git a/SECURITY.md b/SECURITY.md index a703d7a8f..374b591db 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,3 +1,8 @@ # Reporting a Vulnerability -For security vulnerability reporting and our complete security policy, please see: https://github.com/rust-nostr/guidelines +1. **Preferred channel:** Open a private report via GitHub Security Advisories: . This keeps the discussion confidential until a fix ships. +2. **Alternative channel:** If GitHub is unavailable for you, send an encrypted message following the instructions in the organization guidelines (). That document lists the current security PGP keys. +3. **What to include:** affected crate(s) and versions, a minimal proof-of-concept, the impact you observed, and any suggested mitigation ideas. Logs and environment info (`rustc -V`, OS, enabled features) are extremely helpful. +4. **Response targets:** we aim to acknowledge new reports within **5 business days** and keep you posted every time we cross a milestone (triage, fix ready, release, disclosure). + +Please do **not** open public issues for security problems. We appreciate coordinated disclosure and will credit you in the release notes unless you ask otherwise. diff --git a/contrib/README.md b/contrib/README.md index e69de29bb..f70399dea 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -0,0 +1,26 @@ +# Contrib utilities + +Helper scripts and docs that keep the workspace consistent. + +## Scripts + +Located under `contrib/scripts/` and wired into the root `justfile`. + +| Script | Purpose | +| --- | --- | +| `check-fmt.sh [check]` | Format the entire workspace (default) or verify formatting when called with `check`. | +| `check-crates.sh` | Runs `cargo check`/`clippy` across the workspace with the default feature set. | +| `check-docs.sh` | Ensures `cargo doc` builds for every crate, catching broken intra-doc links. | +| `check-deny.sh` | Executes `cargo deny check` using the repo’s `deny.toml`. | +| `contributors.py` | Generates the CONTRIBUTORS list used for release notes. | + +Invoke them directly (`bash contrib/scripts/check-crates.sh`) or via the `just` recipes (`just check`, `just precommit`). + +## Release playbooks + +`contrib/release/RELEASE_STEPS.md` contains the checklist we follow before publishing crates. Keep it updated whenever the release process changes. + +## Funding + verification + +- `contrib/fund` stores the assets shown on . +- `contrib/verify-commits` documents the GPG/SSH verification steps used by the maintainers. From 01137a233d3e69e84fd7a5065581d9bbe38ff073 Mon Sep 17 00:00:00 2001 From: alltheseas Date: Wed, 12 Nov 2025 07:08:28 -0600 Subject: [PATCH 4/4] doc: document NIP-09 deletions --- README.md | 1 + crates/nostr-sdk/README.md | 30 +++++++++++++++++++++++++++++ docs/nip09-deletions.md | 39 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) create mode 100644 docs/nip09-deletions.md diff --git a/README.md b/README.md index a0a03787d..73e7b5bc1 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Check the example in the [`embedded/`](./crates/nostr/examples/embedded) directo ## Book Learn more about `rust-nostr` at . +For focused topics that haven’t landed in the public book yet (e.g., NIP-09 deletions), see the notes under [`docs/`](./docs/nip09-deletions.md). ## Getting started diff --git a/crates/nostr-sdk/README.md b/crates/nostr-sdk/README.md index dfcad9ed5..459fb9527 100644 --- a/crates/nostr-sdk/README.md +++ b/crates/nostr-sdk/README.md @@ -82,6 +82,36 @@ async fn main() -> Result<()> { More examples can be found in the [examples/](https://github.com/rust-nostr/nostr/tree/master/crates/nostr-sdk/examples) directory. +### Deleting events (NIP-09) + +Use [`EventDeletionRequest`](https://docs.rs/nostr/latest/nostr/nips/nip09/struct.EventDeletionRequest.html) when you need to retract one or more events you previously signed. NIP-09 is advisory: relays and clients may ignore the request, so always wait for confirmations. + +```rust,no_run +use nostr_sdk::prelude::*; + +# #[tokio::main] +# async fn main() -> Result<(), Box> { +let client = Client::default(); +client.add_relay("wss://relay.example.com").await?; +client.connect().await; + +// Collect the events (or coordinates) you want to delete +let delete = EventDeletionRequest::new() + .id(EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?) + .reason("published by accident"); + +client + .send_event_builder(EventBuilder::delete(delete)) + .await?; +# Ok(()) } +``` + +Guidelines: + +- Only the author’s keys can sign a deletion event for a given note. +- Include `Coordinate` tags (kind:pubkey:d) when deleting replaceable or parameterized replaceable events. +- Keep local state until relays confirm the deletion; replays may still surface the original content. + ## WASM This crate supports the `wasm32` targets. diff --git a/docs/nip09-deletions.md b/docs/nip09-deletions.md new file mode 100644 index 000000000..453649ebf --- /dev/null +++ b/docs/nip09-deletions.md @@ -0,0 +1,39 @@ +# Managing deletions (NIP-09) + +NIP-09 defines a best-effort mechanism for retracting events you previously published. It does **not** guarantee erasure—relays and downstream clients are allowed to cache or ignore deletion requests—so treat it as a courtesy protocol for well-behaved peers. + +## When to send a deletion event + +1. You control the secret key that signed the original event. +2. You know the event IDs (or coordinates for replaceable/parameterized events) you want to retract. +3. You accept that the payload remains public even after the deletion request propagates. + +## Building the request + +```rust +use nostr::prelude::*; + +let delete = EventDeletionRequest::new() + .id(EventId::from_hex("7469af3be8c8e06e1b50ef1caceba30392ddc0b6614507398b7d7daa4c218e96")?) + // optionally add coordinates for replaceable events + // .coordinate(Coordinate::parse("30023:pubkey:identifier")?) + .reason("these posts were published by accident"); + +let event = EventBuilder::delete(delete).sign_with_keys(&keys)?; +``` + +Use `Tag::event` for concrete IDs (`e` tags) and `Tag::coordinate` for replaceable events (`a` tags). The textual `reason` is optional but helps other clients explain why the content disappeared. + +## Broadcasting and follow-up + +1. Send the deletion event to every relay that received the original event. +2. Keep a local record of the IDs you attempted to delete. Some relays respond with `OK` messages or emit `NOTICE`s when they refuse to honor the request; handle both cases. +3. Be prepared for race conditions—if someone republishes the original content, you may have to re-issue a deletion. + +## Caveats + +- Relays are free to ignore deletion events entirely or only apply them to new subscribers. +- Archive relays and scrapers can continue serving the old content indefinitely. +- Deleting a parameterized replaceable event without its coordinate will have no effect. + +In short: use NIP-09 as part of your UX, but do not treat it as a hard delete.