Sashité for Developers
  1. Sashité for Developers
  2. Nostr
  3. NIPs
  4. Elo Rating Attestation

NIP-XX

Elo Rating Attestation

draft optional

Abstract

This NIP defines an event kind for a rating authority to publish a signed attestation of an Elo rating update for a single player following the outcome of a two-player duel. The event is self-contained: it carries the player’s pre-duel and post-duel Elo ratings, the player’s score in the duel, and the K-factor used in the computation. The event does not depend on any other NIP and may be used for any two-player game whose outcome can be expressed as a score in the interval [0, 1] (with 1, 0.5, and 0 as the classical win, draw, and loss).

Motivation

The Elo rating system, devised by Arpad Elo and originally adopted by chess federations, computes a player’s skill rating from their duel outcomes against other rated players. Many two-player abstract strategy games — chess, shogi, xiangqi, go, draughts, and others — use Elo or Elo-derived systems for ranking.

A publicly signed, self-contained rating attestation event serves three purposes:

The event is minimalist by design. It carries one attestation for one player, plus references to the duel and the rating pool. Verification logic, prior-rating lookups, and consumer-side filtering are left to the consuming application.

Specification

Event kind

kind: 3426

Elo Rating Attestation events are regular events per NIP-01: immutable, persistent, and referenced by their event ID.

Signer — the rating authority

The event is signed by a rating authority — a pubkey that computes and certifies the rating update. The rating authority is conceptually independent of the duel’s participants, of any arbiter or other role in the consuming application, and of any timestamping service. Multiple rating authorities MAY publish independent attestations for the same duel; consumers choose which authorities to trust on out-of-band grounds (reputation, NIP-05 verification, federation membership, etc.).

Tags

Reference tags

Tag Cardinality Value Description
e duel exactly one event id, optional relay hint, marker duel reference to the duel event (kind and structure defined by the consuming application)
p exactly one player pubkey the player whose rating is attested

The duel referent is application-defined at the primitive level. In this suite, the duel e tag references the Game Session (kind 3422) event id — the immutable event whose adjudicated outcome the rating consumes; both attestations of a duel (one per player) carry the same reference, which is the deduplication key below.

Rating-pool tags

Tag Cardinality Value Description
game exactly one game identifier matching ^[a-z][a-z0-9]{0,31}$ the game in which this rating is computed
variant 0 to 2 variant identifier matching ^[a-z][a-z0-9]{0,31}$ the variant(s) of the game; cardinality interpretation below

The variant tag cardinality conveys the variant structure of the duel:

The 02 bound is intrinsic, not an arbitrary restriction: a rating attestation describes a duel, which is two-participant, so at most two distinct variants are present. It is the two-participant projection of the suite’s general 0n variant convention — a kind states the general form its subject admits, and lets the subject’s own arity bound it: a kind describing a position with an unbounded number of participants would state the general form.

Rating computation tags

Tag Cardinality Value Description
score exactly one decimal in [0, 1] the rated player’s score in the duel
elo_pre exactly one non-negative decimal the player’s Elo rating before this duel
elo_post exactly one non-negative decimal the player’s Elo rating after this duel
k_factor exactly one positive decimal the K-factor used in the computation

Score values

The score tag’s value MUST be a decimal number in the closed interval [0, 1], representing the rated player’s outcome in the duel. The opponent’s score is 1 − score.

The three classical values are the common cases:

Values strictly between these represent partial or asymmetric outcomes — for example a rule system that awards a partial win (a result better than a draw but short of a full win) to the player who forces a particular terminal position. Game-specific result notations (chess "1-0" / "1/2-1/2" / "0-1", go "B+R" / "W+R" / "Jigo", etc.) MUST be normalized to a value in [0, 1] when constructing the event. The Elo formula below is defined for any such score, since the actual score enters only through the term (score − expected_score).

Content

The content field MUST be the empty string (""). The semantics of the event are fully expressed by its tags.

Elo formula

For reference, the player’s post-duel rating is computed as:

elo_post = elo_pre + k_factor × (score − expected_score)
expected_score = 1 / (1 + 10^((opponent_elo_pre − elo_pre) / 400))

