- Sashité for Developers
- Nostr
- NIPs
- Pairing
NIP-XX
Pairing
draft optional
This NIP defines a Nostr event kind by which a designated matchmaker pairs two compatible Open Challenges (kind 3418) into a declared session for a turn-based, two-player abstract strategy board game of the chess family. The Pairing is the founding event of the matchmaking path: it stands in the same position as the Accepted Challenge (kind 3421) does for the directed path, and the resulting session is ratified, played, and adjudicated by the same downstream NIPs.
Abstract
Defines kind: 3419, a regular event signed by a matchmaker that references two Open Challenges (kind 3418) and binds them into a session. The Pairing names the two players, fixes the concrete arbiter (and, in attested mode, the timestamper) authorized by both Open Challenges, resolves each player’s variant, and carries the agreed time control. The arbiter then ratifies the session and assigns the seat by publishing a Game Session (kind 3422) that references the Pairing.
Neither player signs the Pairing. Their consent is pre-committed in their Open Challenges: a Pairing is conforming only if it respects the terms both referenced Open Challenges declared (§Consent constraints). This bounds the matchmaker’s authority to selecting which compatible pair to match, when, and which concrete values to choose within each Open Challenge’s authorization.
Motivation
A symmetric matchmaking pool needs an event that turns two independent “I am available” authorizations into one session, signed by a party neither player is. The Pairing is that event. The matchmaker observes the pool of Open Challenges (kind 3418) that authorize it, finds two that are mutually compatible, and publishes a Pairing referencing both.
The design separates pool entry (kind 3418, signed by each player) from pairing (this NIP, signed by the matchmaker) so that:
- Each player commits, in their Open Challenge, to the third parties and the terms under which they accept being paired — before any opponent is known.
- The matchmaker consummates a pairing without a round-trip acceptance, enabling low-latency pool matching.
- The matchmaker cannot overreach: a Pairing that violates either Open Challenge’s terms is non-conforming and is ignored by all consumers (§Consent constraints). The matchmaker’s discretion is confined to liveness (whether and when to pair) and to selecting within consented bounds.
The Pairing declares the session. Whether and when it becomes playable depends on the arbiter publishing the Game Session (kind 3422); time-control accounting anchors at t₀, established by the Game Session’s canonical timing. See §From Pairing to Game Session.
Specification
Event kind
kind: 3419
Pairing events are regular events per NIP-01: immutable once signed and broadcast.
Signing party
The event MUST be signed by the pubkey designated as matchmaker by both referenced Open Challenges (kind 3418). Any other signer’s event is not a conforming Pairing for those Open Challenges and MUST be ignored. The matchmaker is identified by signature; it is not carried as a p tag.
NIP-26-delegated signatures from the matchmaker are NOT accepted as authoritative, consistent with the suite’s delegation policy for designated parties (kind 3420 §Security considerations).
Attestation by the designated timestamper (attested mode)
This section applies in attested mode only (a timestamper is designated by both Open Challenges). In self-timed mode the Pairing’s canonical timing is its own created_at, established by acceptance at a designated timing relay (Canonical Timing §Canonical timing of an event); it then serves the same roles — greedy matching and the accept-deadline check — without an attestation.
The designated timestamper publishes an Event Timestamp Attestation (kind 3410) referencing this Pairing via the required attests-marked e tag. The attestation’s created_at is then the Pairing’s canonical timing for greedy matching (§Race resolution) and for the accept-deadline check against each Open Challenge’s accept_until (§Consent constraints).
["e", "<pairing_event_id>", "<relay_hint>", "attests"]
Only the attests-marked e tag is required; like the attestation of the Accepted Challenge (kind 3421), it carries no discovery marker, because the Game Session does not yet exist when the Pairing is attested. Until the Pairing has canonical timing (an attestation in attested mode, or established acceptance by a designated timing relay in self-timed mode), it is pending (Canonical Timing §The pending state): it has no position in greedy matching and cannot yet be ratified. NIP-26-delegated signatures from the timestamper are NOT accepted as authoritative.
In attested mode, the Pairing’s own created_at is informational and MUST NOT be used for any timing-dependent decision; in self-timed mode it IS the canonical timing, made non-backdatable by the designated timing relay’s ingestion window. See Canonical Timing.
Tags
Reference tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
e open_challenge |
exactly two | event id, optional relay hint, marker | the two Open Challenges (kind 3418) being paired |
Each reference is ["e", "<open_challenge_event_id>", "<relay_hint>", "open_challenge"]. The two referenced Open Challenges MUST have distinct signers (the two players). The relay hint is OPTIONAL but SHOULD be present.
Player and authority tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
p |
three or four | pubkey, optional relay hint, role marker | exactly two player; exactly one arbiter; and, in attested mode, exactly one timestamper (absent in self-timed mode, the default) |
["p", "<player_pubkey>", "<relay_hint>", "player"]— a session player. Exactly two, matching the signers of the two referenced Open Challenges.["p", "<arbiter_pubkey>", "<relay_hint>", "arbiter"]— the concrete arbiter.["p", "<timestamper_pubkey>", "<relay_hint>", "timestamper"]— the concrete timestamper. Present in attested mode (exactly one, mirroring both Open Challenges); absent in self-timed mode.["timing_relay", "<wss://relay.example.com>"]— in self-timed mode, the designated timing relays, mirroring both Open Challenges’ identicaltiming_relaysets verbatim; absent in attested mode (Canonical Timing §Timing modes and mode selection).
The p tags serve a dual purpose: semantic (the participants and the concrete third parties) and structural (routing via standard #p filters, so each player and the arbiter receive the Pairing). The arbiter and the timing designation (timestamper, or timing_relay tags) are carried explicitly so the Game Session (kind 3422) can verify them by the same logic it uses for the directed path.
Match-terms tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
game |
exactly one | game identifier | the game context (equal to both Open Challenges’) |
variant |
exactly two (one per player) | pubkey + variant identifier | each player’s resolved variant |
time_control |
one or more | duration + optional increment + optional plies | the agreed time-control configuration |
The game tag is ["game", "<game_id>"] and MUST equal the game of both referenced Open Challenges.
Each variant tag is ["variant", "<player_pubkey>", "<variant_id>"] — pubkey-based, now that both players are known (the role→pubkey transition described in kind 3418 §Role-based ownership). Exactly one per player, unconditionally: the Pairing resolves every preference left open by the Open Challenges, so both players’ variants are fixed here — a single-variant game simply repeats its one style’s identifier. The resolved variants MUST satisfy both Open Challenges’ role preferences (§Consent constraints). The identifiers’ meaning stays opaque to the protocol (the arbiter’s rule system interprets them), mirroring kind 3422 §Variants.
The time_control tag uses the format and multi-period semantics of the Direct Challenge (kind 3420 §Match-terms tags) and MUST be identical to the time-control configuration of both referenced Open Challenges. Identity is compared on the parsed period sequence: the same number of periods in the same order, each pair equal in duration, in increment presence and value, and in plies presence and value. No normalization is applied: a period with no increment and a period with an explicit increment of 0 are not identical (kind 3420 §Match-terms tags).
No seat tag is carried. The seat assignment is performed by the arbiter in the Game Session (kind 3422), which assigns each player a seat at ratification time. See §From Pairing to Game Session.
There is no nonce tag: like the Game Session (kind 3422) and the Adjudication (kind 3425), a Pairing is signed by a designated third party, not subjected to proof of work. Spam resistance for the founding path is carried by the proof of work on the Open Challenges (kind 3418).
Content
The content field MUST be the empty string (""). A Pairing carries no narrative payload; its semantics are fully expressed by its tags.
Consent constraints
A conforming Pairing event MUST satisfy all of the following. Constraints 11 and 12 are checkable from the event alone; the rest require the two referenced Open Challenges (and, for constraints 9 and 10, external data: the Pairing’s canonical timing, the filtering players’ contact lists, and the pinned rating attestations).
- Exactly two
open_challenge-markedetags, referencing retrievable, signature-valid, conforming Open Challenge events (kind3418, per its §Semantic constraints) whose signers are distinct. - The event’s
pubkey(the matchmaker) equals thematchmaker-marked pubkey of both referenced Open Challenges. - Exactly two
player-markedptags, whose pubkeys equal the signers of the two referenced Open Challenges (one each). - Exactly one
arbiter-markedptag whose pubkey equals thearbiter-marked pubkey of both Open Challenges. - Timing designation matches both Open Challenges (Canonical Timing §Timing modes and mode selection): in attested mode (both designate the same timestamper), exactly one
timestamper-markedptag whose pubkey equals that designation, and notiming_relaytag; in self-timed mode (both designate the sametiming_relayset),timing_relaytags equal to that set (as a set), and notimestamper-markedptag. A Pairing whose timing designation disagrees with either Open Challenge — or two Open Challenges whose designations differ from each other — cannot found a session; such a Pairing is non-conforming. - Exactly one
gametag whose value equals thegameof both Open Challenges. - Exactly two
varianttags are present, one per player pubkey, and the resolved per-player variants satisfy, for each player P with resolved variantvP: (a) if P’s own Open Challenge carries aselfvariant,vPequals it; and (b) if the other player’s Open Challenge carries anopponentvariant,vPequals it. A player whose variant is constrained by neither (a) nor (b) MAY be assigned any variant by the matchmaker — but the assignment is always written: the Pairing carries one resolved variant per player, unconditionally. - The
time_controlconfiguration is identical to that of both Open Challenges. - Each player satisfies the other player’s Open Challenge
filter, evaluated at the Pairing’s canonical timing:- for
everyone(or an absent filter): trivially; - for
following: the filtering player follows the other, per the filtering player’s most recent NIP-02 contact list (kind3) whosecreated_atis less than or equal to the Pairing’s canonical timing — the suite’s uniform at-or-before anchoring for consulting a replaceable list. The contact list being a replaceable event, this leg is fully verifiable only by consumers who observed the list at the relevant time (the matchmaker, the players, live observers); a late verifier who can no longer reconstruct the list state SHOULD treat the matchmaker’s pairing and the arbiter’s ratification as evidence of satisfaction rather than presume non-conformance; - for
rating: both players’ ratings come from the pinned rating source carried by the filter (the rating-authority pubkey, attestation kind, and declared pool scope — kind3418§Match-terms tags). Each player’s rating is the most recent conforming attestation by that authority for that player in the scope the filter declares —pergame: attestations carrying the session’sgame, regardless ofvarianttags;pervariant: attestations whosegameand singlevarianttag match that player’s resolved variant in this Pairing — withcreated_atless than or equal to the Pairing’s canonical timing; the other player’s rating MUST be withinmax_deltaof the filtering player’s. A player with no such attestation is unrated and the filter is NOT satisfied. Under apervariantscope, aratingfilter is satisfiable only when the Pairing resolves the same variant for both players (a multi-variant session has no shared (game, variant) pool to compare in); under apergamescope, the single game pool is shared regardless of the resolved variants, and the filter binds across any variant combination. This leg is objective and retroactively verifiable from public data alone: rating attestations are regular, persistent events, and the scope is written in the filter.
- for
- The Pairing’s canonical timing is less than or equal to the smaller of the two Open Challenges’
accept_untilvalues. A Pairing whose canonical timing exceeds either deadline (or whose referenced Open Challenge has none) is non-conforming. - The matchmaker pubkey is distinct from both player pubkeys.
- The
contentfield is the empty string ("").
A Pairing violating any constraint is non-conforming and MUST be ignored by all consumers (clients, the arbiter, rating verifiers). Non-conforming Pairings are not retracted from relays; they simply have no effect. This is the mechanism that bounds the matchmaker: it may choose the pair, the timing, and any value left free by both Open Challenges, but it cannot bind a player to an arbiter, timestamper, opponent, variant, time control, or counterparty outside that player’s signed authorization.
Race resolution — greedy matching
A given Open Challenge participates in at most one canonical Pairing. Because a Pairing references two Open Challenges, canonicalization is a matching problem rather than a single-slot selection; it is resolved deterministically and anchored, like the rest of the suite, on canonical timing (Canonical Timing §Meta-resolution):
- Consider all conforming Pairings that have canonical timing. Order them by their canonical timing (ascending), with the smallest Pairing event id as tiebreaker.
- Process them in that order, maintaining a set of “consumed” Open Challenges (initially empty). A Pairing is canonical iff both of its referenced Open Challenges are still free; on acceptance, mark both as consumed. A Pairing referencing an already-consumed Open Challenge is non-canonical and is skipped.
This greedy procedure yields the same matching for every consumer from the public events alone. If an Open Challenge appears in two conforming Pairings, the earlier-attested Pairing wins it; the later Pairing is void, and the other player it named remains free to be matched by a subsequent Pairing (in practice, their client re-enters the pool). A Pairing with no canonical timing does not participate.
From Pairing to Game Session
The Pairing declares the session; it does not make it playable. The designated arbiter ratifies by publishing a Game Session (kind 3422) whose founding-reference e tag points to the canonical Pairing (marker pairing) and whose signer is the arbiter named by the Pairing. The Game Session:
- carries the two players (from the Pairing’s
playertags) and their variants (from the Pairing’svarianttags); - assigns the seat: because the matchmaking founding path carries no seat, the arbiter assigns each player a seat at ratification — typically a fair random draw, analogous to the arbiter’s choice of a randomized initial position for variants such as chess960;
- establishes the initial position and, via its own canonical timing, t₀.
From the Game Session onward the lifecycle is identical to the directed path: Ply (kind 3423), Adjudication Request (kind 3424), Adjudication (kind 3425), and optional rating attestations (kinds 3426/3427) all apply unchanged. A matchmade session contributes to ratings exactly as a directed one does, subject to the pool scope in effect (a pervariant scope excludes multi-variant sessions from rating filters; a pergame scope rates them together).
Validation responsibility
Validation of a Pairing against the consent constraints is the responsibility of the client, the arbiter (which MUST validate before ratifying), and rating verifiers. A non-conforming Pairing does not declare a session; the arbiter SHOULD decline to publish a Game Session for it.
Examples
The examples below use placeholder pubkeys, event ids, relay URLs, and timestamps for illustration.
Example 1 — Same-variant pairing
The matchmaker pairs Alice’s and Bob’s sanki ōgi 5+3 Open Challenges:
{
"kind": 3419,
"pubkey": "<matchmaker_pubkey>",
"created_at": 1700000100,
"tags": [
["e", "<alice_open_challenge_id>", "wss://relay.example.com", "open_challenge"],
["e", "<bob_open_challenge_id>", "wss://relay.example.com", "open_challenge"],
["p", "<alice_pubkey>", "wss://relay.example.com", "player"],
["p", "<bob_pubkey>", "wss://relay.example.com", "player"],
["p", "<arbiter_pubkey>", "wss://relay.example.com", "arbiter"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["variant", "<alice_pubkey>", "ogi"],
["variant", "<bob_pubkey>", "ogi"],
["time_control", "300", "3"]
],
"content": "",
"id": "...",
"sig": "..."
}
The arbiter then publishes a Game Session (kind 3422) referencing this Pairing (marker pairing) and assigns Alice and Bob their seats.
Example 2 — Resolved multi-variant pairing
Bob’s Open Challenge fixed self = ogi and left opponent open; Dana’s fixed self = chess and left opponent open. The matchmaker resolves a multi-variant session (Bob ōgi vs Dana chess), valid because neither Open Challenge constrained the other’s variant:
{
"kind": 3419,
"pubkey": "<matchmaker_pubkey>",
"created_at": 1700000100,
"tags": [
["e", "<bob_open_challenge_id>", "wss://relay.example.com", "open_challenge"],
["e", "<dana_open_challenge_id>", "wss://relay.example.com", "open_challenge"],
["p", "<bob_pubkey>", "wss://relay.example.com", "player"],
["p", "<dana_pubkey>", "wss://relay.example.com", "player"],
["p", "<arbiter_pubkey>", "wss://relay.example.com", "arbiter"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["variant", "<bob_pubkey>", "ogi"],
["variant", "<dana_pubkey>", "chess"],
["time_control", "300", "3"]
],
"content": "",
"id": "...",
"sig": "..."
}
This is a multi-variant session; a rating filter declaring the pergame scope binds across it, while a pervariant declaration would have excluded it (constraint 9). Had Bob’s Open Challenge fixed opponent = ogi, this Pairing would be non-conforming (constraint 7) and ignored.
Client guidelines
Clients implementing kind: 3419 SHOULD:
- Subscribe to kind
3419with a#pfilter on the user’s pubkey to receive Pairings naming the user asplayer(and, for arbiter implementations, asarbiter). - Validate a received Pairing against all consent constraints (§Consent constraints), retrieving both referenced Open Challenges, before treating the user as matched.
- Apply the greedy matching rule (§Race resolution) when an Open Challenge is referenced by more than one Pairing; act only on the canonical Pairing.
- After the canonical Pairing, display “matched; awaiting arbiter” and follow the Game Session (kind
3422); do not allow Ply submission until t₀ is established.
Arbiter implementations SHOULD, on observing a canonical Pairing naming them, validate it, assign seats (fairly, e.g. at random), and publish a Game Session (kind 3422) within a reasonable time, or decline by silence if the terms cannot be honored.
Security considerations
- Signatures: a Pairing is signed by the matchmaker; consumers MUST verify the signature and that the signer is the matchmaker authorized by both Open Challenges (constraint 2).
- Bounded matchmaker authority: the consent constraints (§Consent constraints) prevent the matchmaker from binding terms a player did not authorize. The matchmaker retains liveness discretion (whether/when to pair, which compatible pair, values left free by both Open Challenges) and can withhold pairings; these are not prevented in-protocol — a consuming application’s documentation SHOULD analyze them for its users.
- Greedy-matching determinism: canonicalization is anchored on canonical timing, so the timing authority can influence which of several competing Pairings wins by adjusting it — in attested mode a malicious timestamper via attestation
created_at; in self-timed mode a colluding or misconfigured relay via which events it admits. The same timing latitude is documented for the rest of the suite. Mitigations are structural (distinct matchmaker / arbiter / timing-authority operators) and reputational. See Canonical Timing §Security. - Deadline enforcement: a Pairing is valid only if its canonical timing is at or before both Open Challenges’
accept_until(constraint 10), reusing the directed path’s deadline mechanism. - Seat fairness: the seat is assigned by the arbiter (§From Pairing to Game Session), removing seat selection from the matchmaker; this inherits the arbiter’s documented discretion over arbiter-chosen values (cf. randomized initial positions).
- Delegated signatures (NIP-26): not accepted as authoritative from the matchmaker or (in attested mode) the timestamper.
References
- NIP-01 — Basic protocol flow description
- NIP-02 — Follow List (the
followingfilter mode) - Kind
3418— Open Challenge (the events this Pairing references) - Kind
3422— Game Session (ratifies the Pairing and assigns the seat) - Kind
3420/3421— Direct Challenge / Accepted Challenge (the directed founding alternative) - Event Timestamp Attestation — primitive for the Pairing’s attestation
- Canonical Timing — timing modes, canonical timing, meta-resolution
