Add protected chapter
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
# 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::{Event, HashedEvent};
|
||||
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. Both `HashedEvent` and `Event` carry a
|
||||
`Tags` field, so the method facades delegate to the free function.
|
||||
|
||||
```rust {file=coracle-lib/src/protected.rs}
|
||||
impl HashedEvent {
|
||||
/// Whether this event carries the NIP-70 protected marker.
|
||||
pub fn is_protected(&self) -> bool {
|
||||
is_protected(&self.tags)
|
||||
}
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Whether this event carries the NIP-70 protected marker.
|
||||
pub fn is_protected(&self) -> bool {
|
||||
is_protected(&self.tags)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 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.
|
||||
Reference in New Issue
Block a user