150 lines
5.2 KiB
Markdown
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.
|