Sashité for Developers
  1. Sashité for Developers
  2. Nostr
  3. NIPs
  4. Composition

NIP-XX

Composition

draft optional

This NIP defines a Nostr event kind by which anyone publishes a complete composed puzzle as a single, self-signed artifact: a board position under a stipulation — the problemists’ “White to play and mate in two” — together with the proof that the stipulation holds, a forcing-strategy certificate answering every legal defence. The exercise and its proof are one event because they are one act: to compose a puzzle is to pose an exercise one has already solved. A Validation (kind 3441) attests, in time linear in the certificate’s size, that the proof realizes the stipulation; until such a Validation exists from a validator the consumer trusts, a Composition is a claim nobody believes.

Abstract

Defines kind: 3440, a regular event by which any pubkey — the composer — publishes, under one signature, a position and a strategy certificate for it: at each solver turn the solver’s move, then one branch per legal opponent reply, down to leaves the game classifies as achieved. The stipulation is carried in tags — exactly one objective, completed by a required moves bound, the problemists’ “mate in N” spoken whole — and is asserted, not verified, at publication: nothing downstream believes it, and a Composition whose certificate does not realize its stipulation simply never acquires a valid Validation.

Verifying a Composition — every move legal, every opponent node exhaustive, every leaf achieved, then the derived outcome equal to the stipulated objective and the derived depth equal to the moves bound — requires no search. Producing it is where the work lives, and that work is the composer’s, performed once at the edge by an engine that had to run anyway.

Because the artifact is signed by its author, it rides the social graph natively — no authority’s key stands between a composer and their audience. It becomes activity once it is validated: activity surfaces render it when a trusted validator’s valid Validation exists — “a mate in two by X”, the stipulation realized — and until then it is reachable, not broadcast-visible (§Client guidelines). The event MAY carry an optional provenance reference to the Game Session the position arose from, and an optional routing reference to a validator.

Motivation

Two inversions produced this kind, and both are worth stating because each removed a whole apparatus.

The first was the proof. The withdrawn v1 funnel (kinds 6416/6417/30421) put correctness in the right place — nothing submitted was believed — but made the authority search, and a forced-objective search is exponential in depth. The authority therefore needed a search bound (Dmax), a triviality minimum, and a spam-hardened examination pipeline; and the resulting artifact was signed by the authority, so a composer’s puzzle never appeared under their own key. Carrying a certificate inverts the expensive step: the composer searches, the validator checks. A certificate is a proof — it covers every defence — and checking a proof is linear in its size. The bounds fall away mechanically: nothing is searched, so nothing needs a Dmax; a mate in one is a one-node certificate, so nothing needs a floor; and the natural ceiling becomes the relay’s event-size policy, an operator matter rather than a protocol constant.

The second was the merge. An intermediate revision split the artifact in two — a Problem (the stipulated position) and a Solution (the certificate), joined by an event reference — so that a position could be posed by one pubkey and proved by another. The split bought a community-solving layer, and priced it in machinery: a pool of competing Solutions, a canonical-solution fold to pick one, race resolution over that pool, a solver credit distinct from the composer’s, a transient decline for an unretrievable Problem, and a non-atomic paired publish — Nostr offers no multi-event atomicity, so a crash or a relay rejection between the two legs left a stipulated position standing with no proof in sight. Carrying both in one event deletes all six at once: there is one artifact, one signature, one proof of work, one failure mode, and nothing left to arbitrate. What it gives up — a position posed without a solution, and a proof attached by a third party — is recorded, with the loss accepted, in this suite’s design notes (application layer: Design Notes — Puzzles §The merged composition).

What the event keeps from the composition tradition is the stipulation. A position does not determine an exercise: the same diagram may hold a draw in 2 and a mate in 6, two different puzzles with different solutions and very different difficulty, and stipulating which one is being posed is what makes a puzzle a puzzle rather than an invitation to analyse. The required moves bound completes it — “mate in two” is the tradition’s whole sentence — and an objective without a distance is a study, not a problem statement: a genre this family deliberately does not pose.

Specification

Event kind

kind: 3440 — a regular event per NIP-01: immutable once signed and broadcast. A composer who wants to amend a Composition publishes a new one; the old one stands, and its Validation stands with it.

Signer — the composer

Any pubkey whatsoever. No role designates a composer and publishing confers no standing. The composer is credited as such wherever the Composition is rendered (kind 3441 §Credit); credit is attribution, not authority.

Timing

Not part of any session’s lifecycle, never attested; canonical timing is the event’s own created_at. Nothing in this family depends on Composition ordering.

