147 lines
6.3 KiB
Markdown
147 lines
6.3 KiB
Markdown
# 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](../../docs/ARCHITECTURE.md). The gateway
|
|
mirrors this exact wire format in its own
|
|
[`gateway/authn`](../../gateway/authn) package; cross-module byte
|
|
parity and round-trip sign/verify are exercised by
|
|
[`gateway/authn/parity_with_ui_core_test.go`](../../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
|
|
|
|
```text
|
|
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
|
|
|
|
```sh
|
|
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
|
|
|
|
- [`../../docs/ARCHITECTURE.md` §15](../../docs/ARCHITECTURE.md) —
|
|
authoritative byte contract.
|
|
- [`../../gateway/authn`](../../gateway/authn) — server mirror of
|
|
the same canonical bytes.
|
|
- [`../PLAN.md`](../PLAN.md) Phase 3 — the staged plan that
|
|
describes how this module fits into the wider client.
|