NIP-32009
Single-Subject Digital Trust Statement#
draft optional
Status#
Experimental proposal for event kind 32009. Coordinate registration before
treating it as a stable interoperable standard.
Abstract#
Kind 32009 records one author's explicit trust (1), Neutral (0),
distrust (-1), or Delete (empty v) of exactly one subject.
It is addressable (30000–39999). For each author and replaceable slot, a
newer event replaces the older one by using the same d tag. The d tag is
always a 64-character lowercase hex SHA-256 digest.
The event is a direct trust input, not an objective score. A dedicated kind
lets relays filter with kinds: [32009]. Tags k, s, and c refine relay
queries. Scope (s) is for relay discovery (and participates in d); graph
context fallback uses hierarchical c only. Subject tags may also carry
advisory identity hints (structured class:property:value, or a bare
npub1… bech32 for the subject's linked Nostr pubkey). These values are
advisory and do not enter d material.
Motivation#
Follows and likes are not trust. Kind 32009 is an explicit directed edge
with:
"1"/"0"/"-1"as active trust, Neutral, or distrust (0may carry a reason incontentand optional labels);- empty
vas a Delete tombstone for that replaceable slot; - optional hierarchical context (
c) for purpose (broad → narrow via:); - optional scope (
s) for domain/namespace relay filters; - typed subjects (
p/e/i) where platform ids do not embed the site name insidei. - optional structured hints after the primary
p/e/ivalue (class:property:value), or a bare canonicalnpub1…pubkey hint onifor X account subjects. - optional labels (
l) to augment the statement with further human clarification (not ind, not a hop).
Exactly one subject per event keeps (author, d) replacement deterministic.
Clients MUST reject zero or more than one subject (p / e / i).
Canonical shape (recommended)#
i = "<class>:<property>:<value>" # required typed subject; e.g. user:id:11348282
v = "" | "1" | "0" | "-1" # required tag; empty = Delete; "0" is Neutral
k = "<class>:<property>" # optional; e.g. user:id (typed prefix in i)
s = "<domain>" # optional; e.g. x.com
c = "<purpose hierarchy>" # optional; omit for global; e.g. identity
l = "<label>" [ "<description>" ] # optional; zero or more; not in d
x = "<unix-seconds>" # optional activation time
y = "<unix-seconds>" # optional expiration time
| Tag | Role | Example |
|---|---|---|
i | Typed subject (class + : + property + : + value) — required for this shape | user:id:11348282 |
v | Trust value or Delete — required tag | 1 / 0 / -1 / (empty) |
k | Typed subject prefix (#k filter) — optional | user:id |
s | Domain/namespace (#s filter; also in d) — optional | x.com |
c | Purpose hierarchy (graph fallback; also in d) — optional; omit = global | (omit) / identity |
l | Augments the statement with further clarification — not in d | reviewer |
x | Activation time (Unix seconds) — optional | 1672531600 |
y | Expiration time (Unix seconds) — optional | 1672618000 |
The first value in a p, e, or i tag carries the primary subject. Values
after the first are optional identity hints; they do not create additional
subjects. A hint is either structured class:property:value or a bare
NIP-19 npub1… (subject's linked pubkey — not the issuer). Do not
publish k / s / c / l without a subject and v. k alone is not a subject.
x and y bound when the statement is active; omit both for always-active
statements.
Examples:
["k", "user:id"],
["i", "user:id:11348282"],
["s", "x.com"],
["v", "1"]
["k", "post:id"],
["i", "post:id:2080659774136291424"],
["s", "x.com"],
["v", "-1"]
NIP-73 inspired i consists of a typed prefix (class:property) plus a value;
k repeats that prefix for class-wide filters. Global schemes like isbn:
often need no domain axis. Opaque platform ids do: put the site in s, not in
i. Do not use ext:twitter_* / ext:x:* forms for kind 32009 subjects.
NIP-85 kind 30385 scores NIP-73 ids as provider outputs (rank, …). It
is not a substitute for ternary personal trust or for s/c as used here.
Relationship to other events#
- Kind
3: following, not trust. - NIP-39: identity linking (
twitter/twitter_idon kind10011) stays on that wire format. Trust subjects for X useuser:id/post:id+s. A barenpub1…hint on theitag MAY advertise the subject's linked Nostr pubkey for decentralized sync; it does not replace the signed10011claim or Bio/post evidence. - NIP-85: optional derived assertions from
32009graphs. - Kind
32010: multi-subject draft;32009requires one subject. - Kind
32014: graded subject rating (worth the reader's time). Never a graph hop. Emptyscore/ emptyvare Delete tombstones;"0"is a real Neutral (32009) or worth-no-time (32014) value. See NIP-32014.md.
Specification#
Kind#
kind: 32009
Address: 32009:<author-pubkey>:<d-tag>.
Content#
Optional human-readable reason, ≤ 1024 Unicode characters. Not structured
protocol data. Empty content is valid. Clients MUST NOT treat missing or
empty content as incomplete trust. Attention product policy for reasons
(optional UI, ~144-character compose cap, plain text, detail-only display) is
in design.md § Trust statement content.
Tags#
Required:
- exactly one
d; - exactly one subject:
p,e, ori(the subject tag is required); - exactly one
v("1"|"0"|"-1"|"") — required tag; the value MAY be empty (Delete).
For typed / external identifiers the subject MUST be i (required). k is
only a denormalized typed-prefix hint and MUST NOT appear without i. When
k is present, it MUST equal the <class>:<property> prefix of i (so i
equals k + ":" + <value> with non-empty value).
Optional:
- at most one
k(identifier class for#kfilters; recommended withi); - zero or more
s(domain/namespace; optional for all subject types); - at most one
c(purpose; optional — omit for global trust); - zero or more
l(labels; not ind); - at most one
x(activation time; optional); - at most one
y(expiration time; optional; if bothxandyare present,xMUST be<= y).
["d", "<64-hex-sha256>"]
["p", "<64-hex-pubkey>", "<class>:<property>:<value>" | "npub1…", ...] |
["e", "<64-hex-event-id>", "<class>:<property>:<value>", ...] |
["i", "<class>:<property>:<value>", "<class>:<property>:<value>" | "npub1…", ...] // exactly one subject
["v", "" | "1" | "0" | "-1"] // required; empty = Delete
["k", "<class>:<property>"] // optional; only with i
["s", "<scope>"] // optional; zero or more
["c", "<context>"] // optional; at most one
["l", "<label>", "<human description>"] // optional; zero or more; not in d; tag[2] optional
["x", "<unix-seconds>"] // optional
["y", "<unix-seconds>"] // optional
p / e subjects#
p: 64 lowercase hex pubkey. PositivepMAY be a WoT traversal edge.e: 64 lowercase hex event id (decodenote1/nevent1first). Terminal.
Subject hints#
The primary subject is always the second element (tag[1]) of the single
p, e, or i tag. Any later elements are optional hints about that primary
subject. Each hint is either:
- Structured form:
<class>:<property>:<value> - Bare NIP-19
npub1…(canonical lowercase) — the subject's linked Nostr pubkey (Attention uses this unders=x.comfori=user:id:<digits>).
class and property are lowercase protocol names using
[a-z0-9][a-z0-9._-]*. value is non-empty. Clients split structured hints
on the first two colons only. A malformed hint MUST NOT be reinterpreted as a
second subject. A client that does not understand a hint MUST ignore it.
Hints NEVER enter d material.
Examples:
["p", "<64-hex-pubkey>", "user:name:John Doe"]
["e", "<64-hex-event-id>", "post:title:Example: a title"]
["i", "user:id:11348282", "user:name:nasa", "npub1…"]
Publishers that have locally verified an X account SHOULD attach the winning
npub1… as an i hint when publishing trust of that user:id. Consumers MAY
use that hint for decentralized identity sync only under their own WoT policy;
the hint alone does not prove control of the X account.
i / k subjects#
Use i for typed non-p/e subjects — the i tag is required for this
subject form. Its canonical typed-subject shape is
<class>:<property>:<value>. The optional companion k repeats the
<class>:<property> prefix for #k relay filters (same habit as NIP-73).
k grammar: [a-z0-9][a-z0-9._-]*(?::[a-z0-9][a-z0-9._-]*)*
i : <class> + ":" + <property> + ":" + value
(required for this shape; when k is present, k MUST equal the typed prefix)
i (required) | k (optional) | Notes |
|---|---|---|
user:id:11348282 | user:id | Decimal digits; s recommended |
post:id:2080659774136291424 | post:id | Decimal digits; s recommended |
hash:<64-hex> | hash | Content digest |
web:https://example.com/path | web | Absolute http(s), lowercase host, no fragment |
Terminal subjects (not traversal edges). Prefer user:id / post:id with an
optional s (for example x.com) over embedding a platform name in i.
Publishers SHOULD include s for opaque platform ids so relays and clients can
disambiguate sites; s remains optional. Publishers SHOULD emit k with i
for class-wide #k queries; k remains optional. Clients MUST reject k
without a subject i (or reject events with no subject).
Scope (s)#
Optional relay discovery facet (and part of d material when present).
- Omit
sentirely when no domain/namespace filter is needed (scope=""ind). - Each value: non-empty, ≤ 128 UTF-8 bytes; DNS-style hosts lowercase
(e.g.
x.com). - Multiple
stags allowed so one event is indexed under several namespaces. Fordmaterial, unique values are sorted lexicographically then joined (tag order does not matter). - NIP-01: values inside
#sare OR;#swith#k/#cis AND across keys. - Graph context fallback does not walk
s. Resolve uses subject +c. salso selects the vocabulary used to interpret subject hints. The same structured value can be meaningful under one scope and opaque under another.- For
s=x.com, an X account subject hint MAY useuser:name:<handle>for display/discovery and a barenpub1…for the subject's linked Nostr pubkey. These values remain untrusted metadata. - When several scopes are present, clients MAY interpret a hint under each supported scope. If the interpretation is not unambiguous, clients MUST ignore the hint rather than infer a cross-scope identity.
Attention product policy (publish, sync, and X eligibility — not graph
slots): s=x.com for X user and post subjects; older empty-scope user
statements remain valid and apply on X; on X, x.com precedes empty when both
exist; relay pulls must cover #s=x.com and empty-scope events. Full rules:
architecture.md § Scope policy.
Context (c)#
Optional. Omit the tag (or use empty) for global trust — a statement
with no context applies generally, not under a narrower purpose. When present,
at most one c tag.
Non-empty values:
[a-z0-9][a-z0-9._-]*(?::[a-z0-9][a-z0-9._-]*)*
≤ 128 UTF-8 bytes, lowercase, no empty segments. Fallback: exact → parent
prefixes → global (missing/"") → none. Walk c only. One purpose
per event; more purposes ⇒ more events.
When the newest event for a context slot is a Delete (v = "") or outside
its x/y window, that slot contributes no active edge. Clients MUST
continue the fallback walk to broader c prefixes and then global — a Delete
does not shadow parent or global statements. Neutral (v = "0") occupies the
slot like trust or distrust and MUST NOT fall through.
No c = global — general trust of the subject (recommended default when
trusting an X user). Narrower purposes use non-empty c, for example:
identity— trust that the subject is who they claim to be (account / authenticity). This does not imply trust of what they write; use post subjects (post:id) or another purpose for content judgments.security:access:frontdoor— purpose-specific access trust.
Trust value (v)#
Required tag. Exactly one v tag. Clients MUST reject a missing or
duplicate v. An empty value (["v", ""]) is valid: it is the Delete
tombstone. Whitespace-only values are invalid.
| Wire value | Meaning |
|---|---|
"" | Delete / retract. The slot contributes no active edge. |
"0" | Neutral. Active statement; stored in the graph; not a traversal hop. Shown in evidence lists. MAY carry content and l. |
"1" | Trust. Positive p MAY be a traversal hop. |
"-1" | Distrust. Evidence, not a hop. |
This matches kind 32014: "0" is a value; empty is Delete. Earlier drafts
used v = "0" as cancel; clients MUST treat existing v = "0" winners as
Neutral. There is no automatic rewrite.
A Delete (v = "") MUST use the same subject, canonical scope (s), and
context (c) as the statement it clears — i.e. the same replaceable d
slot. To delete a global statement, omit c (or use empty) as the prior event
did. To delete an identity statement, publish v = "" with c = "identity" (and the same subject / s). A global Delete does not delete
a contextual statement, and a contextual Delete does not delete global or
sibling contexts.
Neutral, trust, and distrust likewise replace only the matching d slot.
Labels (l)#
Optional. Zero or more l tags. i / k / s are system tags so
clients can identify the subject and operate. l is a human statement
about the subject (why / how the author sees them). Labels MUST NOT
enter d. They MUST NOT affect Web-of-Trust traversal, hop expansion,
hitting degree, or score counts.
The primary value (tag[1]) uses the same grammar as c (lowercase, no
spaces), e.g. reviewer. Optional tag[2] is a human description of
that label, not a class:property:value identity hint and not an extra
subject. Further elements MAY be ignored.
["l", "<label>", "<human description>"]
Example: ["l", "reviewer", "Trusted reviewer of aerospace accounts"].
A badge is a subset of label: publish the badge name as l. There is no
separate badge tag.
Duplicate l values on one event SHOULD be collapsed; if duplicate tokens
carry descriptions, keep the first non-empty description. Clients MUST NOT
treat duplicates as extra weight. Unknown labels MUST be preserved and MAY
be ignored for display. Changing labels without changing subject, scope, or
c replaces the same slot.
Clients MUST treat content and label descriptions as untrusted display
text (strip markup and control characters before showing them). Do not
rewrite the signed event to sanitize.
Building d (always SHA-256)#
d is always:
d = lowercase_hex( SHA-256( UTF-8( material ) ) )
64 hex characters. Never a raw pubkey, never subject:context plaintext.
Canonical scope string#
Let scope be:
""if there are nostags;- otherwise the unique
svalues, sorted lexicographically, joined with,. Tag order does not matter; duplicates are collapsed.
Material#
Empty c is "". Separators between subject, scope, and context are always
present so empty fields cannot collide:
| Subject | material |
|---|---|
i | <i> + ":" + scope + ":" + <c> |
p | "p:" + <pubkey> + ":" + scope + ":" + <c> |
e | "e:" + <event-id> + ":" + scope + ":" + <c> |
Examples of material (before hashing):
| Setup | material |
|---|---|
i=user:id:343, s=x.com, c=identity | user:id:343:x.com:identity |
i=user:id:343, no s, c=identity | user:id:343::identity |
i=user:id:343, s=x.com, no c | user:id:343:x.com: |
i=user:id:343, no s, no c | user:id:343:: |
i=user:id:343, s=twitter.com + s=x.com, c=identity | user:id:343:twitter.com,x.com:identity |
p=<hex>, s=x.com, c=development | p:<hex>:x.com:development |
p=<hex>, no s, no c | p:<hex>:: |
Without fixed : separators, s="" + c="ab" and s="a" + c="b" could
hash identically. The trailing/middle colons prevent that.
Clients MUST recalculate d from tags. Mismatch ⇒ invalid.
Additional subject-hint values (structured or bare npub1…), v, l, x,
y, and content are intentionally excluded from material. Hints and
labels are advisory metadata, not slot identity. Adding, removing, or
changing a hint or label MUST NOT change d.
Replacement#
Same author + same d ⇒ addressable replacement (latest created_at; NIP-01
tie-break on id). Changing s and/or c and/or subject changes material
and therefore d (new slot), except when the canonical scope and c strings
are unchanged. Neutral or Delete MAY omit or change advisory hints and
labels, but MUST preserve the primary subject, canonical scope, and context
so they target the same d.
Time (x / y)#
Optional. Omit both tags when the statement is always active.
x— inclusive activation time (Unix seconds); at most one.y— inclusive expiration time (Unix seconds); at most one.
If both are present, x MUST be less than or equal to y. Outside the window:
the event remains newest for the slot, but contributes no active edge (same
as Delete). Do not revive older replaced events.
Validation checklist#
- Signature valid.
- Exactly one
d, one subject (p/e/i), onev; the subject's second element is the primary subject. - At most one
k,c,x,y. Zero or morel. kwithoutiis invalid.- If
kis present withi:kequals the<class>:<property>prefix andi === k + ":" + valuefor a non-empty value. user:id/post:idvalue (in the primaryielement) is decimal digits.- Every additional
p/e/ielement is either a structured<class>:<property>:<value>hint or a canonical barenpub1…. - Each
svalue (when present) is non-empty and within limits. v∈ {1,0,-1,``}; time tags integer;x <= yif both.- Non-empty
cand eachlcanonical ≤ 128 bytes;lis not ind. - Recalculated
dequals publisheddwithout using hints,v, orl. - Content within safety limit.
Unknown tags MAY be kept and ignored. Hints that are syntactically valid but unsupported by the client MUST be ignored.
Querying#
Kind 32009 isolates trust traffic. Within that kind, use tag filters for
class, domain, and purpose.
NIP-01 ANDs different filter keys and ORs values inside one key. With single-valued tags on the event, you can ask:
{
"kinds": [32009],
"#k": ["user:id"],
"#s": ["x.com"],
"#c": ["identity"]
}
{
"kinds": [32009],
"#k": ["post:id"],
"#s": ["x.com"]
}
{
"kinds": [32009],
"#i": ["user:id:11348282"],
"#k": ["user:id"],
"#s": ["x.com"]
}
That last filter is “everything said about this X user” (AND across #i,
#k, and #s).
OR inside one key — either scope matches:
{"kinds": [32009], "#s": ["x.com", "github.com"]}
Author-bounded WoT sync (interval and continuous frontier):
{"kinds": [32009], "authors": ["<wot-author>"]}
Attention omits #s on author 32009 pulls so empty-scope user trusts and
explicit s=x.com both match; unrelated scopes are dropped client-side.
Subscribe all uses the same kind without an author filter, starting at now
plus overlap rather than full history. Adding another global kind requires
an explicit product review of validation, winner reduction, and storage.
Exact replaceable slot when d is known:
{
"kinds": [32009],
"authors": ["<author>"],
"#d": ["<64-hex-sha256>"]
}
#p, #e, and #i match the primary subject in tag[1]; relay filters do
not assign subject identity to later hint elements. Clients MUST interpret
hints using s before using them.
Examples#
d values below are illustrative placeholders; publishers MUST compute the real
SHA-256 of material.
Trust an X user#
Global trust of a user (no c — context is global):
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531300,
"tags": [
["d", "<sha256(user:id:11348282:x.com:)>"],
["k", "user:id"],
["i", "user:id:11348282", "user:name:nasa", "npub1…"],
["s", "x.com"],
["v", "1"]
],
"content": ""
}
The user:name value and bare npub1… are optional advisory metadata.
material = user:id:11348282:x.com: (empty context); hints are not part
of d.
Trust an X user's identity#
c = "identity" means trust of the account's authenticity (who they are), not
trust of their posts or writing. Judge content with post:id subjects (or
another purpose), not by overloading global user trust.
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531310,
"tags": [
["d", "<sha256(user:id:11348282:x.com:identity)>"],
["k", "user:id"],
["i", "user:id:11348282"],
["s", "x.com"],
["c", "identity"],
["v", "1"]
],
"content": ""
}
material = user:id:11348282:x.com:identity (separate slot from global).
Trust an X post#
Global trust of a post (no c — context is global):
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531350,
"tags": [
["d", "<sha256(post:id:2080659774136291424:x.com:)>"],
["k", "post:id"],
["i", "post:id:2080659774136291424"],
["s", "x.com"],
["v", "1"]
],
"content": ""
}
material = post:id:2080659774136291424:x.com: (empty context).
Distrust an X post#
Global distrust of a post (no c):
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531400,
"tags": [
["d", "<sha256(post:id:2080659774136291424:x.com:)>"],
["k", "post:id"],
["i", "post:id:2080659774136291424"],
["s", "x.com"],
["v", "-1"]
],
"content": ""
}
material = post:id:2080659774136291424:x.com: (same global slot as trust;
v changes the statement).
Multiple scopes (relay OR + one d)#
Global user trust indexed under either hostname:
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531450,
"tags": [
["d", "<sha256(user:id:11348282:twitter.com,x.com:)>"],
["k", "user:id"],
["i", "user:id:11348282"],
["s", "x.com"],
["s", "twitter.com"],
["v", "1"]
],
"content": "Discoverable under either hostname; one replaceable slot."
}
scope = twitter.com,x.com (unique values, lexicographically sorted).
#s:["x.com"] or #s:["twitter.com"] can both match. material =
user:id:11348282:twitter.com,x.com:.
Trust a Nostr pubkey#
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531200,
"tags": [
["d", "<sha256(p:<subject-pubkey>:nostr:development)>"],
["p", "<subject-pubkey>"],
["s", "nostr"],
["c", "development"],
["v", "1"]
],
"content": "Maintains the library and reviews security reports."
}
material = p:<subject-pubkey>:nostr:development. Omitting s and c uses
p:<subject-pubkey>::.
Trust a Nostr event#
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531250,
"tags": [
["d", "<sha256(e:<subject-event-id>::news:accuracy)>"],
["e", "<subject-event-id>"],
["c", "news:accuracy"],
["v", "1"]
],
"content": "Primary sources support the claims in this event."
}
material = e:<subject-event-id>::news:accuracy (empty scope).
Neutral#
Neutral global user statement (same subject and s, no c) with a reason.
v = "0" is an active opinion, not a Delete:
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531490,
"tags": [
["d", "<sha256(user:id:11348282:x.com:)>"],
["k", "user:id"],
["i", "user:id:11348282"],
["s", "x.com"],
["v", "0"],
["l", "reviewer"]
],
"content": "Neither endorsed nor opposed; watching this account."
}
l and content are not part of d. Clients SHOULD show this Neutral in
the subject's evidence list and MUST NOT follow it as a hop.
Delete#
Delete the prior global user statement (same subject and s, no c):
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531495,
"tags": [
["d", "<sha256(user:id:11348282:x.com:)>"],
["k", "user:id"],
["i", "user:id:11348282"],
["s", "x.com"],
["v", ""]
],
"content": ""
}
Delete the prior global post statement (same subject and s, no c):
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531500,
"tags": [
["d", "<same-sha256-as-prior-slot>"],
["k", "post:id"],
["i", "post:id:2080659774136291424"],
["s", "x.com"],
["v", ""]
],
"content": ""
}
Delete a prior identity statement (authenticity only — same subject, s,
and c = "identity"):
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531510,
"tags": [
["d", "<sha256(user:id:11348282:x.com:identity)>"],
["k", "user:id"],
["i", "user:id:11348282"],
["s", "x.com"],
["c", "identity"],
["v", ""]
],
"content": ""
}
That Delete empties only the identity slot. A separate global statement for
the same subject (if any) remains until deleted with matching (empty) c.
Time-bound trust#
{
"kind": 32009,
"pubkey": "<statement-author>",
"created_at": 1672531600,
"tags": [
["d", "<sha256(p:<subject-pubkey>:corp.example:access)>"],
["p", "<subject-pubkey>"],
["s", "corp.example"],
["c", "access"],
["v", "1"],
["x", "1672531600"],
["y", "1672618000"]
],
"content": "Temporary trust for this incident-response window."
}
material = p:<subject-pubkey>:corp.example:access (corp.example is an
imaginary scope for illustration).
Client behavior#
Clients SHOULD:
- sync with
kinds: [32009]plus#s/#k/#cas needed; - use positive
pas traversal edges; treate/ias terminal; - resolve one newest valid event per
(author, d); - treat
"1"/"0"/"-1"as active (Neutral is not a hop; show it in evidence lists); - Delete with empty
vusing the same subject, scope, andcas the slot being cleared; - fall back on
chierarchy only; skip deleted/inactive slots and continue walking to parents then global (Neutral occupies the slot); - preserve subject hints and labels as advisory metadata, but interpret
hints only under a supported
svocabulary; - if a trusted issuer supplies a bare
npub1…(or other identity hint), treat it as fallback-only and independently verify Bio/post/NIP-39 evidence before creating any identity link; - not treat missing relay data as trust;
- bound depth/fan-out; keep provenance; never present results as objective global reputation.
Local “question” UI MAY stay unpublished; published v is "1", "0",
"-1", or empty.
Security considerations#
- Statements are public;
-1has consequences. - Results are root- and policy-relative.
- Bound expansion against Sybils.
- Do not silently widen
c. - Interpret
user:id/post:idtogether withsto avoid cross-site collisions. iand subject hints (including barenpub1…) do not prove ownership; any issuer can publish misleading values. Prefer Bio/post evidence and NIP-39 (or similar); independently verify before creating a control link.- A subject hint describes the statement's subject, not the statement author.
Relay-received hint metadata MUST NOT by itself create an
xIdentitiesrow, a verified NIP-39 claim, or a graph edge. - Prefer stable ids over URLs when the platform provides them.
- Compromised author keys can replace all of that author's slots.
contentand label descriptions are untrusted human text. Clients MUST sanitize them for display and MUST NOT interpret them as protocol identifiers or HTML.
Implementation rationale#
One subject per event keeps slots simple. Kind 32009 isolates trust traffic.
k/s optimize relay download; c optimizes in-memory broad/narrow resolve.
d is always opaque SHA-256 over a colon-stable material so empty s or c
cannot collide. Subject hints, labels, and v remain outside that material so
advisory updates and Neutral/Delete do not fork replacement slots. Derived
ranks belong in NIP-85 (or app state), not in v.