Tags

Tag Cardinality Value Description
game exactly one identifier matching ^[a-z][a-z0-9]{0,31}$ the game whose rules the position and the certificate are read in; determines both encodings (reference: sanki)
objective exactly one mate, draw, advantage, or a game-defined token (§The stipulated objective) the stipulation: the outcome the solver is asked to force
moves exactly one positive integer, decimal, no leading zero the stipulation’s bound: the exact number of solver moves the exercise is posed in (§The moves bound)
L exactly one a NIP-32 namespace token the label namespace all l tags carry (§Index labels)
l exactly three the three prefixes of <game>:<objective>:<moves>, each with the same namespace as its third element NIP-32 labels: the relay-indexable projection of the stipulation (§Index labels)
p validator 0 or more pubkey, optional relay hint, role marker validator OPTIONAL routing hints, one tag per validator: the validators whose verdicts the composer is seeking (§Addressing validators)
e game_session 0 or 1 event id, optional relay hint, marker game_session OPTIONAL provenance: the Game Session (kind 3422) the position arose from; absent for a hand-composed position
nonce exactly one nonce + committed difficulty NIP-13 proof of work
alt 0 or 1 human-readable string NIP-31 fallback (e.g. "A Sanki puzzle: mate in 2.")

The game tag is the one discriminant a reader needs: a position whose encoding is not named is unreadable (the transport interprets nothing — a suite-wide principle — and game is how a consumer knows what not to interpret). The encoding itself — FEEN and the sanki half-move triple for sanki, FEN or SFEN or anything else for another game — is the game’s own definition, exactly as on every other kind of the suite.

The nonce tag follows NIP-13, with the committed difficulty required as its third element: this is an open-authorship kind and inherits the suite’s two spam defences, per-event proof of work and consumer author-scoping.

There are deliberately no claim tags beyond the stipulation: no depth, no outcome, no soundness. The certificate is the answer to the stipulation, and what it achieves is derived from it at validation and compared with the stipulation there (kind 3441 §The stipulation check, §The depth check). A value that is a pure function of the certificate is redundancy — restating derivable data only creates a second copy that can drift from the first.

The stipulated objective

The objective tag holds a single lowercase token, the stipulation. Three values are reserved by this NIP and carry a fixed meaning in every game:

Value The solver forces… Guaranteed outcome
mate a terminal state the rule system scores maximally in the solver’s favour decisive, for the solver
draw a terminal state the rule system scores as a draw drawn
advantage a non-terminal state the game deems decisively better for the solver none

The three are stated in terms of outcomes, not mechanisms, which is what keeps them rule-system-agnostic: nothing here presumes a board, a piece set, or a check — mate is the tradition’s name for the maximal-terminal class, not a mechanism requirement, and a game whose maximal terminal is not literally a checkmate still binds the class to it. Which of a game’s states realize a leaf of each class is the game’s own binding, in its supporting document (kind 3441 §Leaf classification; for the reference sanki rule system: Puzzle Specification — Sanki).

The vocabulary is open beyond the three: a game MAY define further objectives — the composition traditions contain stipulations no outcome class expresses, helpmates and selfmates among them. A token MUST match ^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$ and be at most 32 characters — no colon; namespacing uses a dot — and a game-defined token SHOULD be namespaced with the defining application’s prefix (sashite.helpmate), keeping the reserved/extension boundary legible at a glance. The colon is reserved as the index labels’ separator (§Index labels), which is what keeps a label unambiguously decomposable. A validator that does not serve a stipulated objective declines unsupported (kind 3441 §Content): an unrecognized stipulation makes a Composition unservable by that validator, never non-conforming.

The stipulation is asserted, not verified: no conformance condition, no relay, and no reader is asked to believe it. Its verification lives at validation, where the certificate’s derived outcome MUST equal the stipulated objective exactly — a certificate deriving anything else, weaker or stronger, is declined objective_mismatch (kind 3441 §The stipulation check).

The moves bound

The REQUIRED moves tag holds a positive decimal integer with no leading zero: the exact number of solver moves the exercise is posed in, counted as the certificate’s derived depth counts them — the maximum solver moves along any branch (§Content). The stipulation is the pair, outcome and distance — “mate in two”, the tradition’s whole sentence — and an objective without a distance is a study, “White wins, find how”: a genre this family deliberately does not pose. The exclusion is recorded, with the loss it accepts, in this suite’s design notes (Design Notes — Puzzles §The mandatory bound); it is smaller than it reads — a certificate is a finite tree and always derives a depth, so a composer, who by construction holds the certificate, always holds the bound to state.

