Add authenticated protocol 2 and TypeScript WASM client

This commit is contained in:
sergeych committed 2026-10-19 04:17:34 +04:00
1 parent 88ceeb2ae9
commit 237d404bcd
52 files changed
+3249 -150

No files matched your search

+126
View File
@@ -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.