pip

package
v0.0.0-...-ae481c8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package pip implements the Player Identity Protocol (PIP) from PLAYER_IDENTITY.md.

PIP builds decentralized player authentication on top of ODP. A player's identity travels with them as a Redirection Token (RT) — an ODP packet whose action is "redirect", whose subject is the player's UUID and whose data is a MessagePack-encoded PlayerProfile. Bulky profile fields can be carried out of band and vouched for with an ExtensionSignature.

The package covers four concerns:

Specification ambiguities and interoperability

The Cookie channel description leaves the exact padding and segment length slightly open; this package resolves it as follows, and interoperating implementations must match:

  • Every cookie's data section is exactly SegmentSize (5118) bytes, so a cookie is 2-byte magic + 5118-byte payload = 5120 bytes = 5 KiB. This is the reading consistent with both "read the 5118 bytes after the magic" and "pad segments to 5 KiB alignment". The final (or only) segment is zero-padded up to 5118 bytes; this applies to single-cookie tokens too, not just multi-segment ones. See Chunk.
  • Reassembly returns the padding as-is. Callers strip it using a context-supplied length (ExtensionSignature.Size, or the size argument to Assemble); when no length is known the payload is self-delimiting (MessagePack ignores trailing zero padding) and capped at MaxAssembledSize.

Redirection Tokens embed the PlayerProfile as the ODP data field, so the profile's MessagePack encoding is what ODP hashes and signs. See the odp package's "MessagePack canonicalization" note for the encoding rules.

Example

Issue a Redirection Token, carry it through the Cookie channel, then assemble, parse and verify it on the destination server.

package main

import (
	"bytes"
	"fmt"
	"time"

	"github.com/icebear67/mfp-go"
	"github.com/icebear67/mfp-go/pip"
)

func main() {
	gateway, _ := mfp.IdentityFromSeed(bytes.Repeat([]byte{7}, mfp.SeedSize))
	destination, _ := mfp.IdentityFromSeed(bytes.Repeat([]byte{8}, mfp.SeedSize))
	now := time.Unix(1_700_000_000, 0)

	token, _ := pip.Issue(gateway, pip.TokenParams{
		Target:  destination.Public(),
		Subject: []byte("player-uuid"),
		Profile: &pip.PlayerProfile{Name: "Steve"},
		Time:    now.Add(-time.Minute),
		Until:   now.Add(time.Minute),
	})

	// Source server: encode the token and split it across client cookies.
	encoded, _ := token.Marshal()
	cookies, _ := pip.Chunk("pip:redirect", encoded)

	// Destination server: reassemble the cookies and verify the token.
	assembled, _ := pip.Assemble("pip:redirect", -1, pip.FetcherFromMap(cookies))
	parsed, _ := pip.Parse(assembled)
	err := parsed.Verify(pip.VerifyParams{
		Recipient: destination,
		Known:     mfp.NewKeySet(gateway.Public()),
		Now:       func() time.Time { return now },
	})

	fmt.Println("cookies:", len(cookies))
	fmt.Println("player:", parsed.Profile.Name)
	fmt.Println("verify:", err)
}
Output:
cookies: 1
player: Steve
verify: <nil>

Index

Examples

Constants

View Source
const (
	// SegmentSize is the data payload carried by each cookie (5118 bytes), so
	// that magic (2) + payload equals 5120 bytes (5 KiB).
	SegmentSize = 5118
	// MaxAssembledSize is the default cap on assembled data when no context
	// size is known (0.5 MiB).
	MaxAssembledSize = 524288
)

Cookie channel constants.

View Source
const (
	// ActionArrive is the action of a PlayerRedirectionArrived payload.
	ActionArrive = "arrive"
	// ActionCommit is the action of a PlayerRedirectionCommitted payload.
	ActionCommit = "commit"
)

Redirection-receipt ODP/RTDP actions.

View Source
const (
	ExtTexture = "pip:texture"
	ExtCape    = "pip:cape"
)

Conventional cookie-channel ids for profile fields lifted into extensions, per PLAYER_IDENTITY.md.

View Source
const ActionRedirect = "redirect"

ActionRedirect is the fixed ODP action of a Redirection Token.

Variables

