Nostr Compass #36
- Top Stories
- Amber 6.5.0 closes a relay-auth confused deputy and encrypts stored secrets
- Cambium 0.4.0 signs for websites and sheds relay-auth bursts
- Citrine 3.1.0 turns a phone relay into a group host and a site host
- Vector 0.4.2 makes community moderation survive a spam wave
- Sonar brings threaded replies to a mesh messenger with NIP-C7
- Nostria starts publishing podcasts and asks relays to count
- Tagged Releases
- MDK 0.9.14: fail-closed group history through faster group creation
- Divine Mobile 1.0.20: minting a badge without leaving the app
- ClipRelay 0.2.0: pairing a signer with a camera
- Bark 1.3.9: a browser signer that runs on Android
- Bray 3.0.0 and Toll Booth 6.0.0 move to a shared wallet-connect library
- NoorNote 1.3.4: joining encrypted communities from an invite link
- Mostro moves dispute chat off gift wrap
- NYM 3.73.522: encrypted group chats and an encrypted local store
- Morganite 0.0.4: verifying a blob before it is cached
- Newly Discovered
- In Development
- Amethyst rebuilds its relay authentication decision flow
- nostrord implements an unmerged encryption-key proposal
- nostream adds a relay monitor and mints invite codes
- rust-nostr fixes a gift-wrap identifier and rejects protected reposts
- NDK adds post-quantum direct messages and drops a GPL dependency
- Nostter adds bookmark lists, profile badges, and Blossom uploads
- Zap Cooking binds admin routes to signed requests and encrypts stored wallet connections
- Protocol and Spec Work
- NIP Deep Dive
Welcome back to Nostr Compass, your weekly guide to Nostr.
This week: Amber (maintainer: @greenart7c3) hardens relay auth and encrypts stored secrets, Cambium @TheCryptoDonkey signs for websites under relay-auth load, Citrine hosts groups and static sites on a phone relay, Vector @VectorPrivacy queues moderation under spam and syncs mutes across devices, Sonar @Vincenzo Palazzo adds threaded mesh replies, Nostria (maintainer: @sondreb) publishes podcasts, and Nail bridges email as gift-wrapped events. Releases cover MDK group state, badge minting, QR signer pairing, Android browser signing, and shared wallet-connect. Protocol work reaches comment patches, encrypted file metadata, thread formatting, Marmot restart guarantees, and Concord membership lists. Deep dives: badges and comments.
Top Stories
Amber 6.5.0 closes a relay-auth confused deputy and encrypts stored secrets
Amber is an Android NIP-55 and NIP-46 signer. Version 6.5.0 closes four disclosed gaps: a confused deputy in relay authentication that let any caller obtain a kind 22242 NIP-42 event for relays the user never approved; a NIP-46 replay gap; plaintext connection secrets and local keys now envelope-encrypted at rest; and an eight-item hardening batch covering caller authorization before decrypt, fail-closed permission parsing, plain-ws:// warnings, secure QR screens, log redaction, lazy key zeroing on logout, and optional unlocked-device Keystore use.
Version 6.5.1 re-encrypts stored NIP-46 secrets when the Keystore key rotates after toggling the unlocked-device requirement and fixes a permission-editor crash. Version 6.5.2 stops decrypting columns the applications list never renders, caches the Keystore handle, warms the account cache at startup, and debounces relay status notifications.
Last week’s 6.4.0 made grouped signing decisions explicit; 6.5.x changes what Amber will authorize at all.
Cambium 0.4.0 signs for websites and sheds relay-auth bursts
Cambium is an Android NIP-55 proxy to a Heartwood hardware signer over NIP-46. Six releases shipped in two days.
Version 0.4.0 extends signing to websites. A page can request a signature through a validated nostrsigner: callback without inheriting the permissions granted to native applications, which keeps a browser tab from borrowing another app’s approval. The same release fixes the specification’s minimal event shape: an event carrying only kind and content now signs correctly, with Cambium supplying the paired NIP-46 identity, the current timestamp, and an empty tag array before handing the event to rust-nostr. Native rust-nostr instrumentation became a required continuous-integration gate in the same change.
Version 0.3.6 repairs pairing against spec-following signers. Cambium’s older rust-nostr build accepted only the literal string ack as the result of a NIP-46 connect call, so a signer that answers by echoing the bunker URI’s secret, which is what the current specification asks for and what Heartwood’s firmware does, ended pairing with an unexpected-response error. Moving from rust-nostr 0.44.2 to 0.44.8 makes both forms acceptable, verified against live hardware and against nak bunker, which still answers ack.
The 0.4.1 through 0.4.3 releases are about admission control under load. Version 0.4.1 gives reactions, posts, deletions, and encryption a reserved queue slot ahead of relay authentication and background decryption, bounds queued calls, discards them once the caller has timed out, and returns a terminal unavailable result on overload instead of opening a foreground signing screen. Version 0.4.2 discards timed-out or long-idle NIP-46 sessions before the next request and lets concurrent copies of the same kind 22242 authentication event share one hardware signature. Version 0.4.3 admits at most one distinct authentication challenge per identity into the hardware worker, never retries authentication internally, and opens a sixty-second per-identity cooldown after a timeout while still answering exact cached duplicates. The measurements in the release notes come from a GrapheneOS phone driving Amethyst @amethyst: a cold-start burst produced thirty-three immediate overload responses and thirteen completed requests with no signer timeouts, and a fresh login during an authentication burst returned 1.254 seconds after approval.
Citrine 3.1.0 turns a phone relay into a group host and a site host
Citrine is an on-device Android relay. Version 3.1.0 adds three capabilities that change what the relay can host.
Support for NIP-29, the relay-based group specification where the relay itself holds membership and moderation state, means a phone can host a group instead of joining one. Support for NIP-86, the relay management API that exposes administrative actions over authenticated JSON-RPC, arrives with a settings screen, so allowlists and bans can be driven from the API as well as from the app. Support for NIP-5A static websites lets the relay serve nsites to web clients, with a modernized browse list carrying icons, search, sort-by-last-update, install progress, descriptions, and a configurable set of relays for fetching them that defaults to nsite.run, nos.lol, and nostr.land.
The moderation surface grew alongside it in the same release. Banning a public key locally now offers to purge that author’s stored events, a configurable REJECTED_KINDS list blocks kinds the operator does not want to store, and access control can import from existing lists. A rebroadcast tool pushes stored events back out to selected relays, which gives a phone-held archive a way to reseed the network. The release also removes the WebSocket permessage-deflate extension, tightens the query hot path, fixes Tor failing to start or stop when the expose-via-Tor setting changes, and moves logs into a local database with logcat limited to debug builds.
Vector 0.4.2 makes community moderation survive a spam wave
Vector is a desktop and Android Concord messenger. Version 0.4.2 focuses on moderation under load.
Rapid bans previously overwrote each other. They now queue, stack, and settle as a single operation, so banning a wave of accounts costs one key rotation instead of one per account. Accepting an invitation to a community that has since been dissolved now explains why and removes the invitation from every device the user owns, and dissolving a community the user owns clears it from their community list everywhere, a fix that arrived in version 0.4.3. Community messages that arrive during a background catch-up no longer ring notifications as though they had just been sent, and the typing indicator expires from the moment it was sent so a delayed signal cannot linger in a channel.
The sharded community list defined by Concord took a cross-client review pass with Armada, the other Concord client. Renames no longer inflate the list, ties resolve identically on both clients, and unchanged data is no longer republished to relays. Muting also moved out of the direct-message path: a user can now mute someone straight from a community with no prior message history, and the mute applies to notifications and badges across channels and direct messages while leaving the messages themselves visible. Pinned messages became a shared channel surface with clickable links, and edits to a pinned message follow it wherever it appears. Block lists, mutes, and nicknames now sync between a user’s devices, as do pinned chats. Version 0.4.3 also stops Vector from announcing that the user is typing when a different Nostr client is signed in as the same identity, and unblocks Tor bootstrap on Windows, which had frozen at fifteen percent on both x64 and ARM64.
Sonar brings threaded replies to a mesh messenger with NIP-C7
Sonar is a Bluetooth mesh and Nostr messenger. Version 0.1-alpha.13.1 adds Signal-style replies on NIP-C7 kind 9 chat, plus mentions, bounded Bluetooth reassembly, backup caps, mesh-path signature verification, and FCM push fallback. Versions 0.1-alpha.13.2 and 0.1-alpha.13.3 fix chat-open crashes on Android and iOS keyboard overlap.
Nostria starts publishing podcasts and asks relays to count
Nostria is a web client. Version 4.1.70 and 4.1.71 add podcast publishing for premium subscribers, with episodes as signed Nostr events. Version 4.1.69 uses NIP-45 COUNT for reaction, reply, and zap totals in feeds and completes localization. Last week’s 4.1.67 expanded encrypted-community administration.
Tagged Releases
MDK 0.9.14: fail-closed group history through faster group creation
MDK @marmotprotocol is the Rust development kit for Marmot, an encrypted group-messaging protocol carried over Nostr. Version 0.9.12 makes several group-state paths fail closed instead of guessing. A missing fork anchor is now a hard error (PR #1329), a leave proposal is persisted atomically so a crash cannot leave a half-applied departure (PR #1360), and incident replay refuses to guess a format on manifest-less newline-delimited JSON streams (PR #1140). Convergence testing widened at the same time, with retained-history cross-route recovery (PR #1350), cross-adapter convergence assurance (PR #1372), and generalized isolated convergence campaigns (PR #1357). Relay rejection diagnostics are preserved instead of collapsed into a generic failure (PR #1361).
Version 0.9.13 landed on 18 August with storage format v2 (PR #1421), migration rails, and delta writes replacing live account snapshots (PR #1435), plus faster invite catch-up (PR #1444) and macOS bindings (PR #1402). Version 0.9.14 followed on 19 August with group-creation polish: pre-uploaded founding images (PR #1498), KeyPackage batching (PR #1494), atomic initial message retention (PR #1497), and profile publication with account-owned relays (PR #1495). MarmotKit 0.9.14 and wn-agent 0.9.14 ship with the core crate.
Divine Mobile 1.0.20: minting a badge without leaving the app
Divine Mobile (maintainer rabble: @rabble) is a short-video client that publishes and retrieves video through Nostr. Version 1.0.20 lets a user mint a NIP-58 badge, the signed award events described in this issue’s first deep dive, and hand it to someone without leaving the app. Tapping a badge on a profile explains what earning it required, which is the part of the specification that usually goes unimplemented because the definition event and the award event are stored separately.
The rest of the release is client work: a light theme, crop, rotate, and flip in the stop-motion editor, drafts one tap from the recorder, caption timing against the video, a feed that de-prioritizes already-watched material, screen-reader support across the editor, recorder, and profile tabs, reduced-motion handling, and account settings that manage a Divine email and password and link or unlink accounts. Deleted videos now leave local state, and bookmarks persist. Last week’s 1.0.19 hardened account isolation and private-message validation; badge issuance is a new publishing surface on top of that.
ClipRelay 0.2.0: pairing a signer with a camera
ClipRelay (maintainer: @hoppe2) synchronizes a clipboard between devices over Nostr. Android version 0.2.0 adds nostrconnect:// QR login, so a user can sign in with a signer app on another phone, and adds camera scanning of bunker URLs, which removes the habit of pasting a secret-bearing string through a messenger. Bunker connection now times out after sixty seconds instead of hanging, and the retry button after a failed Amber login works. Desktop version 0.2.0 carries the timeout and login-tab fixes.
Version 0.1.4 added sensitive-clipboard sync with short relay expiry, pinned signer session relays, and a liveness probe that requires a real round trip instead of a locally synthesized EOSE. Last week’s 0.1.3 restored connections after idle periods.
Bark 1.3.9: a browser signer that runs on Android
Bark is a browser extension that provides the NIP-07 window.nostr interface, the object a web page calls to request a signature or an encryption operation. Version 1.3.9 declares Android support for the Firefox build, so the add-ons listing installs on a phone. Firefox on Android implements no windows API, so every approval that opened a popup window would have been denied outright; the approval surface now falls back to a foreground tab where dismissing denies, a review action foregrounds it, and the background closes it once the request settles. The release notes record verification on a Pixel 10 Pro XL running GrapheneOS with Firefox 153.0.4, and state plainly that Chromium on Android compiles out the extension subsystem, so no Chromium-derived Android browser can run Bark at all.
Version 1.3.8 fixed a NIP-46 interoperability defect in the other direction. Bark probed for a compact Heartwood signing dialect by sending the event as a JSON object, which strictly typed signers including nak and bunkers built on rust-nostr cannot parse and silently drop, so signing hung. The probe is now sent only to signers that identified themselves as Heartwood, and every other signer receives a standard sign_event call from the first signature onward.
Bray 3.0.0 and Toll Booth 6.0.0 move to a shared wallet-connect library
Bray and Toll Booth both pay through NIP-47 Nostr Wallet Connect, the specification that lets an application request payments from a wallet over encrypted Nostr events. Bray 3.0.0 and Toll Booth 6.0.0 each declare a breaking change adopting nwc-kit for wallet payments, and Toll Booth removes its payer credential flow in the same change. Both publish reproducible builds whose output was byte-identical across two independent runners, with the tarball hash printed in the release notes so a reader can verify the registry artifact.
Three Toll Booth patches followed: 6.0.1 pins the negotiated deploy host key, 6.1.1 pins cashu-ts to the version its patch targets, and 6.1.2 restores an image build.
NoorNote 1.3.4: joining encrypted communities from an invite link
NoorNote (maintainer: @alp) is a Nostr client for desktop, web, and Android. Version 1.3.4 adds encrypted Armada and Concord communities as an addon: a user joins through an invite link, sees joined communities listed in settings, and receives activity notifications. The same release adds a control for hiding external quote posts, the highlight notes that quote a paragraph of a web article, either globally or per author, with reposts of them hidden too and the user’s own highlights kept visible. Profile resolution was also repaired, so profiles no longer render as a truncated public key or as an anonymous placeholder.
Version 1.3.5 adds an expander for long notes and corrects the layout of the Armada invite-link input. Last week’s 1.3.2 moved article discovery into the social graph; community membership is a separate surface.
Mostro moves dispute chat off gift wrap
Mostro @Mostro is a peer-to-peer trading daemon whose orders and messages travel as Nostr events, with mostro-core as its shared library and Mostro Mobile as its client. Mobile 1.3.2 migrates dispute chat from NIP-59 gift wrap to a kind 14 chat envelope and backs the backlog with durable per-conversation cursors. mostro-core 0.14.5 serializes the rumor identifier inside the gift wrap (PR #164), 0.14.4 fixes a ratings average bug (PR #163), and Mobile 1.3.1 switches to Blossom servers that retain encrypted chat attachments. Use daemon 0.18.2 or 0.18.4.
NYM 3.73.522: encrypted group chats and an encrypted local store
NYM (lead developer Luxas: @Luxas) is a Nostr client with its own assistant integration. Version 3.73.522 encrypts the local SQLite store after 3.73.521 refines encrypted group chat, and 3.73.520 fixes a content-security-policy break and duplicate-new-message presentation.
Morganite 0.0.4: verifying a blob before it is cached
Morganite is an Android Blossom server, the media protocol where a file is addressed by the SHA-256 hash of its content and served from any host that holds it. Version 0.0.4 verifies a blob’s hash during download in a single pass before caching it, which is the check that makes content addressing meaningful on the receiving side. The release also tracks cache size incrementally instead of rescanning the directory on every save, moves blocking network calls onto input and output threads, reuses Tika instances for MIME detection, and persists logs to a local database.
Newly Discovered
Nail brings email onto Nostr as gift-wrapped events
Nail is an MIT-licensed mail bridge and web client from the Formstr team, the group behind Formstr and nostr-calendar. It went to launch on 18 August with PR #7, a 22-file change that added k tags to mail events, key recovery in settings, and a welcome message. Its deployment runs at mailstr.app, which serves the bridge’s own _smtp NIP-05 record, the DNS-based scheme that maps a name at a domain to a Nostr public key.
The mail itself is a Nostr event. The client’s constants define a kind 1301 mail rumor carried inside a kind 1059 NIP-59 gift wrap, so a message reaches its recipient through the same metadata-hiding envelope used for private direct messages. Delivery relays come from a kind 10050 NIP-17 inbox list with a kind 10002 NIP-65 relay list behind it, folders are kind 1985 NIP-32 labels under a mail namespace, and client settings live in a kind 30078 NIP-78 application-data event. Attachments larger than 60,000 bytes go to Blossom instead of into the event, because NIP-44 caps encrypted plaintext at 65,535 bytes. An address is an npub at a domain, and a local domain with no NIP-05 record is treated as a mailbox that does not exist.
The bridge half is a Node LMTP server that runs beside a mailcow deployment without patching it: Postfix routes matching domains to the bridge, and the bridge injects replies back through SMTP. That design forces an honest answer to the hardest question in an email bridge, which is what a From header proves. Nail’s receive path grades every message into one of four provenance states: the configured bridge sealed it and refuses to relay a sender it did not verify upstream, the user sealed it themselves, the address’s NIP-05 record resolves to the sealing key, or nothing corroborates the header at all. In that last case the interface falls back to the sealing public key, which is the only identity the event can actually prove. Bridge API calls are authenticated with NIP-98 signed HTTP events.
Glow stores wallet labels on relays under a passkey-derived identity
Glow is a Breez self-custodial Lightning wallet. Passkey login derives a Nostr identity, and wallet labels list from and save to relays under that identity, with byte-identical duplicates collapsed across partial relay coverage.
In Development
Amethyst rebuilds its relay authentication decision flow
Amethyst is an Android Nostr client. A block of merged work reshapes how it handles NIP-42 client-to-relay authentication. The permissions interface and the decision flow were redesigned (PR #3899), authentication now waits for a challenge to resolve instead of timing out (PR #3905), new accounts default to always authenticating with relays (PR #3931), and an “always log in” choice is honored for relays the account does not use itself (PR #3937). Authentication also recognizes NIP-29 groups and Concord communities as joined venues (PR #3906), which is what stops a relay-hosted group from looking like an unfamiliar relay every time it is opened.
Two other changes touch protocol surfaces. Proof-of-work mining under NIP-13 refreshes created_at while it mines and gains a GPU path analysis (PR #3911), and full-screen napplet hosts handle input-method insets (PR #3932). A guided first-run key backup with a settings entry point also merged (PR #3909), alongside the ability to mute public chats (PR #3939).
nostrord implements an unmerged encryption-key proposal
nostrord @Nostrord is a Nostr chat client organized around relay-scoped groups. It merged an implementation of NIP-4e, an unmerged proposal to decouple message encryption from the identity key that Compass last described in the 15 July issue. The account announces its own kind 10044 encryption key, holds the private half locally, and decrypts inbound direct messages in process, which takes a bunker or browser extension out of the read path entirely (PR #261). Device pairing over kinds 4454 and 4455 moves that key to a second device, and a self-archive republishes history addressed to the new key. Sending was addressed to the announced key first (PR #247), and a follow-up fixed pairing that negotiated successfully without handing the key over (PR #271). The pull request states that the wire format follows the deployed Jumble implementation where it diverges from the open proposal, which places this specification’s working definition in shipped code instead of in the document.
Group identity was tightened in the same batch. A group identifier is now unique only within its relay (PR #269), so the same identifier on two relays is treated as two groups (PR #272), and thread posts render as forum posts (PR #274). Connection churn that produced repeated kind 22242 signing prompts was also stopped (PR #268), which is the same class of signer pressure Cambium spent three releases on this week.
nostream adds a relay monitor and mints invite codes
nostream (maintainer: @Cameri 🦦) is a TypeScript relay implementation. It merged a cluster worker and probe scheduler that publish NIP-66 relay monitoring events, the discovery specification that lets a monitor announce liveness and capability data about other relays (PR #724), with a settings schema and defaults (PR #689) and integration tests (PR #733). A command-line tool now mints NIP-43 invite codes, the relay access metadata scheme that gates admission (PR #732), and the relay finally advertises NIP-13 proof of work in its supported list (PR #680), which it had implemented without announcing. Data vending machine jobs also gained a persistence migration and repository (PR #727), and the relay now traps NIP-90 (data-vending-machine job requests) and records them through the job repository (PR #729).
rust-nostr fixes a gift-wrap identifier and rejects protected reposts
rust-nostr (maintainer: @Yuki Kishimoto) is the Rust library and software development kit behind a large share of the Rust and mobile client work in this issue. It now ensures the rumor identifier is computed before the gift-wrap seal is encrypted (PR #1444), the same class of defect Mostro fixed in its own library this week. Its local relay rejects a repost of a NIP-70 protected event (PR #1445), which is the protection that specification exists to provide, and NIP-47 response parsing tolerates missing and null amounts (PR #1450) instead of failing on a wallet that omits them. Relay URL parsing was hardened (PR #1451).
NDK adds post-quantum direct messages and drops a GPL dependency
NDK (maintainer: @PABLOF7z) is a Dart development kit for Nostr. It merged hybrid post-quantum encryption for direct messages using ML-KEM-1024, the lattice key-encapsulation mechanism standardized as FIPS 203 (PR #713), placing it alongside the classical key agreement instead of in place of it. A separate change replaced a GPL-3.0-only Dilithium implementation with fips204, the ML-DSA signature standard (PR #712), which removes a licensing constraint for applications embedding the kit. Connections also moved to one identity each (PR #710).
Nostter adds bookmark lists, profile badges, and Blossom uploads
Nostter is a web client. It merged support for both the standard and the legacy shapes of NIP-51 bookmark lists (PR #2311), updated its handling of NIP-58 profile badges (PR #2281), added a Blossom media uploader (PR #2298), and now shows a NIP-05 identifier, the DNS-based verification name, in the mention autocomplete (PR #2303).
Zap Cooking binds admin routes to signed requests and encrypts stored wallet connections
Zap Cooking @ZapCooking is a recipe site built on Nostr long-form events. A security batch encrypts stored Nostr Wallet Connect connection strings at rest inside a NIP-44 envelope (PR #622), replaces a spoofable public-key comparison on administrative routes with NIP-98 HTTP authentication, the scheme that signs an event to authorize an HTTP request (PR #626), and clears account data on logout while bounding pending NIP-46 records (PR #627).
Protocol and Spec Work
NIPs
No pull request merged into nostr-protocol/nips during this window. Six proposals opened after the previous issue closed, three of them on 18 August after the draft first circulated.
NIPs PR #2438 proposes NIP-9A, comment-based patching. A patch is a kind 1111 comment referencing the patched event as its parent whose content begins with the literal label PATCH, followed by patch lines. A line beginning with a number edits the target’s content in the form <index> -<deleted> +<inserted> <inserted characters>, counted in unicode characters instead of bytes, and a line beginning with t replaces a human-facing tag such as title, description, subject, or picture. The design is deliberately backwards compatible: a client that does not understand the format displays the patch as an ordinary labeled comment, and a client that does understand it applies the patch and hides the comment. The proposal names kinds 1, 11, 1111, 24, and 1621 as patchable and asks both writers and readers to refuse patches that are too large, too numerous, or published long after the original event, which is an explicit attempt to keep the feature from becoming a general editing channel for immutable events.
NIPs PR #2437 proposes file encryption for NIP-94, the file-metadata specification that describes an uploaded file in a kind 1063 event. It adds three optional tags: encryption-algorithm, with aes-gcm as the only listed value, plus a hex-encoded decryption-key and decryption-nonce. The tag semantics shift accordingly, with m describing the MIME type before encryption, x holding the hash of the encrypted file, and ox holding the hash of the original, and any thumb, image, and fallback sources encrypted under the same key and nonce. The stated purpose is that a public Blossom operator hosting the bytes cannot tell what they are, and the author frames the change as copying NIP-17 private-message encryption properties into file metadata so the same treatment works inside an imeta tag.
NIPs PR #2436 amends NIP-7D, the forum-thread specification built on kind 11 thread events with NIP-22 kind 1111 comments as replies. It adds a formatting section stating that a thread post can be formatted like a kind 1 note, with inline images, links, and NIP-27 references, and could also support Djot, a lightweight markup language with an unambiguous grammar. The author’s argument is that leaving formatting unspecified invites an eventual Markdown implementation by default, and the pull request points at squalk as an existing Djot implementation.
NIPs PR #2439 adds assign and unassign methods to NIP-86 (relay management commands), so a relay administrator can grant admin permissions to another pubkey without sharing the master key.
NIPs PR #2442 succeeds the audio-track proposal Compass covered in January while that draft remained open; the earlier pull request has since closed and this one ships in production at lightning.fm as kind 31337 track events, with kind 31339 release objects, band profiles, per-track contributors, and optional NIP-57 zap splits while keeping sales in NIP-99. The interop contract is published at lightning.fm/interop, and the desktop publisher and self-hosted seller daemon are open source.
Marmot
Marmot PR #416 merged on 13 August and adds a durability and restart contract to the protocol core. The adopted documents already defined deterministic convergence, retained candidate-parent material, publish-before-apply ordering, and fail-closed behavior on missing history, without one unambiguous rule for what happens when a process is interrupted at the seams between them. The change defines recoverable logical facts, restart equivalence, publish and convergence interruption boundaries, observer-atomic transitions, handling of missing or corrupt material, and application-effect recovery, then adds crash and restart conformance scenarios for each. It leaves transactions, journals, snapshots, replay strategy, schedulers, and storage formats implementation-defined, and states that it requires no wire encoding change. The specific failure it forecloses is a publication externally accepted but not locally confirmed, or a partially applied selected branch, producing implementation-dependent protocol results after a restart.
Concord and CORDs
Concord PR #18, covered previously in last week’s issue as an open proposal, has now merged on 15 August. It shards the encrypted community list across kind 33302 events, removes the fifty-membership limit, and prunes retired entries so the list stays inside relay size limits. Vector’s release notes this week record the client-side half of that change, including tie resolution and the decision to stop republishing unchanged data.
Concord PR #22 proposes community-owned audio and video brokers. The CORD-02 metadata entity would carry an optional av_brokers list beside its relays, evolving by edition like the rest of that entity, and the CORD-07 rendezvous would draw from that list, or from the member’s own broker when the community publishes none, ordered by the existing room-keyed tie-break. The broker tag on presence remains readable and useful for reporting a residual split, and the proposal’s argument for demoting it from routing is direct: routing on it lets a fellow member’s untrusted input outrank the community’s own instruction.
Concord PR #23 makes an existing implementation behavior normative in CORD-05. Before persisting a join, the owner’s genesis metadata edition must open under the delivered keys, with rotated planes anchoring on the compaction pair. The pull request states up front that this was never a live vulnerability: Vector’s bundle acceptance already refuses a bundle whose delivered root cannot open the owner’s genesis and never parks an invitation for a community already held, and Armada already drops any bundle that would move a held community’s base. The gap was that neither behavior was required by the specification, so a specification-faithful client could have shipped the vulnerable version.
Blossom upgrade documents, Napplet application proposals, and the Gamma Markets specification recorded no changes in this window.
NIP Deep Dive
Badges (NIP-58)
NIP-58, defined by its primary specification, gives one Nostr identity a way to award a named token to another, and gives the recipient control over whether it appears on their profile. The problem it addresses is that any statement about a person on Nostr is otherwise just a note: there is no structure that says who issued a claim, what the claim is called, what it looks like, or whether the subject accepted it. Badges give that claim three separate signed events with three separate authors’ intentions encoded in them.
The mechanics are built from an addressable definition, an award, and a display list. A badge definition is a kind 30009 event published by the issuer, addressable through its d tag, so the issuer can revise the badge’s name, description, image, and thumb tags later without changing the identifier anything else points at. The award is a kind 8 event published by the same issuer, carrying an a tag holding the 30009:<issuer-pubkey>:<d-identifier> coordinate of the definition and one or more p tags naming recipients. The display list is a kind 30008 event published by the recipient with the fixed d value profile_badges, listing a and e tag pairs where the a tag is the definition coordinate and the e tag is the specific award event. Those pairs are ordered and are read as pairs: an a tag whose matching award is absent, or an e tag whose matching definition is absent, is ignored, so a half-referenced badge silently does not render.
The design tradeoffs are visible in what the specification refuses to do. There is no revocation mechanism and no expiry, so an award is a permanent statement by the issuer about a moment in time, and an issuer who changes their mind can only change the definition the award points at. There is no transfer, so a badge cannot circulate as a token. There is no notion of a trusted issuer registry, which pushes the entire trust question to the client and the reader: a badge is worth exactly what its issuer’s public key is worth to the person looking at it. The specification also grants clients latitude to display fewer badges than the recipient listed and to choose which image size to render, which keeps a profile from becoming a wall of graphics chosen entirely by third parties.
The closest adjacent specification is NIP-51, the list specification, and comparing the two shows why badges need three events instead of one. A list is a single author curating references; the author of the list is the author of the claim. A badge splits authorship in half, with the issuer signing that the award happened and the recipient signing that they accept its display. Neither party can produce the visible result alone, which is what separates a badge from a self-applied label.
A live kind 8 award recovered from nos.lol and relay.primal.net this week:
{
"id": "08504dec368939bd63849a349cab83dea0ac199a852129dbf68cf35fe5c64e96",
"pubkey": "bef514bd58c8ceea4beb9e6b84a8d983935f7be26f49e14df68098f1ba64156e",
"created_at": 1787051248,
"kind": 8,
"tags": [
["a", "30009:bef514bd58c8ceea4beb9e6b84a8d983935f7be26f49e14df68098f1ba64156e:blocks_orange_league"],
["p", "92dfa05d915196a7a09152fa3f57871debfd422e1d278ac5af266a70c3350b1f", "wss://relay.damus.io"]
],
"content": "Badge awarded!",
"sig": "5bf0218dfec5e56b47339b0b4b992cceedd2e18798fb3d47cafea51850c00827f66251e4a3e08190370e04a5e1d4d092eeb441141b7219acdd18b80290a022f8"
}
Current implementations cover issuance, display, and reading. Divine Mobile 1.0.20 mints and awards a badge inside the app and explains an earned badge when a reader taps it, Nostter PR #2281 updates profile badge handling in a web client, and Amethyst publishes award events carrying its own client tag, one of which appears in relay data alongside the example above.
Comments (NIP-22)
NIP-22, defined by its primary specification, provides a general comment event for replying to things that are not short text notes. Short-note threading already had NIP-10, whose tag conventions grew around kind 1 and its reply chains. NIP-22 exists because a video, an article, a calendar event, a wiki page, or a URL needs a reply structure that identifies what kind of thing is being replied to, and that works when the target is addressable, or is an external resource with no Nostr event at all.
The mechanics turn on a case distinction. A comment is a kind 1111 event that carries two sets of tags: uppercase tags describing the root of the discussion and lowercase tags describing the immediate parent. E, A, and I name a root event, a root addressable coordinate, or a root external identifier, K names the root’s kind, and P names the root author. The lowercase e, a, i, k, and p name the same facts about the parent, which is the root itself for a top-level comment and another kind 1111 comment for a nested reply. Splitting them means a client can fetch an entire discussion with one filter on the uppercase root tags, without walking the reply chain, while still rendering nesting correctly from the lowercase parent tags. The I and i variants carry external identifiers in the NIP-73 format, which is what lets a comment thread attach to a web page, a podcast episode, or a book.
The tradeoffs are mostly about what NIP-22 declines to absorb. The specification states that comments must not be used to reply to kind 1 notes, which keeps two threading models from competing over the same objects and leaves NIP-10 in place where it already works. Nesting is permitted but the root stays fixed, so a deep thread never loses its anchor even when intermediate events are unavailable. The kind tags are the load-bearing part: a client that fetches a comment without its target can still tell what it is looking at from K and k, and decide whether it can render that kind at all. What the specification does not provide is any ordering or moderation model, so display order, collapsing, and hiding are entirely client policy.
Compared to NIP-10, the difference lies in typing. NIP-10 assumes the target is a note and encodes position in a thread; NIP-22 encodes the target’s identity and kind explicitly and assumes nothing else about it. That explicit typing is why the newer proposals in this issue reach for kind 1111: a comment already carries a machine-readable statement about what it is attached to.
A live kind 1111 comment recovered from nos.lol and relay.primal.net this week, replying to another comment under a video:
{
"id": "c8d335f8bfea58ecd1a943d6000fb2045f4bddf4a36c67df53eb661671f7ab45",
"pubkey": "3e911baba55ae247339cf805dd6ff49ad2cd6bee84ac44e088ce66450c49104f",
"created_at": 1787062681,
"kind": 1111,
"tags": [
["E", "1c492f2bac17b79d66934a340fa43d8d30d0aea4c9fa329346c05573ef912d70", "", "482d024b8acfde50e7429e5ac561d764f3a53a8b4fb0b6975369d9f0926ef839"],
["A", "34236:482d024b8acfde50e7429e5ac561d764f3a53a8b4fb0b6975369d9f0926ef839:e64ba9ea157b1a315caff51dbca656ed73ce817d4494e3966adf24055a86f5c5", ""],
["K", "34236"],
["P", "482d024b8acfde50e7429e5ac561d764f3a53a8b4fb0b6975369d9f0926ef839"],
["e", "7a14723b9ef999e74b1757a0fb74942cb6c121138d4ddafe096a57a67ed0a442", "", "8b69e548402afa997343d73e8088224a440f256350f6257b61acc4bb1fa4af4f"],
["k", "1111"],
["p", "8b69e548402afa997343d73e8088224a440f256350f6257b61acc4bb1fa4af4f"],
["client", "Divine", "31990:d95aa8fc0eff8e488952495b8064991d27fb96ed8652f12cdedc5a4e8b5ae540:divine-mobile", "wss://relay.divine.video"]
],
"content": "niiice",
"sig": "a5517fdea07647efa7ab1730fbea8df882690bba667e93ea5aeba4a73be6a49af1ee17c045535483650caf41dbbcb0897d5803fa39b59f395fd6f9bb193bb789"
}
The uppercase tags hold the video and its author while the lowercase e and k point at the parent comment, which is the shape the specification describes. Implementations reading and writing kind 1111 include Divine Mobile, whose client tag appears in the event above, Amethyst, whose comments appear in the same relay results, and nostrord, which renders thread posts as forum posts this week. The proposed patch format in NIPs PR #2438 builds on the same kind.
Send a NIP-17 DM to share a project or news item through the Nostr Compass project.
Write a comment