BitGate read standard events but published a private vocabulary, making it an
island. NIP-32 (kind 1985) is the shared moderation vocabulary; BitGate now
reads and writes it. See ADR 0007.
The design keeps flexibility central, since the goal is a reusable building
block. A label is (namespace, value, target) and carries no built-in meaning —
"deny", "nsfw", and "MIT" are the same kind of object. What a label means is a
policy decision an application supplies via labelMapping { namespace,
denyValues, allowValues }. The codec itself is vocabulary-free; decodeLabels
returns every label regardless of namespace for generic use.
A matching label becomes a contribution authored by the labeller, gated
identically: it denies someone only if the labeller holds the capability in the
roster, exactly like a community source. An untrusted labeller's deny does
nothing, and labels fail closed without verification like any administrative
event.
- consume: ingestEvent maps kind 1985; loadLabels(labellers) fetches them
- emit: commands.publishLabel(target, value), gated by the same capability as
denying, so the shared form is no weaker than the private one
- the widget bundle re-exports the label API for the drop-in path
Validated against 40 real kind:1985 events from public relays (ISO-639-1
language labels among them): all decoded with targets and namespaces, zero
throws. 897 tests; cold gates green; audit clean.
3.3 KiB
Nostr spec coverage
What BitGate implements, what it deliberately does not, and where the gaps are. Verified against the NIPs repository; specs move, so re-check before relying on this table for anything load-bearing.
Implemented
| NIP | Kind | How BitGate uses it |
|---|---|---|
| NIP-01 | — | Event shape, replaceable selection with a deterministic same-timestamp tiebreak, and signature verification via @bitgate/verify (id recomputed, then schnorr-checked) |
| NIP-02 | 3 | The viewer's follow list is the trust graph. Relay hints in the third tag position are captured as an outbox fallback. |
| NIP-19 | — | bech32 npub/note decoding at the codec edge, so the core stays hex-only |
| NIP-32 | 1985 | Labels, both directions. Consumed as capability-gated contributions via a configurable namespace/vocabulary; emitted with publishLabel. See ADR 0007. |
| NIP-51 | 10000 | Mute lists. Public p entries always; the viewer's own private entries decrypted through their signer. |
| NIP-44 | — | Decryption of the viewer's own private mute entries, delegated to the signer |
| NIP-56 | 1984 | Reports. The report type is read from the 3rd tag position, with fallbacks for clients that place it elsewhere. |
| NIP-65 | 10002 | Outbox model: each contact's mute list is fetched from their write relays |
| NIP-78 | 30078 | Canonical governance documents — roles, policy, and contribution lists — under namespaced d identifiers |
Deliberate choices
The viewer's private mutes are decrypted; nobody else's are. Another account's private list is none of our business, and the check lives at the codec boundary rather than relying on callers to behave. A user who mutes privately should still have their mutes take effect — ignoring them reads as the product being broken.
Report categories follow NIP-56 exactly in the social preset. Declaring a threshold for a category no standard client emits is dead configuration.
Commerce categories are non-standard on purpose. scam, counterfeit, and
not-as-described have no NIP-56 equivalent. Reports from generic clients
arrive as other and fall through to a default threshold, so they are never
silently ignored — including at checkout, where an unclassified complaint earns
review rather than an automatic denial.
Relays are transport, never authority. Signatures are verified through an injected verifier before anything authoritative is accepted.
Known gaps
Trust is binary. resolveTrustSet is "in the viewer's follows, or not".
Most things called web-of-trust weight by hop distance — follows-of-follows at
reduced weight. Adding depth naively would be wrong: a two-hop reporter would
count the same as someone the viewer actually follows. It needs hop-weighted
counting, which is a change to aggregation, not just to the set.
NIP-72 / NIP-29 communities. The role roster is a parallel invention to
NIP-72's moderator p tags. Note NIP-72 is now marked "unrecommended: try
NIP-29 instead", so a compat codec is only worth building against a specific
target application.
NIP-86 relay management, NIP-70 protected events. Relay-side moderation is a different layer and out of scope; BitGate governs what a client shows, not what a relay stores.