block / block/buzz

Relay rejects spec-compliant NIP-17 DMs: gift-wrap (kind 1059) timestamps fail the freshness window

Open Beginner friendly
#4,192 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
32.7k
Forks
4.3k
Avg merge
1d 13h
Merged PRs (30d)
253

Description

# Relay rejects spec-compliant NIP-17 DMs: gift-wrap (kind 1059) timestamps fail the freshness window

## Summary

NIP-59 instructs clients to randomize a gift wrap's `created_at` into the past — "The `created_at` timestamp SHOULD be tweaked to thwart time-analysis attacks" (canonically up to 2 days back; `nostr-tools` and other mainstream implementations do this by default). The Buzz relay applies its event-freshness window to the **outer kind-1059 wrap** and rejects it:

```
["OK", "", false, "invalid: event timestamp too far from server time"]
```

Practical effect: **no spec-following NIP-17 client can deliver a private DM through a Buzz relay.** The wraps produced by `nostr-tools`' `nip17.wrapEvent()` (its defaults backdate the wrap) are rejected 100% of the time.

## Reproduction

Against a relay at `63496cc1d` (observed 2026-08-01):

```js
import { wrapEvent } from 'nostr-tools/nip17';
const wrap = wrapEvent(senderSecretKey, { publicKey: recipientHex }, 'hello');
// wrap.created_at is randomized into the past per NIP-59
// send over authenticated WS:
ws.send(JSON.stringify(['EVENT', wrap]));
// → ["OK", id, false, "invalid: event timestamp too far from server time"]
```

Re-signing the outer wrap with `created_at = now` (fresh ephemeral key, same sealed payload) is accepted — confirming the freshness check on the wrap's timestamp is the only blocker.

## Where

`crates/buzz-relay/src/handlers/ingest.rs:1524` — the freshness rejection is applied uniformly, with no carve-out for `KIND_GIFT_WRAP` (`buzz-core/src/kind.rs:60`, already registered in the accepted-kinds tables).

## Suggested fix

Exempt kind 1059 from the `created_at` freshness window (or widen its window to NIP-59's two days plus skew). The wrap's timestamp is deliberately meaningless — ordering/retention decisions for gift wraps cannot rely on it anyway, and the inner seal/rumor timestamps are end-to-end encrypted. Rejecting backdated wraps only forces clients to defeat NIP-59's privacy mechanism (our current workaround: re-stamp the outer wrap to `now`, leaking exactly the timing metadata the spec hides).

## Side observation (probably intentional, mentioning for completeness)

The HTTP bridge rejects gift wraps outright with `invalid: kind 1059 is only accepted via WebSocket`. If that restriction is by design it may be worth a line in the bridge docs, since every other event kind we use round-trips fine over `POST /events`.

Contributor guide

Open the contributing guide

Research direction

Start at crates/buzz-relay/src/handlers/ingest.rs:1524 and inspect the freshness check alongside the KIND_GIFT_WRAP definition in buzz-core/src/kind.rs:60. Verify the behavior with a kind-1059 WebSocket event using a deliberately backdated timestamp; done means spec-compliant gift wraps are accepted while freshness checks for other event kinds remain unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, rust
Domain
backend, networking
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.