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, nonet/url, no gRPC client. - No storage. No
os(outside_test.gofixtures), 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 ingateway/authn. - No goroutines, no channels, no
syncprimitives in production files. Pure functions, deterministic outputs. - No re-export of
crypto/ed25519types in the public API. Callers see opaque[]byteblobs andstringrepresentations so the WASM bridge can hand them across the JS boundary asUint8Arrayand string primitives. - Randomness is injected.
keypair.Generate(reader io.Reader)takes a caller-supplied reader. Production code passescrypto/rand.Reader; tests use deterministicbytes.NewReader; WASM later passes acrypto.getRandomValuesadapter.
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 fored25519.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 ingateway/proto/galaxy/gateway/v1/. Each exposes aSigningFields()method to project onto the correspondingcanon.*SigningFields.ProtocolVersionV1 = "v1",ResultCodeOK = "ok"— the only result string that is part of the stable client contract; any otherresult_codeis 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-webon 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 minfreshness checks remain ingateway/internal/grpcapi/freshness_replay.go. The client is expected to stamp its owntimestamp_msaccurately viatime.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
../../docs/ARCHITECTURE.md§15 — authoritative byte contract.../../gateway/authn— server mirror of the same canonical bytes.../PLAN.mdPhase 3 — the staged plan that describes how this module fits into the wider client.