From 520b4595ebaff7889f8b3cf18b2394a66a3524b1 Mon Sep 17 00:00:00 2001 From: grunch Date: Fri, 23 Oct 2026 19:14:54 -0300 Subject: [PATCH] feat(announcements): add the project's announcement keys MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Step 6 of specs/006 §10, the one commit that makes the channel live: the three npubs the maintainer handed over, pinned to their hex form by a test so a bech32 slip cannot trust a stranger. The reader subscribes to them on the next release that carries this. --- rust/src/nostr/announcements.rs | 35 ++++++++++++++++++++++---- specs/006-announcement-channel/spec.md | 15 ++++++----- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/rust/src/nostr/announcements.rs b/rust/src/nostr/announcements.rs index 43d3930b6..04a57dc14 100644 --- a/rust/src/nostr/announcements.rs +++ b/rust/src/nostr/announcements.rs @@ -20,15 +20,24 @@ pub const KIND_ANNOUNCEMENT: u16 = 38387; /// The keys allowed to publish announcements, as `npub`s (§4.1). /// -/// **Empty on purpose.** An empty allowlist means no authors, and no authors -/// means no subscription: every step of the feature can merge without the -/// channel being live. Filling it is a one-line commit of its own, with each -/// `npub` checked against a source outside this repo. +/// Three project keys, handed over by the maintainer on 2026-10-23 and held +/// equal to their hex form by `the_shipped_allowlist_is_the_project_keys`, +/// so a one-character slip in an `npub` cannot quietly swap a publisher. +/// Changing this list is a one-line commit of its own, with each `npub` +/// checked against a source outside this repo. An empty list means no +/// authors, and no authors means no subscription. /// /// Never a maintainer's personal key, a Mostro node key, or a key the app /// derives (`m/44'/1237'/38383'/0/N`). `npub` rather than hex because it is /// what a reviewer compares with the published value. -const ALLOWLIST: &[&str] = &[]; +const ALLOWLIST: &[&str] = &[ + // 00005b73d9f868d7ed4ab555c1d34e462608a4e0d0c93f27cd95ad5470911b8b + "npub1qqq9ku7elp5d0m22k42ur56wgcnq3f8q6ryn7f7djkk4guy3rw9su9q2k5", + // 000021340198eb372618e44309a705dec3e6c084a121076de0fde5c7b786aa00 + "npub1qqqzzdqpnr4nwfscu3psnfc9mmp7dsyy5ysswm0qlhju0dux4gqqzurqvu", + // 0000025590337c61ed410aa017fee518cf86c9047b406fbd56f036dc040cc14f + "npub1qqqqy4vsxd7xrm2pp2sp0lh9rr8cdjgy0dqxl02k7qmdcpqvc98s0x0lqw", +]; /// The locales every announcement carries, all of them and no others (§3.2). /// They are the app's locales — `lib/l10n/app_*.arb` — and change with them. @@ -568,6 +577,22 @@ mod tests { assert_eq!(allowed_authors().len(), ALLOWLIST.len()); } + /// The `npub`s above, in the hex form the keys were handed over in: a + /// bech32 typo that still decodes would otherwise trust a stranger. + #[test] + fn the_shipped_allowlist_is_the_project_keys() { + let hex: Vec = allowed_authors().iter().map(|k| k.to_hex()).collect(); + + assert_eq!( + hex, + [ + "00005b73d9f868d7ed4ab555c1d34e462608a4e0d0c93f27cd95ad5470911b8b", + "000021340198eb372618e44309a705dec3e6c084a121076de0fde5c7b786aa00", + "0000025590337c61ed410aa017fee518cf86c9047b406fbd56f036dc040cc14f", + ] + ); + } + #[test] fn another_kind_is_refused() { let keys = Keys::generate(); diff --git a/specs/006-announcement-channel/spec.md b/specs/006-announcement-channel/spec.md index 3cc38ecfc..3c34f3adb 100644 --- a/specs/006-announcement-channel/spec.md +++ b/specs/006-announcement-channel/spec.md @@ -2,7 +2,8 @@ **Feature Branch**: `006-announcement-channel` **Created**: 2026-08-26 -**Status**: In progress — steps 1 to 5 of §10 are built (the publisher tool is +**Status**: Built — every step of §10 is in, the allowlist holds the project's three keys, +and the channel goes live with the next release that carries them (the publisher tool is `rust/src/bin/announce.rs`, the procedure `docs/ANNOUNCEMENTS.md`): parsing and the allowlist in `rust/src/nostr/announcements.rs`, the reader and its cache in `rust/src/nostr/announcement_reader.rs`, the bridge and the Announcements toggle in @@ -356,10 +357,12 @@ Three rules: 3. **An entry that fails to decode is dropped with a `log::warn!`, and the rest still work.** A typo in one constant must not silence the channel. -**The allowlist ships empty**, and an empty allowlist means no authors, which means §5.1 -opens no subscription. Every phase of §10 can therefore merge without the channel being -live; turning it on is a one-line commit, reviewed on its own, with the npubs checked -against a source outside this repo. +**The allowlist shipped empty** through steps 1–5 of §10 — an empty allowlist means no +authors, which means §5.1 opens no subscription, so every phase could merge without the +channel being live. Step 6 filled it with the project's three keys (handed over by the +maintainer, hex-pinned by `the_shipped_allowlist_is_the_project_keys`); changing it is a +one-line commit, reviewed on its own, with the npubs checked against a source outside this +repo. The list cannot be updated over the wire, on purpose: a remotely updatable allowlist is a channel for taking over the channel. @@ -775,7 +778,7 @@ PR per step, per the workflow in CLAUDE.md. | 3 | ~~The bridge surface + the Announcements toggle (§6.1, §7), then `./scripts/frb-generate.sh`~~ — **done** | The off switch before the surface | | 4 | ~~Rendering into the existing notification surface, seven locales of chrome (§6.2)~~ — **done** | Last, when there is something to show | | 5 | ~~`rust/src/bin/announce.rs` + the publishing doc (§8)~~ — **done** | With or just after step 1, sharing its validator | -| 6 | **Add the real npubs** — one line, its own PR, npubs checked against a source outside this repo | The only commit that makes the channel live | +| 6 | ~~**Add the real npubs** — one line, its own PR, npubs checked against a source outside this repo~~ — **done** (three keys, §4.1) | The only commit that makes the channel live | | 7 | **Land MostroP2P/protocol#56** — the `38387` registration (§3) | A number reserved only in this repo is not reserved; it should land before the reader ships, and it depends on nothing here | Steps 1–5 can all merge without the channel existing in production. That is the point.