View Source
var (
	// MagicLast (CA FE) marks the final (or only) segment.
	MagicLast = [2]byte{0xCA, 0xFE}
	// MagicMore (CA AC) marks a segment followed by more.
	MagicMore = [2]byte{0xCA, 0xAC}
)

Cookie magic numbers.

View Source
var (
	// ErrCookieID means an id does not match namespace:value.
	ErrCookieID = errors.New("pip: invalid cookie id")
	// ErrCookieMissing means a required segment cookie was not supplied.
	ErrCookieMissing = errors.New("pip: missing cookie segment")
	// ErrCookieMagic means a segment had an unrecognized magic number.
	ErrCookieMagic = errors.New("pip: invalid cookie magic")
	// ErrCookieShort means a segment was shorter than its magic number.
	ErrCookieShort = errors.New("pip: cookie segment too short")
	// ErrCookieSize means the assembled data was smaller than the requested size.
	ErrCookieSize = errors.New("pip: assembled data shorter than expected")
)

Cookie channel errors.

View Source
var (
	// ErrProfileField means a required profile field is missing or malformed.
	ErrProfileField = errors.New("pip: invalid player profile")
	// ErrUnknownExtIssuer means an extension's issuer is not a known key.
	ErrUnknownExtIssuer = errors.New("pip: unknown extension issuer")
	// ErrExtSize means the extension data length does not match its declared size.
	ErrExtSize = errors.New("pip: extension size mismatch")
	// ErrExtSignature means an extension signature did not verify.
	ErrExtSignature = errors.New("pip: invalid extension signature")
)

Profile / extension errors.

View Source
var (
	// ErrReceiptRejected means the source server acknowledged the arrival with
	// a non-empty error message.
	ErrReceiptRejected = errors.New("pip: source rejected redirection arrival")
	// ErrReceiptUnexpected means an unexpected packet arrived during the receipt
	// handshake.
	ErrReceiptUnexpected = errors.New("pip: unexpected packet during receipt")
)

Receipt errors.

View Source
var (
	// ErrNotRedirect means the ODP packet's action is not "redirect".
	ErrNotRedirect = errors.New("pip: token action is not redirect")
	// ErrNoRecipient means Verify was called without a recipient identity, so
	// the mandatory target check could not be performed.
	ErrNoRecipient = errors.New("pip: recipient identity required to verify a token")
)

Token errors.

Functions

func Arrived

func Arrived(subject []byte, transactionID int32, tokenSignature []byte) *rtdp.PeerPayload

Arrived builds a PlayerRedirectionArrived payload: action "arrive", the player's UUID as subject, the given transaction id, and the token signature (the RT's ODP signature) as data.

func Assemble

func Assemble(id string, size int, fetch Fetcher) ([]byte, error)

Assemble reconstructs data written by Chunk. It walks segments via fetch until a MagicLast segment is seen. When size >= 0 the result is trimmed to exactly size bytes (and it is an error to assemble fewer); when size < 0 the full assembled payload — including any trailing zero padding — is returned, capped at MaxAssembledSize.

func Chunk

func Chunk(id string, data []byte) (map[string][]byte, error)

Chunk splits data into cookie segments keyed by name. Every segment's payload is padded with zeros to SegmentSize so each cookie is exactly 5 KiB; the final segment carries MagicLast and the rest carry MagicMore.

func Committed

func Committed(subject []byte, transactionID int32) *rtdp.PeerPayload

Committed builds a PlayerRedirectionCommitted payload: action "commit", the player's UUID as subject, the matching transaction id, and no data.

func NewTransactionID

func NewTransactionID() (int32, error)

NewTransactionID returns a random non-zero transaction id for use with Arrived. A non-zero id obliges the peer to acknowledge.

func SegmentName

func SegmentName(id string, i int) string

SegmentName returns the cookie name for segment i of id: id for i == 0, and "id-i" otherwise.

func SendReceipt

func SendReceipt(sess *rtdp.Session, subject []byte, tokenSignature []byte) error

SendReceipt drives the destination-server side of the receipt handshake over an established session with the source server:

  1. send PlayerRedirectionArrived (action "arrive");
  2. await the source server's rtdp.PeerAcknowledge for the same id and fail if it carries an error;
  3. send PlayerRedirectionCommitted (action "commit").

