Files
coracle-rust/book/10-protected-events.md
T
2026-05-20 14:27:26 -07:00

150 lines
5.2 KiB
Markdown

# Protected Events
The previous chapter introduced behavior tags through expiration: a tag
that requests something of the network without touching what the event
itself means. NIP-70 adds another, aimed at a different concern. Where
expiration asks relays to stop serving an event after a time,
NIP-70's `protected` tag asks them to be careful about who they accept
it from in the first place. Its shape on the wire is a single, valueless
tag:
```json
["-"]
```
That is the whole thing — a tag whose name is a lone dash and which
carries no value. Its mere presence is the signal. An event marked this
way should be accepted by a relay only when it is published *directly by
its author*: the connection delivering it must have authenticated over
NIP-42, and the authenticated key must match the event's `pubkey`.
## What the marker asks for
A relay that honors NIP-70 runs a small decision when a protected event
arrives. If the connection has not authenticated, the relay issues a
NIP-42 `AUTH` challenge and rejects the event for now, inviting the
client to prove who it is and try again. If the connection *has*
authenticated but the authenticated key is not the event's author, the
relay rejects the event outright — someone other than the author is
trying to push it. Only an authenticated author gets through.
As with expiration, the marker is a cooperative request, not a guarantee.
A relay is free to ignore it; a relay that has already stored the event
keeps it; the signature stays valid regardless. The tag asks well-behaved
relays to gate acceptance on authorship.
## The module
```rust {file=coracle-lib/src/lib.rs}
pub mod protected;
```
```rust {file=coracle-lib/src/protected.rs}
//! NIP-70 protected events: the valueless `["-"]` marker that asks
//! relays to accept an event only from its authenticated author.
//!
//! This module is the stateless half of NIP-70 — building the tag and
//! detecting it. The relay-side enforcement it implies (a NIP-42 `AUTH`
//! challenge followed by a pubkey comparison) needs a live, authenticated
//! connection and is handled in the relay layer.
use crate::events::HasTags;
use crate::tags::Tags;
```
## Detecting the marker
Because the tag has no value, detection collapses to a single question:
is a tag named `"-"` present? There is nothing to parse, so there is no
`Option` and no malformed-versus-absent distinction to draw — the answer
is a plain `bool`. `Tags::has` already answers exactly this.
```rust {file=coracle-lib/src/protected.rs}
/// Whether the tag set carries the NIP-70 `["-"]` protected marker.
///
/// A protected event asks relays to accept it only from its
/// authenticated author. This function reports the marker's presence;
/// acting on it is the relay's job.
pub fn is_protected(tags: &Tags) -> bool {
tags.has("-")
}
```
## Building the tag
The constructor produces a tag with the name `"-"` and no values. We
build it through `Tag::new` like every other tag, passing an empty value
iterator so the result is the one-element `["-"]`.
```rust {file=coracle-lib/src/tags.rs}
impl Tag {
/// Build a NIP-70 `["-"]` protected tag.
///
/// The tag has a name (`"-"`) and no values; its presence asks
/// relays to accept the event only from its authenticated author.
pub fn protected() -> Tag {
Tag::new("-", std::iter::empty::<String>())
}
}
```
## Methods on event types
A caller holding an event wants to ask whether it is protected without
reaching for its tags directly. Following the pattern from expiration, the
query is an extension trait bounded on [`HasTags`], so one default method
serves every event type. With `coracle_lib::prelude::*` in scope,
`event.is_protected()` works on both `HashedEvent` and `Event`.
```rust {file=coracle-lib/src/protected.rs}
/// Protected-event queries on any event that carries tags.
pub trait EventExtensionProtected: HasTags {
/// Whether this event carries the NIP-70 protected marker.
fn is_protected(&self) -> bool {
is_protected(self.tags())
}
}
impl<T: HasTags> EventExtensionProtected for T {}
```
```rust {file=coracle-lib/src/prelude.rs}
pub use crate::protected::EventExtensionProtected;
```
## Usage patterns
**Marking an event protected.** Attach the tag at template construction,
just like any other:
```rust
let event = EventContent::new()
.content("members only")
.tags(vec![Tag::protected()])
.kind(1)
.stamp(now)
.own(pubkey);
```
**Checking on receipt.** A relay or client deciding how to handle an
incoming event asks the event directly:
```rust
if event.is_protected() {
// only accept from an authenticated author
}
```
## What's next
Marking an event protected is half of NIP-70; honoring the mark is the
other half, and it lives where connections and authentication do. The
[Relay Authentication](26-relay-authentication.md) chapter implements the
`AUTH` exchange and the author check that turn this tag into a rule.
One consequence is worth flagging now: a protected event should not be
re-broadcast by being embedded inside someone else's repost, since that
would route it past the very acceptance check it asked for. Reposts are
their own topic; the protected marker is simply something that logic will
need to consult.