- 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 Direct Challenge (kind 3420) does for the directed path, and the resulting session is founded, played, and concluded 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, mirrors the timing designation authorized by both Open Challenges, resolves each player’s variant, draws the seats, and carries the agreed time control and rule system. Either player then founds the session by publishing a Game Session (kind 3422) that references the Pairing; the Pairing leaves nothing open, so the Game Session is fully determined.
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. It becomes playable when either player publishes 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, the accept-deadline check and the founding window — 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; 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 founded. 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 |
two or three | pubkey, optional relay hint, role marker | exactly two player; 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", "<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 timing authority) and structural (routing via standard #p filters, so each player receives the Pairing). The timing designation (timestamper, or timing_relay tags) is carried explicitly so the Game Session (kind 3422) can mirror and verify it 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 |
rules |
exactly one | digest + optional retrieval hint | the rule-system document (equal to both Open Challenges’) |
seat |
exactly two (one per player) | pubkey + seat-name | each player’s seat, drawn by the matchmaker |
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 rule-system document named by rules 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).
The rules tag is ["rules", "<digest>", "<hint>"] (kind 3420 §Match-terms tags) and MUST carry the digest of both referenced Open Challenges, which MUST be equal: the rule-system document is a matching term, not a term the matchmaker resolves. The hint MAY be either Open Challenge’s or the matchmaker’s own.
Each seat tag is ["seat", "<player_pubkey>", "<seat_name>"], the seat-name being first or second (kind 3420 §Match-terms tags); the two players MUST hold the two distinct values. The matchmaker draws the seats — a fair random draw, typically — since the pool path has no challenger to declare one and the suite designates no other party to draw: the matchmaker is already the party whose choices within consented bounds the pool accepts, and an explicit draw here is preferable to any derivation from event ids it could grind. The Game Session mirrors the assignment (kind 3422 §Seats).
Lifecycle tags
| Tag | Cardinality | Value | Description |
|---|---|---|---|
found_until |
exactly one | unix timestamp (seconds) | the latest moment at which a Game Session founded on this Pairing is valid |
The found_until value MUST be a Unix timestamp in seconds, strictly greater than the event’s created_at. It bounds the founding window: a Game Session (kind 3422) whose canonical timing exceeds it is non-conforming, the Pairing lapses, and the two players re-enter the pool. The window is the matchmaker’s choice — a deployment sets it to the interval within which a connected client founds automatically (a minute or two), so that a player who disconnected between pool entry and pairing does not hold the other in a session that never starts.
There is no nonce tag: 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 4 and 12–15 are checkable from the event alone; the rest require the two referenced Open Challenges (and, for constraints 10 and 11, 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 two
seattags, one per player pubkey, carryingfirstandsecond(one each). - 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. - Exactly one
rulestag is present, whose digest (second element) equals therulesdigest of both Open Challenges. Two Open Challenges with differentrulesdigests are incompatible and MUST NOT be paired, whatever their other terms. - 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 players’ founding of the session 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 (""). - Exactly one
found_untiltag is present, with a value strictly greater thancreated_at. - No
arbiter-markedptag is present (the suite designates no arbiter).
A Pairing violating any constraint is non-conforming and MUST be ignored by all consumers (clients, 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, the seats, and any value left free by both Open Challenges, but it cannot bind a player to a timestamper, opponent, variant, time control, rule system, 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. Either player founds it by publishing a Game Session (kind 3422) whose founding-reference e tag points to the canonical Pairing (marker pairing), within the Pairing’s found_until window; should both publish, the canonical Game Session is decided by meta-resolution (kind 3422 §Idempotence and race resolution) — the two candidates being identical in content, since the Pairing leaves nothing open. The Game Session:
- carries the two players (from the Pairing’s
playertags), their variants (from the Pairing’svarianttags) and their seats (from the Pairing’sseattags); - mirrors the timing designation and the
rulesterm; - carries the initial position the rule-system document prescribes and, via its own canonical timing, establishes t₀.
From the Game Session onward the lifecycle is identical to the directed path: Ply (kind 3423), Conclusion (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 (each player’s client MUST validate before founding) and rating verifiers. A non-conforming Pairing does not declare a session; a client MUST NOT 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"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["rules", "3f6d1a0c9e4b2a7d5c8e1f0a9b3c7d2e4f6a8b0c1d3e5f7a9b2c4d6e8f0a1b3c", "https://blobs.example.com"],
["variant", "<alice_pubkey>", "ogi"],
["variant", "<bob_pubkey>", "ogi"],
["seat", "<bob_pubkey>", "first"],
["seat", "<alice_pubkey>", "second"],
["time_control", "300", "3"],
["found_until", "1700000220"]
],
"content": "",
"id": "...",
"sig": "..."
}
Alice or Bob then publishes the Game Session (kind 3422) referencing this Pairing (marker pairing), mirroring the drawn seats, before 1700000220.
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"],
["timing_relay", "wss://relay.example.com"],
["game", "sanki"],
["rules", "3f6d1a0c9e4b2a7d5c8e1f0a9b3c7d2e4f6a8b0c1d3e5f7a9b2c4d6e8f0a1b3c", "https://blobs.example.com"],
["variant", "<bob_pubkey>", "ogi"],
["variant", "<dana_pubkey>", "chess"],
["seat", "<dana_pubkey>", "first"],
["seat", "<bob_pubkey>", "second"],
["time_control", "300", "3"],
["found_until", "1700000220"]
],
"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. - 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, found the session: publish the Game Session (kind
3422) immediately — its content is fully determined — unless a canonical Game Session for the Pairing is already observed; then follow the Game Session and do not allow Ply submission until t₀ is established.
Matchmaker implementations SHOULD draw the seats with a fair source of randomness and SHOULD publish their found_until policy, so that players know how long a Pairing waits for a founder.
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 / 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 11), reusing the directed path’s deadline mechanism. - Seat fairness: the seat is drawn by the matchmaker (§Match-terms tags). A biased matchmaker could favour one player systematically; this is matchmaker discretion of the same kind as pairing selection, not prevented in-protocol, and detectable statistically across many Pairings. The players cannot alter the draw: the Game Session must mirror it.
- 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 (founds the session on the Pairing; signed by either player) - Kind
3420— Direct Challenge (the directed founding alternative) - Event Timestamp Attestation — primitive for the Pairing’s attestation
- Canonical Timing — timing modes, canonical timing, meta-resolution
