Skip to content

About

Pair Open Challenges into Pairings (kinds 3418/3419) for Nostr: a game-agnostic matchmaking primitive for two-player abstract strategy board games of the chess family.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

nostchmaker

Pair Open Challenges into Pairings (kinds 3418 / 3419) for Nostr: a game-agnostic matchmaking primitive for turn-based, two-player abstract strategy board games of the chess family.

Status — proposed NIP. The kinds 3418 (Open Challenge) and 3419 (Pairing) belong to a NIP suite that is still a draft. The kind numbers and wire format may change. Pin an exact version and review the suite before relying on it in production.

A player enters a matchmaking pool by publishing a signed Open Challenge (kind 3418) that names a matchmaker and exactly one timing designation — a timestamper (attested mode) or one timing_relay (self-timed, the default) — and carries the session terms (game, the rules reference to a Rule System event of kind 3417, per-role variant preferences, time control, opponent filter) — but no opponent. A designated matchmaker pairs two compatible Open Challenges by publishing a Pairing (kind 3419), without any acceptance signature from the players: their consent is pre-committed in their Open Challenges, and a Pairing is binding only if it respects both. The Pairing also carries what the matchmaker resolves — both players' variants, a drawn seat per player, and a founding window — and either player then founds the session by publishing a Game Session (kind 3422). The suite designates no arbiter (ADR-0033).

This crate implements the primitive only, and is game-agnostic: it does not know any game's variant vocabulary, performs no I/O, draws no random number, never fetches or runs a rule system's module, and is silent on which game, rule system or parties an application designates. Those are a higher layer's concern (for example a matchmaker service that reads the relay, resolves the Rule System events, draws the seats, and signs Pairings).

Pipeline

kind 3417 event ──parse──▶ RuleSystem ───┐
                                         ▼ check_rule_system (each challenge)
kind 3418 event ──parse──▶ OpenChallenge ─┐
kind 3418 event ──parse──▶ OpenChallenge ─┴─evaluate(facts)─▶ Compatible{variants}
                                                                     │
                                      Resolution{variants, seat, found_until}
                                                                     │
                                                          PairingBuilder ──▶ kind 3419
  • open_challenge — OpenChallenge::parse turns a kind-3418 event into a typed, validated value (the event-local semantic constraints, decidable from the event alone); check_rule_system is the cross-event half of the rules constraint, once the referenced Rule System event is held.
  • rule_system — RuleSystem::parse turns a kind-3417 event into a typed value (the module's digest and ABI, the game, the retrieval URLs, the documentary spec and source), under the NIP's structural constraints.
  • compatibility — evaluate(a, b, facts) decides whether two Open Challenges can be paired (common matchmaker, identical timing designation, common game, identical time control, the same rules reference, satisfiable variants, and each player satisfying the other's filter), and resolves each player's variant.
  • pairing — PairingBuilder lays a compatible pair out, under the matchmaker's Resolution (both variants, the seat draw, found_until), as an unsigned Pairing EventBuilder for the matchmaker to sign.

Usage

use nostchmaker::compatibility::{evaluate, Compatibility, Facts, RatingPool};
use nostchmaker::open_challenge::{OpenChallenge, RatingKind};
use nostchmaker::pairing::{PairingBuilder, Resolution, Seat};
use nostr::key::PublicKey;

// The consumer resolves the external facts the filters need, anchored at the
// Pairing's canonical timing: the contact list as of the anchor, and the most
// recent attestation by the *pinned* authority (under the pinned kind) with
// created_at at or before the anchor — in the pool the filter's declared
// scope selects (decision M-9). `everyone`-filtered pools need none of this.
struct MyFacts;
impl Facts for MyFacts {
    fn follows(&self, _follower: &PublicKey, _target: &PublicKey) -> bool { false }
    fn rating_within(
        &self,
        _authority: &PublicKey,
        _kind: RatingKind,
        _pool: RatingPool<'_>,
        _a: &PublicKey,
        _b: &PublicKey,
        _max_delta: u16,
    ) -> bool { false }
}

// `a` and `b` are two parsed kind-3418 events (OpenChallenge::parse), each
// already checked against the Rule System event its `rules` reference names
// (`a.check_rule_system(&rule_system)`, after fetching kind 3417 by id);
// `a_first` is the matchmaker's fair coin, `now` its clock.
fn pair(a: &OpenChallenge, b: &OpenChallenge, a_first: bool, now: u64) {
    if let Compatibility::Compatible { a_variant, b_variant } = evaluate(a, b, &MyFacts) {
        // A variant left free (None) is the matchmaker's choice, from the
        // vocabulary of the rule system both entries name (what its module's
        // `describe` reports — not this crate's knowledge).
        let a_variant = a_variant.unwrap_or_else(|| "chess".to_string());
        let b_variant = b_variant.unwrap_or_else(|| "chess".to_string());
        let resolution = Resolution {
            a_variant: &a_variant,
            b_variant: &b_variant,
            a_seat: if a_first { Seat::First } else { Seat::Second },
            found_until: now + 120, // the founding window
        };
        let _pairing = PairingBuilder::new(a, b, resolution)
            .rules_hint("wss://relay.example.com")
            .to_event_builder(); // sign with the matchmaker
    }
}

Consent constraints

evaluate encodes the consent constraints of kind 3419 that are decidable from the two Open Challenges plus the resolved facts: distinct signers, a common matchmaker, an identical timing designation (the same timestamper, or the same timing_relay), a common game, an identical time control, the same rules reference (the rule system is a matching term the matchmaker cannot resolve), a satisfiable variant resolution, and each player satisfying the other's filter. A following filter binds against the filterer's contact list; a rating filter binds against the rating authority the filterer pins in their Open Challenge (an authority pubkey plus the attestation kind, 3426 Elo or 3427 Glicko-2). The comparison pool follows the filter's declared pool scope — its sixth element, pergame or pervariant (decision M-9), so satisfaction is objective from public data alone: under pervariant the filter is satisfiable only for a same-variant pairing; under pergame (e.g. Sashité's sanki pools) it binds across any variant combination, resolved or free.

It is deliberately silent on the rest, which a higher layer enforces:

  • the accept-deadline (a timing decision anchored on the Pairing's canonical timing — its timestamper attestation in attested mode, its own relay-enforced created_at in self-timed mode);
  • the resolution of each rules reference — fetching the Rule System event by id and checking it (OpenChallenge::check_rule_system), and whatever the consumer requires of the module it names (kind 3417);
  • the Pairing event's own shape (it is built by PairingBuilder);
  • any operational policy such as a game allow-list, a rule-system allow-list or NIP-51 mute lists.

Safety and reliability

  • No unsafe (unsafe_code = "forbid"), no I/O, no clock access — pure functions.
  • The parsing path consumes untrusted events but only inspects fields that nostr has already parsed: it never reparses, never allocates on input size, and is total. The lint set denies panic-capable operations (unwrap/expect/panic/indexing/arithmetic).
  • Building is infallible.
  • Supply chain is policed in CI by cargo-deny (advisories, licenses, sources).

Status and MSRV

nostr 0.45. Developed and tested on Rust 1.96. See the status note near the top of this document regarding the proposed NIP and the tentative kind numbers.

License

Licensed under the Apache License, Version 2.0. See NOTICE.

About

Pair Open Challenges into Pairings (kinds 3418/3419) for Nostr: a game-agnostic matchmaking primitive for two-player abstract strategy board games of the chess family.

Topics

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages