# Plan: Protected Events ## Topic Summary NIP-70 defines an optional, valueless `["-"]` tag — the "protected" marker. Its presence asks relays to accept the event only when published directly by its author: the publishing connection must be NIP-42-authenticated and the authenticated pubkey must equal the event's `pubkey`. Relays receiving a protected event over an unauthenticated connection should issue an `AUTH` challenge and reject it for now; relays receiving one from an authenticated but non-matching connection should reject it outright. Like NIP-40 expiration, this is a **behavior tag** — orthogonal to kind, a cooperative request rather than a guarantee. This chapter adds tag-layer support only: a constructor, a boolean predicate, and method facades. Relay-side enforcement is deferred to the Relay Authentication chapter (ch 26), which has the NIP-42 machinery. The tag carries no value, so — unlike expiration — there is nothing to parse and no `get_*` function. ## Chapter Outline 1. **Framing** — protected as a behavior tag, sibling to expiration; the `["-"]` tag has no value; the cooperative, relay-enforced (not cryptographic) nature of the request. Pick up the thread chapter 9 left dangling. 2. **What the relay rule is** — state the NIP-42 auth-then-compare rule in prose so the reader understands what the tag *asks for*, and explain why none of that logic lives here (the tag layer is stateless; enforcement needs a connection and an authenticated pubkey — ch 26). 3. **The module declaration** — `pub mod protected;` in `lib.rs` plus the module header doc comment. 4. **The predicate** — `is_protected(&Tags) -> bool`. A thin wrapper over `Tags::has("-")`; justify it on consistency and discoverability (a named NIP-70 function), the same justification the expiration chapter used for its constructor. No value to parse means no `Option`, just `bool`. 5. **Building the tag** — `Tag::protected() -> Tag` returning `["-"]`. One line; the narrative explains the unusual single-dash, valueless shape. 6. **Methods on event types** — `is_protected(&self)` on both `HashedEvent` and `Event`, delegating to the free function. 7. **Usage patterns** — building a protected event with the template builder; checking on receipt. Brief. 8. **What's next** — bridge forward: enforcement lands with relay authentication (ch 26); note the NIP-18 repost interaction (a protected event shouldn't be embedded in a repost) as a forward reference, not implemented here. ## API Design ### Module `coracle-lib/src/protected.rs` ```rust use crate::events::{Event, HashedEvent}; use crate::tags::Tags; /// True if the tag set carries the NIP-70 `["-"]` protected marker. pub fn is_protected(tags: &Tags) -> bool; impl HashedEvent { pub fn is_protected(&self) -> bool; } impl Event { pub fn is_protected(&self) -> bool; } ``` `is_protected(tags)` is `tags.has("-")`. ### Tag constructor in `coracle-lib/src/tags.rs` ```rust impl Tag { /// Build a NIP-70 `["-"]` protected tag. pub fn protected() -> Tag; } ``` Implemented as `Tag::new("-", std::iter::empty::())` — a tag with the name `"-"` and no values, serializing as `["-"]`. ## Code Organization - **`coracle-lib/src/protected.rs`** — new module: the free function and the two method impls. Mirrors `expiration.rs`. - **`coracle-lib/src/lib.rs`** — add `pub mod protected;` (after `expiration`, matching SUMMARY order: ch 10 follows ch 9). - **`coracle-lib/src/tags.rs`** — append a `Tag::protected` constructor inside a new `impl Tag` block (code blocks targeting the same file concatenate; another `impl Tag` block at the end is valid Rust, exactly as `Tag::expiration` does). - **`coracle-lib/tests/protected.rs`** — hand-written integration test, not tangled. Covers the constructor shape, the predicate (present / absent), the method facades on both event types, and tag survival through hash + sign. ## Dependencies None. Pure std and existing crate types. No new `Cargo.toml` entries, no new workspace members. ## Narrative Notes - **Lead by connecting to expiration.** Chapter 9 ends with an explicit promise: "The next chapter introduces another — NIP-70's `protected` tag, which tells relays not to accept an event from anyone but its author." Open by fulfilling that promise. The reader already has the behavior-tag mental model. - **The valueless tag is the one novel thing.** Spend a sentence on why `["-"]` has no value and why that collapses the whole API to a boolean — there is no malformed-vs-absent distinction to make, no parse to fail. - **Be honest about enforcement.** This is the "hint, not guarantee" point from expiration, sharpened: the tag asks relays to require NIP-42 auth from the author. Without a cooperating relay, the tag does nothing. State the relay rule precisely (auth challenge, then pubkey comparison) so the reader knows what they're requesting, then say plainly that implementing it is ch 26's job. - **Justify the free function.** It is `tags.has("-")` and nothing more. Defend it the way ch 9 defended `Tag::expiration`: a named, discoverable NIP-70 entry point reads better at call sites and at the doc level than a bare string literal, and it keeps the protected-events surface symmetrical with the rest of the library (a free function on `&Tags` plus method sugar on the events). - **Keep it short.** This is the shortest behavior-tag chapter — no parsing, no timestamp comparison, no clock. Resist padding. The interest is conceptual (what the tag asks of the network), not mechanical. - **Forward references, not detours.** Mention enforcement (ch 26) and the repost interaction once each; do not explain them. ## Design Decisions - **Boolean, not `Option`.** The tag has no value; presence is the entire signal. `is_protected -> bool` is correct and matches every reference (`isProtectedEvent`, `IsProtected`, `Event::is_protected`). - **No `get_protected`.** Nothing to return but a bool, which `is_protected` already gives. A `get_*` would imply a value that does not exist. - **Free function on `&Tags` plus method facades.** Matches the `get_expiration`/`get_pow` pattern exactly. The free function is reusable by any caller holding a tag slice (e.g., a relay inspecting raw tags before reconstructing an `Event`); the methods are the ergonomic surface. - **Constructor in `tags.rs`, not `protected.rs`.** Lives with `Tag::new` and `Tag::expiration`; discoverable where callers build tags; no import from `protected` just to construct one. Identical placement reasoning to ch 9. - **Enforcement deferred to ch 26.** The acceptance rule needs an authenticated connection and a pubkey to compare against — state, not a stateless tag query. Putting a `should_accept`-style helper here (the option the user declined) would strand half the logic away from where the connection lives. The chapter describes the rule and points forward. - **No `set_protected(bool)` builder helper.** ndk/applesauce expose a toggle, but our builder surface is already chainable via `.tags(Tags::new().add(...))` / `Tag::protected()`. Adding a toggle would be ceremony, consistent with ch 9 declining `set_expiration`. - **Constructor uses an empty value iterator.** `Tag::new("-", iter::empty())` produces `["-"]`. Verified against `Tag::new`'s signature (name plus an `IntoIterator` of values); an empty iterator yields a one-element tag. ## Open Questions - Whether to show the relay-enforcement pseudocode inline. Decision: no — a prose statement of the rule is enough; code belongs in ch 26 where the connection and auth state exist. Showing non-tangled relay code here would invite the reader to think enforcement is part of this layer. - Whether to add a `Tags`-level convenience beyond the free function. Skip, per ch 9 reasoning: keep `Tags` a generic container; the named function lives in `protected.rs`. - Whether to document the NIP-18 repost interaction in depth. Keep it to a single forward-reference sentence; reposts are their own topic.