- Sashité for Developers
- Nostr
- NIPs
- Self-Timed Timing Relay
NIP-XX
Self-Timed Timing Relay
draft optional
This NIP defines a relay profile: the strict created_at ingestion policy by which a relay becomes a usable timing authority for applications that treat an event’s own created_at as authoritative timing (“self-timed” applications), together with the NIP-11 advertisement consumers verify. It defines no event kind. The Canonical Timing NIP defines how consuming applications turn acceptance by a relay conforming to this profile into an authoritative timestamp.
Abstract
An application that orders events, runs clocks, or resolves races on the events’ own created_at needs that field to be trustworthy — yet created_at is a self-claim any signer can set freely. A relay conforming to this profile makes the claim trustworthy at ingestion: it refuses any event whose created_at lies outside a tight window around its own clock, so an accepted event’s created_at is, within the window’s tolerance, the moment the relay received it. The relay advertises the enforced window via NIP-11 so consumers can verify the policy before relying on it — and so publishing clients can classify a timing rejection by computation, with no message parsing.
Motivation
Signed third-party timestamps (e.g. an Event Timestamp Attestation) make timing portable but require a designated attesting party per context. Many applications instead want a zero-extra-party baseline: the relay the application already uses vouches for timing implicitly, by refusing to store what it did not receive now. This profile pins the two pieces such applications need to interoperate: the ingestion window, and the discoverable advertisement from which everything else — including a client’s retry decision — is derived.
Specification
The ingestion window
A conforming timing relay enforces, on every event kind its timing service covers:
- It MUST reject any event whose
created_atis in the past relative to the relay’s own clock, beyond a small past tolerance covering clock skew and one-way network delay. This is the anti-backdating rule: a signer cannot obtain a timing earlier than the moment the relay received the event. The past tolerance SHOULD be a few seconds at most; a relay admitting minutes of backdating is not a usable timing authority. - It SHOULD reject any event whose
created_atis in the future beyond a future tolerance — hygiene against forward-dating and stuck events, not an anti-cheat requirement (forward-dating is self-penalizing in most timing-consuming applications). The window MAY be asymmetric: tight against the past, where backdating lives; looser toward the future.
Rejections
A rejection under either bound SHOULD use the standardized invalid: prefix in the OK message’s reason (NIP-01), and its human-readable remainder SHOULD name created_at for diagnosability (e.g. invalid: created_at too old). The reason text is not a wire contract: NIP-01 standardizes only the prefix, and deployed strict relays reject stale timestamps with arbitrary wording. A client MUST NOT key behavior on the reason text; it classifies a timing rejection by computation — it knows the created_at it sent, its own clock, and the relay’s advertised bounds (§NIP-11 advertisement), which side of the window it fell on follows (§Client publishing guidance).
A rejected event was never stored: it acquires no timing and carries no penalty beyond the retry.
NIP-11 advertisement
A conforming timing relay MUST advertise its enforced window in its NIP-11 information document, as limitation.created_at_lower_limit (the past tolerance) and limitation.created_at_upper_limit (the future tolerance). This profile requires both fields to be interpreted as deltas in seconds relative to the relay’s current time — how far into the past, respectively the future, an accepted created_at may lie. (NIP-11 itself does not fix delta-versus-absolute semantics in prose; the delta reading is the de-facto one, consistent with NIP-11’s own example values, and this profile makes it a requirement.) The advertised bounds MUST equal the enforced tolerances — derived from the same configuration, so the advertised and the enforced window cannot drift apart. Because the bounds are relative, the advertisement is a constant of the configuration: the document changes only when the configuration does.
Consumer rule
A consumer MUST rely on self-timed timing only for events it reads from a relay whose advertised past bound it has verified to be a few seconds at most (or from a mirror it trusts to be faithful to such a relay). An absent information document, absent limitation, or absent lower limit reads as not conforming. Events re-encountered outside a conforming relay carry only their self-claimed created_at; nothing distinguishes an honestly-timed event from a backdated one accepted by a lax relay. A consumer in that position MUST treat the event as pending per the Canonical Timing NIP §The pending state: no canonical timing is established until the event is retrieved from a conforming relay the consumer relies on.
Client publishing guidance
Because acceptance requires created_at to be at or ahead of the relay’s clock (within the past tolerance), a publishing client SHOULD set created_at to its best estimate of the relay’s current time plus a small forward buffer. On a rejection of a time-sensitive event, the client classifies by computation: if the created_at it sent is older than its clock estimate minus the advertised past tolerance, the rejection was stale-timing — it retries with a bumped value a bounded number of times and raises its per-relay clock-offset estimate; if the value was ahead of the estimate plus the advertised future tolerance, it lowers the estimate instead and never pushes created_at further forward. The reason text plays no part in this classification.
Security considerations
- The relay is the trust anchor. A malicious or misconfigured relay can accept backdated events and leaves no public artifact of having done so. Applications SHOULD treat relay-operator honesty as part of their trust model, prefer operators independent of other per-context authorities, and offer a signed-attestation mode where late verifiability matters.
- No late verifiability. Conformance vouches for timing at ingestion, toward the relay’s readership; it embeds no proof in the event. Consumers who cannot read from the conforming relay gain nothing from the profile.
- Mirrors. A mirror of a conforming relay preserves timing trust only if it replicates faithfully and its consumers trust that replication; the profile itself says nothing about mirrors.
- Rate limiting. A relay serving clock-timed applications SHOULD rate-limit acceptances per signer for flood-prone kinds, so a burst of candidates cannot inflate consumers’ work; this is the self-timed analogue of an attesting party’s rate limit.
Reference deployment
Sashité’s timing relay ships this profile with a past tolerance of 1 second and a future tolerance of 5 seconds; its client treats a relay as conforming when the advertised past bound is at most 5 seconds, uses a 1-second forward buffer, a 2-second bump per retry (at most 3), and a per-relay clock offset clamped to ±60 seconds. These are deployment choices, not part of the profile; a client conforms through §Client publishing guidance alone.
References
- NIP-01 —
created_at, theOKmessage and itsreason. - NIP-11 — the relay information document and its
limitationobject. - Event Timestamp Attestation — the signed-attestation alternative for portable, late-verifiable timing.
- Canonical Timing — how consuming applications turn relay-vouched
created_atinto canonical timing, pending state, and race resolution.
