Digital Trust Protocol

Status: draft · 2026-09-30 · normative; extends wot-model.md §2.5

On Nostr, trust points at a pubkey (p). On X, trust points at an X user id (i=user:id:X). A trust statement about an X account becomes a hop only when, for the observer, user:id:X is bound to the npub that controls it. This document defines when that happens, in two modes:

ModeIdeaEffect
LenientBelieve the X ↔ npub link your web of trust reportsRoutes can differ per observer: two people with different webs of trust may bind the same X account to different npubs
StrictBoth sides must agree: someone you trust saw the link on X, and the npub claims the X account in kind 10011Unverified hints, false hints, and npubs without a 10011 claim are cut; routes converge on the key that claims the account

The mode is a query parameter (bindingMode). The same mode on the same data gives the same answer in every runtime.

1. Inputs#

InputMeaningWhere it comes from
Own evidenceThe observer's own client saw the link on X: the npub in X's Bio, or in a linking post already loaded by XAttention xIdentities (xNpub, postNpub), used by the extension's local heap only. Personal Server and Global Server: none — X data stays in the extension.
Witness statementA kind 32009 about i=user:id:X whose primary i tag carries a bare npub1… hint, read under s=x.comRelays
ClaimPubkey P's winning, valid kind 10011 contains twitter_id:X (with the matching twitter tag, per NIP-39)Relays

Only user:id:<digits> subjects bind. post:id, e, and other i subjects never bind.

2. Witness statements#

For each author A and X, A's witness is A's newest active v=1 statement about user:id:X that passes every row below (tie on created_at → lower event id). One witness per author.

ConditionRuleWhy
Hint presentThe primary i tag carries a bare npub1…; the first one countsSame as subjectNpubFromHints today
Hint well-formedA malformed hint makes the event invalid at the protocol layer (NIP-32009 checklist item 7); it never reaches the model—
ScopeThe statement's scope set contains x.comNIP-32009: s selects the hint vocabulary; an empty-scope hint is not interpreted
Valuev = 10 and -1 statements are evidence about the account, not identity witnesses. They are not vetoes.
Not selfHint npub ≠ AAn issuer naming itself is a claim, worth the same as its 10011
ContextAny cThe hint describes who controls the account, whatever the purpose
Author counts for this rootA is the root (own witness), or A qualifies at level ℓ in the root's walk (WoT witness)A hint from outside your web of trust never counts

Witness-only statements: a verifier service (for example X OAuth plus a signed Nostr challenge) can publish with c=identity:attest. That context is not in Chain(32009, identity), so the statement witnesses a bind without becoming a trust hop or trust evidence.

3. Claim states#

P's kind 10011LenientStrict
Valid winner names twitter_id:XConfirmedConfirmed
No 10011 known for PAllowed, unconfirmedCut
Winner names other X accounts onlyAllowed, unconfirmed (one npub may have several X accounts)Cut (false hint)
Winner revoked (X tags removed)Allowed, unconfirmedCut
Invalid 10011Treated as noneCut

A 10011 alone never creates a candidate. Anyone can claim any X id; a claim only confirms a candidate that someone the observer trusts has witnessed.

4. Bind decision#

bind(R, X, ℓ) — the npub X is bound to when the root R counts statements made by authors at level ℓ. It is a pure function of the root's walk up to level ℓ, the witnesses, the claims, the mode, and the own evidence.

StepLenientStrict
1. Own evidence exists (Bio beats linking post)Bound to that P. Stop.Bound to that P if P claims X, else unbound. Stop. Other witnesses are not consulted: the X owner's own Bio outranks anyone's hint.
2. Own witness existsBound to that P. Stop.Bound to that P if P claims X, else unbound. Stop.
3. WoT witnesses by qualifying authors at levels ≤ ℓAll countOnly witnesses whose P claims X count
4. Rank the remaining witnessesLower author level, then higher author trust count, then newer created_at, then lower author pubkeySame
5. ResultThe top witness's P, or unbound if noneSame

Because ranking starts with the lowest level, a bind made at level ℓ never changes at deeper levels: the nearest eligible witness decides. In strict mode a nearer false hint is skipped, so a correct farther witness can still bind.

5. Situations, side by side#

From one root's point of view. "Claims" means P's 10011 names X.

#SituationLenientStrict
1Own Bio (or linking post) shows P; P claims XBound to PBound to P
2Own Bio shows P; P has no 10011Bound to PUnbound
3Own Bio shows P; your friends' hints say Q, and Q claims XBound to PUnbound (Bio outranks hints; P unconfirmed)
4Your own 32009 hint says P; P claims XBound to PBound to P
5Your own 32009 hint says P; P has no 10011Bound to PUnbound
6Nearest WoT witness says P; P claims XBound to PBound to P
7Nearest WoT witness says P (no claim); a farther witness says Q (claims X)Bound to PBound to Q
8Two witnesses at the same level: P (author trust count 5) and Q (count 3); only Q claims XBound to PBound to Q
9Witness says P; P's 10011 names a different X accountBound to PUnbound (false hint)
10Only authors outside your web of trust hint PUnboundUnbound
11No witness at all; P's 10011 claims XUnboundUnbound
12Witness says P, but in a v=0 / v=-1 statement, or without s=x.com, or the hint names the author itselfNot a witnessNot a witness
13Witnesses say P; another key Q also claims X in its 10011, nobody witnessed QBound to PBound to P if P claims X, else unbound (Q is never a candidate)