Where opponent_elo_pre is the opponent’s pre-duel Elo rating in the same rating pool (same game, same variant, same rating authority). The opponent’s pre-duel rating is obtained by the consumer from the rating authority’s prior Elo Rating Attestation for the opponent (or the rating authority’s documented initial-rating convention if no prior attestation exists).

Semantic constraints

Throughout these constraints, a decimal number is a string matching ^(0|[1-9][0-9]*)(\.[0-9]+)?$ — no sign, no exponent, no leading zeros, . as the separator; non-negative admits any such string, and positive additionally excludes representations of zero (0, 0.0, …).

A conforming Elo Rating Attestation event MUST satisfy all of the following:

  1. Exactly one e tag with marker duel, whose second element is a valid event id.
  2. Exactly one p tag.
  3. Exactly one game tag whose second element matches ^[a-z][a-z0-9]{0,31}$.
  4. Zero, one, or two variant tags, each whose second element matches ^[a-z][a-z0-9]{0,31}$.
  5. Exactly one score tag whose second element is a decimal number (grammar above) in the closed interval [0, 1].
  6. Exactly one elo_pre tag whose second element is a non-negative decimal number (grammar above).
  7. Exactly one elo_post tag whose second element is a non-negative decimal number (grammar above).
  8. Exactly one k_factor tag whose second element is a positive decimal number (grammar above).
  9. The content field is the empty string ("").

All nine constraints are checkable from the event alone. Verifying that elo_post is the correct result of the Elo formula applied to elo_pre, the opponent’s pre-duel rating, score, and k_factor requires cross-event lookups and is the consumer’s responsibility.

Race resolution

A rating authority SHOULD publish at most one Elo Rating Attestation per (rating authority, player, duel) triple. If multiple events exist for the same triple, the canonical event is determined deterministically:

  1. Smallest created_at first.
  2. Smallest event ID as tiebreaker.

Non-canonical events MUST be ignored by consumers.

This rule uses the event’s own created_at — the rating authority’s self-declared timestamp. The NIP does not require external timestamping. Consumers requiring authoritative, third-party-verified timing (for example, to detect a rating authority backdating their attestations) SHOULD layer an external mechanism such as the Event Timestamp Attestation NIP on top of these events.

A rating authority who realizes they computed incorrectly cannot override their first Attestation by publishing a later one (the later event would have a larger created_at and thus be non-canonical). Their options are NIP-09 deletion (with the usual caveats about relay support and asynchronous propagation) or accepting the error as historical. Signed events are immutable.

Examples

Example 1 — Standard chess duel, decisive

Alice (rated 1500) defeats Bob (rated 1500) in a chess duel. The rating authority publishes Alice’s attestation:

{
 "kind": 3426,
 "pubkey": "<rating_authority_pubkey>",
 "created_at": 1700000000,
 "tags": [
 ["e", "<duel_event_id>", "wss://relay.example.com", "duel"],
 ["p", "<alice_pubkey>"],
 ["game", "chess"],
 ["variant", "chess"],
 ["score", "1"],
 ["elo_pre", "1500"],
 ["elo_post", "1516"],
 ["k_factor", "32"]
 ],
 "content": "",
 "id": "...",
 "sig": "..."
}

Verification: with K=32, equal pre-ratings give expected_score = 0.5, so elo_post = 1500 + 32 × (1 − 0.5) = 1516. ✓

A symmetric attestation is published for Bob with score = 0, elo_pre = 1500, elo_post = 1484.

Example 2 — Go duel, draw

Alice (rated 1842) draws Bob (rated 1800) in a go duel. The rating authority publishes Alice’s attestation:

{
 "kind": 3426,
 "pubkey": "<rating_authority_pubkey>",
 "created_at": 1700000000,
 "tags": [
 ["e", "<duel_event_id>", "wss://relay.example.com", "duel"],
 ["p", "<alice_pubkey>"],
 ["game", "go"],
 ["score", "0.5"],
 ["elo_pre", "1842"],
 ["elo_post", "1840.56"],
 ["k_factor", "24"]
 ],
 "content": "",
 "id": "...",
 "sig": "..."
}

Verification: expected_score = 1 / (1 + 10^((1800 − 1842) / 400)) ≈ 0.56015, so elo_post = 1842 + 24 × (0.5 − 0.56015) ≈ 1840.56. ✓ The variant tag is absent: the rating pool is the base go game. Decimal precision is permitted in elo_post.

