Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions rust/src/nostr/announcements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<String> = 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();
Expand Down
15 changes: 9 additions & 6 deletions specs/006-announcement-channel/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading