Add authenticated protocol 2 and TypeScript WASM client
This commit is contained in:
1 parent
88ceeb2ae9
commit
237d404bcd
52 files changed
+3249
-150
No files matched your search
@@ -0,0 +1,126 @@
|
||||
# Kiloparsec TypeScript + WASM
|
||||
|
||||
Full-protocol TypeScript client for the existing Kotlin Kiloparsec server, backed
|
||||
by libsodium WASM. No Kotlin runtime, no light handshake, no TLS-only identity
|
||||
fallback. This is an evaluated implementation candidate, **not a production release**.
|
||||
|
||||
This module is maintained alongside the Kotlin implementation in this repository.
|
||||
Both peers use the hardened 0.8 protocol: encrypted packets authenticate their
|
||||
RPC envelope as well as their payload. Legacy peers are deliberately rejected;
|
||||
there is no automatic downgrade. See [security and migration](docs/SECURITY.md).
|
||||
|
||||
The [assessment](docs/ASSESSMENT.md) records the original implementation choice
|
||||
and historical size measurements. Re-run the measurement script for this revision;
|
||||
those recorded measurements describe the earlier published protocol baseline.
|
||||
|
||||
## API
|
||||
|
||||
```ts
|
||||
import {
|
||||
Client, LocalInterface, command, string, generateSigningKey,
|
||||
} from '@sergeych/kiloparsec';
|
||||
|
||||
const echo = command('echo', string, string);
|
||||
const clientEcho = command('clientEcho', string, string);
|
||||
const local = new LocalInterface<{ requests: number }>()
|
||||
.on(clientEcho, (scope, text) => {
|
||||
scope.session.requests++;
|
||||
return text;
|
||||
});
|
||||
const identity = await generateSigningKey();
|
||||
// Persist identity.exportSecret() in your secure store before registering it.
|
||||
// Register identity.publicKey with the application separately.
|
||||
const client = await Client.connect('wss://your-service.example/kp', {
|
||||
expectedServerKey, // trusted raw 32-byte Ed25519 key, provisioned independently
|
||||
identity,
|
||||
local,
|
||||
session: { requests: 0 },
|
||||
});
|
||||
try {
|
||||
const text = await client.call(echo, 'Hello');
|
||||
} finally {
|
||||
client.close();
|
||||
identity.destroy();
|
||||
}
|
||||
```
|
||||
|
||||
`expectedServerKey` is mandatory. A signature by an arbitrary key supplied by the
|
||||
peer is insufficient. Anonymous *client* sessions are available by omitting
|
||||
identity; their permissions remain a server application decision. `restoreSigningKey`
|
||||
restores raw 64-byte secret exports; generic Kiloparsec uses bytes, while an SDK
|
||||
such as GrandMobile may wrap them in its own opaque versioned format.
|
||||
|
||||
`command(name, argsCodec, resultCodec)` mirrors Kotlin `Command<A,R>`.
|
||||
`LocalInterface.on` registers typed callbacks before connection; each connection
|
||||
gets a snapshot of the registry and a session object. A callback can call its
|
||||
peer and await the result without blocking packet dispatch. Methods on `Client`:
|
||||
|
||||
- `call(command, args, {signal, timeoutMs, priority})`: typed authenticated payload
|
||||
or `RemoteError`. `ConnectionError` describes local transport failure.
|
||||
- `push(command, args)`: sends an id=0 notification; successful return means local
|
||||
enqueue only, not acknowledged remote execution.
|
||||
- `ping()`: encrypted liveness check. Automatic heartbeat defaults to 30 seconds;
|
||||
set `heartbeatMs: 0` to disable it.
|
||||
- `close()` and `closed`: dispose the connection and observe local disposal.
|
||||
|
||||
Timeout/abort stops waiting, not remote execution. No transparent replay or
|
||||
reconnect: create a fresh connection and let the application resolve uncertain
|
||||
business outcomes using its durable operation IDs. Transport IDs are unrelated.
|
||||
Local handler failures are sanitized unless explicitly returned as `RemoteError`.
|
||||
|
||||
Node >=22 uses its standard WebSocket. Other runtimes can provide `socketFactory`.
|
||||
Both ws and wss carry the same encrypted protocol; deployment policy chooses the
|
||||
URL. Browser type/build compatibility is present but browser runtime tests have
|
||||
not been performed. Do not treat Node interoperability as browser qualification.
|
||||
|
||||
## Serialization compatibility
|
||||
|
||||
Interop tests build the Kotlin server from this checkout through a Gradle
|
||||
composite build; no published Kiloparsec snapshot is needed.
|
||||
`src/bipack.ts` implements explicit codecs; it does not dynamically recreate all
|
||||
Kotlin serialization. Exported codecs cover strings, bytes, bool, Unit, integers,
|
||||
nullable values and lists. Compose records with `codec`, `BipackReader` and
|
||||
`BipackWriter` in the same field order as Kotlin; no field names go on the wire.
|
||||
|
||||
`uint32`/`uint64` encode unsigned Smartint (`@Unsigned` semantics). Published
|
||||
mp_bintools 0.3.2 serializes unannotated Kotlin UInt through its signed backing
|
||||
Int: the handshake codec preserves this behavior. Newer source 0.4.0 changes that
|
||||
behavior. Do not upgrade either peer silently. Signed Smartint cannot represent
|
||||
Long.MIN_VALUE correctly in this baseline; TS rejects it explicitly.
|
||||
|
||||
Packet sizes, concurrent incoming work, pending calls and send buffers are bounded.
|
||||
Receive replay tracking permits out-of-order counters within 4096 positions and
|
||||
fails closed outside the window. Authenticated envelopes additionally bind packet kind, RPC ID and priority
|
||||
to the encrypted payload. Priority trailers
|
||||
are preserved, but this first client uses WebSocket FIFO rather than a priority
|
||||
scheduler. No TCP/UDP adapters, carrier cycling, automatic reconnection, or code
|
||||
generator are included in this version.
|
||||
|
||||
## Build and verify
|
||||
|
||||
```sh
|
||||
npm ci --ignore-scripts
|
||||
npm test # build + local codec/crypto/RPC regression tests
|
||||
npm run test:interop # build current Kotlin fixture + cross-language tests
|
||||
npm run test:all # both suites
|
||||
# Optional optimized Kotlin comparison baseline:
|
||||
npm run baseline:build
|
||||
npm ci --prefix evaluation --ignore-scripts
|
||||
npm run measure
|
||||
```
|
||||
|
||||
JDK 21 and the Android SDK configuration required by the parent Kotlin build must
|
||||
be available for interoperability and baseline builds. `test:interop` starts and
|
||||
stops its own disposable localhost JVM fixture. Generated outputs and dependencies
|
||||
are not source-controlled. Package publication remains disabled pending release
|
||||
qualification. From the parent repository, `bin/test-all.sh` runs Kotlin and TS
|
||||
unit tests followed by interoperability tests sequentially.
|
||||
|
||||
## Follow-up: reusable utility exports
|
||||
|
||||
After the GrandMobile game API, complete the supported public utility surface:
|
||||
BiPack, key generation/restoration, signing/verification, standalone encryption
|
||||
and hashes. BiPack and signing identities are already exported from index.ts;
|
||||
hash and session-crypto implementations currently remain internal package modules.
|
||||
Define documented stable entry points, formats and usage examples before exposing
|
||||
additional utilities. This is deferred work, not a prerequisite for the game API.
|
||||
Reference in new issue
Block a user