- Sashité for Developers
- Nostr
- NIPs
- Accepted Challenge
NIP-XX
Accepted Challenge
draft optional
This NIP defines a Nostr event kind for accepting a Direct Challenge in a turn-based, two-player abstract strategy board game of the chess family. The Accepted Challenge event responds to a specific Direct Challenge, completes any session terms left unfixed by it, and instantiates the session.
Abstract
Defines kind: 3421, a regular event by which a challenged player accepts a Direct Challenge (kind 3420). The event references the Direct Challenge, mirrors the arbiter designation and, when present, the timestamper designation from the Direct Challenge, completes any session terms the Direct Challenge left open (either player’s variant and the seat assignment), and is signed by the challenged player.
The Accepted Challenge declares the session parameters but does not by itself make the session playable. The session becomes playable when the designated arbiter publishes the Game Session (kind 3422), which establishes the canonical session-start moment (t₀) used for time-control accounting (§Canonical session start).
Motivation
A session in the suite is established by a sequence of signed events: the Direct Challenge (kind 3420) carries the challenger’s proposal; the Accepted Challenge (kind 3421) carries the challenged player’s binding acceptance; and the Game Session (kind 3422) carries the arbiter’s ratification and any variant-specific initialization data.
The design separates challenge and acceptance into two events so that:
- The challenger commits to proposed terms before the opponent has agreed.
- The challenged player has the option to fill in any terms the challenger left open (the challenged player’s own variant, the challenger’s variant when the challenger delegated it by omission, the seat assignment).
- A session is declared if and only if a valid
(Direct Challenge, Accepted Challenge)pair exists (per §Semantic constraints).
The Accepted Challenge declares the session. Whether and when the session becomes playable depends on the arbiter publishing the Game Session (kind 3422); time-control accounting anchors at t₀, derived from the Game Session’s canonical timing and optional start_at (kind 3422 §Canonical session start). See §Canonical session start.
Specification
Event kind
kind: 3421
Accepted Challenge events are regular events per NIP-01: immutable once signed and broadcast.
Operating mode
This NIP inherits the role designations of the referenced Direct Challenge (kind 3420), which carries an arbiter role marker (REQUIRED) and exactly one timing designation — a timestamper role marker (attested mode) or timing_relay tags (self-timed mode); see the Canonical Timing NIP §Timing modes and mode selection. The Accepted Challenge MUST mirror the Direct Challenge’s designations exactly:
- The Direct Challenge MUST carry exactly one
arbiter-markedptag with pubkey P_a; the Acceptance MUST carry anarbiter-markedptag with the same pubkey P_a. - The timing designation MUST match: if the Direct Challenge carries a
timestamper-markedptag with pubkey P_t (attested mode), the Acceptance MUST carry atimestamper-markedptag with the same pubkey P_t and notiming_relaytag; if it carriestiming_relaytags (self-timed mode), the Acceptance MUST carry the sametiming_relayset (as a set) and notimestamper-markedptag.
The challenged player consents to the role designations by accepting; they cannot unilaterally alter them. No mismatch between the Direct Challenge and the Acceptance is permitted.
Attestation by the designated timestamper (attested mode)
This section applies in attested mode only (a timestamper is designated). In self-timed mode the Accepted Challenge’s canonical timing is its own created_at, established by acceptance at a designated timing relay (Canonical Timing §Canonical timing of an event), and the paragraphs below do not apply.
The designated timestamper publishes an Event Timestamp Attestation (kind 3410) referencing this Accepted Challenge via the required attests-marked e tag. The attestation’s created_at is the authoritative timing for the Accepted Challenge for the purposes of race resolution and any downstream temporal reasoning. See Canonical Timing.
Until the Accepted Challenge has been attested by the designated timestamper, the session is in a pending state: the Acceptance exists on Nostr but has no canonical position in any consumer’s view of the session lifecycle. The Game Session (kind 3422) cannot be canonically anchored until its referenced Acceptance is itself canonically anchored.
The structure of the Acceptance Attestation is:
["e", "<accepted_challenge_event_id>", "<relay_hint>", "attests"]
Only the attests-marked e tag is required. NIP-26-delegated signatures from the timestamper are NOT accepted as authoritative; see kind 3420 §Security considerations.
Tags
Reference tag
| Tag | Cardinality | Value | Description |
|---|---|---|---|
e direct_challenge |
exactly one | event id, optional relay hint, marker | reference to the Direct Challenge being accepted |
The e tag identifies the Direct Challenge event (kind 3420) this acceptance responds to, and MUST carry the marker direct_challenge as its fourth element:
["e", "<direct_challenge_event_id>", "<relay_hint>", "direct_challenge"]
The relay hint (third element) is OPTIONAL but SHOULD be present. The marker follows the suite’s uniform convention — every cross-kind reference names its role — so a marker-driven parser handles this founding reference exactly like every other.
Player tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
p |
two or three | pubkey, optional relay hint, role marker | exactly one opponent (the challenger); exactly one arbiter; and, in attested mode, exactly one timestamper (each mirroring the Direct Challenge) |
timing_relay |
zero or more | relay URL (wss://…) |
in self-timed mode, the designated timing relays, mirroring the Direct Challenge’s set verbatim; absent in attested mode |
The p tags MUST carry a role marker as the fourth element:
["p", "<challenger_pubkey>", "<relay_hint>", "opponent"]— the challenger (the signer of the referenced Direct Challenge). Exactly one such tag is required.["p", "<arbiter_pubkey>", "<relay_hint>", "arbiter"]— the designated arbiter. Exactly one such tag is required, mirroring the Direct Challenge.["p", "<timestamper_pubkey>", "<relay_hint>", "timestamper"]— the designated timestamper. In attested mode, exactly one such tag, mirroring the Direct Challenge; absent in self-timed mode.["timing_relay", "<wss://relay.example.com>"]— in self-timed mode, the designated timing relays, mirroring the Direct Challenge’s set; absent in attested mode.
The p tags serve a dual purpose: semantic (identifying the participants) and structural (notification routing via standard Nostr #p filters). The role-carrying tags are duplicated from the Direct Challenge to enable standard #p discovery for each designated party without requiring chained subscription logic.
The relay hint (third element) 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 challenged player). - The
arbiterpubkey MUST be distinct from the challenger and the opponent. - In attested mode, the
timestamperpubkey MUST be distinct from the challenger and the opponent, and MAY be identical to thearbiterpubkey (same actor cumulating both roles).
Match-terms tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
variant |
zero to two (at most one per player) | pubkey + variant identifier | a player’s variant, if not fixed by the Direct Challenge |
seat |
zero or one | first or second |
seat declared by the challenged player (the challenger implicitly receives the other value); present only if the Direct Challenge did not fix it |
These tags follow the same format as in the Direct Challenge:
["variant", "<pubkey>", "<variant_id>"]— the second element MUST be the event’spubkey(the challenged player) or the challenger’s pubkey; the third element is a variant identifier matching^[a-z][a-z0-9]{0,31}$. Avarianttag carrying the challenger’s pubkey supplies the variant the challenger delegated by omitting it from the Direct Challenge (kind3420§Match-terms tags).["seat", "<seat_name>"]— the second element is a seat-name, one of exactly two protocol-defined values:first(moves first) orsecond(moves second); the traditional labels (white/black, sente/gote, …) are presentation, mapped by the rule system (kind3420§Match-terms tags). Because the Accepted Challenge is signed by the challenged player, the seat carried by theseattag is always the challenged player’s. The challenger implicitly receives the other value. To assign the challenger to a specific seat, the challenged player declares the other value for themselves.
The Direct Challenge–Acceptance pair MUST jointly specify both players’ variants and the seat assignment. The Acceptance supplies the missing pieces (and only those):
- For each player (the challenger and the challenged player): if the Direct Challenge does NOT include a
varianttag for that player, the Acceptance MUST include exactly one suchvarianttag. Otherwise, the Acceptance MUST NOT include avarianttag for that player. - If the Direct Challenge does NOT include a
seattag, the Acceptance MUST include exactly oneseattag. Otherwise, the Acceptance MUST NOT include aseattag.
Presence of a variant tag for the same player, or of a seat tag, in both the Direct Challenge and the Acceptance invalidates the pair.
When the challenged player has no preference for a variant the Direct Challenge delegated to them, their client SHOULD apply a deterministic default rather than block the acceptance. The RECOMMENDED default is the mirror rule: assign the delegating challenger the same variant the challenged player plays (always computable, since the challenged player’s own variant is necessarily known at acceptance time — fixed by the Direct Challenge or supplied in this Acceptance — and requiring no variant vocabulary at this NIP’s level). The default is a client convention, not a protocol constraint: whatever variant the Acceptance carries is binding once signed, and the delegating challenger consented to any choice by omitting the tag. A deployment MAY document an alternative deterministic default (e.g., derived from the Direct Challenge’s event id over the deployment’s variant vocabulary).
Note that the game tag is fixed by the Direct Challenge and is NOT mirrored or referenced in the Acceptance — the game context is already established and immutable from the moment the Direct Challenge is signed.
Lifecycle tag
| Tag | Cardinality | Value | Description |
|---|---|---|---|
nonce |
exactly one | nonce + difficulty | NIP-13 proof of work |
The nonce tag follows NIP-13.
Content
The content field MAY carry a free-form text message from the challenged player to the challenger (e.g., a greeting, a comment on the proposed terms). 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 acceptance. The challenger’s client MAY display it to the user, but MUST NOT base any normative decision 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).
Canonical session start
The Acceptance declares the session parameters but does not define the canonical session-start moment (t₀) used for time-control accounting. t₀ is the later of the Game Session’s start_at tag value (when present) and the Game Session’s canonical timing — in either mode (Canonical Timing §t₀). See kind 3422 §Canonical session start for the complete t₀ semantics, including the structure of the Session Start Attestation in attested mode.
The accept-deadline check uses the Acceptance’s canonical timing (§Semantic constraints, constraint 6): in attested mode the timestamper attestation’s created_at (the Acceptance’s own created_at being ignored); in self-timed mode the Acceptance’s own created_at, made non-backdatable by the designated timing relay. See Canonical Timing.
Semantic constraints
A conforming Accepted Challenge event MUST satisfy all of the following:
- The event contains exactly one
direct_challenge-markedetag, referencing a retrievable, signature-valid, conforming Direct Challenge event (kind3420, per its §Semantic constraints). - The event contains exactly one
opponent-markedptag, exactly onearbiter-markedptag, and exactly one timing designation: onetimestamper-markedptag and notiming_relaytag (attested mode), or one or moretiming_relaytags and notimestamper(self-timed mode). - The
opponent-markedptag’s pubkey matches thepubkeyof the referenced Direct Challenge event (i.e., the challenger). - The
arbiter-markedptag’s pubkey value MUST match thearbiter-marked tag’s pubkey in the referenced Direct Challenge. The timing designation MUST match: the sametimestamperpubkey (attested mode), or the sametiming_relayset as a set (self-timed mode). - The event’s
pubkey(the challenged player) matches the pubkey of theopponent-markedptag in the referenced Direct Challenge. - The Acceptance’s canonical timing (Canonical Timing §Canonical timing of an event — the timestamper attestation’s
created_atin attested mode, the Acceptance’s owncreated_atonce accepted by a designated timing relay in self-timed mode) is less than or equal to the referenced Direct Challenge’saccept_untiltag value. An Acceptance whose canonical timing exceeds the Direct Challenge’saccept_untilis non-conforming and MUST be ignored by all consumers. - For each player P — the challenged player (the event’s
pubkey) and the challenger (the referenced Direct Challenge’spubkey): if the referenced Direct Challenge does NOT include avarianttag whose second element is P’s pubkey, the Acceptance MUST include exactly one suchvarianttag, with a valid variant identifier matching^[a-z][a-z0-9]{0,31}$; otherwise, the Acceptance MUST NOT include avarianttag for P. At most onevarianttag is present per player, and novarianttag carries any other pubkey. - If the referenced Direct Challenge does NOT include a
seattag, the Acceptance MUST include exactly oneseattag, whose second element isfirstorsecond. Otherwise, the Acceptance MUST NOT include aseattag. - The pubkeys of all
ptags MUST be distinct from the event’spubkey(the challenged player). Theopponentpubkey MUST be distinct from thearbiterand (in attested mode)timestamperpubkeys. Thearbiterandtimestamperpubkeys MAY be identical (same actor cumulating both roles). - 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 2, 9, and 10 are checkable from the event alone. Constraints 1, 3–5, 7, and 8 require cross-event validation against the referenced Direct Challenge: constraint 1 requires retrieving and validating it, constraints 3–5 compare designations and pubkeys, and constraints 7 and 8 condition on which variant and seat tags the Direct Challenge carries. Constraint 6 requires both the referenced Direct Challenge and the Acceptance’s canonical timing, and is therefore the latest constraint to become checkable in the event lifecycle. In attested mode, the Acceptance’s own created_at is informational and MUST NOT be used for these checks; in self-timed mode, it IS the canonical timing (made non-backdatable by the designated timing relay). See Canonical Timing.
Validation responsibility
Validation of an Accepted Challenge against the semantic constraints (§Semantic constraints) is the responsibility of the client, the relay, and the arbiter. A relay additionally enforces its advertised NIP-13 minimum difficulty at ingestion — a relay policy, distinct from conformance.
If any constraint fails — e.g., the referenced Direct Challenge cannot be retrieved, the canonical timing exceeds the Direct Challenge’s accept_until, a conditionally required tag is missing or duplicated, or the timing designation does not match — the Accepted Challenge is invalid and the session has not been instantiated.
Race conditions
A single Direct Challenge MAY receive multiple validly-signed Accepted Challenge events (e.g., the challenged player publishes from multiple concurrent client instances). The canonical Accepted Challenge is determined per Canonical Timing §Meta-resolution: the candidate with the smallest canonical timing, with the smallest event ID as tiebreaker.
Non-canonical Accepted Challenges MUST be ignored by clients and by any arbitration layer. They are not retracted from relays; they are simply not effective.
Examples
The examples below use placeholder pubkeys, relay URLs, and timestamps for illustration. They assume reference to a corresponding Direct Challenge event (kind 3420).
Example 1 — All terms fixed by the Direct Challenge
The referenced Direct Challenge fixed both players’ variants, the seat assignment, the time control, and designated an arbiter. Bob accepts with no conditional tags:
{
"kind": 3421,
"pubkey": "<bob_pubkey>",
"created_at": 1700000300,
"tags": [
["e", "<direct_challenge_event_id>", "wss://relay.example.com", "direct_challenge"],
["p", "<alice_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["timing_relay", "wss://relay.example.com"],
["nonce", "94821", "16"]
],
"content": "Let's go!",
"id": "...",
"sig": "..."
}
The session is instantiated at 1700000300.
Example 2 — Variant and seat deferred
The referenced Direct Challenge was in game="sanki", fixed only Alice’s variant (chess), and left Bob’s variant and the seat assignment open. Bob now supplies his variant (ogi) and takes seat second (the challenger Alice implicitly receives first):
{
"kind": 3421,
"pubkey": "<bob_pubkey>",
"created_at": 1700000300,
"tags": [
["e", "<direct_challenge_event_id>", "wss://relay.example.com", "direct_challenge"],
["p", "<alice_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["timing_relay", "wss://relay.example.com"],
["variant", "<bob_pubkey>", "ogi"],
["seat", "second"],
["nonce", "84217", "16"]
],
"content": "",
"id": "...",
"sig": "..."
}
The session is now instantiated at 1700000300 in the Sanki game context: Alice plays seat first with variant chess, Bob plays seat second with variant ogi. The session is multi-variant (Alice and Bob play different variants on a shared 8x8 board); a rating filter declaring the pergame scope rates it together with the game’s other variants, while a pervariant declaration would exclude it (kind 3418 §Match-terms tags).
Example 3 — Both variants deferred (delegation + mirror default)
The referenced Direct Challenge was in game="sanki" and fixed no variant: the challenger Bob delegated his own variant and left Alice’s open (see kind 3420 §Match-terms tags; its Example 3 is such a challenge). Alice chooses xiongqi for herself; having no preference for Bob’s variant, her client applies the mirror default and assigns Bob xiongqi too. She also takes seat first:
{
"kind": 3421,
"pubkey": "<alice_pubkey>",
"created_at": 1700000300,
"tags": [
["e", "<direct_challenge_event_id>", "wss://relay.example.com", "direct_challenge"],
["p", "<bob_pubkey>", "wss://relay.example.com", "opponent"],
["p", "<carol_pubkey>", "wss://relay.example.com", "arbiter"],
["timing_relay", "wss://relay.example.com"],
["variant", "<alice_pubkey>", "xiongqi"],
["variant", "<bob_pubkey>", "xiongqi"],
["seat", "first"],
["nonce", "50291", "16"]
],
"content": "",
"id": "...",
"sig": "..."
}
The session is same-variant xiongqi. Alice supplying Bob’s variant here is not an imposition of an opponent’s variant (the form a deployment MAY gate behind its own eligibility policy — kind 3420 §Validation responsibility): Bob consented to her choice by omitting his own variant tag from the Direct Challenge.
Client guidelines
Clients implementing kind: 3421 SHOULD:
- Subscribe to kind
3421events with a#pfilter on the user’s pubkey to receive notifications of acceptances addressed to them asopponent(the challenger) orarbiter. - Inspect the role marker (fourth element of each
ptag) to identify the user’s role. - Retrieve and validate the referenced Direct Challenge event before treating the Acceptance as binding.
- Validate the Acceptance against all semantic constraints (§Semantic constraints) before treating the session as instantiated, including the mirroring constraints between the Direct Challenge and the Accepted Challenge.
- Apply the race-resolution rule (Canonical Timing §Meta-resolution) when multiple Accepted Challenge events reference the same Direct Challenge.
- Treat the canonical session-start timestamp (t₀) as established by the Game Session (kind
3422), not by this Acceptance. Until the Game Session is observed, display the session as “declared, awaiting arbiter ratification”. Do not allow the user to submit Plies until the Game Session is available. See kind3422§Canonical session start.
Clients MAY:
- Allow the challenged player 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 challenger with appropriate output escaping (§Security considerations) and without altering acceptance logic.
Security considerations
-
Signatures: Accepted Challenge events are signed by the challenged player. Receiving clients MUST verify signatures per NIP-01 before treating the session as instantiated.
-
Proof of work: The
noncetag follows NIP-13. 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. -
Cross-event validation: An Accepted Challenge is meaningful only in conjunction with its referenced Direct Challenge. Verifiers MUST retrieve and validate the Direct Challenge as part of validating the Acceptance.
-
Mirror consistency: The arbiter designation and the timing designation (the timestamper, or the
timing_relayset) are determined by the Direct Challenge and MUST be mirrored exactly by the Acceptance. A mismatch — a different arbiter pubkey, a different, missing, or extra timestamper, or a differingtiming_relayset — invalidates the Acceptance. -
Deadline enforcement: An Accepted Challenge whose canonical timing is greater than the referenced Direct Challenge’s
accept_untilMUST be treated as invalid by verifiers. In attested mode the canonical timing is the timestamper attestation’screated_at(the Acceptance’s owncreated_atbeing ignored); in self-timed mode it is the Acceptance’s owncreated_at, made non-backdatable by the designated timing relay. See Canonical Timing. In either mode, an Acceptance with no canonical timing yet is pending and is neither valid nor invalid until it acquires one. -
Race conditions: Multiple Accepted Challenges for the same Direct Challenge are resolved deterministically (§Race conditions). Non-canonical events are ignored, not retracted.
-
Arbiter coordination: This NIP does not require the arbiter to act at acceptance time. The arbiter is a reactive participant, intervening only when invoked (out of scope for this NIP).
- Content rendering: The
contentfield is untrusted user input controlled by the challenged player. 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 challenged player can place anything within the allowed character set into
content, including phishing attempts, social engineering, or misleading claims about their 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 challenged player). -
Session start timestamp: The canonical session-start moment (t₀) is not established by this Acceptance. It is the later of the Game Session’s
start_at(when present) and the Game Session’s canonical timing — its Session Start Attestation’screated_atin attested mode, its owncreated_atat a designated timing relay in self-timed mode (Canonical Timing §t₀). The Acceptance’screated_atis informational only and does not contribute to time-control accounting. In all cases, clock skew between participants is not corrected by the protocol; significant skew may produce timing disputes resolvable only by out-of-band means. See §Canonical session start. -
Arbitration scope: This NIP does not itself provide game-outcome arbitration, ply progression, or termination semantics. Those concerns are addressed by other NIPs in the suite (Ply, Adjudication Request, Adjudication).
References
- NIP-01 — Basic protocol flow description
- NIP-13 — Proof of Work
- Kind
3420— Direct Challenge (the event being accepted) - Kind
3422— Game Session (the next event in the lifecycle) - Event Timestamp Attestation — primitive for the Acceptance’s attestation
- Canonical Timing — timing modes, canonical timing, meta-resolution, t₀
