@muhkoo/connect 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muhkoo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,365 @@
1
+ # @muhkoo/connect
2
+
3
+ Client SDK for the Muhkoo Accelerator. The headline surface is a single
4
+ **`Client`** that hangs everything off three namespaces —
5
+ `client.auth` / `client.storage` / `client.message` — backed by
6
+ end-to-end-encrypted messaging primitives (Double Ratchet + ECDH P-384), a
7
+ turnkey multi-peer `BroadcastChannel`, ZK identity + personal/shared spaces,
8
+ and a Cloudflare-Workers-compatible Groth16 verifier — all bundled from a
9
+ single TypeScript source tree.
10
+
11
+ > Status: alpha. The library is consumed in tree by `../accelerator` and the
12
+ > `../web` SPA.
13
+
14
+ ## What's in the box
15
+
16
+ - **`Client`** — the unified entry point. `new Client({ apiKey, baseUrl })`
17
+ exposes `auth` (ZK register/login), `storage` (per-user encrypted KV +
18
+ files), and `message` (pub/sub + E2E direct messages) over one shared
19
+ session. This is the supported surface; everything below is a building block
20
+ it composes.
21
+
22
+ - **`BroadcastChannel`** — multi-peer end-to-end-encrypted "room". Wires
23
+ `WSTransport` (socket lifecycle, reconnect, offline queue) together with
24
+ `EncryptedSession` (per-peer Double Ratchet, handshake fan-out, recipient
25
+ filtering). Drops onto a chat protocol without changing the wire format.
26
+ - **`EncryptedSession`** — transport-agnostic per-peer ratchet manager. Lets you
27
+ bring your own transport.
28
+ - **`WSTransport`** — pure WebSocket lifecycle (connect / auto-reconnect /
29
+ buffered outbound queue / lifecycle events).
30
+ - **`KeyStore`** — singleton holding ECDH + ECDSA P-384 keypairs per identity,
31
+ with dehydrate/hydrate helpers for persistence.
32
+ - **`DoubleRatchet`** + **`DoubleRatchetManager`** — the underlying ratchet
33
+ primitives (browser/server builds only — see below).
34
+ - **`PersonalSpaceClient`** — HTTP client for the accelerator's
35
+ `/api/personal/:commitment/*` ZK-gated KV API. Generates a fresh Groth16
36
+ proof per request using snarkjs and the `preimagePoK` circuit.
37
+ - **`wrapWithPassphrase` / `unwrapWithPassphrase`** — PBKDF2-SHA256 (200k
38
+ iterations) + AES-256-GCM passphrase wrap. Pairs naturally with
39
+ `PersonalSpaceClient` for client-side payload encryption.
40
+ - **`verifyGroth16` / `initBn128Wasm`** — universal Groth16 verifier driven by
41
+ bn128.wasm. Runs anywhere WebAssembly does (Node, browsers, CF Workers).
42
+ Used by the accelerator's `verifyZkAuthProof`.
43
+ - ZK building blocks re-exported from `crypto/ZeroKnowledge` (`Field`,
44
+ `Poseidon`, `PreimagePoK`, `HashKnowledge`, `AuthPublicInput`,
45
+ `verifyHashKnowledge`, `verifyPreimagePoK`, `quickVerify`,
46
+ `compilePrograms`, `initializeCircuits`, `encodeToHex`, `decodeFromHex`).
47
+ - Messaging utilities (`Message`, `Packet`, `SerializeMessage`) and event
48
+ primitives (`EventCore`, `EventCoreEvents`).
49
+
50
+ ## Three build targets
51
+
52
+ `yarn build` produces three bundles from one source tree. They are selected via
53
+ the `exports` field in `package.json`:
54
+
55
+ | Target | Entry | Output |
56
+ | --- | --- | --- |
57
+ | `browser` | `src/browser/index.ts` | `dist/browser/index.js` |
58
+ | `server` | `src/server/index.ts` | `dist/server/index.js` |
59
+ | `workers` | `src/workers/index.ts` | `dist/workers/index.js` |
60
+
61
+ The `workers` build deliberately excludes anything that pulls in
62
+ `snarkjs` / `ffjavascript` transitive deps (which need `URL.createObjectURL`
63
+ and other APIs workerd doesn't expose):
64
+
65
+ - `ZeroKnowledge.ts` — `Field`, `Poseidon`, `PreimagePoK`, `verifyPreimagePoK`,
66
+ etc.
67
+ - `Authenticator.ts`
68
+ - `DoubleRatchetManager.ts`
69
+ - `personal/` — `PersonalSpaceClient`, `wrapWithPassphrase`,
70
+ `unwrapWithPassphrase`
71
+
72
+ The `workers` build keeps `KeyStore`, `DoubleRatchet`, `EncryptedSession`,
73
+ `BroadcastChannel`, `WSTransport`, and the bn128.wasm-driven
74
+ `groth16-verifier` — that last one is the only viable Groth16 path on the edge.
75
+
76
+ Types in `dist/connect.d.ts` (bundled from the flat `src/browser/index.ts`
77
+ entry) include everything from all three runtimes so consumers can reference
78
+ types even when the runtime impl is absent for their target. Calling
79
+ `verifyGroth16` / `initBn128Wasm` works under workerd; importing
80
+ `PersonalSpaceClient` and calling it in a worker will fail at runtime.
81
+
82
+ ### Bare specifiers and snarkjs
83
+
84
+ Browser and server builds externalize bare specifiers — the consumer's bundler
85
+ (or an import map for direct browser use) resolves them. The accelerator's
86
+ chat app provides `snarkjs` via an esm.sh import map; Node consumers add
87
+ `snarkjs` as a peer dependency.
88
+
89
+ ## Installation
90
+
91
+ ```bash
92
+ npm install @muhkoo/connect
93
+ # or: yarn add @muhkoo/connect / pnpm add @muhkoo/connect
94
+ ```
95
+
96
+ ESM-only, with bundled type declarations. `snarkjs` is an **optional peer
97
+ dependency** — install it only if you use the ZK-proof features
98
+ (`PersonalSpaceClient`, shared-space writes, client-side proof generation):
99
+
100
+ ```bash
101
+ npm install snarkjs
102
+ ```
103
+
104
+ ## Quick start
105
+
106
+ ### The unified client (recommended)
107
+
108
+ `Client` is the supported entry point. One object, one app key, three
109
+ namespaces — `auth`, `storage`, `message` — all sharing a single session.
110
+
111
+ ```typescript
112
+ import { Client } from "@muhkoo/connect";
113
+
114
+ const client = new Client({
115
+ apiKey: "mk_test_pk_…", // app / publishable key
116
+ baseUrl: "https://accelerator.example.dev",
117
+ // circuits default to `${baseUrl}/circuits/build/preimagePoK{.wasm,_0001.zkey}`
118
+ });
119
+ ```
120
+
121
+ > **`apiKey`** is transitionally optional (auth + storage work without it
122
+ > today) but is becoming **required** as part of productizing the Accelerator:
123
+ > it's how each app authenticates and gets metered/billed, and it authorizes
124
+ > messaging websockets. Always pass one for new integrations.
125
+
126
+ ```typescript
127
+ // (continued)
128
+
129
+ // Auth — deterministic ZK identity derived from (username, password).
130
+ const user = await client.auth.zk.login("alice", "correct horse battery staple");
131
+ // └ { username, commitment }
132
+
133
+ // Storage — per-user persistent KV (collection + id), encrypted at rest.
134
+ await client.storage.set("todos", "t1", { title: "Buy groceries", completed: false });
135
+ const todo = await client.storage.get("todos", "t1");
136
+ const ids = await client.storage.list("todos");
137
+ await client.storage.delete("todos", "t1");
138
+
139
+ // Message — realtime pub/sub + end-to-end-encrypted direct messages.
140
+ const sub = client.message.subscribe("todos", (e) => console.log("update:", e.data));
141
+ await client.message.publish("todos", { id: "t1", done: true });
142
+ await client.message.send("user:abc", { text: "Hello!" });
143
+ ```
144
+
145
+ `subscribe(subject)` / `publish(subject, data)` are plaintext fan-out to
146
+ everyone on that subject. `send("user:<id>", …)` is an **end-to-end-encrypted**
147
+ direct message (Double Ratchet); the recipient receives it by subscribing to
148
+ their *own* id — `client.message.subscribe("user:<me>", …)`. Both modes ride a
149
+ shared-space room websocket under the hood (opened with a short-lived signed
150
+ ticket); E2E delivery needs both parties online, like any ratchet.
151
+
152
+ For full **group rooms** (multi-peer E2E chat + room-scoped file sharing), use
153
+ `client.message.room(name)` — a shared-space handle exposing the group channel
154
+ (`connect`/`on`/`send`/`announce`/`peers`/`sendRaw`) plus
155
+ `putFile`/`getFile`. `subscribe`/`publish`/`send` are conveniences layered over
156
+ this same room primitive.
157
+
158
+ Auth lifecycle:
159
+
160
+ ```typescript
161
+ await client.auth.zk.register({ username, password, email }); // signs in by default
162
+ await client.auth.zk.restore(); // re-validate a persisted token on boot
163
+ await client.auth.zk.unlock(password); // re-derive identity for a restored session
164
+ await client.auth.zk.logout();
165
+ client.user; // { username, commitment } | null
166
+ ```
167
+
168
+ Two credentials ride on every request: the app key (`X-Muhkoo-Key`) and, once
169
+ signed in, the user session token (`X-Muhkoo-Session`). The accelerator
170
+ validates the token and authorizes per-user spaces without a fresh proof per
171
+ call. Persist the token across reloads with a custom `sessionStore` (e.g. a
172
+ `localStorage`-backed one); identity material is never persisted — `unlock()`
173
+ re-derives it from the password when you need to decrypt or message.
174
+
175
+ > The sections below document the lower-level building blocks the client is
176
+ > composed from (`BroadcastChannel`, `EncryptedSession`, `PersonalSpaceClient`,
177
+ > the Groth16 verifier, …). Reach for them directly only when you need control
178
+ > the client doesn't expose.
179
+
180
+ ### Multi-peer encrypted room (chat-style)
181
+
182
+ ```typescript
183
+ import { BroadcastChannel, BroadcastChannelEvents } from "@muhkoo/connect";
184
+
185
+ const channel = new BroadcastChannel({
186
+ url: "wss://accelerator.example.dev/room/abc",
187
+ myId: "alice@example.dev",
188
+ });
189
+
190
+ channel.on(BroadcastChannelEvents.MESSAGE, (e) => {
191
+ const { from, text } = e.detail;
192
+ console.log(`${from}: ${text}`);
193
+ });
194
+
195
+ channel.on(BroadcastChannelEvents.PEER_HANDSHAKE, (e) => {
196
+ console.log("new peer ready:", e.detail.peerId);
197
+ });
198
+
199
+ await channel.connect(); // generates keys + opens WS
200
+ await channel.announce(); // broadcast our keyExchange (re-fires on reconnect)
201
+ await channel.send("hello room"); // fan-out one cipherMessage per peer
202
+ ```
203
+
204
+ The wire protocol is JSON frames: `{ keyExchange: ... }`, `{ cipherMessage: ... }`,
205
+ plus arbitrary frames you ship with `channel.sendRaw(obj)` and receive via the
206
+ `RAW_FRAME` event.
207
+
208
+ ### Bring-your-own-transport encrypted session
209
+
210
+ ```typescript
211
+ import { EncryptedSession } from "@muhkoo/connect";
212
+
213
+ const session = new EncryptedSession({ myId: "alice" });
214
+ await session.initialize();
215
+
216
+ // outbound handshake
217
+ const kx = await session.getOwnKeyExchange();
218
+ yourTransport.send(JSON.stringify(kx));
219
+
220
+ // inbound frame
221
+ const result = await session.receive(JSON.parse(rawFrame));
222
+ if (result.kind === "plaintext") {
223
+ console.log("from", result.from, ":", result.text);
224
+ } else if (result.kind === "handshake" && result.outbound) {
225
+ yourTransport.send(JSON.stringify(result.outbound)); // reciprocate
226
+ }
227
+
228
+ // outbound message
229
+ const frames = await session.encrypt("hi");
230
+ for (const f of frames) yourTransport.send(JSON.stringify(f));
231
+ ```
232
+
233
+ `EncryptedSession` assigns roles deterministically with
234
+ `isClient = (myId < peerId)` lexicographically and dedups handshake
235
+ reciprocation with an internal `sentHandshakeTo` set.
236
+
237
+ ### ZK-gated personal storage
238
+
239
+ ```typescript
240
+ import {
241
+ PersonalSpaceClient,
242
+ wrapWithPassphrase,
243
+ unwrapWithPassphrase,
244
+ } from "@muhkoo/connect";
245
+
246
+ const client = new PersonalSpaceClient({
247
+ baseUrl: "https://accelerator.example.dev",
248
+ commitment, secret, salt, ecdsaPub, ecdsaPubHash, // decimal BigInt strings
249
+ circuits: {
250
+ wasmUrl: "/circuits/build/preimagePoK_js/preimagePoK.wasm",
251
+ zkeyUrl: "/circuits/build/preimagePoK_0001.zkey",
252
+ },
253
+ });
254
+
255
+ // Each call: POST /challenge -> reduce nonce -> snarkjs.groth16.fullProve -> POST gated endpoint
256
+ const wrapped = await wrapWithPassphrase("hunter2", new TextEncoder().encode("secret"));
257
+ await client.put("notes", wrapped);
258
+
259
+ const got = await client.get<typeof wrapped>("notes");
260
+ const plaintext = await unwrapWithPassphrase("hunter2", got!);
261
+ ```
262
+
263
+ Identity derivation (deriving `commitment / secret / salt / ecdsaPub / ecdsaPubHash`
264
+ from a username + password) lives on the consumer side. The accelerator's chat
265
+ app uses PBKDF2(password, "muhkoo-zk-v1:" + username) -> 32-byte seed ->
266
+ HKDF-Expand for each material -> P-256 via `@noble/curves` -> Poseidon
267
+ commitment.
268
+
269
+ ### Universal Groth16 verification (Workers / Node / browser)
270
+
271
+ ```typescript
272
+ import {
273
+ initBn128Wasm,
274
+ verifyGroth16,
275
+ PREIMAGE_POK_VERIFICATION_KEY,
276
+ } from "@muhkoo/connect";
277
+
278
+ const { instance, memory, initialPFree } = await initBn128Wasm();
279
+ const ok = await verifyGroth16(
280
+ instance, memory, initialPFree,
281
+ PREIMAGE_POK_VERIFICATION_KEY,
282
+ proof, publicSignals,
283
+ );
284
+ ```
285
+
286
+ Initialize once at boot. For CF Workers, pass a pre-compiled
287
+ `WebAssembly.Module` to skip runtime compilation:
288
+ `await initBn128Wasm(myWasmModule)`.
289
+
290
+ ## KeyStore
291
+
292
+ Singleton store for ECDH + ECDSA P-384 keypairs keyed by identity ID.
293
+
294
+ ```typescript
295
+ import { KeyStore } from "@muhkoo/connect";
296
+
297
+ const ks = KeyStore.getInstance();
298
+ await ks.generateOwnKeyPair("alice"); // ECDH + ECDSA pair
299
+ const dehydrated = await ks.dehydrateKeyPair("alice"); // JWK -> string fields
300
+ await ks.hydrateKeyPair("bob", dehydrated);
301
+
302
+ await ks.storeRemotePublicKeys("carol", carolEcdhPub, carolEcdsaPub);
303
+ const packed = await ks.packDehydratedKeys("alice"); // base64 blob
304
+ await ks.hydrateFromPacked("alice", packed);
305
+ ```
306
+
307
+ Dehydrated form is JWK-based; the personal-space wrap/unwrap helpers above are
308
+ how you'd persist the packed blob (passphrase-wrapped) into the accelerator's
309
+ ZK-gated KV layer.
310
+
311
+ ## Repo layout
312
+
313
+ ```
314
+ src/
315
+ browser/ rollup entrypoint for the browser build
316
+ server/ rollup entrypoint for the server / default build
317
+ workers/ rollup entrypoint for the workers build (+ groth16-verifier + bundled bn128.wasm)
318
+ crypto/ KeyStore, DoubleRatchet, DoubleRatchetManager, Authenticator, ZeroKnowledge
319
+ sessions/ EncryptedSession, BroadcastChannel
320
+ transport/ WSTransport
321
+ personal/ PersonalSpaceClient + wrap.ts (excluded from workers build)
322
+ events/ EventCore + EventCoreEvents
323
+ messaging/ Message, Packet, decorators
324
+ types/ shared type defs (incl. zk.ts with PREIMAGE_POK_VERIFICATION_KEY)
325
+ network/ legacy Network class (not exported from any build)
326
+ storage/ Reed-Solomon Storage (not exported from any build)
327
+ utilities/ Logger, base58, ID generation, decorators
328
+ ```
329
+
330
+ ## Scripts
331
+
332
+ ```bash
333
+ yarn build # rm -rf dist && yarn rollup:browser && yarn rollup:server && yarn rollup:workers
334
+ yarn dev # all three targets in watch mode (concurrently)
335
+ yarn rollup:browser # one-off browser build
336
+ yarn rollup:server # one-off server build
337
+ yarn rollup:workers # one-off workers build
338
+ yarn build:dts # complete .d.ts bundle -> dist/connect.d.ts
339
+ yarn build:docs # typedoc -> docs/
340
+
341
+ yarn test # vitest
342
+ yarn test:unit # vitest --run (no integration tests)
343
+ yarn test:integration # TEST_TYPE=integration vitest --run tests/integration
344
+ yarn lint # eslint ./src
345
+ ```
346
+
347
+ ## Compatibility
348
+
349
+ - Node >= 20
350
+ - All three builds rely on globalThis-level WebCrypto (`crypto.subtle`,
351
+ `crypto.getRandomValues`), available natively in modern browsers, Node 16+,
352
+ Bun, Deno, and CF Workers.
353
+ - The bn128.wasm verifier needs runtime `WebAssembly.compile()` available
354
+ (true for all of the above; on CF Workers, pre-compile via wrangler's
355
+ `.wasm` import if you want to skip startup cost).
356
+
357
+ ## Related
358
+
359
+ - [`muhkoo/accelerator`](https://github.com/muhkoo/accelerator) — the CF
360
+ Workers + DOs backend this SDK targets, including the personal-space DO,
361
+ message-bus DO, and the bn128.wasm-driven `verifyZkAuthProof`.
362
+
363
+ ## License
364
+
365
+ GPL-3.0 — see `LICENSE`.