6. Hop rules#

A qualifying author A at level ℓ has a selected statement e about subject s (wot-model §4.3). Both modes, with bind(R, X, ℓ) from §4:

sBind at level ℓe.vEffect on the walk
p:P—1Trust counts for P at level ℓ+1; P is queued and hops if it qualifies
p:P—-1 / 0Distrust / Neutral counts for P; no hop
user:id:XBound to P1Same as p:P: X and P are one node; hop
user:id:XBound to P-1 / 0Same as p:P; no hop
user:id:XUnboundanyTerminal evidence about the X account; this branch stops
post:id, e, other inever boundanyTerminal evidence
  • If A has statements about both p:P and a bound user:id:X, one of them counts (wot-model §4.3 tie-break).
  • For a query about user:id:X, the evidence at hitting degree d includes statements about p:P only when bind(R, X, d − 1) is P.

7. Different observers, different routes#

Carol and Dave both trust X's account and hint different npubs. P2 is the real owner and published a 10011; P1 did not.

Alice (trusts Carol)Bob (trusts Dave)
WitnessCarol (level 1): X is P1Dave (level 1): X is P2
LenientX → P1: Alice's trust in X flows to P1, and P1's statements become hops for AliceX → P2
StrictCarol's hint is cut (P1 has no claim); X is unbound for Alice unless another witness names P2X → P2

Lenient keeps each person's view; a wrong witness misleads exactly the people for whom that witness is the nearest. Strict gives up hops for accounts without a 10011 and closes that door.

8. Result provenance#

Trust and rating results about user:id:X carry an additive binding field (resolver-api.md):

{
  "mode": "strict",
  "pubkey": "<hex>",
  "source": "own-bio",
  "confirmed": true,
  "witnessEventId": "<id>",
  "witnessDegree": 2,
  "claimEventId": "<id>"
}

source is one of own-bio, own-post, own-statement, witness, none (own-bio / own-post occur only in the extension's local heap; servers hold no X data).

witnessDegree is the degree of the witness statement, counted like the result degree: level(A) + 1 for witness author A. The root's own statement (own-statement) has witnessDegree: 1; a witness by a level-1 author has 2. It is present exactly when witnessEventId is: for sources own-statement and witness, and on a strict-mode unbound result with a cutCandidate, where both describe the cut candidate's witness. A bind used at level ℓ always has witnessDegree ≤ ℓ + 1.

Which level's bind a result reports. A connected result at degree d reports bind(R, X, d − 1), the bind its evidence was counted under. A disconnected result reports bind(R, X, ℓ*) at the level of the walk's last hit check (wot-model.md §6.1): D − 1 at maxDepth, the last non-empty level when the frontier ran out, or completeDepth when a budget stopped the walk. Unbound results carry source: "none" and, in strict mode, the top cut candidate and the reason (no-claim, false-hint) so the UI can explain it.

9. Publishing rule#

A client SHOULD attach an npub hint to its trust statements only from own evidence (Bio or linking post), or from a bind that is confirmed by a 10011. It SHOULD NOT re-publish a bind it learned only from someone else's hint: that turns a second-hand guess into what looks like a first-hand witness for the next observer.

Attention today attaches its winning npub, which can come from another issuer's hint (primaryNpubFromRow → eventNpub). See conformance gap G9.

10. Runtimes#

RuntimeOwn evidenceWitnessesClaimsDefault mode
Attention (local heap)xIdentities Bio / linking postSynced 3200910011 fetched for Bio npubs today; strict also needs claims for witnessed candidatesLenient (decision D5); strict on request
Personal ServerNone — public events only; step 1 of §4 never appliesSynced 32009 and the extension's own signed eventsSynced 10011 for candidatesSet per query (bindingMode); lenient by default (decision D5)
Global ServerNone — public events only; step 1 of §4 never appliesAll 32009 its Sync Servers pushAll 10011 its Sync Servers pushSet per query (bindingMode); lenient by default (decision D5)

Both servers bind the same way. When Attention uses a server, the user's own X observation (Bio / linking post) counts only once it is published as the user's own witness (§9): Attention attaches that npub as the hint when the user trusts the account. Until then only the local heap can use it.

11. Security notes#

  • Lenient risk: a trusted but careless or malicious witness can bind an X account to the wrong npub for the people who trust that witness most closely. The damage is limited to their webs of trust and visible in binding provenance.
  • Strict cost: X accounts whose owners never published 10011 stay terminal, including accounts with an npub in their Bio. Fewer hops, especially early in adoption.
  • Both modes: a 10011 alone, or a hint from outside the observer's web of trust, never binds. Distrust statements are not vetoes of a bind.
  • A verifier service is just an issuer. Observers who trust it (directly or through their web of trust) get its binds; there are no server-wide aliases outside this model.