Summary
A kind-9 channel message composed and sent from Buzz Desktop, fetched back via the relay's POST /query, fails every NIP-01 verification a third party can perform:
id ≠ sha256(json([0, pubkey, created_at, kind, tags, content])) over the delivered fields
schnorr.verify(sig, id, pubkey) — false (signature does not verify over the stated id)
schnorr.verify(sig, recomputed_id, pubkey) — false (nor over the recomputed one)
This holds for text-only messages and for messages with attachments alike — the imeta enrichment is not the cause. Kind-7 reactions from the same community, fetched the same way, verify correctly (id recomputes, sig valid), so signing is not uniformly broken; it appears specific to the Desktop kind-9 authoring path.
Environment
- Buzz Desktop 0.5.x (macOS), hosted relay (
<community>.communities.buzz.xyz)
- Events fetched via
POST /query with NIP-98 auth; verification with @noble/curves schnorr (BIP-340) over the sha256 of the canonical NIP-01 serialization — the same code path that verifies this community's kind-7 reactions successfully every day
Reproduction (~2 minutes)
- In Buzz Desktop, post any message into a channel (no attachments needed).
- Fetch it back:
POST /query with [{ "kinds": [9], "#h": ["<channel-id>"], "since": ... }].
- Recompute the id:
sha256(JSON.stringify([0, pubkey, created_at, kind, tags, content])) over the delivered fields — it will not equal the delivered id.
schnorr.verify(sig, id, pubkey) and schnorr.verify(sig, recomputed_id, pubkey) — both false.
Observed on four separate messages (three with attachments, one text-only), captured byte-exact by redirecting the query response to a file. As a control, kind-7 reactions fetched from the same relay in the same session verify correctly.
One possibly related observation: the delivered content shows formatting the author did not type (paragraphs re-indented with two leading spaces, whitespace-bearing blank lines), suggesting the content is transformed somewhere between composition and delivery.
Expected
Per NIP-01 — and Buzz's own framing that every message is a signed event — a delivered event should verify: the id should be the hash of its serialized fields, and the sig should verify over that id under the delivered pubkey.
Guess at the cause
The shape of the failure (sig valid under no checkable combination, pubkey delivered as the author's identity key) is what you'd see if a device key signs the event and the relay rewrites pubkey to the author's identity key on delivery, possibly alongside a content transform. If that's the design, is there a documented way for a third party to verify authorship — a delegation/attestation tag binding device keys to identity keys, or an endpoint exposing the binding?
Why it matters
Any integration that verifies events before acting on them — bots, bridges, moderation tooling, audit systems — currently has to choose between trusting the relay's word for authorship or ignoring every Desktop-authored message. We hit this building a bot that imports content posted by allowlisted team members: the signature gate we wanted had to be replaced with relay-membership trust, with a pinned test watching for the day verification starts passing.
Happy to share sanitized event JSON (the messages are from a private channel) or run diagnostics against a test community if useful.
Summary
A kind-9 channel message composed and sent from Buzz Desktop, fetched back via the relay's
POST /query, fails every NIP-01 verification a third party can perform:id≠sha256(json([0, pubkey, created_at, kind, tags, content]))over the delivered fieldsschnorr.verify(sig, id, pubkey)— false (signature does not verify over the stated id)schnorr.verify(sig, recomputed_id, pubkey)— false (nor over the recomputed one)This holds for text-only messages and for messages with attachments alike — the imeta enrichment is not the cause. Kind-7 reactions from the same community, fetched the same way, verify correctly (id recomputes, sig valid), so signing is not uniformly broken; it appears specific to the Desktop kind-9 authoring path.
Environment
<community>.communities.buzz.xyz)POST /querywith NIP-98 auth; verification with@noble/curvesschnorr (BIP-340) over the sha256 of the canonical NIP-01 serialization — the same code path that verifies this community's kind-7 reactions successfully every dayReproduction (~2 minutes)
POST /querywith[{ "kinds": [9], "#h": ["<channel-id>"], "since": ... }].sha256(JSON.stringify([0, pubkey, created_at, kind, tags, content]))over the delivered fields — it will not equal the deliveredid.schnorr.verify(sig, id, pubkey)andschnorr.verify(sig, recomputed_id, pubkey)— both false.Observed on four separate messages (three with attachments, one text-only), captured byte-exact by redirecting the query response to a file. As a control, kind-7 reactions fetched from the same relay in the same session verify correctly.
One possibly related observation: the delivered
contentshows formatting the author did not type (paragraphs re-indented with two leading spaces, whitespace-bearing blank lines), suggesting the content is transformed somewhere between composition and delivery.Expected
Per NIP-01 — and Buzz's own framing that every message is a signed event — a delivered event should verify: the id should be the hash of its serialized fields, and the sig should verify over that id under the delivered pubkey.
Guess at the cause
The shape of the failure (sig valid under no checkable combination, pubkey delivered as the author's identity key) is what you'd see if a device key signs the event and the relay rewrites
pubkeyto the author's identity key on delivery, possibly alongside a content transform. If that's the design, is there a documented way for a third party to verify authorship — a delegation/attestation tag binding device keys to identity keys, or an endpoint exposing the binding?Why it matters
Any integration that verifies events before acting on them — bots, bridges, moderation tooling, audit systems — currently has to choose between trusting the relay's word for authorship or ignoring every Desktop-authored message. We hit this building a bot that imports content posted by allowlisted team members: the signature gate we wanted had to be replaced with relay-membership trust, with a pinned test watching for the day verification starts passing.
Happy to share sanitized event JSON (the messages are from a private channel) or run diagnostics against a test community if useful.