The bound shares the stipulation’s whole discipline. It is asserted, not verified, at publication, and checked where the objective is checked — post-walk, at validation — and exactly: a sound certificate whose derived depth is anything other than the bound, deeper or shallower, is declined depth_mismatch (kind 3441 §The depth check). The shallow direction is deliberate: a “mate in 2” that admits a mate in 1 is a cooked problem — the tradition’s own verdict — and the family declines it by design rather than silently crediting the stronger answer. The bound adds no parameter to verification: it is read off the event exactly as the objective is, and the check stays linear.

Index labels

Relays index only single-letter tag names (NIP-01), so objective and moves are invisible to every filter. The stipulation is nevertheless the one axis on which a solver selects work — a mate in two, in sanki — so the event carries a relay-indexable projection of it, in the two label tags NIP-32 already defines. The letters are not taken from the ecosystem: L and l already mean label namespace and label, so no future NIP can reclaim them, and no query built on them can silently widen.

A conforming Composition carries exactly one L tag — a well-formed NIP-32 namespace token, echoed by every l tag — and exactly three l tags, each with that namespace as its third element, holding the three prefixes of the exercise facet <game>:<objective>:<moves>. Which namespace to use is a deployment convention, not a conformance condition: each consuming deployment fixes one for its puzzle corpus and filters by it (#L), rather than declaring other namespaces non-conforming (the reference deployment uses sashite.puzzle). With that namespace:

["L", "sashite.puzzle"],
["l", "sanki",        "sashite.puzzle"],
["l", "sanki:mate",   "sashite.puzzle"],
["l", "sanki:mate:2", "sashite.puzzle"]

Three properties earn the shape:

A consumer queries with the namespace scoped, which is what makes the label values collision-proof across the ecosystem:

{"kinds": [3440], "#L": ["sashite.puzzle"], "#l": ["sanki:mate:2"]}

Two limits are inherent to NIP-01 filters and belong in any client’s expectations. Filters compare by equality only: moves <= 3 is not expressible in any encoding, and difficulty ranges must be materialized by a curator or resolved consumer-side. And validation state is not filterable: a valid Validation lives on kind 3441, one hop away, so “validated mate-in-two puzzles” is a join no relay performs. Consumers resolve it themselves, or read a curated set published by an indexer they trust — an application-layer service this NIP does not define and does not need to.

Addressing validators

Each OPTIONAL validator-marked p tag names a validator whose verdict the composer is seeking. A Composition MAY carry none, one, or several: a composer serving two audiences — an application’s own validator and a third party’s — addresses both in the one event and receives, in time, one verdict from each.

They are routing hints and nothing more: each lets a validator subscribe to its own work queue with one #p filter instead of watching the whole kind, and lets a client render “submitted to X and Y” while a verdict is outstanding.

They bind no one, in either direction. A named validator owes no answer — an unjudged Composition is unjudged, not invalid (kind 3441 §Validator discretion) — and any validator MAY judge the Composition, named or not, since a Validation is reactive and unaddressed. Consumers select verdicts by their own trusted set, never by the composer’s address list, and that is what makes the plurality harmless: naming ten validators buys a composer ten chances of a verdict and not one reader’s trust. Divergent verdicts on one Composition are the expected consequence of addressing several, not a fault — they are public, attributable and replayable, and a consumer resolves them against its own trusted set exactly as it resolves unsolicited ones.

The list says where a composer went looking for a verdict; it never says which verdicts count. Its absence is equally conforming: a validator that watches the kind finds the work anyway.

Provenance

The OPTIONAL game_session-marked e tag records where the position came from — a game the composer played or watched — and is absent for a hand-composed position. It is a display reference first: clients offer navigation to the source game. It is nevertheless verifiable in principle — the position occurs in the session’s canonically replayed chain or it does not — and a consumer holding an engine MAY check that and treat a reference that fails it as spoofed. Validators are NOT required to verify provenance: it bears on no certificate’s soundness, and a Validation attests the proof, never the origin story.

A consumer that makes no use of provenance MUST still accept the tag and validate its shape (§Semantic constraints, constraint 7). Ignoring a tag’s meaning and rejecting the event that carries it are different things, and the second would decline a conforming Composition for a reason this NIP does not authorize.

Content — the position and the certificate

The content field carries a JSON object (serialized per NIP-01) with two REQUIRED members:

{
  "position":    <string>,        the position, in the game's encoding
  "certificate": <certificate>    the forcing-strategy proof
}

Members this NIP does not define are ignored, as everywhere in Nostr. The envelope is protocol-defined; both values are game-defined, and the transport interprets neither.

position — a non-empty string of at most 512 characters, encoded per the game tag, which a game’s supporting document MAY tighten. Commentary belongs in ordinary notes referencing this event, never here. Whether the string parses, and whether it denotes a legal position, are game-specific questions settled at validation — not transport-level conformance conditions.

certificate — the proof, recursively:

certificate = [ move, replies ]
replies     = [ [opponentMove, certificate], … ]   # possibly empty
move        = a half-move in the game's encoding — the same encoding the
              game's Plies use (for sanki: the [source, destination, actor] triple)

Reading: from the current node’s position, the solver plays move; if the resulting position is achieved (a terminal the game scores for the solver, or a non-terminal state meeting a game-defined objective criterion — kind 3441 §Leaf classification), replies MUST be empty and the branch ends; otherwise replies MUST contain exactly one entry per legal opponent reply in that position, each continuing with the certificate for the position after that reply.

The exhaustiveness is on the defence side and there alone: at each solver node the certificate names one move, at each opponent node it answers every legal one. That asymmetry is the whole proof. A certificate closes the universal quantifier a puzzle’s statement contains — forces the outcome against every defence — where a solution line closes nothing: the opponent’s replies in a line are the proposer’s own choice, so a checked line witnesses only that a cooperative opponent can be mated. A single line is nevertheless the degenerate certificate in which every opponent node happens to have exactly one legal reply — a forced mate in 200 by perpetual constraint is a 400-half-move certificate, a few kilobytes. There is no minimum: a mate in one is [move, []].

Two quantities are derived by any checker and stated by nobody:

A third quantity is derived for display, by a rule this NIP fixes so that every client shows the same thing:

Whether the JSON parses, whether the tree is well-shaped, and whether it survives the mechanical check are validation questions, answered with an informative verdict (kind 3441) — not conformance conditions. A malformed event earns silence; a well-formed event carrying a broken certificate earns an answer that helps the composer fix it.

Semantic constraints

A conforming Composition event MUST satisfy all of the following:

  1. The content field is a non-empty string. (Whether it parses as the two-member envelope, whether the position respects the game’s length limit, and whether the certificate is well-shaped are validation questions — §Content — never conformance conditions: a conforming event can be recognized without a JSON parser, and a well-formed event carrying a broken payload earns an informative verdict rather than silence.)
  2. The game tag is present exactly once and matches ^[a-z][a-z0-9]{0,31}$.
  3. The objective tag is present exactly once and matches ^[a-z][a-z0-9]*(\.[a-z][a-z0-9]*)*$, at most 32 characters. (Whether the objective is forceable, and whether any validator serves it, are validation questions — never conformance conditions.)
  4. The moves tag is present exactly once and its value is a positive decimal integer with no leading zero. (Whether the certificate realizes the bound is a validation question — kind 3441 §The depth check — never a conformance condition.)
  5. The L tag is present exactly once, carrying a well-formed NIP-32 namespace token; the l tag is present exactly three times, each carrying that same namespace as its third element, and the three values are exactly <game>, <game>:<objective> and <game>:<objective>:<moves>, taken from the tags of constraints 2–4. (Checkable from the event alone — the labels are a pure function of the stipulation and the declared namespace. Which namespace a deployment consumes is a filtering convention, never a conformance condition.)
  6. Every p tag, if any are present, carries the marker validator as its fourth element and a 64-character lowercase hex pubkey as its value, and no pubkey appears in more than one p tag. (How many answer, and whether any does, are nobody’s conformance conditions.)
  7. The e tag, if present, appears at most once, carries the marker game_session as its fourth element, and its value is a 64-character lowercase hex event id. (Whether it resolves, and whether the position really occurs in that game, are consumer verification questions — not conformance conditions.)
  8. The nonce tag is present exactly once, 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).

A non-conforming Composition MUST be ignored.

Duplication

Compositions are regular events, so the same exercise may exist under several event ids, by one composer or many — and, since the certificate travels with the exercise, two Compositions of one exercise may carry different proofs: the two keys of a dual, or two independent searches of the same strategy. This is accepted and is the family’s only remaining plurality. A consumer MAY merge displays by exact equality of the exercise tuplegame, objective, moves and the position member, which the l label’s longest prefix already carries but for the position — and nothing requires it to. Where the merge matters most is in play: a client that accepts a solver’s move against the union of the validated certificates sharing an exercise accepts every proved answer to it, not merely the one it happened to load (kind 3441 §Client guidelines).

Client guidelines

Security considerations

References