Sashité for Developers
  1. Sashité for Developers
  2. Nostr
  3. NIPs
  4. 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 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 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:

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:

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:

The Direct Challenge–Acceptance pair MUST jointly specify both players’ variants and the seat assignment. The Acceptance supplies the missing pieces (and only those):

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:

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:

  1. The event contains exactly one direct_challenge-marked e tag, referencing a retrievable, signature-valid, conforming Direct Challenge event (kind 3420, per its §Semantic constraints).
  2. The event contains exactly one opponent-marked p tag, exactly one arbiter-marked p tag, and exactly one timing designation: one timestamper-marked p tag and no timing_relay tag (attested mode), or one or more timing_relay tags and no timestamper (self-timed mode).
  3. The opponent-marked p tag’s pubkey matches the pubkey of the referenced Direct Challenge event (i.e., the challenger).
  4. The arbiter-marked p tag’s pubkey value MUST match the arbiter-marked tag’s pubkey in the referenced Direct Challenge. The timing designation MUST match: the same timestamper pubkey (attested mode), or the same timing_relay set as a set (self-timed mode).
  5. The event’s pubkey (the challenged player) matches the pubkey of the opponent-marked p tag in the referenced Direct Challenge.
  6. The Acceptance’s canonical timing (Canonical Timing §Canonical timing of an event — the timestamper attestation’s created_at in attested mode, the Acceptance’s own created_at once accepted by a designated timing relay in self-timed mode) is less than or equal to the referenced Direct Challenge’s accept_until tag value. An Acceptance whose canonical timing exceeds the Direct Challenge’s accept_until is non-conforming and MUST be ignored by all consumers.
  7. For each player P — the challenged player (the event’s pubkey) and the challenger (the referenced Direct Challenge’s pubkey): if the referenced Direct Challenge does NOT include a variant tag whose second element is P’s pubkey, the Acceptance MUST include exactly one such variant tag, with a valid variant identifier matching ^[a-z][a-z0-9]{0,31}$; otherwise, the Acceptance MUST NOT include a variant tag for P. At most one variant tag is present per player, and no variant tag carries any other pubkey.
  8. If the referenced Direct Challenge does NOT include a seat tag, the Acceptance MUST include exactly one seat tag, whose second element is first or second. Otherwise, the Acceptance MUST NOT include a seat tag.
  9. The pubkeys of all p tags MUST be distinct from the event’s pubkey (the challenged player). The opponent pubkey MUST be distinct from the arbiter and (in attested mode) timestamper pubkeys. The arbiter and timestamper pubkeys MAY be identical (same actor cumulating both roles).
  10. Exactly one nonce tag 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:

  1. Subscribe to kind 3421 events with a #p filter on the user’s pubkey to receive notifications of acceptances addressed to them as opponent (the challenger) or arbiter.
  2. Inspect the role marker (fourth element of each p tag) to identify the user’s role.
  3. Retrieve and validate the referenced Direct Challenge event before treating the Acceptance as binding.
  4. 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.
  5. Apply the race-resolution rule (Canonical Timing §Meta-resolution) when multiple Accepted Challenge events reference the same Direct Challenge.
  6. 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 kind 3422 §Canonical session start.

Clients MAY:

Security considerations

The protocol-level character restrictions do not replace context-appropriate output escaping. They close a narrow class of attacks that escaping alone cannot address.

References