# MTP WASM Client The WASM client is a browser-compatible MTP implementation that uses the **WebTransport** API to communicate with an MTP host over QUIC (HTTP/3). It compiles from Rust to WebAssembly via `wasm-bindgen` and exposes a JavaScript/TypeScript API through the `mtp-wasm` npm package. ## Package The compiled package lives in `wasm/pkg/` and contains: - `mtp_wasm.js` -- generated JS glue - `mtp_wasm_bg.wasm` -- the WebAssembly binary - `mtp_wasm.d.ts` -- TypeScript type declarations - `package.json` -- npm package definition Install or copy these files into your web project. Then initialise the module: ```typescript import init, { WasmClient } from 'mtp-wasm'; await init(); ``` ## Browser Support WebTransport is required. Check availability at runtime: ```typescript if (!WasmClient.is_supported()) { // fall back or show an error } ``` ## Connecting to a Host ### ConnectionConfig ```typescript const config = new ConnectionConfig("https://host.example.com:4433"); config.client_id = 12345n; // optional, for re-authentication config.server_certificate_hashes = [ // optional, for certificate pinning "sha256:abc123...", ]; ``` `client_id` is only needed for authenticated login (`auth_connect`). For registration (`auth_register`) it is ignored. ### TLS Certificate Handling By default — when `server_certificate_hashes` is not set — the browser uses its **built-in root certificate store** to verify the server's TLS certificate, just like any other HTTPS/WebSocket connection. This works with publicly-trusted certificate authorities automatically. For development or self-signed certificates, pin the server certificate by providing its hash: ```typescript config.server_certificate_hashes = [ "sha256:abcd1234...", // hex-encoded hash value ]; ``` The hash format is `":"`. When hashes are provided, the browser **only** trusts certificates matching one of the given hashes and ignores its root store for this connection. ### Callbacks The client uses three callbacks for state, messages, and errors: ```typescript const client = new WasmClient( (state: number) => console.log("state", state), // ConnectionState enum (data: Uint8Array) => console.log("msg", data), // raw frame bytes (err: any) => console.error("err", err), // error description ); ``` ### Connection States | Value | Name | |-------|--------------| | 0 | Disconnected | | 1 | Connecting | | 2 | Connected | | 3 | Failed | Poll `client.state` at any time. ## Connection Methods ### Unauthenticated Connect ```typescript await client.connect(config); ``` Sends an `Identification` frame with the protocol version and client ID. The host may accept or reject. No cryptographic handshake occurs. ### Authenticated Login (existing client ID) ```typescript const confirmedId = await client.auth_connect( config, hostPublicKeyBytes, // Uint8Array: serialized PublicKeyBundle from the host keyringBytes, // Uint8Array: serialized Keyring matching the client ID clientId, // bigint: previously assigned client ID ); ``` Exchange: client sends a signed `Identification` frame, the host verifies it and responds with a signed `IdentificationResponse`. Returns the confirmed client ID. ### Registration (new client) ```typescript const newId = await client.auth_register( config, hostPublicKeyBytes, // Uint8Array: serialized PublicKeyBundle from the host keyringBytes, // Uint8Array: serialized Keyring for the new identity ); ``` Exchange: client sends a signed `Register` frame with public keys, the host assigns a new ID and responds with a signed `RegisterResponse`. Returns the newly assigned client ID. ## Sending and Receiving Messages ### Send ```typescript const frame = build_ping_frame(clientId, "hello", timestamp, data); await client.send(frame); ``` `send()` takes raw frame bytes (a serialized `CommunicationValue`). Build frames with the provided helper functions or construct them manually. ### Receive Incoming frames arrive on the `on_message` callback registered in the constructor. The callback receives a `Uint8Array` of raw frame bytes. Parse with `CommunicationValue.from_bytes()` on the Rust side or handle the bytes in JS. ### Disconnect ```typescript client.disconnect(); ``` Gracefully closes the WebTransport session. ## Building Frames ### `build_ping_frame` ```typescript function build_ping_frame( clientId: bigint, description: string, timestamp: bigint, data: Uint8Array, ): Uint8Array; ``` Constructs a basic `Ping` message with description, timestamp, and optional binary payload. Useful for health checks and simple messaging. ### `build_demo_message` ```typescript function build_demo_message( clientId: bigint, keyringBytes: Uint8Array, ): Uint8Array; ``` Constructs a `Ping` frame that demonstrates encrypted, signed, and signed+encrypted containers using a deterministic demo key. The paired host handler can decrypt and verify these containers if it knows the same shared secret. ### `parse_auth_response` ```typescript function parse_auth_response(response: Uint8Array): any; ``` Parses an `IdentificationResponse` or `RegisterResponse` frame into a JS object: ```typescript { connected: boolean, clientNonce?: Uint8Array, assignedId?: number, timestamp?: number, signature?: Uint8Array, } ``` ## Crypto Primitives ### Key Generation ```typescript const result = ed25519_generate(); // result.signer -> WasmEd25519Signer // result.secretKey -> Uint8Array (32 bytes) // result.publicKey -> Uint8Array (32 bytes) ``` ### Keyring A `Keyring` bundles all key material for an identity. For Ed25519-only setups: ```typescript const keyringBytes = keyring_from_ed25519(secretKey, publicKey); // keyringBytes is ready for WasmClient.auth_register or WasmClient.auth_connect ``` Full keyring with KEM + ML-DSA requires constructing on the Rust side. The serialized bytes are portable: ```typescript const keyring = WasmKeyring.from_bytes(keyringBytes); const bundle = keyring.public_key_bundle(); // bundle.kem_public_key -> Uint8Array // bundle.sig_cl_public_key -> Uint8Array // bundle.sig_pq_public_key -> Uint8Array ``` ### Signing and Verification ```typescript const signer = new WasmEd25519Signer(secretKey); const sig = signer.sign(message); // Uint8Array signer.verify(message, sig); // throws on mismatch // Standalone verification (no signer object needed): ed25519_verify(publicKey, message, signature); ``` ### Symmetric Encryption ```typescript const cipher = new WasmChaCha20Poly1305(key); // 32-byte key const encrypted = cipher.encrypt(plaintext, aad); // nonce || ciphertext const decrypted = cipher.decrypt(encrypted, aad); ``` ### Hashing and KDF ```typescript const hash = wasm_sha256(data); // 32 bytes const double = wasm_sha256_double(data); // SHA-256(SHA-256(data)) const derived = wasm_hkdf_expand(ikm, salt, info, len); const encKey = wasm_derive_encryption_key(ikm, salt, context); // 32 bytes ``` ## Lifecycle and Best Practices 1. **Key persistence** -- serialise keyring bytes after registration and store them (e.g. in `localStorage`). On next visit, load the saved keyring and call `auth_connect` instead of registering again. 2. **Ownership** -- call `config.free()` after connecting if the config object is no longer needed. WASM objects (`WasmClient`, `WasmKeyring`, etc.) are garbage-collected, but explicit `free()` or `dispose()` reclaims memory sooner. 3. **Receive loop** -- once `connect`, `auth_connect`, or `auth_register` resolves, the receive loop is running in the background. Incoming frames arrive on the `on_message` callback. There is no need to poll. 4. **Single active client** -- a `WasmClient` manages one WebTransport session. Create a new instance for each connection. 5. **State transitions** -- after `disconnect()` the client transitions to `Disconnected`. The instance is reusable; call a connect method again to open a new session.