Files
galaxy-game/ui/core
Ilia Denisov dc1c9b109c phase 3
2026-05-07 09:40:37 +02:00
..
2026-05-07 09:40:37 +02:00
2026-05-07 09:40:37 +02:00
2026-05-07 09:40:37 +02:00
2026-05-07 09:40:37 +02:00
2026-05-07 09:40:37 +02:00
2026-05-07 09:40:37 +02:00

ui/core — Galaxy Client Compute Module

ui/core (Go module galaxy/core) is the compute boundary of the Galaxy cross-platform UI client. It carries v1 transport-envelope canonical bytes, signature verification, and Ed25519 keypair helpers. Network I/O and persistent storage live elsewhere on purpose: this module compiles unchanged to WASM (Phase 5), gomobile (Phase 32), and Wails-embedded native (Phase 31).

The authoritative byte contract is defined in docs/ARCHITECTURE.md §15. The gateway mirrors this exact wire format in its own gateway/authn package; cross-module byte parity and round-trip sign/verify are exercised by gateway/authn/parity_with_ui_core_test.go.

Invariants

  • No network. No net/http, no net/url, no gRPC client.
  • No storage. No os (outside _test.go fixtures), no SQL, no filesystem, no keychain.
  • TinyGo-friendly. Production files do not import crypto/x509, encoding/pem, or any package not supported by the WASM target. PKCS#8 PEM is server-only and stays in gateway/authn.
  • No goroutines, no channels, no sync primitives in production files. Pure functions, deterministic outputs.
  • No re-export of crypto/ed25519 types in the public API. Callers see opaque []byte blobs and string representations so the WASM bridge can hand them across the JS boundary as Uint8Array and string primitives.
  • Randomness is injected. keypair.Generate(reader io.Reader) takes a caller-supplied reader. Production code passes crypto/rand.Reader; tests use deterministic bytes.NewReader; WASM later passes a crypto.getRandomValues adapter.

Layout

ui/core/
├── go.mod               module galaxy/core (Go 1.26.0)
├── canon/               canonical-bytes builders and verifiers
│   ├── canon.go         length-prefix helpers
│   ├── request.go       galaxy-request-v1 fields and signing input
│   ├── response.go      galaxy-response-v1 fields and verifier
│   ├── event.go         galaxy-event-v1 fields and verifier
│   ├── signature.go     base64 client-key request verification
│   └── testdata/        committed JSON golden vectors
├── keypair/             Ed25519 generate / sign / verify / marshal
└── types/               full transport envelopes + result codes

Public API

galaxy/core/canon

  • RequestDomainMarkerV1, ResponseDomainMarkerV1, EventDomainMarkerV1 — UTF-8 domain prefixes that bind a signature to a specific envelope kind.
  • RequestSigningFields, ResponseSigningFields, EventSigningFields — exact subsets of envelope fields covered by the v1 signature.
  • BuildRequestSigningInput, BuildResponseSigningInput, BuildEventSigningInput — produce canonical bytes ready for ed25519.Sign / ed25519.Verify.
  • VerifyRequestSignature(clientPublicKey string, signature []byte, fields RequestSigningFields) error — accepts the base64 string form the backend stores in the device session.
  • VerifyResponseSignature(publicKey ed25519.PublicKey, signature []byte, fields ResponseSigningFields) error, VerifyEventSignature(publicKey ed25519.PublicKey, signature []byte, fields EventSigningFields) error — used by the client to validate server output.
  • VerifyPayloadHash(payloadBytes, payloadHash []byte) error.
  • Sentinel errors: ErrInvalidPayloadHash, ErrPayloadHashMismatch, ErrInvalidClientPublicKey, ErrInvalidRequestSignature, ErrInvalidResponseSignature, ErrInvalidEventSignature.

galaxy/core/keypair

  • Generate(reader io.Reader) (privateKey, publicKey []byte, err error).
  • Sign(privateKey, message []byte) ([]byte, error) — returns a 64-byte raw Ed25519 signature.
  • Verify(publicKey, message, signature []byte) bool.
  • MarshalPublicKey(publicKey []byte) (string, error) — base64 StdEncoding, the wire format documented in §15.
  • UnmarshalPublicKey(value string) ([]byte, error).
  • PublicKeyFromPrivate(privateKey []byte) ([]byte, error).
  • Sentinel errors: ErrInvalidPrivateKey, ErrInvalidPublicKey, ErrInvalidPublicKeyEncoding.

galaxy/core/types

  • RequestEnvelope, ResponseEnvelope, EventEnvelope — full Go envelope structs mirroring the protobuf messages in gateway/proto/galaxy/gateway/v1/. Each exposes a SigningFields() method to project onto the corresponding canon.*SigningFields.
  • ProtocolVersionV1 = "v1", ResultCodeOK = "ok" — the only result string that is part of the stable client contract; any other result_code is downstream-opaque and must not be hard-coded by clients.

Testing

go test -count=1 ./ui/core/...

The canon test suite combines:

  • byte-equality on golden JSON fixtures under canon/testdata/ for three request types (user.account.get, lobby.my.games.list, user.games.command), one response (ok), and one event (gateway.server_time);
  • mutation tests proving every signed field is bound into the signature;
  • round-trip sign-then-verify across all three envelope kinds;
  • negative tests for tampered hashes, mismatched timestamps and request IDs, invalid signature lengths, and bad public-key encodings.

Cross-module parity (gateway accepts ui/core signatures and vice versa) is enforced from gateway/authn/parity_with_ui_core_test.go.

What this module is not

  • Not a network client. ConnectRPC over @connectrpc/connect-web on the TypeScript side is the only network surface (Phase 5+).
  • Not a key store. Per-platform secure storage lives in Phase 6.
  • Not a freshness gate. Server-side ±5 min freshness checks remain in gateway/internal/grpcapi/freshness_replay.go. The client is expected to stamp its own timestamp_ms accurately via time.Now, but does not enforce a window.
  • Not a FlatBuffers codec — that lands in a later phase, so the module today is small on purpose.

Cross-references