- Sashité for Developers
- Nostr
- NIPs
- Direct Challenge
NIP-XX
Direct Challenge
draft optional
This NIP defines a Nostr event kind for publicly proposing a session in a turn-based, two-player abstract strategy board game of the chess family. The Direct Challenge event names a specific opponent, designates an arbiter and, optionally, a timestamper, and carries the proposed session terms.
Abstract
Defines kind: 3420, a regular event by which a player publicly proposes a session to a specific opponent. The event identifies the challenged player, designates the arbiter and an optional timestamper, and carries the game context (shared), an optional proposed variant for each player, a time-control configuration, an optional seat assignment, and an acceptance deadline.
A complementary acceptance event (defined separately) responds to a Direct Challenge to instantiate the session. A challenge that receives no acceptance before its deadline expires without effect.
Motivation
Initiating a session on Nostr requires a public, signed proposal that fixes session terms cryptographically. A Direct Challenge expresses one player’s offer to play another specific player under specific terms, witnessed by relays. The challenged player either responds with an acceptance event (instantiating the session) or remains silent (letting the challenge expire). No “decline” event is defined; silence is the refusal.
This NIP carries only the structural and lifecycle aspects of the challenge. Game-specific semantics — what a given variant identifier means within a given game context, what the piece movements are, how time accounting elapses — are determined by the designated arbiter’s rule system.
A Direct Challenge MAY leave either player’s variant — the challenged player’s, the challenger’s own, or both — or the seat assignment unfixed. In that case, the challenged player supplies the missing piece(s) in their acceptance event. Omitting one’s own variant is a deliberate delegation: the challenger consents, by omission, to the challenged player choosing the variant the challenger will play.
Specification
Event kind
kind: 3420
Direct Challenge events are regular events per NIP-01: immutable once signed and broadcast.
Operating mode
This NIP requires a designated arbiter, plus exactly one timing designation selecting the session’s timing mode per the Canonical Timing NIP §Timing modes and mode selection:
- Arbiter (marker
arbiter): a third party who holds binding authority to rule on the session outcome via Adjudication (kind3425). The arbiter is REQUIRED for every session, carried by anarbiter-markedptag. - Timestamper (marker
timestamper): a third party who provides signed receipt timing by publishing Event Timestamp Attestations (kind3410) for the session’s events. Carried, in attested mode, by exactly onetimestamper-markedptag. - Timing relay(s) (
timing_relaytags): the relay(s) whose acceptance establishes the session events’ canonical timing in self-timed mode, each conforming to the Self-Timed Timing Relay profile. Carried, in self-timed mode, as["timing_relay", "<wss://relay.example.com>"], one or more.
The designation fixes the timing mode (Canonical Timing §Canonical timing of an event):
- Attested mode — a timestamper IS designated (and no
timing_relaytag is carried). The canonical timing of the session’s events is the designated timestamper’s canonical attestationcreated_at; the events’ owncreated_atis ignored. - Self-timed mode (the default) — NO timestamper is designated, and one or more timing relays ARE. The canonical timing of each event is its own
created_at, once its acceptance by a designated timing relay is established — the relay’s strict ingestion window (it refuses any event whosecreated_atis in the past relative to its clock, beyond a small past tolerance) is what makes the self-claim trustworthy. The designated relays’ policies are verifiable via NIP-11 (Self-Timed Timing Relay §NIP-11 advertisement).
The self-timed guarantee is scoped to the designated relay(s): canonical timing is established for consumers who retrieve the session’s events from a designated relay (or a mirror they trust to be faithful to one), because acceptance itself is the timing bound. It leaves no in-event trace — an event re-encountered elsewhere, on an arbitrary relay or in an export, carries only its self-declared created_at, and nothing distinguishes an honestly-timed event from a backdated one accepted by a lax relay. A consumer who cannot establish acceptance by a designated relay MUST treat the event as pending (Canonical Timing §The pending state); the designation is what tells every verifier, forever, which relays to ask.
Designating a timestamper trades the self-timed baseline for signed, portable, third-party-verifiable timing — closing exactly the late-verifier gap above; whether a deployment offers it is a deployment choice, outside this generic NIP. The arbiter and, when used, the timestamper MAY be the same pubkey (two p tags with different markers, same value); combined-role configurations weaken the structural defense against timing-related misbehavior (§Security considerations).
In attested mode, the timestamper publishes Event Timestamp Attestations (kind 3410, see the Event Timestamp Attestation NIP) for the session’s attested events: on this directed path, the Accepted Challenge (kind 3421), the Game Session (kind 3422), each Ply (kind 3423), each Adjudication Request (kind 3424), and the Adjudication (kind 3425) — and, on the matchmaking path, the Pairing (kind 3419) in the same position as the Accepted Challenge. The Direct Challenge itself (kind 3420) is NOT attested. Attestations from any signer other than the designated timestamper are ignored. In self-timed mode there are no attestations; the same events acquire canonical timing from their own created_at once a designated timing relay accepts them. Kinds 3426 (Elo Rating Attestation) and 3427 (Glicko-2 Rating Attestation) are generic, suite-independent NIPs signed by a rating authority outside the session’s timing mode. The canonical timing is the authoritative source for race resolution and clock accounting; see Canonical Timing.
The role designations propagate to the complementary acceptance event, which MUST mirror them exactly — the same arbiter, and the same timing designation (the same timestamper, or the same timing_relay set).
Tags
Player tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
p |
two or three | pubkey, optional relay hint, role marker | exactly one opponent (the challenged player); exactly one arbiter; and, in attested mode, exactly one timestamper |
timing_relay |
zero or more | relay URL (wss://…) |
in self-timed mode (the default), one or more designated timing relays; absent in attested mode |
The p tags MUST carry a role marker as the fourth element:
["p", "<challenged_pubkey>", "<relay_hint>", "opponent"]— the challenged player. Exactly one such tag is required.["p", "<arbiter_pubkey>", "<relay_hint>", "arbiter"]— the designated arbiter. Exactly one such tag is required.["p", "<timestamper_pubkey>", "<relay_hint>", "timestamper"]— the designated timestamper. Present (exactly one) puts the session in attested mode.["timing_relay", "<wss://relay.example.com>"]— a designated timing relay. One or more present puts the session in self-timed mode (the default). See §Operating mode.
The p tags serve a dual purpose: semantic (identifying the participants) and structural (notification routing via standard Nostr #p filters). The dual purpose is intentional — repeating identifiable pubkeys in p tags enables compatibility with the standard Nostr discovery mechanism without requiring chained subscription logic on the client side.
The relay hint (third element of a p tag) is OPTIONAL but SHOULD be present. When omitted, an empty string MAY be used in its position.
Constraints on the pubkeys:
- The
opponentpubkey MUST be distinct from the event’spubkey(the challenger). - The
arbiterpubkey MUST be distinct from the challenger and the opponent. - When a
timestamperis designated (attested mode), its pubkey MUST be distinct from the challenger and the opponent, and MAY be identical to thearbiterpubkey (same actor cumulating both roles, represented by two distinctptags with the same pubkey value).
Match-terms tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
game |
exactly one | game identifier | the broader game context shared by both players |
variant |
zero to two | pubkey + variant identifier | variant assigned to a player |
time_control |
one or more | duration + optional increment + optional plies | session time-accounting period |
seat |
zero or one | first or second |
seat declared by the challenger (the challenged player implicitly receives the other value) |
At least one time_control tag MUST be present. Multiple time_control tags MAY be present to encode multi-period time controls (e.g., classical chess time controls with extra time after a given move count).
A game identifier and a variant identifier are each a non-empty ASCII string starting with a lowercase letter, followed by zero or more lowercase letters or digits, with total length between 1 and 32 characters inclusive. Each MUST match the regular expression:
^[a-z][a-z0-9]{0,31}$
This NIP does not prescribe a vocabulary for these identifiers. The interpretation of a given game or variant identifier is the responsibility of the consuming application. Conceptually:
gameidentifies the broader game context. Examples:chess(a standalone chess application),sanki(a multi-game 8x8 platform),go,xiangqi,shogi.variantidentifies the specific variant played within that game. Examples:chess,chess960,ogi,xiongqi.
The relationship between game and variant is application-defined. A given application MAY (and typically does) constrain which variants are valid within each game it supports, including constraints such as a shared board dimension. The protocol itself does not validate this relationship; it is the responsibility of the consuming application.
The game tag is structured ["game", "<game_id>"] and applies to the entire session (singleton).
Each variant tag is structured ["variant", "<pubkey>", "<variant_id>"]:
- The second element is the pubkey of the player to whom this variant is assigned. It MUST be either the challenger’s pubkey or the challenged player’s pubkey.
- The third element is the variant identifier.
Each player’s variant is OPTIONAL in the Direct Challenge — the challenger’s own (the variant tag whose second element matches the event’s pubkey) as well as the challenged player’s. Any variant the Direct Challenge does not fix is supplied by the challenged player in the acceptance event (kind 3421 §Match-terms tags): the (Direct Challenge, Accepted Challenge) pair always jointly fixes both variants. A challenger who omits their own variant thereby delegates its choice to the challenged player; a challenger who omits the challenged player’s variant leaves that player free to choose their own, as before.
When both players’ variants are specified and they differ, the session is a multi-variant session. Multi-variant sessions are valid at the protocol level; whether a rating filter binds across them depends on the declared pool scope (kind 3418 §Match-terms tags): excluded under a pervariant scope, rated together under a pergame scope that unifies the game’s variants.
A time_control tag describes one period of session-wide time accounting:
["time_control", "<duration>", "<increment>", "<plies>"]
| Element | Position | Type | Required | Description |
|---|---|---|---|---|
| duration | 2nd | non-negative integer (seconds) | yes | Time budget for the period |
| increment | 3rd | non-negative integer (seconds) | no | Fischer increment per ply within this period |
| plies | 4th | strictly positive integer | no | Move-count quota — period covers N plies, then repeats |
Trailing optional fields MAY be omitted. If increment is omitted, plies MUST also be omitted. Each present element is a bare decimal integer matching ^(0|[1-9][0-9]*)$ — no leading zeros — and an empty-string element is malformed: omit the element rather than leaving it empty. A duration of 0 is valid only in the three-element per-move form (duration: 0 + increment + plies, the last row below); in any other form the duration MUST be strictly positive. An omitted increment and an explicit increment of 0 are distinct configurations: no normalization is applied anywhere in the suite, so ["time_control", "300"] and ["time_control", "300", "0"] are different time controls — and with leading zeros excluded, comparing configurations for identity (kind 3419 §Match-terms tags) reduces to element-wise string equality.
The semantics:
| Configuration | Semantic |
|---|---|
duration only |
Fixed time bank; duration depletes linearly per ply. |
duration + increment |
Fischer time control: increment is added to the budget after each ply. |
duration + increment + plies |
Quota-based time control (byōyomi, Canadian): the period covers plies plies, then resets. |
duration: 0 + increment + plies |
Per-move time accounting with no main clock. |
When multiple time_control tags are present, they are applied sequentially in tag order (e.g., main time bank followed by overtime periods). The precise per-ply accounting — spend rules, increment timing, period transitions, and timeout detection — is rule-system-defined; the game’s supporting documentation specifies it (for the reference sanki rule system: Time Accounting — Sanki, in this suite’s application layer).
A seat tag declares the seat the signer (the challenger) claims in the proposed session:
["seat", "<seat_name>"]
- The second element is a seat-name, one of exactly two protocol-defined values:
first(moves first) orsecond(moves second). Seat-names are play-order indices, not game vocabulary: every two-player, alternating-turn game has a first mover, which is the only invariant the protocol needs. The traditional labels (white/blackfor chess, sente/gote for shogi, red/black for xiangqi) are presentation: the rule system maps its labels ontofirst/second, and applications display whichever the game calls for. - The challenged player implicitly receives the other value (
first↔second). - Because the Direct Challenge is signed by the challenger, the seat carried by the
seattag is always the challenger’s. To assign the challenged player to a specific seat, the challenger declares the other value for themselves.
If the seat tag is absent, the seat assignment is unfixed and supplied in the acceptance event.
Lifecycle tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
accept_until |
exactly one | unix timestamp (seconds) | the latest moment at which an acceptance event is valid |
nonce |
exactly one | nonce + difficulty | NIP-13 proof of work |
The accept_until value (second element) MUST be a Unix timestamp in seconds, strictly greater than the event’s created_at. An Accepted Challenge whose canonical timing is greater than this value MUST be treated as invalid by verifiers; see kind 3421 §Semantic constraints (constraint 6) for the precise rule and Canonical Timing. A NIP-40 expiration tag MUST NOT be carried: NIP-40 authorizes relays to delete the event, and a consumed Direct Challenge must remain retrievable indefinitely — the Accepted Challenge’s founding reference is verified against it (kind 3421 §Semantic constraints), so garbage-collecting an expired-but-accepted challenge would amputate the founding evidence of a live session.
The nonce tag follows NIP-13.
Content
The content field MAY carry a free-form text message from the challenger to the challenged player (e.g., a greeting, context, or terms clarification). It is OPTIONAL: an empty string is permitted.
The content field MUST satisfy:
- Length: 0 to 255 Unicode code points inclusive (measured after NIP-01 JSON unescaping).
- Encoding: valid UTF-8.
- Forbidden characters: any code point in the
contentfield MUST NOT fall in the following ranges: - U+0000 to U+0008, U+000B, U+000C, U+000E to U+001F (C0 control characters, except line feed U+000A and horizontal tab U+0009).
- U+007F to U+009F (DEL and C1 control characters).
- U+202A to U+202E (bidirectional formatting and override characters).
- U+2066 to U+2069 (directional isolate characters).
The content field MUST NOT alter the interpretation of the challenge. The challenged player’s client MAY display it to the user, but MUST NOT base any normative decision (acceptance, seat selection, terms negotiation) on its value. The semantic meaning of the event is fully carried by the tags.
The protocol intentionally does NOT restrict characters such as <, >, &, ", ', `, or \ — these are legitimate text in many natural contexts (mathematics, programming notation, casual messaging across multiple scripts); handling them is the job of output-layer escaping in the client (see §Security considerations).
Semantic constraints
A conforming Direct Challenge event MUST satisfy all of the following:
- The event contains exactly one
opponent-markedptag, exactly onearbiter-markedptag, and exactly one timing designation: either exactly onetimestamper-markedptag and notiming_relaytag (attested mode), or notimestamperand one or moretiming_relaytags each carrying awss://URL (self-timed mode, the default) — per the Canonical Timing NIP §Timing modes and mode selection. - The pubkeys of all
ptags MUST be distinct from the event’spubkey(the challenger). Theopponentpubkey MUST be distinct from thearbiterand (in attested mode)timestamperpubkeys. Thearbiterandtimestamperpubkeys MAY be identical (same actor cumulating both roles). - Exactly one
gametag is present, with a valid game identifier matching^[a-z][a-z0-9]{0,31}$. - Zero, one, or two
varianttags are present. Eachvariant’s third element is a valid variant identifier matching^[a-z][a-z0-9]{0,31}$. - Each
varianttag’s second element matches either the challenger’s pubkey or the challenged player’s pubkey. At most onevarianttag is present per pubkey. - At least one
time_controltag is present. Eachtime_controltag has a valid format (per §Match-terms tags: no leading zeros;duration0only in the three-element per-move form). - If a
seattag is present, its second element isfirstorsecond. - Exactly one
accept_untiltag is present, with a value strictly greater thancreated_at. - Exactly one
noncetag is present, carries its committed difficulty as its third element, and the event id satisfies NIP-13 against that committed difficulty — never against a relay’s advertised minimum (an ingestion policy, not conformance).
Constraints 1–9 are checkable from the event alone.
Validation responsibility
Validation of a Direct Challenge against the semantic constraints (§Semantic constraints) is the responsibility of the client and the relay. A relay additionally enforces its advertised NIP-13 minimum difficulty at ingestion — a relay policy, distinct from conformance.
Validation of the proposed terms in the broader sense (e.g., whether the designated arbiter supports the proposed game / variant combination, whether the variant is permitted within the game per the application’s rules, or whether the time-control configuration is honored) is the responsibility of the challenged player’s client when deciding whether to accept the challenge. If the challenged player accepts terms that the arbiter cannot honor, the resulting session may fail at the arbitration step (out of scope for this NIP).
Separately, a deployment MAY gate the eligibility to impose certain optional terms behind its own policy — independent of format validity, which stays as above. Such gates are deployment policy layered on top of this generic NIP, not part of it.
Examples
The examples below use placeholder pubkeys, relay URLs, and timestamps for illustration.
Example 1 — All terms fixed by the challenger
Alice proposes a session to Bob, designating Carol as arbiter. The game context is chess (a standalone chess application), and both players play the standard chess variant. Alice plays seat first with a 5-minute Fischer time control with 3-second increment.
{
"kind": 3420,
"pubkey": "<alice_pubkey>",
"created_at": 1700000000,
"tags": [
["p", "<bob_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["p", "<dave_pubkey>", "wss://relay.example.com", "timestamper"],
["game", "chess"],
["variant", "<alice_pubkey>", "chess"],
["variant", "<bob_pubkey>", "chess"],
["seat", "first"],
["time_control", "300", "3"],
["accept_until", "1700003600"],
["nonce", "62473", "16"]
],
"content": "Up for a game?",
"id": "...",
"sig": "..."
}
Example 2 — Terms partially deferred
Alice proposes a session against Bob in the chess game context. She fixes her own variant (chess) but leaves Bob’s variant and the seat assignment open for Bob to decide at acceptance.
{
"kind": 3420,
"pubkey": "<alice_pubkey>",
"created_at": 1700000000,
"tags": [
["p", "<bob_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["p", "<dave_pubkey>", "wss://relay.example.com", "timestamper"],
["game", "chess"],
["variant", "<alice_pubkey>", "chess"],
["time_control", "600"],
["accept_until", "1700003600"],
["nonce", "11337", "16"]
],
"content": "",
"id": "...",
"sig": "..."
}
Example 3 — Fully deferred variants
Bob challenges Alice in the sanki game context without fixing any variant: he delegates the choice of his own variant to Alice, and leaves Alice free to choose hers. Alice supplies both in her acceptance (kind 3421), explicitly or via her client’s deterministic default.
{
"kind": 3420,
"pubkey": "<bob_pubkey>",
"created_at": 1700000000,
"tags": [
["p", "<alice_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["time_control", "300", "3"],
["accept_until", "1700003600"],
["nonce", "27193", "16"]
],
"content": "Your pick — both sides.",
"id": "...",
"sig": "..."
}
No eligibility gate is involved: Bob imposes nothing on Alice, and Alice completing Bob’s variant at acceptance is a delegation Bob consented to by omission (see §Validation responsibility).
Example 4 — Self-timed session (no timestamper)
Alice proposes a sanki session to Bob, designating Carol as arbiter and running in self-timed mode: no timestamper is designated, and a timing_relay tag names the relay whose acceptance will establish the session events’ canonical timing — its strict ingestion window (it refuses any event whose created_at is in the past relative to its clock, beyond a small past tolerance) makes each accepted event’s own created_at trustworthy.
{
"kind": 3420,
"pubkey": "<alice_pubkey>",
"created_at": 1700000000,
"tags": [
["p", "<bob_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["variant", "<alice_pubkey>", "ogi"],
["variant", "<bob_pubkey>", "chess"],
["seat", "first"],
["time_control", "300", "3"],
["accept_until", "1700003600"],
["nonce", "62473", "16"]
],
"content": "Up for a game of sanki?",
"id": "...",
"sig": "..."
}
Example 5 — Sashité Sanki session with multi-period time control
Alice proposes a Sanki session against Bob: the game is sanki (a multi-game 8x8 platform), and both players choose the ogi variant (an 8x8 adaptation of shōgi). 60 minutes main time, followed by 30 seconds per ply overtime.
{
"kind": 3420,
"pubkey": "<alice_pubkey>",
"created_at": 1700000000,
"tags": [
["p", "<bob_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["p", "<dave_pubkey>", "wss://relay.example.com", "timestamper"],
["game", "sanki"],
["variant", "<alice_pubkey>", "ogi"],
["variant", "<bob_pubkey>", "ogi"],
["time_control", "3600"],
["time_control", "0", "30", "1"],
["accept_until", "1700003600"],
["nonce", "84217", "16"]
],
"content": "",
"id": "...",
"sig": "..."
}
The two time_control tags express sequential periods: first a 60-minute fixed bank, then per-ply overtime.
Client guidelines
Clients implementing kind: 3420 SHOULD:
- Subscribe to kind
3420with a#pfilter on the user’s pubkey to receive incoming challenges. - Inspect the role marker (fourth element of each
ptag) to identify whether the user is theopponentor thearbiterfor a given challenge. - Surface to the user the session’s timing designation — the designated timestamper’s identity (attested mode) or the designated timing relay URLs (self-timed mode) — since it provides authoritative timing for both clock accounting and race resolution. The user SHOULD assess whether they trust the designated parties before accepting.
- Validate received Direct Challenge events against the semantic constraints (§Semantic constraints) before presenting them to the user.
- Verify out-of-band that the designated arbiter is reachable and operational before allowing the user to accept.
- Filter out challenges whose
accept_untilhas already elapsed. - Allow users to dismiss unwanted challenges locally without publishing any event.
- Respect the user’s NIP-51 mute list (kind
10000): Direct Challenges signed by a pubkey listed in the user’s mute list SHOULD be silently filtered out from display, unless the user explicitly opts in to view challenges from muted senders. - Before sending a Direct Challenge, fetch the recipient’s Challenge Policy (kind
30420) for the proposedgameand apply the lookup rules defined in that NIP’s §Mode lookup (the policy is per-game and variant-agnostic). If the policy is likely to filter the challenge (e.g.,nobodymode,followingmode without the sender being followed,ratingmode with the sender outside the rating threshold — or with no determinable comparison pool), warn the sender before publishing — the challenge would consume proof of work without ever being surfaced to the recipient. - When receiving a Direct Challenge, apply the user’s Challenge Policy (kind
30420) filter for the proposedgame(per that NIP’s §Mode lookup), after the NIP-51 mute list filter. Challenges that do not satisfy the policy SHOULD be silently filtered out from display, unless the user explicitly opts in. - When displaying a Direct Challenge to the user, attempt to verify the NIP-05 identifier of the challenger (event signer) and of the designated arbiter. Display verified NIP-05 identifiers prominently. When verification fails (network error, DNS issue, or no identifier published), display the pubkey directly and indicate the identity as unverified — but do not block the challenge solely on this basis.
Clients MAY:
- Allow the challenger to attach a free-form text message via
content, subject to the constraints in §Content (255 code points, no control or bidi-override characters). - Display received
contentto the challenged player with appropriate output escaping (§Security considerations) and without altering challenge logic.
Security considerations
-
Signatures: Direct Challenge events are signed by the challenger. Receiving clients MUST verify signatures per NIP-01 before presenting them to the user.
-
Proof of work: The
noncetag follows NIP-13 and protects against unsolicited spam challenges. Relays SHOULD enforce a minimum difficulty appropriate to their policy. The nonce values shown in this NIP’s examples are illustrative placeholders; real implementations compute the nonce via proof-of-work brute force until the event ID achieves the target leading-zero-bit difficulty. -
Deadline enforcement: The
accept_untiltag is a signed business-logic constraint. Verifiers MUST reject Accepted Challenges whose canonical timing is greater than the corresponding challenge’saccept_until; see kind3421§Semantic constraints (constraint 6) and Canonical Timing. -
Decline by silence: This NIP does not define a “decline” event. A challenged player refuses by not publishing an acceptance event before
accept_until. Clients MAY mark unwanted challenges as locally dismissed for UX purposes without publishing any event. -
Trust model: Designating an arbiter and/or timestamper implies trust that the designated actor will rule honestly, attest timings honestly, stay responsive, and not be impersonated. The protocol cryptographically constrains specific adversarial behaviors but is explicit about its limits with respect to others. In brief: signatures and the consent constraints cryptographically bind WHAT a designated actor may declare; WHETHER and WHEN it acts (liveness, ruling and pairing discretion) is reputational; NIP-26-delegated signatures from designated parties are rejected by default; and NIP-05 identity verification mitigates vanity-pubkey impersonation. The full enumeration belongs to the consuming application’s documentation (this suite’s application layer maintains one); challengers SHOULD review it before designating an arbiter or timestamper for stakes that would justify dispute.
-
Term mismatches: This NIP does not validate whether the designated arbiter supports the proposed
game/variantcombination or time-control configuration. If the arbiter cannot honor the terms, the session fails at the arbitration step (out of scope for this NIP). Clients SHOULD pre-validate terms against the arbiter’s published capabilities when known. -
Privacy implications: Publishing game events discloses information about the player and the session — the player’s pubkey, opponent pubkeys, move data, outcomes, timing, and (via aggregation) activity patterns and skill profiles. None of this can be hidden by a public-relay protocol; the mitigations are identity-side (a dedicated pubkey per context). The full enumeration of direct and inferable disclosures belongs to the consuming application’s documentation (this suite’s application layer maintains one); players SHOULD review it before publishing events under an identity they wish to protect.
- Harassment mitigation: A determined adversary can repeatedly send Direct Challenges to a target. The NIP-13 proof of work (§Lifecycle tags) raises the per-event cost but does not eliminate targeted harassment from a motivated sender. To protect users:
- Clients SHOULD integrate NIP-51 mute lists (kind
10000): incoming Direct Challenges from pubkeys in the user’s mute list SHOULD NOT be surfaced without explicit opt-in. - Clients SHOULD provide a UI affordance to mute a challenger from within the challenge view (e.g., a “block this user” action that adds the challenger’s pubkey to the user’s mute list).
- Relays MAY apply mute-list-aware filtering on the wire, though this is out of scope for this NIP.
NIP-13 proof of work and NIP-51 mute lists are complementary: PoW raises the per-event cost of bulk spam; mute lists eliminate persistent senders entirely.
-
Challenge policy: Users MAY publish a Challenge Policy (kind
30420) declaring which Direct Challenges they wish to receive for each game (the policy is variant-agnostic within a game). The policy is advisory: it does not prevent a determined sender from publishing challenges that will be filtered by the recipient’s client. Compliant sender clients SHOULD respect the policy to avoid wasting proof of work on challenges that will be silently filtered. The policy is applied AFTER the NIP-51 mute list filter — a muted pubkey is filtered regardless of policy mode. - Content rendering: The
contentfield is untrusted user input controlled by the challenger. Although the protocol restricts the character set (§Content) to exclude C0/C1 control characters and Unicode bidirectional override characters — mitigating “trojan source” attacks (CVE-2021-42574), null-byte injection, and ANSI-escape injection — clients displaying the field MUST apply context-appropriate output escaping: - HTML context: escape
<,>,&,",'. - Markdown context: escape characters significant to the rendering flavor (
`,*,_,[,], etc.). - Terminal context: avoid passing the content through interpreters that may execute escape sequences.
The protocol-level character restrictions do not replace context-appropriate output escaping. They close a narrow class of attacks that escaping alone cannot address.
-
Content trust: A malicious challenger can place anything within the allowed character set into
content, including phishing attempts, social engineering, or misleading claims about the challenger’s identity. Clients SHOULD treat the field as adversarial input and present it accordingly (e.g., visually distinct from system-generated text, clearly attributed to the challenger). -
Arbitration scope: This NIP does not itself provide game-outcome arbitration, session instantiation handshake, or termination semantics. Those concerns are addressed by other NIPs in the suite (Accepted Challenge, Adjudication Request, Adjudication).
References
- NIP-01 — Basic protocol flow description
- NIP-05 — Mapping Nostr keys to DNS-based internet identifiers
- NIP-09 — Event Deletion Request
- NIP-13 — Proof of Work
- NIP-51 — Lists
- Event Timestamp Attestation — the primitive on which the suite’s timing is built
- Canonical Timing — timing modes, mode selection, canonical timing, meta-resolution
- Self-Timed Timing Relay — the relay profile behind the
timing_relaydesignation