Example 3 — Multi-variant duel

Alice plays the chess variant, Bob plays the ogi variant, on a shared board (a multi-variant duel in a hybrid platform). The rating authority publishes Alice’s attestation, listing both variants present in the duel:

{
 "kind": 3426,
 "pubkey": "<rating_authority_pubkey>",
 "created_at": 1700000000,
 "tags": [
 ["e", "<duel_event_id>", "wss://relay.example.com", "duel"],
 ["p", "<alice_pubkey>"],
 ["game", "sanki"],
 ["variant", "chess"],
 ["variant", "ogi"],
 ["score", "0"],
 ["elo_pre", "1700"],
 ["elo_post", "1684"],
 ["k_factor", "32"]
 ],
 "content": "",
 "id": "...",
 "sig": "..."
}

Two variant tags signal the multi-variant nature of the duel. The pool the attestation contributes to follows the rating authority’s own pool computation (§Rating pool tags): an authority computing pergame pools — the reference deployment’s choice for sanki — credits the duel to the single sanki pool, the variant tags being descriptive only. A pervariant authority would exclude the duel from its pools and publish no attestation for it.

Example 4 — Partial-win outcome

Some rule systems award a partial result that is neither a full win nor a draw. (This is a generic capability of the attestation; the reference sanki arbiter never emits one — per the reference authority’s supporting document, Rating Specification — Elo.) Here a hypothetical rule system scores Alice’s terminal position as a partial win (0.75). Both players played the same (game, variant), so the rating belongs to that single pool:

{
 "kind": 3426,
 "pubkey": "<rating_authority_pubkey>",
 "created_at": 1700000000,
 "tags": [
 ["e", "<duel_event_id>", "wss://relay.example.com", "duel"],
 ["p", "<alice_pubkey>"],
 ["game", "<game>"],
 ["variant", "<variant>"],
 ["score", "0.75"],
 ["elo_pre", "1500"],
 ["elo_post", "1508"],
 ["k_factor", "32"]
 ],
 "content": "",
 "id": "...",
 "sig": "..."
}

Verification: with equal pre-ratings expected_score = 0.5, so elo_post = 1500 + 32 × (0.75 − 0.5) = 1508. ✓ Alice’s opponent receives a symmetric attestation with score = 0.25, elo_post = 1492.

Client guidelines

Clients implementing kind: 3426 SHOULD:

  1. Subscribe to kind: 3426 with appropriate #p filters to track displayed players’ ratings, or with authors filters when tracking specific rating authorities.
  2. Validate received events against the semantic constraints (§Semantic constraints) before treating them as authoritative.
  3. Decide which rating authorities to trust based on user preferences or application policy; ratings from different authorities SHOULD NOT be averaged or combined silently.
  4. To determine a player’s current rating in a pool from a trusted rating authority, select the most recent Elo Rating Attestation matching the player and the pool scope in effect (where the consumer is evaluating a rating filter, the scope the filter declares — kind 3418 §Match-terms tags): under a pervariant scope, attestations whose game and single variant tag identify the (game, variant) pool; under a pergame scope (e.g., the reference deployment’s sanki authority), all attestations carrying that game, regardless of their variant tags (zero, one, or two). “Most recent” is determined by the event’s created_at (largest first), with the smallest event ID as tiebreaker. Apply §Race resolution first — deduplicate to the canonical attestation per (authority, player, duel) triple — and select among the survivors, so a republished duplicate never masquerades as a newer rating. This created_at-based selection is sound only for an authority that publishes in duel order and never back-fills older duels (the reference authority is forward-only — Rating Specification — Elo §Signed-snapshot production) — under a back-filling authority, an attestation for an older duel published later would wrongly become “current”, and consumers of such an authority need a duel-timing-based selection instead.
  5. When verifying a rating computation, fetch the opponent’s contemporaneous pre-duel Elo rating from the same rating authority’s prior Attestation and recompute the Elo formula.
  6. For a player with no Attestation from a trusted authority, treat the player as unrated. Initial-rating conventions are at the authority’s discretion.

Security considerations

References