Digital Trust Protocol

Attention currently uses:

  • addressable kind 32009 for single-subject trust, Neutral, distrust, and Delete;
  • addressable kind 32014 for subject ratings (worth the reader's time);
  • replaceable kind 10011 for verified NIP-39 X identity links.

Kind 32014 is never a Web-of-Trust hop. Ratings are ingested for authors already on the kind 32009 frontier and queried with QUERY_RATING / QUERY_RATING_BATCH. The two questions 32009 and 32014 answer are in wot-questions.md.

Optional l tags on kind 32009 and kind 32014 augment the trust or rating with further human clarification. They are not hops and are not part of d.

Kind 32009 trust statements#

NIP-32009 defines one addressable slot per author and d tag. The required tags are:

  • exactly one subject tag: p, e, or i;
  • d, always sha256(material) where material is subject:scope:context;
  • v: 1 for trust, 0 for Neutral, -1 for distrust, or empty to Delete;
  • optional k (identifier class) and s (domain/namespace);
  • optional c for a canonical hierarchical context (omit for global);
  • optional l labels (augment the statement with further clarification; optional description on tag[2]; not in d; not a hop);
  • optional structured hints after the primary p / e / i value (class:property:value), or a bare npub1… for the subject's linked Nostr pubkey;
  • optional x and y activation and expiration times.

The current X UI publishes stable i subjects:

user:id:<numeric-account-id>
post:id:<numeric-post-id>

Profile publishing is disabled until a numeric account ID is resolved; a mutable handle is never a durable trust subject. New X person-trust statements use s=x.com and c=identity. Post trust and ratings omit c. Older empty-context user statements remain valid, and X prefers an x.com statement over an empty-scope statement for the same author, subject, and context.

The second element of a p, e, or i tag is always the primary subject. Later elements are advisory hints (class:property:value or bare npub1…). Hints are ignored for graph edges and do not enter d.

The question control is deliberately local-only. It updates the current card and publishes no Nostr event.

Example account statement:

{
  "kind": 32009,
  "tags": [
    ["d", "<sha256(user:id:11348282:x.com:)>"],
    ["i", "user:id:11348282", "user:name:nasa", "npub1…"],
    ["k", "user:id"],
    ["s", "x.com"],
    ["v", "1"]
  ],
  "content": ""
}

Example post statement:

{
  "kind": 32009,
  "tags": [
    ["d", "<sha256(post:id:2080659774136291424:x.com:)>"],
    ["i", "post:id:2080659774136291424"],
    ["k", "post:id"],
    ["s", "x.com"],
    ["v", "-1"]
  ],
  "content": ""
}

Content is an optional short human explanation (ternary v is the machine edge). Attention product policy: optional, never required; ~144-character plain-text compose cap; show in path/detail views only; prefer reasons on account trust over posts — see design.md § Trust statement content. Attention does not put X post bodies or X authentication data in events.

Validation and replacement#

Before storage or graph use, the backend verifies the Nostr shape, event hash, signature, kind, one-subject rule, primary subject, subject-hint syntax (structured or bare npub1…), value, canonical context, deterministic d tag, activation/expiration interval, and content limits. Hints are excluded from d, so changing them does not create a new replacement slot.

The newest valid event for (author pubkey, d) wins by greatest created_at; the lexically lower event ID wins a timestamp tie. The winning empty-v event Deletes the slot and does not revive an older statement. Neutral (v = "0") is an active statement kept in the graph and shown in evidence lists, but is not a traversal hop. Context lookup tries the exact context, its nearest parents, then the empty general context, skipping deleted or inactive slots along the way. Neutral occupies the slot and does not fall through.

Local WoT interpretation#

Positive active kind 32009 statements with a p subject are traversable trust edges. Neutral and negative p statements are evidence but not traversal edges. e and i subjects, including X account and post subjects, are terminal evidence.

Relay synchronization and query traversal are bounded by depth, fan-out, authors, and event count. Data Synchronization settings choose interval frontier REQ/EOSE (with a selectable wake interval), heap-frontier live subscriptions, or Subscribe all (author-unfiltered live 32009 / 32014 / 10011 only — not an all-kinds firehose). Continuous modes keep the service worker warm so live sockets and the Graph heap are not idled off between events; interval mode may sleep until the next alarm. Query results retain direct and reachable statements, paths, source event IDs, graph version, and truncation state. The resolution is trusted, distrusted, mixed, or none; it is local to the selected root and context and is not an objective or numerical Web-of-Trust score.

Current winning signed events and reducer indexes are durable in IndexedDB; superseded addressable replacements should not be retained locally (see architecture.md § Minimal data and memory). Per-relay, per-scope cursors use an overlap window and advance after EOSE. Publishing is write-through to IndexedDB and a durable per-relay outbox before delivery is attempted.

Kind 32014 subject rating#

NIP-32014 defines an addressable score (0–100; empty score = Delete) for “is this artifact worth my time?” It reuses kind 32009 d material and subjects, is never a graph hop, and is resolved with exact c only. See wot-questions.md. Attention stores winners in the existing events table (addressKey = 32014:pubkey:d) and indexes active claims beside the trust graph. Query aggregation uses the nearest trusted degree only, matching kind 32009.

Kind 10011 X identity linking#

NIP-39 links the event author's Nostr key to an X account with two matching i tags:

{
  "kind": 10011,
  "tags": [
    ["i", "twitter:nasa", "2080659774136291424", "post:id:2080659774136291424"],
    ["i", "twitter_id:11348282", "2080659774136291424", "post:id:2080659774136291424"]
  ],
  "content": ""
}

Both tags retain the standard raw proof-post ID in element 3. The optional fourth element repeats that reference in the structured hint form for clients that understand scope-aware hints; legacy three-element tags remain valid. The handle is informational; twitter_id is the stable account identifier. When Attention updates this replaceable event, it removes prior X-provider tags and preserves unrelated provider tags and content.

The backend generates the NIP-39 proof text and verifies the event signature and matching twitter / twitter_id tags. A kind 10011 claim is recorded from those tags. Attention does not fetch the public X profile to map the handle to a numeric ID. Verified claims and provenance are persisted in IndexedDB.

Linking an X account uses the bio workflow. Attention does not open X compose to publish a proof post. Kind 10011 is published from the signed-in account's handle and numeric ID.