From 2dcf9426a73dd41dd410b9f51cce401c41ad0702 Mon Sep 17 00:00:00 2001 From: Datawav <222291538+Datawav@users.noreply.github.com> Date: Wed, 8 Jul 2026 07:34:51 +0000 Subject: [PATCH 1/2] Add Newsletter #30 (2026-07-08) - Marmot spec marked adopted and MDK v0.9.x line with MarmotKit bindings - Mostro Transport v2 on NIP-44 (mostrod v0.18.0, Mobile v1.3.0) - Bitchat 1.5.4 NIP-13 PoW and mesh-to-Nostr gateway - 14 tagged releases, 13 unreleased-changes sections including SafeBox Phase 3 progress report - NIP Updates: NIP-51/37 name alignment merged, plus NIP-AD, NIP-86 claims, HSL role colors, NIP-01 pagination proposals - NIP deep dives: NIP-13 (Proof of Work), NIP-40 (Expiration Timestamp) - Updated topic pages for NIP-13 and NIP-40 --- .../en/newsletters/2026-07-08-newsletter.md | 274 ++++++++++++++++++ content/en/topics/nip-13.md | 1 + content/en/topics/nip-40.md | 3 +- 3 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 content/en/newsletters/2026-07-08-newsletter.md diff --git a/content/en/newsletters/2026-07-08-newsletter.md b/content/en/newsletters/2026-07-08-newsletter.md new file mode 100644 index 0000000..9bc2929 --- /dev/null +++ b/content/en/newsletters/2026-07-08-newsletter.md @@ -0,0 +1,274 @@ +--- +title: "Nostr Compass #30" +date: 2026-07-08 +publishDate: 2026-07-08 +draft: false +type: newsletters +description: "Marmot marks the spec adopted and MDK cuts v0.9.0 through v0.9.3 with MarmotKit bindings and encrypted group avatars, Mostro ships Transport v2 on NIP-44, Bitchat adds NIP-13 proof-of-work and a mesh-to-Nostr gateway, and rust-nostr adds NIP-40 expiration to gift wrap and private DM builders." +--- + +Welcome back to Nostr Compass, your weekly guide to Nostr. + +**This week:** the [Marmot spec is marked adopted](#marmot-marks-the-spec-adopted-and-mdk-cuts-v09x) across 42 files as MDK cuts v0.9.0 through v0.9.3 with encrypted group avatars, external signer support, and MarmotKit iOS and Android bindings. [Mostro ships Transport v2](#mostro-v0180-and-mobile-v130-ship-transport-v2-on-nip-44) on NIP-44 direct messages with anti-spam gates and a coexistence window in both mostrod v0.18.0 and Mobile v1.3.0. [Bitchat adds NIP-13 proof-of-work](#bitchat-154-adds-nip-13-proof-of-work-and-an-opt-in-mesh-to-nostr-gateway) to geohash channel messages and an opt-in mesh-to-Nostr gateway that lets one online phone uplink a whole crowd. [Amber](#amber-v623-scopes-profile-subscriptions-and-adds-a-tor-status-notification) scopes profile subscriptions per account, fetches NIP-65 relay lists before profile metadata, and adds a live Tor status notification with a restart action. [rust-nostr](#rust-nostr-adds-nip-40-expiration-to-gift-wrap-and-private-dm-builders) adds NIP-40 expiration to gift wrap and NIP-17 DM builders, anchored to the wrap's randomized timestamp. [Amethyst](#amethyst-spends-the-week-hardening-negentropy-sync-and-adding-nip-50-search) merges 43 PRs of negentropy sync hardening, NIP-50 full-text search infrastructure, and event kinds for niche verticals. [Nostrord ships v2.0.0 and v2.1.0](#nostrord-v200-and-v210-fold-the-relay-pool-and-heal-zombie-websockets) with a folded relay pool, zombie WebSocket detection, and a full disk-first cache seam. [Ngit v2.6.2](#ngit-v262-stops-duplicate-pr-status-events-on-default-branch-push), [Jumble v26.7.1](#jumble-v2671-makes-blossom-the-default-upload-service-in-a-dm-focused-cut), [Applesauce signers 6.2.2](#applesauce-signers-622-drops-an-nbunksec-dependency), [Bray v1.33.0](#bray-v1330-cli-picks-up-a-bunker-profile-persona-and-tor-outbound), [Deepmarks 1.0.0](#deepmarks-100-hardens-the-nostr-bookmarking-surface), [Bitcredit Core v0.5.13](#bitcredit-core-v0513-unencrypts-block-metadata-on-the-nostr-wire), [Coop Mobile v0.2.4](#coop-mobile-v023-and-v024), [Granary v11.0](#granary-v110-adds-nip-71-video-event-support), and [Nostr-relay v0.0.244](#nostr-relay-v00244-adds-a-firestore-backend) also ship, and [SafeBox marks Phase 3 substantially complete](#safebox-publishes-a-phase-3-progress-report-and-a-freebsd-jail-runbook) alongside a FreeBSD jail deployment runbook and an OpenETR spin-off for electronic transferable records. The NIPs repository merges a [NIP-51 and NIP-37 name alignment](#merged-nip-51-and-nip-37-align-the-kind-10013-name) and opens four proposals: [NIP-AD Nostr Web Addresses](#open-nip-ad-nostr-web-addresses-via-well-known-lookup), [NIP-86 invite-code claim management](#open-nip-86-claim-management-for-invite-codes), an [HSL role color format](#open-role-color-as-h-s-l-tuple), and a [pagination fix in NIP-01](#open-nip-01-pagination-hardening). Deep dives cover [NIP-13 (proof-of-work)](#nip-deep-dive-nip-13-proof-of-work) and [NIP-40 (expiration timestamp)](#nip-deep-dive-nip-40-expiration-timestamp). + +--- + +## Lead stories + +### Marmot marks the spec adopted and MDK cuts v0.9.x + +The [Marmot protocol repository](https://github.com/marmot-protocol/marmot) merged [PR #170](https://github.com/marmot-protocol/marmot/pull/170) on July 3, changing 42 files from `Status: draft for internal review` (and `experimental draft`) to `Status: adopted`. The README title moved from framing the repo as a work in progress to "Marmot Protocol" as the adopted text, the MIP-era documents were re-framed as the deprecated version of the protocol, and the "Review Status" section ("This is not adopted spec text yet") became "Review Guidance" for editing the current spec. The `v2` label disappears throughout: MIP-contrast phrasing ("new in v2", "the v2 spec keeps") is replaced with "this spec" and "under this spec". Two documents keep their draft status by design: `implementation-model.md` remains non-normative, and the multi-device feature's own document stays a draft. + +The same repository landed [PR #171](https://github.com/marmot-protocol/marmot/pull/171) aligning admin-policy, membership, and role-change invariants. The cross-component check that a Remove cannot orphan an admin is now stated as a property of every resulting epoch, evaluated against the prior epoch's admin set when a commit does not carry an admin-policy update. Convergence's candidate-branch rule is tightened so "validates" means full commit validity including cross-component resulting-epoch checks, which prevents an invariant-violating commit from creating a candidate edge on any branch. State notifications derived from a superseded commit MUST be withdrawn when branch selection replaces it, which closes the "losing rename renders as a successful system message" bug at the spec level. A new "Realizing removal" section in `member-departure.md` defines the primary realization input (the accepted canonical commit removing your last leaf) and the fallback for clients that never applied the removing commit: authenticated post-eviction evidence now surfaces as a `SelfEvicted` outcome with retain-inactive semantics for the removed group copy. [PR #236](https://github.com/marmot-protocol/marmot/pull/236) then tightened wire-boundary validation, pinning KeyPackage lifetime acceptance to 84 days plus a one-hour skew margin, adding a Nostr tag-cardinality table for group `h`, gift-wrap `p`, welcome `e` and `relays`, and KeyPackage tags, and stating that unverified Nostr event ids and metadata are not trusted routing, replay, or telemetry evidence. + +Downstream, the [MDK workspace](https://github.com/marmot-protocol/mdk) cut [v0.9.0](https://github.com/marmot-protocol/mdk/releases/tag/v0.9.0) on July 6 with a full workspace version bump, followed by [v0.9.1](https://github.com/marmot-protocol/mdk/releases/tag/v0.9.1), [v0.9.2](https://github.com/marmot-protocol/mdk/releases/tag/v0.9.2), and [v0.9.3](https://github.com/marmot-protocol/mdk/releases/tag/v0.9.3) over the following two days. v0.9.0 rotates stale keyring entries when a new SQLite database is created and lands validate-before-mutate discipline across the storage layer. v0.9.1 routes every outbound connection through one host-safety dial chokepoint via [PR #732](https://github.com/marmot-protocol/mdk/pull/732), closing the class of bugs where different call sites reached the network with different validation. v0.9.3 exposes encrypted group avatars to the uniffi bindings through `download_group_image` and `image_hash_hex` via [PR #771](https://github.com/marmot-protocol/mdk/pull/771), adds external-signer support, and marks `wn-opencode` production-ready via [PR #781](https://github.com/marmot-protocol/mdk/pull/781). Alongside the MDK cuts, MarmotKit ships iOS and Android bindings at each version (a MarmotKit.xcframework plus Swift bindings for iOS and Kotlin bindings plus JNI libraries for Android, both generated from a pinned MDK commit hash), and a new wn-agent release channel provides shell installers that pin the WN Agent version to an immutable release tag so downstream apps can pull the current agent with a single `curl` command. + +### Mostro v0.18.0 and Mobile v1.3.0 ship Transport v2 on NIP-44 + +Mostro is the peer-to-peer Bitcoin trading protocol that runs order books, escrow, and dispute resolution over Nostr events, coordinated by a daemon (`mostrod`) that clients speak to over encrypted DMs. Until this week the wire protocol between clients and mostrod was Transport v1. [Mostro v0.18.0](https://github.com/MostroP2P/mostro/releases/tag/v0.18.0) lands Transport v2, wiring the protocol onto [NIP-44](/en/topics/nip-44/) direct messages with anti-spam gates and dual-receive support running server-side. [PR #776](https://github.com/MostroP2P/mostro/pull/776) is the Phase 1 wire change, [PR #780](https://github.com/MostroP2P/mostro/pull/780) adds the Phase 2 anti-spam gates for protocol v2, and [PR #785](https://github.com/MostroP2P/mostro/pull/785) makes the inner protocol version follow the active transport so a v2 client and a v1 client can coexist during the migration window. A related [PR #782](https://github.com/MostroP2P/mostro/pull/782) fixes a NIP-33 info tag by renaming `protocol_versions` to the singular `protocol_version`. Alongside the transport work, the release lands a Phase 4 unified live-quote path with cache-and-staleness enforcement ([PR #783](https://github.com/MostroP2P/mostro/pull/783)) and an El Toque fiat-cross provider covering the Cuban CUP and MLC pairs ([PR #778](https://github.com/MostroP2P/mostro/pull/778)). [PR #779](https://github.com/MostroP2P/mostro/pull/779) adds a slashed-party notification on dispute slash so a user who lost their bond hears from the daemon directly; the previous behavior surfaced only as a missing wallet balance. + +[Mostro Mobile v1.3.0](https://github.com/MostroP2P/mobile/releases/tag/v1.3.0) is the client half of the migration. [PR #613](https://github.com/MostroP2P/mobile/pull/613) migrates the app to Riverpod 3.x, Phase A ([PR #620](https://github.com/MostroP2P/mobile/pull/620)) adds dual-receive support for NIP-44 direct messages on the main isolate and in the background isolate so a v2 mostrod and a v1 client can talk during the migration, Phase B in [PR #624](https://github.com/MostroP2P/mobile/pull/624) adds dual-send, [PR #632](https://github.com/MostroP2P/mobile/pull/632) re-applies dual-send after the Riverpod 3.x cut, and Phase C in [PR #637](https://github.com/MostroP2P/mobile/pull/637) finalizes the migration. The release also adds African payment method coverage: [PR #625](https://github.com/MostroP2P/mobile/pull/625) adds Malawi Kwacha payment methods and [PR #627](https://github.com/MostroP2P/mobile/pull/627) adds KES (Kenyan Shilling), MZN (Mozambican Metical), TZS (Tanzanian Shilling), UGX (Ugandan Shilling), ZAR (South African Rand), and ZMW (Zambian Kwacha) methods while expanding NGN (Nigerian Naira). A restore flow now waits for node connectivity before issuing restore requests, and cause-aware handling distinguishes a dispute-driven bond slash from a timeout-driven one. + +### Bitchat 1.5.4 adds NIP-13 proof-of-work and an opt-in mesh-to-Nostr gateway + +[Bitchat 1.5.4](https://github.com/permissionlesstech/bitchat/releases/tag/v1.5.4) is the Bluetooth-mesh chat app that uses Nostr for its geohash channels and DM handoff. The release does two Nostr-shaped things worth reading. [PR #1382](https://github.com/permissionlesstech/bitchat/pull/1382) adds [NIP-13 (proof-of-work)](/en/topics/nip-13/) to outbound geohash channel messages (kind 20000 ephemeral events): each send mines a `["nonce", "", ""]` tag before publishing, targeting 8 leading zero bits, which averages 256 hash attempts and completes in under one millisecond on an M-series Mac. Inbound events with validated PoW relax the per-sender intake rate limit, so a spammer pays compute per message while a regular sender does not feel the cost. Scope is deliberately narrow: only kind 20000 channel messages mine PoW, and presence heartbeats (kind 20001), kind-1 location notes, and DMs are untouched. + +[PR #1384](https://github.com/permissionlesstech/bitchat/pull/1384) adds gateway mode, an opt-in mesh-to-Nostr uplink for geohash channels. When a mesh-only user (no internet, no reachable relay) sends in a geohash channel and another peer on the mesh advertises the `.gateway` capability, the signed kind 20000 event is wrapped in a new `MessageType.nostrCarrier = 0x28` TLV envelope and sent directed to one gateway. The gateway peer publishes the event to Nostr on the sender's behalf and rebroadcasts inbound channel traffic back onto the mesh with default TTL. Uplink deposits ride the courier envelope path (directed, relayed multi-hop); downlink rides broadcast. The signature happens before the event leaves the sender, so the gateway can decide whether to publish but cannot forge attribution. The stated motivation is disaster and protest scenarios where one connected phone in a crowd is enough to give the whole geohash channel a working Nostr uplink. [PR #1367](https://github.com/permissionlesstech/bitchat/pull/1367) fixes end-to-end favorites, cleaning up peer-list duplicates, Nostr sync, and `/fav` key corruption in the same release. + +--- + +## Tagged releases + +### Amber v6.2.3 scopes profile subscriptions and adds a Tor status notification + +[Amber v6.2.3](https://github.com/greenart7c3/Amber/releases/tag/v6.2.3) is a performance and correctness pass on the Android [NIP-46](/en/topics/nip-46/) signer, and the merged PRs in the week around it point at a coherent theme. The release itself adds a configurable profile fetch interval setting with never and always options ([PR #492](https://github.com/greenart7c3/Amber/pull/492)), shows a profile picture in the account switch bottom sheet, and scopes profile subscriptions by the current account so a signer holding multiple accounts stops fanning out subscriptions for accounts the user is not currently signing with. Bunker permission parsing gains explicit error handling on parse failures. Several StrictMode violations are fixed: a DiskReadViolation from Coil's `onSuccess` logging, a keystore violation from loading the account on the main thread, main-thread reads for the account name and picture in the account switch sheet, and eager `KeyPair()` construction on the login and signup screens now moved off the main thread. In the days after v6.2.3 shipped, [PR #493](https://github.com/greenart7c3/Amber/pull/493) reordered the boot path to fetch the user's [NIP-65](/en/topics/nip-65/) relay list before profile metadata (so the profile fetch queries the relays the user publishes to), and [PR #494](https://github.com/greenart7c3/Amber/pull/494) turned the built-in Tor notification into a live status indicator with a restart action, so a user whose Tor daemon dies during a signing session sees the failure and can bounce it without leaving the signer. [PR #495](https://github.com/greenart7c3/Amber/pull/495) enabled Android Lint in strict warnings-as-errors mode across the codebase. + +### Jumble v26.7.1 makes Blossom the default upload service in a DM-focused cut + +[Jumble v26.7.1](https://github.com/CodyTseng/jumble/releases/tag/v26.7.1) is a Nostr web client cut focused on direct messages and media. The release redesigns media upload settings and makes [Blossom](/en/topics/blossom/) the default upload service, replacing the previous NIP-96 default. DM handling gets a mobile message menu, improved desktop message actions, a "scroll to latest" button, long-press reactions on DM media, and a retry path for failed outgoing DMs from the message list. Custom emoji editing gains a detail view, message bubble sizing improves for invoices and embedded content, several DM scrolling and message ordering issues are fixed, and post-editor issues around emoji insertion, text copy, and file drag are cleaned up. Image orientation is corrected when metadata is stripped on upload, and Linux ARM64 downloads are added to the release matrix. + +### Applesauce signers 6.2.2 drops an nbunksec dependency + +[applesauce-signers@6.2.2](https://github.com/hzrd149/applesauce/releases/tag/applesauce-signers%406.2.2) drops the sub-package's `@sandwichfarm/encoded-entities` dependency in favor of a built-in [nbunksec](/en/topics/nip-46/) helper via [commit d654349](https://github.com/hzrd149/applesauce/commit/d654349). Applesauce's [NIP-46](/en/topics/nip-46/) bunker session encoding, added last week, no longer requires the external encoding library, cutting one supply-chain surface for downstream clients that consume the signers package. + +### Ngit v2.6.2 stops duplicate PR status events on default-branch push + +[Ngit v2.6.2](https://github.com/DanConwayDev/ngit-cli/releases/tag/v2.6.2) is a bug-fix release for the git-over-Nostr CLI. `git push` to the default branch stops publishing duplicate PR merge/applied status events for PRs that are already marked applied, because merge detection now reads the pre-push Nostr repo state (the source of truth for whether a PR was already resolved on the [NIP-34](/en/topics/nip-34/) side of the workflow); the previous heuristic relied on git internals and duplicated the status event. Active repositories using ngit for git-over-Nostr push flows stop emitting duplicate kind-1621 status events into their audience. + +### Bray v1.33.0 CLI picks up a bunker profile, persona, and Tor outbound + +[Bray v1.33.0](https://github.com/forgesworn/bray/releases/tag/v1.33.0) is a Nostr SDK-plus-CLI release. `bunker --profile ` gets an auto-stable connection key and relay fallback so a saved profile can survive a relay outage; `bunker --persona ` signs as a derived nsec-tree identity, letting one signer act as multiple pubkeys from a single derived tree; and all HTTP fetches can be routed through a Tor SOCKS proxy when configured. The release adds wallet subcommands for [NIP-47](/en/topics/nip-47/) NWC, [NIP-29](/en/topics/nip-29/) group admin write operations (create, update, add-user, remove-user, set-roles), NIP-86 admin verbs, and [NIP-65](/en/topics/nip-65/) outbox helpers. Publishing verbs pick up `--jsonl`, `--csv`, and `--tsv` output flags, a `req` verb for generic NIP-01 filter queries, an `event` verb for arbitrary event construction, a `publish-raw` command that signs and broadcasts pre-built events, a `bunker sign` one-shot NIP-46 signing command, and a per-command `--relay` flag on every publishing command. Security work covers three batches of audit deferrals: secret zeroisation discipline, HTTP transport bearer-auth and rate-limit hardening, and SSRF validation on relay URLs. The npm tarball ships at 533,844 bytes with a byte-identical reproducible build verified across two independent CI runners. + +### Deepmarks 1.0.0 hardens the Nostr bookmarking surface + +[Deepmarks 1.0.0](https://github.com/ostermayer/deepmarks-public/releases/tag/v1.0.0) is a security-hardening 1.0 milestone for a public Nostr bookmarking service. Every bookmark is still a signed Nostr event that any client can read. The API and archive worker sit in a privileged network position (they can reach internal Redis, the bunker's relay path, and cloud metadata), so the SSRF guard is load-bearing, and the release fixes a critical IPv6-literal bypass in `isPrivateIp`: bracketed IPv6 literals were being classified as public, so `[::1]`, `[fd00::1]`, and IPv4-mapped `[::ffff:10.0.0.4]` all reached internal targets over dual-stack connect. The guard now strips brackets and folds IPv4-mapped and IPv4-compatible IPv6 down to the embedded v4 before the private-range check on both boxes. Ingested `kind:0` profiles from external relays are now signature-verified at the sink so a hostile relay cannot forge a `nip05` or `lud16` for an arbitrary victim pubkey, and bookmark URLs are scheme-checked at every render sink so a `kind:39701` bookmark published straight to the relay with a `javascript:` or `data:` `d`-tag stops reaching an ``. Zap receipts now survive a transient bunker outage: the settlement handler atomically claims the pending zap, finalizes only after signing succeeds, and releases the claim on failure so a redelivered `invoice_updated` can retry. The `/publish` fan-out drain uses `BLMOVE` into a per-worker processing list with heartbeat-gated recovery so a crashed worker preserves a signed event the client was already 202'd for. + +### Bitcredit Core v0.5.13 unencrypts block metadata on the Nostr wire + +[Bitcredit Core v0.5.13](https://github.com/BitcreditProtocol/Bitcredit-Core/releases/tag/v0.5.13) removes an encryption layer from the Nostr public events used by the credit-bill protocol. Block metadata (block id, hash, signature) is now unencrypted on the Nostr wire; only the block data itself remains encrypted with the corresponding bill key. New apps process old chains, old apps do not process new chains. The release also adds a bill-service function to fetch the bill chain, and switches publishing to an optimistic threshold model: once a configured relay threshold (default one) accepts a publish, remaining relays receive the event asynchronously so publishing is no longer blocked by the slowest relay. + +### Coop Mobile v0.2.3 and v0.2.4 + +[Coop Mobile](https://git.reya.su/reya/coop-mobile) shipped [v0.2.3](https://git.reya.su/reya/coop-mobile/releases/tag/v0.2.3) on July 4 and [v0.2.4](https://git.reya.su/reya/coop-mobile/releases/tag/v0.2.4) on July 7, continuing the Android [NIP-17](/en/topics/nip-17/) direct-messaging client's steady release cadence. v0.2.3 adds inline image and link rendering in chat messages, image attachments, speech-to-text input, and a confirmation dialog for contact removal. v0.2.4 fixes an indicator that got stuck forever, improves the Nostr Connect handshake, and adds `ncryptsec1` import (the [NIP-49](/en/topics/nip-49/) encrypted-private-key format) alongside a redesigned import identity screen. + +### Granary v11.0 adds NIP-71 video event support + +[Granary v11.0](https://github.com/snarfed/granary/releases/tag/v11.0) is the multi-protocol conversion library that powers Bridgy Fed's cross-network bridging. The Nostr module gets three visible changes. [NIP-71](/en/topics/nip-71/) video events (kinds 21, 22, 34235, and 34236) now convert into ActivityStreams 1 notes with video attachments, and the converter extracts the `imeta` image (thumbnail), the video duration, the top-level `published_at` tag, and the `alt` tag as a fallback `displayName` on the first video or audio attachment. On the API side, `sign` is renamed to `hash_and_sign` and `verify` now raises `ValueError` on failure; the `Nostr` constructor raises `ValueError` on an invalid relay URL, and `Nostr.query` skips the [NIP-42](/en/topics/nip-42/) AUTH challenge gracefully when the caller has not set a `privkey`. A follow-up conversion fix stops crashes when a Nostr `article` object arrives without an `id`. Any bridge or reader consuming NIP-71 video events through Granary can now surface them in the format the target reader expects. + +### Nostr-relay v0.0.244 adds a Firestore backend + +[mattn/nostr-relay v0.0.244](https://github.com/mattn/nostr-relay/releases/tag/v0.0.244) adds a Firestore backend via [PR #12](https://github.com/mattn/nostr-relay/pull/12), extending the Go relay's storage layer with a Google Cloud Firestore option alongside its existing backends. The change is small but opens Firestore as a managed serverless database option for a relay operator. + +### Nostrord v2.0.0 and v2.1.0 fold the relay pool and heal zombie WebSockets + +[Nostrord v2.0.0](https://github.com/nostrord/nostrord/releases/tag/v2.0.0) is a major cut of the KMP/WASM Nostr client that speaks NIP-29, NIP-42, NIP-44, NIP-46, NIP-57, NIP-65, and NIP-98. [v2.0.1](https://github.com/nostrord/nostrord/releases/tag/v2.0.1) shipped one day later via [PR #166](https://github.com/nostrord/nostrord/pull/166) with a release-blocking desktop fix: the packaged 2.0.0 (deb, rpm, msi, dmg) crashed at startup with `NoClassDefFoundError: java/sql/DriverManager` because the jpackage jlink image was missing the `java.sql` module the SQLDelight sqlite driver depends on; the fix adds `java.sql` to the runtime image, and the same PR routes optimistic send through the network layer so the message reaches the relay (the previous code path cached silently and never delivered), plus keyboard and scroll behavior on mobile web. + +[v2.1.0](https://github.com/nostrord/nostrord/releases/tag/v2.1.0) followed on July 7 with the "relay pool fold" ([PR #176](https://github.com/nostrord/nostrord/pull/176)), which unifies the previously separate NIP-29 focused relay socket into the shared pool. One reconnect scheduler now covers all relays, [NIP-42](/en/topics/nip-42/) AUTH signing is bounded with retry, publishes fail closed and retry on auth-required, request-storm races in `requestPrivateGroupData` and `fetchGroupPreviews` are closed, kind-10009 user-group-list fetches batch per relay, and the `mux_chat` live subscription now covers every joined group (not just the opened one) and self-heals when a relay silently drops the subscription. UI-side changes replace the layout-shifting "Sending..." row with an inline clock-then-check icon and turn stalled scroll-back into an explicit Retry row. [PR #179](https://github.com/nostrord/nostrord/pull/179) landed the same day to detect zombie WebSockets on Android: mobile networks and Doze mode kill TCP without a close frame, so writes into the dead socket buffer locally without throwing and `isConnected()` stays true even though nothing will ever be received. `NostrGroupClient` now stamps `lastInboundAtMs` on every frame, gains `markDead()` (which cancels the frame loop so the normal reconnect and resubscribe path runs), and `probeLiveness()` (a REQ any relay must answer within 5 seconds), triggered on OK timeout with zero inbound frames or on mux stale plus socket frame silence. A second bug fix in the same PR stops optimistic messages being written to the persistent cache at insert time; they now write only after delivery confirmation. + +--- + +## Unreleased changes + +### rust-nostr adds NIP-40 expiration to gift wrap and private DM builders + +[rust-nostr merged PR #1384](https://github.com/rust-nostr/nostr/pull/1384) adding an `expiration` option to `GiftWrapBuilder` and `PrivateDirectMessageBuilder`. The library takes a `Duration` from the caller: the [NIP-40](/en/topics/nip-40/) expiration tag is anchored to the gift wrap's randomized `created_at` (created_at + duration), which decouples it from the real send time. Letting a caller pass an absolute timestamp would leak the send time to a relay observer (subtract the duration and you recover the original send time), so the library builds the tag internally from the randomized wrap timestamp. The expiration tag goes on the gift wrap event, not on the kind:13 seal (which [NIP-59](/en/topics/nip-59/) requires to have empty tags). NIP-17 hands the same value down to the gift wrap builder from `PrivateDirectMessageBuilder`. The change closes [issue #1381](https://github.com/rust-nostr/nostr/issues/1381) and lands via the same builder pattern rust-nostr uses for `extra_tags`. rust-nostr also merged [PR #1387](https://github.com/rust-nostr/nostr/pull/1387) consolidating `nostr-relay-builder` into `nostr-sdk`, a workspace-flattening move. + +### Amethyst spends the week hardening negentropy sync and adding NIP-50 search + +Amethyst's [main branch](https://github.com/vitorpamplona/amethyst) merged 43 PRs across three coherent themes. The largest thread is negentropy sync on the geode-to-strfry boundary: a refused-window failure mode that used to storm the client into a window-split loop now backs off cleanly ([PR #3480](https://github.com/vitorpamplona/amethyst/pull/3480)), the underlying `negentropyKmp` dependency moves to v1.1.1 ([PR #3475](https://github.com/vitorpamplona/amethyst/pull/3475)), a 1-million-event geode-to-strfry benchmark lands with a strfry-parity mirror ([PR #3478](https://github.com/vitorpamplona/amethyst/pull/3478)), and production benchmarks join the CI matrix alongside broader sync optimizations ([PR #3458](https://github.com/vitorpamplona/amethyst/pull/3458), [PR #3466](https://github.com/vitorpamplona/amethyst/pull/3466)). Lock-free concurrent collections replace the previous mutex-per-relay pattern and a UDP socket threading fix rides along ([PR #3459](https://github.com/vitorpamplona/amethyst/pull/3459)). + +The second thread is [NIP-50](/en/topics/nip-50/) full-text search infrastructure. A `SearchableEvent` interface lands so events can carry index metadata directly ([PR #3452](https://github.com/vitorpamplona/amethyst/pull/3452)), and NIP-50 search extensions are now stripped before querying SQLite FTS so the local search engine no longer chokes on server-side extension syntax ([PR #3464](https://github.com/vitorpamplona/amethyst/pull/3464)). Default search relays get centralized ([PR #3446](https://github.com/vitorpamplona/amethyst/pull/3446)). + +The third thread is protocol integrations for niche verticals. Support for Birdstar bird-detection events (kind 2473) reaches an Android client ([PR #3473](https://github.com/vitorpamplona/amethyst/pull/3473)), and PS1 memory-card save states can be published as signed events on kind 38192 ([PR #3482](https://github.com/vitorpamplona/amethyst/pull/3482)). Rounding out the week: a compose-signature setting auto-appends custom text to posts ([PR #3450](https://github.com/vitorpamplona/amethyst/pull/3450)), the desktop notifications view is redesigned with native OS toasts and a shared filter ([PR #3457](https://github.com/vitorpamplona/amethyst/pull/3457)), the Messages column picks up a privacy lock ([PR #3432](https://github.com/vitorpamplona/amethyst/pull/3432)), `NostrServer.ingest` adds a local write path with per-submission verify skip ([PR #3469](https://github.com/vitorpamplona/amethyst/pull/3469)), and `equals`/`hashCode` contracts are repaired in the OpenTimestamps verify path ([PR #3477](https://github.com/vitorpamplona/amethyst/pull/3477)). + +### Buzz keeps hardening the relay and defines kind 44200 for agent turn metrics + +[Buzz](https://github.com/block/buzz) (the project formerly named Sprout) landed 123 PRs merged in the July 1 through July 7 window. Two threads carry most of the weight. The first is a new event kind for agent telemetry: [PR #1441](https://github.com/block/buzz/pull/1441) defines NIP-AM durable encrypted agent turn metrics as kind 44200, which lands the telemetry as a signed event the user's own relay archives, keeping metrics on user-owned infrastructure. A local archive for the kind follows ([PR #1555](https://github.com/block/buzz/pull/1555)), the remove-kind path is made atomic ([PR #1562](https://github.com/block/buzz/pull/1562)), and the model name is threaded through the emit path so downstream readers can distinguish which model produced which turn ([PR #1564](https://github.com/block/buzz/pull/1564)). + +The second thread is relay performance. Post-commit dispatch is deferred and a verify clone is avoided ([PR #1453](https://github.com/block/buzz/pull/1453)), ingest and fan-out DB round trips are batched with measured p99 ack drops of 7 to 16 percent and p999 tail drops of 29 to 53 percent versus the prior tip ([PR #1454](https://github.com/block/buzz/pull/1454)), multi-filter query execution runs with bounded concurrency ([PR #1457](https://github.com/block/buzz/pull/1457)), and outbound WebSocket data frames batch on send ([PR #1464](https://github.com/block/buzz/pull/1464)). Alongside the perf work, a per-community workspace icon set that admins configure and the relay serves via [NIP-11](/en/topics/nip-11/) extends NIP-11's information document with a per-community customization surface ([PR #1463](https://github.com/block/buzz/pull/1463)), agent owners can delete their agent's messages via relay kind:5 events plus matching desktop and mobile UX ([PR #1519](https://github.com/block/buzz/pull/1519)), OpenTelemetry tracing joins Prometheus metrics on the relay ([PR #1398](https://github.com/block/buzz/pull/1398)), and the git repo-name registry moves to Postgres ([PR #1432](https://github.com/block/buzz/pull/1432)). + +### Divine Video wires up relay signature verification and a NostrConnect extraction + +Divine Video's [mobile app](https://github.com/divinevideo/divine-mobile) merged 97 PRs in the window, and the Nostr-facing thread is trust boundary hardening plus authentication cleanup. [PR #5774](https://github.com/divinevideo/divine-mobile/pull/5774) verifies inbound relay event signatures, closing a class of trust-in-the-relay bugs; [PR #5828](https://github.com/divinevideo/divine-mobile/pull/5828) encrypts the FCM push token in the kind-3080 deregistration event so the user's device token stops appearing in cleartext on the relay when they unsubscribe; and [PR #5831](https://github.com/divinevideo/divine-mobile/pull/5831) chunks the kind:5 deletion REQ so a user with a large delete history no longer overflows the relay frame. On the authentication side, [PR #5826](https://github.com/divinevideo/divine-mobile/pull/5826) extracts a `NostrConnectCoordinator` for the `nostrconnect://` flow, cleaning up the [NIP-46](/en/topics/nip-46/) client-initiated bunker code path ahead of a broader auth refactor tracked under [issue #4741](https://github.com/divinevideo/divine-mobile/issues/4741). [PR #5709](https://github.com/divinevideo/divine-mobile/pull/5709) maps kind-16 reposts when `notification_type` is absent so a repost notification renders correctly even when the sending client omits the hint. + +### Zap Cooking fixes NIP-46 bunker login and adds NIP-50 recipe search + +[Zap Cooking's frontend](https://github.com/zapcooking/frontend) merged 18 PRs in the window along one theme: making Nostr auth surfaces recover from failure. [PR #503](https://github.com/zapcooking/frontend/pull/503) fixes bunker login with an explicit connect handshake, authUrl handling, and error surfacing so a user attaching an external signer sees a real error message on failure where the previous cut hung the login screen. [PR #495](https://github.com/zapcooking/frontend/pull/495) adds NIP-98 auth to the extract-recipe endpoint's image and text upload paths so uploads are pubkey-attributed. A separate feature thread lands NIP-50 full-text recipe search via the nostrarchives search relay backend ([PR #483](https://github.com/zapcooking/frontend/pull/483)), letting a user query recipes across the relay corpus without a client-side index. Content-rendering polish ships alongside: quoted-note content and media now surface directly in the parent note replacing the previous buried-link fallback ([PR #491](https://github.com/zapcooking/frontend/pull/491)), link previews and hashtag sizing land ([PR #492](https://github.com/zapcooking/frontend/pull/492)), multi-word search queries work ([PR #482](https://github.com/zapcooking/frontend/pull/482)), and server-side social preview cards are generated for note, reads, and profile links ([PR #494](https://github.com/zapcooking/frontend/pull/494)). + +### swift-nostr-client v0.6.0 progresses toward a first stable cut + +[yysskk/swift-nostr-client](https://github.com/yysskk/swift-nostr-client) shipped [v0.6.0](https://github.com/yysskk/swift-nostr-client/releases/tag/0.6.0) alongside 30 merged PRs. The Swift Nostr library moves closer to a first stable API surface for Swift Nostr clients that avoid linking the MDK or MarmotKit toolchains. + +### Napplet realigns NIP-5D sandbox surface and drops NAP-RESOURCE + +[Napplet](https://github.com/napplet) shipped 26 more releases across `@napplet/core`, `@napplet/nap`, `@napplet/sdk`, `@napplet/shim`, `@napplet/skills`, and `@napplet/vite-plugin`, plus 9 PRs to the `naps` spec repo. The most substantive spec move is [PR #79](https://github.com/napplet/naps/pull/79) reverting the NAP-RESOURCE merge (sandboxed resource fetching over `https`, `blossom`, `htree`, `nostr`, and `data` schemes was withdrawn from the spec pending more review), and PRs #81 through #86 replace schema pseudocode with tables across NAP-IDENTITY, NAP-INC, NAP-SHELL, NAP-THEME, and NAP-INTENT. On the runtime side, `kehto/web` (the napplet browser and shell) merged 15 PRs on [NIP-5D](/en/topics/nip-5d/) sandbox capability language, CSP insertion, and the injected NAP-KEYS prelude, narrowing the sandbox surface for napplet execution. + +### primal-android extends the remote-signer surface + +[Primal Android](https://github.com/PrimalHQ/primal-android-app) merged 18 PRs in the window. On the Nostr side, [PR #1075](https://github.com/PrimalHQ/primal-android-app/pull/1075) implements `switch_relays` and `logout` methods for the app's remote-signer role, extending Primal's NIP-46 signer surface. [PR #1083](https://github.com/PrimalHQ/primal-android-app/pull/1083) adds a splash-gated local app-migration framework, and [PR #1080](https://github.com/PrimalHQ/primal-android-app/pull/1080) implements note-feed prefetching in the splash view-model. The rest is UI polish across the Home top and bottom bar, Explore hints, and the profile screen. + +### Wisp adds a multi-account switcher and Blossom parser tests + +[Wisp](https://github.com/barrydeen/wisp) merged 9 PRs. [PR #604](https://github.com/barrydeen/wisp/pull/604) adds a multi-account switcher with an explicit cancel path on the add-account flow. [PR #613](https://github.com/barrydeen/wisp/pull/613) adds unit tests for `Blossom.parseServerList`, tightening the [Blossom](/en/topics/blossom/) server-list parser. [PR #574](https://github.com/barrydeen/wisp/pull/574) rewrites the zap sheet for iOS layout with an instant-zap settings surface, [PR #605](https://github.com/barrydeen/wisp/pull/605) turns transaction history into a swipe-up bottom sheet, [PR #611](https://github.com/barrydeen/wisp/pull/611) parses hashtags with non-ASCII Unicode letters, [PR #609](https://github.com/barrydeen/wisp/pull/609) keeps the profile notes feed paginating and renders inline gallery media, and [PR #603](https://github.com/barrydeen/wisp/pull/603) preserves blank lines before inline profile and hashtag segments. + +### TAO and Wired raise the PoW signal to 21 bits and surface fresh-PoW roots + +[smolgrrr/TAO](https://github.com/smolgrrr/TAO) and [smolgrrr/Wired](https://github.com/smolgrrr/Wired) (the same commit set landed in both repos) merged 13 PRs. [PR #84](https://github.com/smolgrrr/TAO/pull/84) raises the default post-signal proof-of-work target to 21 leading zero bits, and [PR #80](https://github.com/smolgrrr/TAO/pull/80) surfaces feed roots from fresh PoW activity so a client can rank the timeline by recent NIP-13 work; the previous ranking was raw event age. [PR #75](https://github.com/smolgrrr/TAO/pull/75) restores a custom emoji picker and [PR #65](https://github.com/smolgrrr/TAO/pull/65) adds first-frame video previews. This is the second Nostr client this week to lean on NIP-13 as a first-class filter for user-generated content, complementing Bitchat's channel-scoped PoW. + +### keep-android polishes NIP-46 UX + +[privkeyio/keep-android](https://github.com/privkeyio/keep-android) shipped [v1.1.5](https://github.com/privkeyio/keep-android/releases/tag/v1.1.5) alongside 13 merged PRs. Keep is a mobile identity vault (covered in [Issue #29](/en/newsletters/2026-07-01-newsletter/#custid-launches-as-a-mobile-identity-vault-with-nip-46-and-nfc-challenge-flow) as CustID). The delta since last week is UX polish on the NIP-46 challenge flow. + +### Heartwood ships the relay-to-serial signing bridge + +[forgesworn/heartwood v0.7.0](https://github.com/forgesworn/heartwood/releases/tag/v0.7.0) lands the relay-to-serial signing bridge that was in flight last week, wiring the HSM-mode data plane for Bray's serial-signer path. [PR #11](https://github.com/forgesworn/heartwood/pull/11) is the bridge itself, [PR #13](https://github.com/forgesworn/heartwood/pull/13) adds serial-frame coverage and fixes the device `read_frame` payload offset, and [PR #14](https://github.com/forgesworn/heartwood/pull/14) extracts the serial frame codec into a shared `heartwood-frame` crate. + +### SafeBox publishes a Phase 3 progress report and a FreeBSD jail runbook + +[SafeBox](https://github.com/trbouma/safebox) is a private portable data vault on Nostr that combines [NIP-47](/en/topics/nip-47/) Nostr Wallet Connect, nAuth, nembed, and relay-mediated record transfer over QR and NFC into one operator-deployable service. A [July 2026 progress report](https://github.com/trbouma/safebox/blob/main/docs/PROGRESS-REPORT-2026-07.md) published on July 6 marks Phase 3 as substantially complete: 49 commits landed since the April report, bringing the repository to 1,136 commits, and the four Phase 3 engineering commitments (harden Phase 2 experiments, support interoperable instances, prepare for scale, add commercial-product discipline) are largely delivered. The report frames the next step as a bounded pilot, and discloses that a telecommunications provider under NDA is exploring a health-records pilot on SafeBox. + +The concrete Nostr-facing work landed earlier in Phase 3 and is summarized in the report: mutating NWC actions are now queued to avoid proof races, failed Lightning melts protect proofs before returning, long-lived NWC listeners now refresh proactively so a session survives past its idle threshold; the previous behavior was a silent stall, and LNURL callbacks use canonical origins with explicit JSON and CORS responses. QR and NFC record exchange gained a unified flow spec covering recipient-presented, sender-presented, and cross-device presentation modes with clearer KEM (Key Encapsulation Mechanism) handling and replay protection through the Open Quantum Safe library. The in-window commit is [`6866dae`](https://github.com/trbouma/safebox/commit/6866dae), which adds a [FreeBSD jail deployment and liboqs build runbook](https://github.com/trbouma/safebox/blob/main/docs/devops/freebsd-jail-from-scratch.md) alongside a [FreeBSD appliance specification](https://github.com/trbouma/safebox/blob/main/docs/devops/SAFEBOX-FREEBSD-APPLIANCE-SPEC.md), documenting ZFS snapshots, jail isolation, `rc.d` service management, host-level reverse proxy configuration, and rollback procedure for a SafeBox deployment on FreeBSD/ARM hardware. + +The report also announces [OpenETR](https://github.com/trbouma/openetr) as a distinct spin-off applying SafeBox's cryptographic-control-plus-portable-records architecture to electronic transferable records: bills of lading, warehouse receipts, promissory notes, and certificates. OpenETR's repo saw 7 commits on July 7 including [`ea612a9`](https://github.com/trbouma/openetr/commit/ea612a9) separating attestation from the core record, [`ca153a3`](https://github.com/trbouma/openetr/commit/ca153a3) on mandate-versus-effect handling, and [`ba84b61`](https://github.com/trbouma/openetr/commit/ba84b61) adding a comparison to verifiable-credential formats. + +--- + +## Protocol work and NIP updates + +### Merged: NIP-51 and NIP-37 align the kind 10013 name + +[PR #2404](https://github.com/nostr-protocol/nips/pull/2404) is a prose-only consistency fix. In [NIP-37](/en/topics/nip-37/), kind 10013 is named `Relay List for Private Content`; in [NIP-51](/en/topics/nip-51/) under `Draft relays`, the same kind was described with different wording. NIP-51 now uses the NIP-37 name for the same event kind. No wire behavior changes and no new tag semantics; the value is that NIP-51 is the umbrella spec for list-shaped events and NIP-37 is the private-content follow-up, and misaligned naming between the two makes it easy to miss that they describe the same kind. + +### Open: NIP-AD Nostr Web Addresses via .well-known lookup + +[PR #2406](https://github.com/nostr-protocol/nips/pull/2406) opens as the successor to a closed PR #2393 with a full spec draft at [`AD.md`](https://github.com/nostr-protocol/nips/blob/2f4b09335c54a993d483bc220195e3f4a33df1ec/AD.md). NIP-AD defines web URLs that carry an optional Nostr counterpart. A client that sees a URL like `https://golf.com/players` requests `https://golf.com/.well-known/nostr.json?ad=/players`, which returns a JSON object mapping paths to `{filter, relays}` pairs. The returned filter is a standard NIP-01 filter (kinds, authors, `#d`, `limit`, etc.), and the relays array names which relays the client should query. With `"limit": 1` the URL resolves to a single event; without it, to a list. In a normal web browser the URL renders HTML like any other URL, so the same domain can serve web users and Nostr clients from one canonical path. The stated use cases include [NIP-29](/en/topics/nip-29/) group names resolving to a kind 39000 event on a specific relay (removing the need for group id farming), [NIP-5A](/en/topics/nip-5a/) nsite lookups, hosted feeds that publish a `{"ids": [...]}` filter, native rendering of pasted `njump.me/nevent1...` and client-specific event URLs, and Nostr-fueled blogs that exist both natively inside Nostr and to visitors outside. The `.well-known/nostr.json` reuse plus path-as-object-key layout is chosen so the resolver can be a static file. + +### Open: NIP-86 claim management for invite codes + +[PR #2408](https://github.com/nostr-protocol/nips/pull/2408) proposes adding three methods to [NIP-86](/en/topics/nip-86/): `listclaims` (params `[]`, returns an array of [NIP-43](/en/topics/nip-43/) invite codes), `createclaim` (params `[claim]`, returns `true`), and `deleteclaim` (params `[claim]`, returns `true`). Today NIP-86 lets a relay admin manage users and role assignments but has no invite-code surface. The PR author's use case is community-relay onboarding: an admin creates an invite code associated with a role, collects payment before the user's identity is created, hands the invite code to the user, and a bot listens for the resulting kind 28935 claim event on the relay and auto-assigns the role. The three methods let that flow run entirely through the relay management RPC. + +### Open: role color as (h, s, l) tuple + +[PR #2402](https://github.com/nostr-protocol/nips/pull/2402) changes the role color format in [NIP-43](/en/topics/nip-43/) from a single `hue` value (0 to 360) to a tuple of `hue` (0 to 360), `saturation` (0 to 1), and `lightness` (0 to 1). Empty strings are permitted for any component so clients can supply their own defaults for a coherent palette, and the spec text recommends providing only `hue` unless a specific color like silver is desired. The change threads through NIP-86 in the same PR: `createrole` and `editrole` now take `[id, label, description, [h, s, l], order]`; the previous signature carried a single-color parameter in the same slot. The motivation is that hue alone forces clients to pick saturation and lightness for the operator, so different clients render the same role at visibly different intensities. + +### Open: NIP-01 pagination hardening + +[PR #2407](https://github.com/nostr-protocol/nips/pull/2407) adds a "Pagination & limits" subsection to NIP-01. The concrete rules: a relay that imposes a maximum `limit` MUST set it greater than the largest number of events sharing a single `created_at` in its database, so no single second can fill a page and stall pagination. Clients paging backwards MUST repeat requests with `until = oldest` (inclusive) and MUST deduplicate by `id` (since the oldest second is re-fetched each round), and paging is complete when a round yields no new events after deduplication. If a full page has oldest and newest events sharing one `created_at`, the client MUST retry that second with a larger `limit`, and if the relay clamps the larger `limit` and still returns a page confined to one second, the client MUST either advance with `until = oldest - 1` (treating unretrieved events as dropped) or abort. Normal paging MUST NOT set `limit`; the relay maximum is authoritative, and a smaller value reintroduces the stall. Raising `limit` to drain a stuck second is the one exception. This fix matters because a naive `since`/`until` cursor either misses events with duplicate timestamps or reprocesses them, and the current NIP-01 text does not tell either side how to escape the trap. + +--- + +## NIP Deep Dive: NIP-13 (Proof of Work) + +[NIP-13](/en/topics/nip-13/) defines a proof-of-work mechanism for Nostr events. It exists because email-style spam is trivial to produce on a public relay network: anyone can generate a keypair and flood a topic, and there is no economic cost per event. NIP-13 lets an event author impose a computational cost per event that a spammer would have to pay in aggregate but a regular sender pays only once per message. Relays and clients can then require or prefer events that meet a difficulty threshold. + +### The mechanism + +An event author picks a difficulty target expressed in bits and mines the event's id (the sha256 hash of the serialized event) until it has at least that many leading zero bits. Because the event id includes the `created_at` timestamp, the tags, and the content, mining requires changing something in the event body to search the hash space. NIP-13 defines a `nonce` tag for exactly this purpose: + +``` +["nonce", "", ""] +``` + +The `nonce_value` is any string the miner picks; the `target_bits` is the difficulty the miner committed to. A verifier counts the leading zero bits of the event id and compares against `target_bits`. The `target_bits` in the tag is a claim, and a verifier measures the actual leading-zero count of the id to confirm it. + +The number of leading zero bits in a random sha256 output follows a geometric distribution: each additional bit doubles the expected work. 8 bits averages 256 hash attempts, 20 bits averages roughly one million, and 28 bits averages roughly 268 million. Bitchat's 8-bit target for geohash-channel messages costs under one millisecond of CPU on modern hardware and completes below any perceptible latency. TAO and Wired's 21-bit default is roughly two million hash attempts per post, which is fast on a laptop but expensive at scale for a bot farm. NIP-13 does not mandate a difficulty; each relay and client picks its own. + +### Example event + +A minimal NIP-13-mined kind-1 note looks like: + +```json +{ + "id": "000000000e9d97a1ab09fc381030b346cdd7a1a8a6f27c9c88f68c8b9d0f6c8a", + "pubkey": "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", + "created_at": 1720368000, + "kind": 1, + "tags": [ + ["nonce", "72847", "28"] + ], + "content": "hello, this cost me 28 bits of PoW", + "sig": "b1a5c9c74cff59f8a48e5c3b3d8e1c8e7e2c1d4a8e2b9f7d1c3e8b4f6a2c8d1e9f4b3c7a1d8e5b2f9c6a3d7e1b8f4c9a2d6e3b7f1c8a4d9e2b5f8c1a7d4e6b9f3c2" +} +``` + +The `id` starts with seven hex zeros (28 leading zero bits, matching the `target_bits` in the nonce tag). The miner varied the `nonce_value` `72847` until the id met the target. A verifier hashes the serialized event and confirms the id has at least 28 leading zero bits, then verifies the signature. NIP-13 adds no new fields; it adds the `nonce` tag and constrains the id's zero-bit count. + +### Where it is used + +Bitchat's 1.5.4 release uses 8-bit PoW on kind 20000 geohash-channel messages: outbound sends mine the tag before publishing and inbound events with validated PoW relax the per-sender intake rate limit. TAO and Wired use 21-bit PoW as the default post-signal threshold and surface feed roots from fresh PoW activity, treating PoW as a timeline ranking signal. [cagliostr](https://github.com/mattn/algia) enforces NIP-13 at the relay layer, rejecting events below a threshold. NoStrudel exposes a client-side PoW mining setting for authors who want to signal to filtering clients. Damus and Amethyst compute leading-zero bits when displaying events, letting a user see the PoW commitment on notes. Coracle exposes PoW both for mining and filtering. NDK and nostr-tools expose PoW mining helpers to library consumers. + +The design property that shapes NIP-13's deployment is that PoW is unforgeable: a claim of `target_bits` counts as evidence only when the id has that many leading zeros, and a counterfeit requires redoing the work. That property lets Bitchat use inbound PoW as a rate-limit relaxer even when a spammer claims a high difficulty; the check is a hash count, not a trust decision. The complementary property is that PoW does not commit the miner to any specific pubkey or content; a spammer can still choose to mine at 8 bits and burn compute, but the compute is a real cost. NIP-13 shifts the spam problem from "impossible" to "quantifiable" and lets clients set their own price. + +--- + +## NIP Deep Dive: NIP-40 (Expiration Timestamp) + +[NIP-40](/en/topics/nip-40/) defines an `expiration` tag that instructs a relay and a client that an event should be considered expired after a given Unix timestamp. It exists because Nostr events are otherwise permanent: once a signed event lands on a relay, the only way to remove it is a NIP-09 delete event, and even then a relay may retain the original. NIP-40 lets an author declare at publication time that an event is short-lived, and asks relays to stop serving it and clients to stop displaying it after the timestamp. + +### The mechanism + +An author adds an `expiration` tag to an event: + +``` +["expiration", ""] +``` + +The timestamp is Unix seconds. A relay MAY reject events whose expiration is already in the past at ingest, MAY stop serving events whose expiration has passed, and SHOULD respect the author's stated expiration. A client SHOULD hide expired events from the user. NIP-40 does not require the relay to delete the event, and it does not overrule NIP-70 protected-event semantics; it is a hint plus a soft contract. + +The tag lives on the event itself (or in the case of wrapped messaging, on the outer wrap). NIP-40 does not define delete semantics; the event remains a signed event that anyone who has it can still read. What NIP-40 gives is a coordinated expectation that the relay and the client will stop surfacing the event after the deadline. This makes NIP-40 useful for ephemeral posts, timed announcements, live-event notes that should stop being served after the event, and NIP-17 direct messages that should not linger past a stated horizon. + +### Interaction with gift wrap + +The rust-nostr PR that landed this week ([PR #1384](https://github.com/rust-nostr/nostr/pull/1384)) is a case study in how NIP-40 interacts with [NIP-59](/en/topics/nip-59/) gift wrap. NIP-59 defines a two-layer envelope: a kind:13 "seal" event signed by the sender's real key, and a kind:1059 "gift wrap" event signed by an ephemeral key. Both layers have randomized `created_at` values, up to 48 hours before the real send time, so a relay observer cannot recover the true send timestamp. NIP-59 mandates that the seal have empty tags. + +That mandate is why the expiration tag has to go on the gift wrap and stay off the seal, and why anchoring the tag to the real send time would defeat gift wrap's timing privacy: if a caller passes an absolute expiration timestamp, an observer subtracts the caller's intended TTL and recovers the real send time. rust-nostr's design decision is to expose the API as a `Duration` from the caller, then compute `expiration = wrap.created_at + duration` inside the library. The wrap's `created_at` is already randomized inside the library, so the expiration timestamp inherits the same randomization and does not leak the true send time. + +### Example event + +A minimal NIP-40 example on a kind-1 note: + +```json +{ + "id": "1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b", + "pubkey": "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2", + "created_at": 1720368000, + "kind": 1, + "tags": [ + ["expiration", "1720454400"] + ], + "content": "this note expires in 24 hours", + "sig": "d2e5b8a1c4f7b0d3e6a9c2f5b8d1e4a7c0f3b6d9e2a5c8f1b4d7e0a3c6f9b2d5e8a1c4f7b0d3e6a9c2f5b8d1e4a7c0f3b6d9e2a5c8f1b4d7e0a3c6f9b2d5e8a1" +} +``` + +`created_at` is the Unix timestamp of publication; the expiration tag says the event should stop being served 86,400 seconds (24 hours) later. A relay that respects NIP-40 stops returning this event to REQs after `1720454400`, and a client that respects NIP-40 hides it from the user after that time. + +### Where it is used + +rust-nostr's builders (`GiftWrapBuilder`, `PrivateDirectMessageBuilder`) now expose expiration as a first-class `Duration` parameter. NDK exposes an expiration helper for kind-1 and DM builders. nostr-tools has a `getExpiration` and `isExpired` pair for reading and enforcing the tag. strfry, nostr-rs-relay, khatru, and other relay implementations respect NIP-40 in REQ handling (rejecting or omitting expired events depending on the operator's policy). Damus, Amethyst, noStrudel, Coracle, and Primal all filter expired events from their timeline rendering. Live-activity clients like zap.stream use NIP-40 on the associated kind-1311 chat events so a live chat stops persisting after the stream ends. + +The design property that lands NIP-40 cleanly in most implementations is that it is opt-in per event and does not require coordinated deployment. An author can add the tag today; a relay that honors it gets a cleaner working set; a relay that ignores it does no worse than before; and a client that hides expired events gives the author what they asked for. The rust-nostr change this week reinforces that the tag's placement matters as much as its presence: in a privacy-preserving envelope like NIP-59 gift wrap, the tag sits on the layer whose timestamp is already randomized, and the API surface prevents a caller from accidentally leaking a real timestamp back into the wrap. + +--- + +That's it for this week. Building something or have news to share? Reach out via NIP-17 DM or find us on Nostr. diff --git a/content/en/topics/nip-13.md b/content/en/topics/nip-13.md index 9e203e8..c0d5572 100644 --- a/content/en/topics/nip-13.md +++ b/content/en/topics/nip-13.md @@ -58,6 +58,7 @@ PoW also shifts spam resistance from account identity to compute availability. T **Mentioned in:** - [Newsletter #7: News](/en/newsletters/2026-01-28-newsletter/#news) - [Newsletter #12: News](/en/newsletters/2026-03-04-newsletter/#news) +- [Newsletter #30: NIP Deep Dive](/en/newsletters/2026-07-08-newsletter/#nip-deep-dive-nip-13-proof-of-work) **See also:** - [NIP-01: Basic Protocol](/en/topics/nip-01/) diff --git a/content/en/topics/nip-40.md b/content/en/topics/nip-40.md index 1ba21ab..c3aad1e 100644 --- a/content/en/topics/nip-40.md +++ b/content/en/topics/nip-40.md @@ -42,7 +42,8 @@ Expiration is a retention hint, not a revocation system. It helps align relay be **Mentioned in:** - [Newsletter #1: News](/en/newsletters/2025-12-17-newsletter/#news) - [Newsletter #3: Notable Code Changes](/en/newsletters/2025-12-31-newsletter/#rust-nostr-library) -- [Newsletter #24: MDK Disappearing Messages and Deep Dive](/en/newsletters/2026-05-28-newsletter/#nip-deep-dive-nip-40-event-expiration) +- [Newsletter #24: MDK Disappearing Messages](/en/newsletters/2026-05-28-newsletter/) +- [Newsletter #30: NIP Deep Dive](/en/newsletters/2026-07-08-newsletter/#nip-deep-dive-nip-40-expiration-timestamp) **See also:** - [NIP-01: Basic Protocol](/en/topics/nip-01/) From 8fdbeb0e543060f6459caf1f7630be2ecc5469f0 Mon Sep 17 00:00:00 2001 From: Datawav <222291538+Datawav@users.noreply.github.com> Date: Wed, 8 Jul 2026 16:15:48 +0000 Subject: [PATCH 2/2] Newsletter #30: follow-up items from 1-day fetch - Bitchat: correct version anchor from 1.5.4 to 1.6.0; NIP-13 PoW and gateway mode PRs shipped in 1.6.0, not 1.5.4. Add prekey bundles, transitive verification, private groups, Cashu ecash chips, geohash bulletin board, store-and-forward expansion. - Keep: bump anchor from v1.1.5 to v1.1.6 with TOCTOU race fix in set_active_share, NIP-98 URL/method disclosure on the auth prompt, RNG fail-closed. - Nostrord v2.1.1: fold in one-line note about iOS platform actuals follow-up (PR #178). - New sections: Manent v1.4.0 (NIP-42 AUTH fix, Blossom http upload fix, media clipboard flows), Routstrd v0.3.7 (Nostr event store now persistent source of truth, 21-min refresh cadence, SDK 0.3.15 upgrade), Nymchat 1.0.1 (PWA on NIP-17 with kind 20000/23333 channels, NIP-46 remote signer, WebRTC over NIP-17), 21Meetup 1.1.0 (kind 21000 signed attendance badges). - New NIP proposal: NIP-80 hardware-attested media provenance (PR #2409, event kinds 1080/1081/1082/11080/31080/31081). - Update TLDR to reference all new items. - .gitignore: track compass-generated data dirs consistently (zapstore, april_history, non_github_sources, coverage_history); add workspace/ for local scratch that must stay out of Hugo's data/ scan path. --- .gitignore | 7 ++++ .../en/newsletters/2026-07-08-newsletter.md | 36 +++++++++++++++---- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 34f6a61..5433f54 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,10 @@ scripts/__pycache__/ data/nostr_nip_discussions/*.json data/nostr_recap/*.json data/shakespeare_apps/*.json +data/zapstore_releases/ +data/april_history/ +data/non_github_sources_*.json +data/coverage_history.json +logs/ +publish/ +workspace/ diff --git a/content/en/newsletters/2026-07-08-newsletter.md b/content/en/newsletters/2026-07-08-newsletter.md index 9bc2929..0a6aef7 100644 --- a/content/en/newsletters/2026-07-08-newsletter.md +++ b/content/en/newsletters/2026-07-08-newsletter.md @@ -9,7 +9,7 @@ description: "Marmot marks the spec adopted and MDK cuts v0.9.0 through v0.9.3 w Welcome back to Nostr Compass, your weekly guide to Nostr. -**This week:** the [Marmot spec is marked adopted](#marmot-marks-the-spec-adopted-and-mdk-cuts-v09x) across 42 files as MDK cuts v0.9.0 through v0.9.3 with encrypted group avatars, external signer support, and MarmotKit iOS and Android bindings. [Mostro ships Transport v2](#mostro-v0180-and-mobile-v130-ship-transport-v2-on-nip-44) on NIP-44 direct messages with anti-spam gates and a coexistence window in both mostrod v0.18.0 and Mobile v1.3.0. [Bitchat adds NIP-13 proof-of-work](#bitchat-154-adds-nip-13-proof-of-work-and-an-opt-in-mesh-to-nostr-gateway) to geohash channel messages and an opt-in mesh-to-Nostr gateway that lets one online phone uplink a whole crowd. [Amber](#amber-v623-scopes-profile-subscriptions-and-adds-a-tor-status-notification) scopes profile subscriptions per account, fetches NIP-65 relay lists before profile metadata, and adds a live Tor status notification with a restart action. [rust-nostr](#rust-nostr-adds-nip-40-expiration-to-gift-wrap-and-private-dm-builders) adds NIP-40 expiration to gift wrap and NIP-17 DM builders, anchored to the wrap's randomized timestamp. [Amethyst](#amethyst-spends-the-week-hardening-negentropy-sync-and-adding-nip-50-search) merges 43 PRs of negentropy sync hardening, NIP-50 full-text search infrastructure, and event kinds for niche verticals. [Nostrord ships v2.0.0 and v2.1.0](#nostrord-v200-and-v210-fold-the-relay-pool-and-heal-zombie-websockets) with a folded relay pool, zombie WebSocket detection, and a full disk-first cache seam. [Ngit v2.6.2](#ngit-v262-stops-duplicate-pr-status-events-on-default-branch-push), [Jumble v26.7.1](#jumble-v2671-makes-blossom-the-default-upload-service-in-a-dm-focused-cut), [Applesauce signers 6.2.2](#applesauce-signers-622-drops-an-nbunksec-dependency), [Bray v1.33.0](#bray-v1330-cli-picks-up-a-bunker-profile-persona-and-tor-outbound), [Deepmarks 1.0.0](#deepmarks-100-hardens-the-nostr-bookmarking-surface), [Bitcredit Core v0.5.13](#bitcredit-core-v0513-unencrypts-block-metadata-on-the-nostr-wire), [Coop Mobile v0.2.4](#coop-mobile-v023-and-v024), [Granary v11.0](#granary-v110-adds-nip-71-video-event-support), and [Nostr-relay v0.0.244](#nostr-relay-v00244-adds-a-firestore-backend) also ship, and [SafeBox marks Phase 3 substantially complete](#safebox-publishes-a-phase-3-progress-report-and-a-freebsd-jail-runbook) alongside a FreeBSD jail deployment runbook and an OpenETR spin-off for electronic transferable records. The NIPs repository merges a [NIP-51 and NIP-37 name alignment](#merged-nip-51-and-nip-37-align-the-kind-10013-name) and opens four proposals: [NIP-AD Nostr Web Addresses](#open-nip-ad-nostr-web-addresses-via-well-known-lookup), [NIP-86 invite-code claim management](#open-nip-86-claim-management-for-invite-codes), an [HSL role color format](#open-role-color-as-h-s-l-tuple), and a [pagination fix in NIP-01](#open-nip-01-pagination-hardening). Deep dives cover [NIP-13 (proof-of-work)](#nip-deep-dive-nip-13-proof-of-work) and [NIP-40 (expiration timestamp)](#nip-deep-dive-nip-40-expiration-timestamp). +**This week:** the [Marmot spec is marked adopted](#marmot-marks-the-spec-adopted-and-mdk-cuts-v09x) across 42 files as MDK cuts v0.9.0 through v0.9.3 with encrypted group avatars, external signer support, and MarmotKit iOS and Android bindings. [Mostro ships Transport v2](#mostro-v0180-and-mobile-v130-ship-transport-v2-on-nip-44) on NIP-44 direct messages with anti-spam gates and a coexistence window in both mostrod v0.18.0 and Mobile v1.3.0. [Bitchat 1.6.0 adds NIP-13 proof-of-work](#bitchat-160-adds-nip-13-proof-of-work-and-an-opt-in-mesh-to-nostr-gateway) to geohash channel messages, an opt-in mesh-to-Nostr gateway that lets one online phone uplink a whole crowd, prekey bundles, transitive verification, and creator-managed encrypted private groups. [Amber](#amber-v623-scopes-profile-subscriptions-and-adds-a-tor-status-notification) scopes profile subscriptions per account, fetches NIP-65 relay lists before profile metadata, and adds a live Tor status notification with a restart action. [rust-nostr](#rust-nostr-adds-nip-40-expiration-to-gift-wrap-and-private-dm-builders) adds NIP-40 expiration to gift wrap and NIP-17 DM builders, anchored to the wrap's randomized timestamp. [Amethyst](#amethyst-spends-the-week-hardening-negentropy-sync-and-adding-nip-50-search) merges 43 PRs of negentropy sync hardening, NIP-50 full-text search infrastructure, and event kinds for niche verticals. [Nostrord ships v2.0.0 and v2.1.0](#nostrord-v200-and-v210-fold-the-relay-pool-and-heal-zombie-websockets) with a folded relay pool, zombie WebSocket detection, and a full disk-first cache seam. [Ngit v2.6.2](#ngit-v262-stops-duplicate-pr-status-events-on-default-branch-push), [Jumble v26.7.1](#jumble-v2671-makes-blossom-the-default-upload-service-in-a-dm-focused-cut), [Applesauce signers 6.2.2](#applesauce-signers-622-drops-an-nbunksec-dependency), [Bray v1.33.0](#bray-v1330-cli-picks-up-a-bunker-profile-persona-and-tor-outbound), [Deepmarks 1.0.0](#deepmarks-100-hardens-the-nostr-bookmarking-surface), [Bitcredit Core v0.5.13](#bitcredit-core-v0513-unencrypts-block-metadata-on-the-nostr-wire), [Coop Mobile v0.2.4](#coop-mobile-v023-and-v024), [Granary v11.0](#granary-v110-adds-nip-71-video-event-support), [Nostr-relay v0.0.244](#nostr-relay-v00244-adds-a-firestore-backend), [Manent v1.4.0](#manent-v140-fixes-nip-42-auth-and-adds-media-clipboard-flows), [Routstrd v0.3.7](#routstrd-v037-makes-the-nostr-event-store-the-persistent-source-of-truth), [Nymchat 1.0.1](#nymchat-101-launches-as-a-progressive-web-app-on-nip-17), and [21Meetup 1.1.0](#21meetup-110-launches-nostr-signed-attendance-badges) also ship, and [SafeBox marks Phase 3 substantially complete](#safebox-publishes-a-phase-3-progress-report-and-a-freebsd-jail-runbook) alongside a FreeBSD jail deployment runbook and an OpenETR spin-off for electronic transferable records. The NIPs repository merges a [NIP-51 and NIP-37 name alignment](#merged-nip-51-and-nip-37-align-the-kind-10013-name) and opens five proposals: [NIP-AD Nostr Web Addresses](#open-nip-ad-nostr-web-addresses-via-well-known-lookup), [NIP-86 invite-code claim management](#open-nip-86-claim-management-for-invite-codes), an [HSL role color format](#open-role-color-as-h-s-l-tuple), [NIP-80 hardware-attested media provenance](#open-nip-80-hardware-attested-media-provenance), and a [pagination fix in NIP-01](#open-nip-01-pagination-hardening). Deep dives cover [NIP-13 (proof-of-work)](#nip-deep-dive-nip-13-proof-of-work) and [NIP-40 (expiration timestamp)](#nip-deep-dive-nip-40-expiration-timestamp). --- @@ -29,11 +29,13 @@ Mostro is the peer-to-peer Bitcoin trading protocol that runs order books, escro [Mostro Mobile v1.3.0](https://github.com/MostroP2P/mobile/releases/tag/v1.3.0) is the client half of the migration. [PR #613](https://github.com/MostroP2P/mobile/pull/613) migrates the app to Riverpod 3.x, Phase A ([PR #620](https://github.com/MostroP2P/mobile/pull/620)) adds dual-receive support for NIP-44 direct messages on the main isolate and in the background isolate so a v2 mostrod and a v1 client can talk during the migration, Phase B in [PR #624](https://github.com/MostroP2P/mobile/pull/624) adds dual-send, [PR #632](https://github.com/MostroP2P/mobile/pull/632) re-applies dual-send after the Riverpod 3.x cut, and Phase C in [PR #637](https://github.com/MostroP2P/mobile/pull/637) finalizes the migration. The release also adds African payment method coverage: [PR #625](https://github.com/MostroP2P/mobile/pull/625) adds Malawi Kwacha payment methods and [PR #627](https://github.com/MostroP2P/mobile/pull/627) adds KES (Kenyan Shilling), MZN (Mozambican Metical), TZS (Tanzanian Shilling), UGX (Ugandan Shilling), ZAR (South African Rand), and ZMW (Zambian Kwacha) methods while expanding NGN (Nigerian Naira). A restore flow now waits for node connectivity before issuing restore requests, and cause-aware handling distinguishes a dispute-driven bond slash from a timeout-driven one. -### Bitchat 1.5.4 adds NIP-13 proof-of-work and an opt-in mesh-to-Nostr gateway +### Bitchat 1.6.0 adds NIP-13 proof-of-work and an opt-in mesh-to-Nostr gateway -[Bitchat 1.5.4](https://github.com/permissionlesstech/bitchat/releases/tag/v1.5.4) is the Bluetooth-mesh chat app that uses Nostr for its geohash channels and DM handoff. The release does two Nostr-shaped things worth reading. [PR #1382](https://github.com/permissionlesstech/bitchat/pull/1382) adds [NIP-13 (proof-of-work)](/en/topics/nip-13/) to outbound geohash channel messages (kind 20000 ephemeral events): each send mines a `["nonce", "", ""]` tag before publishing, targeting 8 leading zero bits, which averages 256 hash attempts and completes in under one millisecond on an M-series Mac. Inbound events with validated PoW relax the per-sender intake rate limit, so a spammer pays compute per message while a regular sender does not feel the cost. Scope is deliberately narrow: only kind 20000 channel messages mine PoW, and presence heartbeats (kind 20001), kind-1 location notes, and DMs are untouched. +[Bitchat 1.6.0](https://github.com/permissionlesstech/bitchat/releases/tag/v1.6.0) is the Bluetooth-mesh chat app that uses Nostr for its geohash channels and DM handoff. The release does two Nostr-shaped things worth reading. [PR #1382](https://github.com/permissionlesstech/bitchat/pull/1382) adds [NIP-13 (proof-of-work)](/en/topics/nip-13/) to outbound geohash channel messages (kind 20000 ephemeral events): each send mines a `["nonce", "", ""]` tag before publishing, targeting 8 leading zero bits, which averages 256 hash attempts and completes in under one millisecond on an M-series Mac. Inbound events with validated PoW relax the per-sender intake rate limit, so a spammer pays compute per message while a regular sender does not feel the cost. Scope is deliberately narrow: only kind 20000 channel messages mine PoW, and presence heartbeats (kind 20001), kind-1 location notes, and DMs are untouched. -[PR #1384](https://github.com/permissionlesstech/bitchat/pull/1384) adds gateway mode, an opt-in mesh-to-Nostr uplink for geohash channels. When a mesh-only user (no internet, no reachable relay) sends in a geohash channel and another peer on the mesh advertises the `.gateway` capability, the signed kind 20000 event is wrapped in a new `MessageType.nostrCarrier = 0x28` TLV envelope and sent directed to one gateway. The gateway peer publishes the event to Nostr on the sender's behalf and rebroadcasts inbound channel traffic back onto the mesh with default TTL. Uplink deposits ride the courier envelope path (directed, relayed multi-hop); downlink rides broadcast. The signature happens before the event leaves the sender, so the gateway can decide whether to publish but cannot forge attribution. The stated motivation is disaster and protest scenarios where one connected phone in a crowd is enough to give the whole geohash channel a working Nostr uplink. [PR #1367](https://github.com/permissionlesstech/bitchat/pull/1367) fixes end-to-end favorites, cleaning up peer-list duplicates, Nostr sync, and `/fav` key corruption in the same release. +[PR #1384](https://github.com/permissionlesstech/bitchat/pull/1384) adds gateway mode, an opt-in mesh-to-Nostr uplink for geohash channels. When a mesh-only user (no internet, no reachable relay) sends in a geohash channel and another peer on the mesh advertises the `.gateway` capability, the signed kind 20000 event is wrapped in a new `MessageType.nostrCarrier = 0x28` TLV envelope and sent directed to one gateway. The gateway peer publishes the event to Nostr on the sender's behalf and rebroadcasts inbound channel traffic back onto the mesh with default TTL. Uplink deposits ride the courier envelope path (directed, relayed multi-hop); downlink rides broadcast. The signature happens before the event leaves the sender, so the gateway can decide whether to publish but cannot forge attribution. The stated motivation is disaster and protest scenarios where one connected phone in a crowd is enough to give the whole geohash channel a working Nostr uplink. + +The same release ships a second batch of Nostr-adjacent work. [PR #1381](https://github.com/permissionlesstech/bitchat/pull/1381) adds prekey bundles for forward-secret asynchronous first contact on the courier mail path, so a sender can compose a message to a peer who is offline and hand it to the mesh without having done a live Noise handshake first. [PR #1380](https://github.com/permissionlesstech/bitchat/pull/1380) adds transitive verification: a peer that has completed the Noise handshake with someone you have already verified is now vouched for over the Noise session, so the trust graph propagates one hop at a time instead of requiring a fresh in-person verification for every new contact. [PR #1383](https://github.com/permissionlesstech/bitchat/pull/1383) adds creator-managed encrypted private groups over the mesh, [PR #1376](https://github.com/permissionlesstech/bitchat/pull/1376) detects, renders, and redeems Cashu ecash tokens with a `/pay` command, and [PR #1379](https://github.com/permissionlesstech/bitchat/pull/1379) adds a persistent signed geohash bulletin board layered on mesh sync. [PR #1372](https://github.com/permissionlesstech/bitchat/pull/1372) expands store-and-forward with open couriers, spray-and-wait routing, a persistent outbox, and a six-hour public history window. Bitchat 1.5.4 shipped [earlier in the week](https://github.com/permissionlesstech/bitchat/releases/tag/v1.5.4) with the end-to-end favorites fix in [PR #1367](https://github.com/permissionlesstech/bitchat/pull/1367) that cleans up peer-list duplicates, Nostr sync, and `/fav` key corruption. --- @@ -79,11 +81,27 @@ Mostro is the peer-to-peer Bitcoin trading protocol that runs order books, escro [mattn/nostr-relay v0.0.244](https://github.com/mattn/nostr-relay/releases/tag/v0.0.244) adds a Firestore backend via [PR #12](https://github.com/mattn/nostr-relay/pull/12), extending the Go relay's storage layer with a Google Cloud Firestore option alongside its existing backends. The change is small but opens Firestore as a managed serverless database option for a relay operator. +### Manent v1.4.0 fixes NIP-42 AUTH and adds media clipboard flows + +[Manent v1.4.0](https://github.com/dtonon/manent/releases/tag/v1.4.0) is the encrypted notes and file storage app built on Nostr with [NIP-44](/en/topics/nip-44/) encryption, [NIP-46](/en/topics/nip-46/) and [NIP-55](/en/topics/nip-55/) signer support, [NIP-65](/en/topics/nip-65/) outbox routing, and Blossom storage. The release fixes [NIP-42](/en/topics/nip-42/) relay authentication (previously broken), corrects Blossom uploads to `http://` hosts (previously mishandled), and rewrites the compression flow. On the media side, users can now copy an image to the clipboard, paste an image from the clipboard, drag and drop files, crop and rotate images, play video and gifs, and take a video with a long press on the camera icon. On Linux, the primary clipboard is accessible via mouse middle-click. Note-loading and scrolling receive several optimizations. + +### Routstrd v0.3.7 makes the Nostr event store the persistent source of truth + +[Routstrd v0.3.7](https://github.com/routstr/routstrd/releases/tag/v0.3.7) is the local daemon for the Routstr decentralized AI inference network, which routes LLM requests via Nostr kind 38421 provider discovery and kind 38425 LGTM reviews. The release adds a `routstrd update` subcommand that downloads new binaries for both routstrd and cocod and gracefully restarts running daemons; the daemon now calls `refreshNostrEvents()` on startup and every 21 minutes so provider discovery and reviews stay fresh without manual intervention. The bundled `@routstr/sdk` upgrades from 0.3.12 to 0.3.15, removing the ProviderRegistry layer in favor of direct `DiscoveryAdapter` use, cleaning up models from disappeared Nostr providers so they no longer leak into rankings, and treating the Nostr event store as a persistent source of truth (the erroneous 210-minute TTL on cached events is gone). Xcashu refund handling tightens: refund tokens are tried before originals in the error path, 404s retry 3× with two-minute intervals, and 425 Too Early is handled without throwing. + +### Nymchat 1.0.1 launches as a Progressive Web App on NIP-17 + +[Nymchat 1.0.1](https://github.com/Spl0itable/NYM) (also known as NYM, Nostr Ynstant Messenger) is a Progressive Web App and native iOS/Android messenger for ephemeral chat over Nostr, bridged with Bitchat. Channels use kind 20000 ephemeral events for geohash channels and kind 23333 for named channels; private messages and group chats ride [NIP-17](/en/topics/nip-17/) gift-wrapped events (kind 1059) with rotating ephemeral recipient keys and automatic post-compromise recovery. Users can generate a per-session ephemeral keypair with no registration or log in with a persistent identity via [NIP-07](/en/topics/nip-07/) browser extensions, a [NIP-46](/en/topics/nip-46/) remote signer, or an nsec. Optional device-local identity encryption uses password, PIN, passkey, or biometric unlock via WebAuthn PRF (passkey and biometric) or PBKDF2 (password and PIN), with the plaintext key never written to disk while encryption is on. Voice and video calls use NIP-17 gift wraps for signaling and WebRTC for the media path. Message reactions use [NIP-25](https://github.com/nostr-protocol/nips/blob/master/25.md), custom emoji use [NIP-30](/en/topics/nip-30/), and the web app is served as static files plus Cloudflare Pages Functions acting as a privacy proxy for relays and media. + +### 21Meetup 1.1.0 launches Nostr-signed attendance badges + +[21Meetup 1.1.0](https://github.com/louisthecat86/Einundzwanzig-Meetup-App) is a Flutter app for the German Einundzwanzig Bitcoin community that records meetup attendance via NFC tags and rolling QR codes. Each attendance badge is a Nostr event (kind 21000) signed by the meetup organizer using BIP-340 Schnorr, so a participant accumulates a set of signed events attesting to specific meetups at specific block heights. The rolling QR code rotates every 10 seconds, so a badge cannot be minted remotely, and the NFC tag is only readable in physical proximity. A trust score is computed locally from the collected badges; the score can be presented as a QR code for verification during peer-to-peer trades. The app targets Bitcoin community reputation, not general-purpose Nostr social, but the badge events themselves are ordinary Nostr events any reader can verify. + ### Nostrord v2.0.0 and v2.1.0 fold the relay pool and heal zombie WebSockets [Nostrord v2.0.0](https://github.com/nostrord/nostrord/releases/tag/v2.0.0) is a major cut of the KMP/WASM Nostr client that speaks NIP-29, NIP-42, NIP-44, NIP-46, NIP-57, NIP-65, and NIP-98. [v2.0.1](https://github.com/nostrord/nostrord/releases/tag/v2.0.1) shipped one day later via [PR #166](https://github.com/nostrord/nostrord/pull/166) with a release-blocking desktop fix: the packaged 2.0.0 (deb, rpm, msi, dmg) crashed at startup with `NoClassDefFoundError: java/sql/DriverManager` because the jpackage jlink image was missing the `java.sql` module the SQLDelight sqlite driver depends on; the fix adds `java.sql` to the runtime image, and the same PR routes optimistic send through the network layer so the message reaches the relay (the previous code path cached silently and never delivered), plus keyboard and scroll behavior on mobile web. -[v2.1.0](https://github.com/nostrord/nostrord/releases/tag/v2.1.0) followed on July 7 with the "relay pool fold" ([PR #176](https://github.com/nostrord/nostrord/pull/176)), which unifies the previously separate NIP-29 focused relay socket into the shared pool. One reconnect scheduler now covers all relays, [NIP-42](/en/topics/nip-42/) AUTH signing is bounded with retry, publishes fail closed and retry on auth-required, request-storm races in `requestPrivateGroupData` and `fetchGroupPreviews` are closed, kind-10009 user-group-list fetches batch per relay, and the `mux_chat` live subscription now covers every joined group (not just the opened one) and self-heals when a relay silently drops the subscription. UI-side changes replace the layout-shifting "Sending..." row with an inline clock-then-check icon and turn stalled scroll-back into an explicit Retry row. [PR #179](https://github.com/nostrord/nostrord/pull/179) landed the same day to detect zombie WebSockets on Android: mobile networks and Doze mode kill TCP without a close frame, so writes into the dead socket buffer locally without throwing and `isConnected()` stays true even though nothing will ever be received. `NostrGroupClient` now stamps `lastInboundAtMs` on every frame, gains `markDead()` (which cancels the frame loop so the normal reconnect and resubscribe path runs), and `probeLiveness()` (a REQ any relay must answer within 5 seconds), triggered on OK timeout with zero inbound frames or on mux stale plus socket frame silence. A second bug fix in the same PR stops optimistic messages being written to the persistent cache at insert time; they now write only after delivery confirmation. +[v2.1.0](https://github.com/nostrord/nostrord/releases/tag/v2.1.0) followed on July 7 with the "relay pool fold" ([PR #176](https://github.com/nostrord/nostrord/pull/176)), which unifies the previously separate NIP-29 focused relay socket into the shared pool. One reconnect scheduler now covers all relays, [NIP-42](/en/topics/nip-42/) AUTH signing is bounded with retry, publishes fail closed and retry on auth-required, request-storm races in `requestPrivateGroupData` and `fetchGroupPreviews` are closed, kind-10009 user-group-list fetches batch per relay, and the `mux_chat` live subscription now covers every joined group (not just the opened one) and self-heals when a relay silently drops the subscription. UI-side changes replace the layout-shifting "Sending..." row with an inline clock-then-check icon and turn stalled scroll-back into an explicit Retry row. [PR #179](https://github.com/nostrord/nostrord/pull/179) landed the same day to detect zombie WebSockets on Android: mobile networks and Doze mode kill TCP without a close frame, so writes into the dead socket buffer locally without throwing and `isConnected()` stays true even though nothing will ever be received. `NostrGroupClient` now stamps `lastInboundAtMs` on every frame, gains `markDead()` (which cancels the frame loop so the normal reconnect and resubscribe path runs), and `probeLiveness()` (a REQ any relay must answer within 5 seconds), triggered on OK timeout with zero inbound frames or on mux stale plus socket frame silence. A second bug fix in the same PR stops optimistic messages being written to the persistent cache at insert time; they now write only after delivery confirmation. [v2.1.1](https://github.com/nostrord/nostrord/releases/tag/v2.1.1) shipped one day later via [PR #178](https://github.com/nostrord/nostrord/pull/178) adding iOS platform actuals, native test support, and app icons alongside the v2.1.0 zombie-WebSocket work. --- @@ -135,9 +153,9 @@ Divine Video's [mobile app](https://github.com/divinevideo/divine-mobile) merged [smolgrrr/TAO](https://github.com/smolgrrr/TAO) and [smolgrrr/Wired](https://github.com/smolgrrr/Wired) (the same commit set landed in both repos) merged 13 PRs. [PR #84](https://github.com/smolgrrr/TAO/pull/84) raises the default post-signal proof-of-work target to 21 leading zero bits, and [PR #80](https://github.com/smolgrrr/TAO/pull/80) surfaces feed roots from fresh PoW activity so a client can rank the timeline by recent NIP-13 work; the previous ranking was raw event age. [PR #75](https://github.com/smolgrrr/TAO/pull/75) restores a custom emoji picker and [PR #65](https://github.com/smolgrrr/TAO/pull/65) adds first-frame video previews. This is the second Nostr client this week to lean on NIP-13 as a first-class filter for user-generated content, complementing Bitchat's channel-scoped PoW. -### keep-android polishes NIP-46 UX +### keep-android polishes NIP-46 UX and lands a TOCTOU fix -[privkeyio/keep-android](https://github.com/privkeyio/keep-android) shipped [v1.1.5](https://github.com/privkeyio/keep-android/releases/tag/v1.1.5) alongside 13 merged PRs. Keep is a mobile identity vault (covered in [Issue #29](/en/newsletters/2026-07-01-newsletter/#custid-launches-as-a-mobile-identity-vault-with-nip-46-and-nfc-challenge-flow) as CustID). The delta since last week is UX polish on the NIP-46 challenge flow. +[privkeyio/keep-android](https://github.com/privkeyio/keep-android) shipped [v1.1.5](https://github.com/privkeyio/keep-android/releases/tag/v1.1.5) alongside 13 merged PRs, then [v1.1.6](https://github.com/privkeyio/keep-android/releases/tag/v1.1.6) on July 8 pinning the underlying keep core to v0.5.0. Keep is a mobile identity vault (covered in [Issue #29](/en/newsletters/2026-07-01-newsletter/#custid-launches-as-a-mobile-identity-vault-with-nip-46-and-nfc-challenge-flow) as CustID). v1.1.5 was UX polish on the [NIP-46](/en/topics/nip-46/) challenge flow. v1.1.6 closes a check-then-set (TOCTOU) race in `set_active_share` from the underlying keep-mobile crate, surfaces the URL and method being authorized on the [NIP-98](/en/topics/nip-98/) HTTP-auth approval prompt so a user can see what they are signing, and switches the RNG health check to fail closed (return an error) instead of panicking. An instrumented test covers the NIP-55 approval-flow kill switch. The v0.5.0 CLI features that came with the underlying release (threshold-OPRF unlock, software DKG, HD FROST wallets) are not surfaced in the Android app yet; v1.1.6 delivers the security fixes only. ### Heartwood ships the relay-to-serial signing bridge @@ -171,6 +189,10 @@ The report also announces [OpenETR](https://github.com/trbouma/openetr) as a dis [PR #2402](https://github.com/nostr-protocol/nips/pull/2402) changes the role color format in [NIP-43](/en/topics/nip-43/) from a single `hue` value (0 to 360) to a tuple of `hue` (0 to 360), `saturation` (0 to 1), and `lightness` (0 to 1). Empty strings are permitted for any component so clients can supply their own defaults for a coherent palette, and the spec text recommends providing only `hue` unless a specific color like silver is desired. The change threads through NIP-86 in the same PR: `createrole` and `editrole` now take `[id, label, description, [h, s, l], order]`; the previous signature carried a single-color parameter in the same slot. The motivation is that hue alone forces clients to pick saturation and lightness for the operator, so different clients render the same role at visibly different intensities. +### Open: NIP-80 hardware-attested media provenance + +[PR #2409](https://github.com/nostr-protocol/nips/pull/2409) opens NIP-80, an event format for media provenance anchored in capture hardware. A camera signs each photo at the moment of capture and publishes the proof to relays keyed by the content itself, so verification survives metadata stripping, re-hosting, and platform takedowns. The proposal defines six new event kinds: kind 1080 for capture attestations, kind 1081 for derivation attestations covering resize, crop, recompress, or redact operations (with a reveal mode or zero-knowledge option), kind 1082 for revocations (regular events, permanent, author-scoped, monotonic), kind 11080 for device announcements, kind 31080 for device endorsements, and kind 31081 for a device set for anonymous attestations (marked experimental and possibly split into a companion NIP). Reused primitives include NIP-94 `x`-tag semantics, [NIP-92](/en/topics/nip-92/) `imeta`, [NIP-65](/en/topics/nip-65/) for revocation discovery, [Blossom](/en/topics/blossom/) for media storage, and optional [NIP-03](https://github.com/nostr-protocol/nips/blob/master/03.md) timestamp anchoring. The signing model pairs a BIP-340 device key with a hardware ECDSA key because mainstream secure elements do not yet produce BIP-340 signatures (Microchip ATECC608 supports P-256, NXP SE050 supports secp256k1 but only ECDSA, TPM 2.0 modules and Infineon OPTIGA Trust M cover P-256/RSA, Apple Secure Enclave and Android StrongBox use P-256). The stated scope explicitly does not attempt to prove the scene is real: an attestation proves this exact image came from this device at approximately this time and was modified only in declared, provable ways, and the specification forbids clients from collapsing results into a bare "authentic" badge. A working prototype [OpenVeilCam](https://github.com/PrarthanaPurohit/OpenVeilCam), a Rust camera runtime for Raspberry Pi using the ATECC608 secure element, is being updated to publish the proposed event kinds alongside a standalone verifier. + ### Open: NIP-01 pagination hardening [PR #2407](https://github.com/nostr-protocol/nips/pull/2407) adds a "Pagination & limits" subsection to NIP-01. The concrete rules: a relay that imposes a maximum `limit` MUST set it greater than the largest number of events sharing a single `created_at` in its database, so no single second can fill a page and stall pagination. Clients paging backwards MUST repeat requests with `until = oldest` (inclusive) and MUST deduplicate by `id` (since the oldest second is re-fetched each round), and paging is complete when a round yields no new events after deduplication. If a full page has oldest and newest events sharing one `created_at`, the client MUST retry that second with a larger `limit`, and if the relay clamps the larger `limit` and still returns a page confined to one second, the client MUST either advance with `until = oldest - 1` (treating unretrieved events as dropped) or abort. Normal paging MUST NOT set `limit`; the relay maximum is authoritative, and a smaller value reintroduces the stall. Raising `limit` to drain a stuck second is the one exception. This fix matters because a naive `since`/`until` cursor either misses events with duplicate timestamps or reprocesses them, and the current NIP-01 text does not tell either side how to escape the trap.