On success the player may be released. The source server does not acknowledge the commit, per PLAYER_IDENTITY.md.

func ValidateCookieID

func ValidateCookieID(id string) error

ValidateCookieID reports whether id matches the required namespace:value form.

Types

type ExtensionSignature

type ExtensionSignature struct {
	Issuer mfp.PublicKey // guarantor's ed25519 public key
	Sign   []byte        // ed25519 signature over SHA3-224(D)
	Size   uint32        // byte length of D
}

ExtensionSignature vouches for an out-of-band extension datum D. The signature is Sign_issuer(SHA3-224(D)).

func SignExtension

func SignExtension(issuer *mfp.Identity, data []byte) ExtensionSignature

SignExtension produces an ExtensionSignature for data.

func (ExtensionSignature) Verify

func (e ExtensionSignature) Verify(data []byte, known *mfp.KeySet) error

Verify checks the extension signature against data. When known is non-nil the issuer must be a member.

type Fetcher

type Fetcher func(name string) ([]byte, bool)

Fetcher retrieves a cookie by name, reporting whether it exists. It models the Minecraft Cookie Request round-trip.

func FetcherFromMap

func FetcherFromMap(m map[string][]byte) Fetcher

FetcherFromMap adapts a name->cookie map to a Fetcher, convenient for tests and for callers that have already downloaded every cookie.

type PlayerProfile

type PlayerProfile struct {
	Name       string                        // in-game name (not the UUID)
	Texture    []byte                        // optional skin data
	Cape       []byte                        // optional cape data
	Extensions map[string]ExtensionSignature // optional; keyed by cookie-channel id
}

PlayerProfile is the "data" payload of a Redirection Token. Texture, Cape and Extensions are optional; a nil slice or empty map means the field is omitted from the encoding.

func ParsePlayerProfile

func ParsePlayerProfile(data []byte) (*PlayerProfile, error)

ParsePlayerProfile decodes a MessagePack-encoded profile. Trailing bytes after the map (e.g. cookie-channel zero padding) are ignored.

func (*PlayerProfile) Marshal

func (p *PlayerProfile) Marshal() ([]byte, error)

Marshal encodes the profile as MessagePack. Extension keys are sorted so the output is deterministic.

type RedirectionToken

type RedirectionToken struct {
	Packet  *odp.Packet
	Profile *PlayerProfile
}

RedirectionToken is a decoded RT: its ODP envelope plus the decoded profile.

func Issue

func Issue(issuer *mfp.Identity, params TokenParams) (*RedirectionToken, error)

Issue builds and signs a Redirection Token.

func Parse

func Parse(data []byte) (*RedirectionToken, error)

Parse decodes an ODP envelope as a Redirection Token, decoding the embedded profile. It does not verify signatures; call RedirectionToken.Verify.

func (*RedirectionToken) Marshal

func (t *RedirectionToken) Marshal() ([]byte, error)

Marshal encodes the token as an ODP envelope (MessagePack).

func (*RedirectionToken) Signature

func (t *RedirectionToken) Signature() []byte

Signature returns the token's ODP signature, which doubles as its unique id (used, for example, as the payload of a redirection receipt).

func (*RedirectionToken) Verify

func (t *RedirectionToken) Verify(params VerifyParams) error

Verify authenticates the token: it enforces the ODP checks and additionally requires the action to be "redirect" and the target to be the recipient.

type TokenParams

type TokenParams struct {
	// Target is the destination server's public key.
	Target mfp.PublicKey
	// Subject is the player's UUID.
	Subject []byte
	// Profile is the player's profile data.
	Profile *PlayerProfile
	// Time and Until bound the token's validity.
	Time  time.Time
	Until time.Time
}

TokenParams describes a Redirection Token to be issued.

type VerifyParams

type VerifyParams struct {
	// Recipient is this server's identity. The token's target must equal its
	// public key (PIP mandates this check). Required.
	Recipient *mfp.Identity
	// Known is the set of authorized issuer keys.
	Known *mfp.KeySet
	// Replay records used token signatures. See [odp.ReplayGuard].
	Replay odp.ReplayGuard
	// Now overrides the clock (defaults to time.Now).
	Now func() time.Time
}

VerifyParams configures RedirectionToken.Verify.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL