@muhkoo/connect 0.14.0-alpha.7 → 0.14.0-alpha.9

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.
@@ -0,0 +1,1188 @@
1
+ declare class Logger {
2
+ private prefix;
3
+ private logLevel;
4
+ private console;
5
+ private showCaller;
6
+ level: string;
7
+ constructor(prefix?: string, LOGLEVEL?: string, showCaller?: boolean);
8
+ private shouldLog;
9
+ /**
10
+ * Extract caller information from stack trace
11
+ */
12
+ private getCallerInfo;
13
+ /**
14
+ * Format caller info for display
15
+ */
16
+ private formatCallerInfo;
17
+ setLevel(level: string): void;
18
+ setShowCaller(show: boolean): void;
19
+ info(...args: any[]): void;
20
+ log(...args: any[]): void;
21
+ verbose(...args: any[]): void;
22
+ success(...args: any[]): void;
23
+ warn(...args: any[]): void;
24
+ error(...args: any[]): void;
25
+ debug(...args: any[]): void;
26
+ assert(condition?: boolean, ...data: any[]): void;
27
+ clear(): void;
28
+ count(label?: string): void;
29
+ countReset(label?: string): void;
30
+ dir(item?: any, options?: any): void;
31
+ dirxml(...data: any[]): void;
32
+ group(...data: any[]): void;
33
+ groupCollapsed(...data: any[]): void;
34
+ groupEnd(): void;
35
+ table(tabularData?: any, properties?: string[]): void;
36
+ time(label?: string): void;
37
+ timeEnd(label?: string): void;
38
+ timeLog(label?: string, ...data: any[]): void;
39
+ trace(...data: any[]): void;
40
+ profile(label?: string): void;
41
+ profileEnd(label?: string): void;
42
+ timeStamp(label?: string): void;
43
+ }
44
+
45
+ declare global {
46
+ interface Console {
47
+ success(...args: any[]): void;
48
+ verbose(...args: any[]): void;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * The SDK's process-global logger.
54
+ *
55
+ * Eight modules under `src/` read a bare `appLogger` off `globalThis` rather
56
+ * than importing one (`src/crypto/{KeyStore,DoubleRatchet,DoubleRatchetManager,
57
+ * Authenticator}.ts`, `src/core/Client.ts`,
58
+ * `src/core/namespaces/HostedAuth.ts`, `src/events/EventCore.ts`), so the
59
+ * binding has to exist before any of them runs, and the `declare global` below
60
+ * is what makes that typecheck.
61
+ *
62
+ * This module is the single place that bootstrap happens. It previously lived
63
+ * inline in three entry files — `src/browser/index.ts`, `src/workers/index.ts`,
64
+ * and `src/core/index.ts` (which `src/server/index.ts` picked up as an import
65
+ * side effect) — three copies that could drift.
66
+ *
67
+ * `??=` makes it idempotent, so importing it from several entries in one
68
+ * process is safe and the first writer wins.
69
+ *
70
+ * KNOWN HAZARD: package.json declares `"sideEffects": false`, which entitles a
71
+ * tree-shaker to drop a module imported purely for effect. The entries import
72
+ * this one for its `export`s as well, so it survives today — but the durable
73
+ * fix is to convert those eight bare reads into real imports of `appLogger`
74
+ * from here, which removes the reliance on a global entirely.
75
+ */
76
+
77
+ declare global {
78
+ var appLogger: InstanceType<typeof Logger>;
79
+ }
80
+
81
+ /**
82
+ * @public
83
+ * @remarks
84
+ * The body type is a generic type that can be any value. Internally we handle the serialization and deserialization of the body.
85
+ */
86
+ type MessageBody = any;
87
+ /**
88
+ * @public
89
+ * @remarks
90
+ * The message headers type is a simple record object that contains key value pairs. Think of these as metadata for the message, similar to HTTP headers.
91
+ */
92
+ type MessageHeaders = Map<string, string>;
93
+ /**
94
+ * @public
95
+ * @remarks
96
+ * The message options type is a record object that contains the options for the message.
97
+ */
98
+ interface MessageOptions {
99
+ id?: string;
100
+ body: MessageBody;
101
+ status?: "pending" | "processed" | "failed" | "delivered";
102
+ checksum?: string;
103
+ }
104
+ /**
105
+ * @public
106
+ * @remarks
107
+ * The message class is a simple class that represents a message object.
108
+ * @sealed
109
+ */
110
+ declare class Message {
111
+ id: string;
112
+ timestamp: number;
113
+ status?: "pending" | "processed" | "failed" | "delivered";
114
+ checksum?: string;
115
+ private _body;
116
+ constructor(msg: MessageBody);
117
+ constructor(msg: MessageOptions);
118
+ get body(): MessageBody;
119
+ set body(value: MessageBody);
120
+ verifyChecksum: () => void;
121
+ private validatebodySize;
122
+ serialize: () => string;
123
+ static deserialize: (data: string) => Message;
124
+ }
125
+
126
+ type Address = string;
127
+ interface PacketOptions {
128
+ id?: string;
129
+ subject: string;
130
+ source: Address;
131
+ target: Address;
132
+ headers?: {
133
+ [key: string]: string | number | boolean | undefined;
134
+ };
135
+ message?: Message;
136
+ ttl?: number;
137
+ timestamp?: number;
138
+ signature?: string;
139
+ }
140
+ /**
141
+ * @public
142
+ * @remarks
143
+ * This class represents a message object that can be sent over the network
144
+ * @sealed
145
+ */
146
+ declare class Packet {
147
+ id: string;
148
+ subject: string;
149
+ source: Address;
150
+ target: Address;
151
+ headers?: PacketOptions['headers'];
152
+ message?: Message;
153
+ timestamp: number;
154
+ ttl?: number;
155
+ signature?: string;
156
+ constructor({ id, subject, source, target, headers, message, ttl, signature, timestamp }: PacketOptions);
157
+ serialize(): string;
158
+ static deserialize(data: string): Packet;
159
+ isExpired(): boolean;
160
+ }
161
+
162
+ declare function SerializeMessage(target: any, propertyKey: string, descriptor: PropertyDescriptor): PropertyDescriptor;
163
+
164
+ declare const decorators: {
165
+ SerializeMessage: typeof SerializeMessage;
166
+ };
167
+
168
+ /**
169
+ * Shared cryptographic types for Connect and Accelerator
170
+ */
171
+ /**
172
+ * Supported ECDH curves
173
+ */
174
+ type ECDHCurve = 'P-256' | 'P-384';
175
+ /**
176
+ * JSON Web Key format for public keys
177
+ */
178
+ interface PublicKeyJWK {
179
+ kty: 'EC';
180
+ crv: 'P-256' | 'P-384';
181
+ x: string;
182
+ y: string;
183
+ ext?: boolean;
184
+ }
185
+ /**
186
+ * Keypair with dehydrated public key (JWK) and CryptoKey private key
187
+ */
188
+ interface Keypair {
189
+ publicKey: PublicKeyJWK;
190
+ privateKey: CryptoKey;
191
+ }
192
+ /**
193
+ * Dehydrated keypair (both keys as strings for transport/storage)
194
+ */
195
+ interface DehydratedKeypair {
196
+ publicKey: string;
197
+ privateKey: string;
198
+ }
199
+
200
+ /**
201
+ * Shared identity types for Connect and Accelerator
202
+ */
203
+ /**
204
+ * User identity - public key is the primary identifier
205
+ */
206
+ interface UserIdentity {
207
+ publicKey: string;
208
+ accountType: 'self-sovereign' | 'custodial';
209
+ did?: string;
210
+ provider?: string;
211
+ createdAt: number;
212
+ }
213
+ /**
214
+ * Session
215
+ */
216
+ interface Session {
217
+ sessionId: string;
218
+ publicKey: string;
219
+ appPublicKey: string;
220
+ createdAt: number;
221
+ expiresAt: number;
222
+ sharedSecret?: string;
223
+ }
224
+ /**
225
+ * Authentication types
226
+ */
227
+ type AuthType = 'self-sovereign' | 'custodial';
228
+ /**
229
+ * OAuth provider types
230
+ */
231
+ type OAuthProvider = 'google' | 'github' | 'discord' | 'twitter';
232
+
233
+ /**
234
+ * Shared permission types for Connect and Accelerator
235
+ */
236
+ /**
237
+ * Permission types for resources
238
+ */
239
+ type Permission = 'read' | 'write' | 'delete' | 'admin' | 'share' | 'invite';
240
+ /**
241
+ * Role types
242
+ */
243
+ type Role = 'owner' | 'admin' | 'editor' | 'viewer' | 'member';
244
+ /**
245
+ * ACL Entry - grants permissions to a user (identified by public key)
246
+ */
247
+ interface ACLEntry {
248
+ subject: string;
249
+ permissions: Permission[];
250
+ role?: Role;
251
+ expiresAt?: number;
252
+ conditions?: {
253
+ ipAllowlist?: string[];
254
+ ipBlocklist?: string[];
255
+ validAfter?: number;
256
+ validBefore?: number;
257
+ };
258
+ }
259
+ /**
260
+ * Access Control List for a resource
261
+ */
262
+ interface ACL {
263
+ resource: string;
264
+ entries: Record<string, ACLEntry>;
265
+ defaultPermissions?: Permission[];
266
+ publicPermissions?: Permission[];
267
+ inheritFromParent?: boolean;
268
+ createdAt: number;
269
+ updatedAt: number;
270
+ createdBy: string;
271
+ }
272
+ /**
273
+ * Participant in a shared space
274
+ */
275
+ interface Participant {
276
+ publicKey: string;
277
+ role: Role;
278
+ addedAt: number;
279
+ addedBy: string;
280
+ customPermissions?: Permission[];
281
+ }
282
+ /**
283
+ * Permission check request
284
+ */
285
+ interface PermissionCheckRequest {
286
+ publicKey: string;
287
+ resource: string;
288
+ requiredPermissions: Permission | Permission[];
289
+ context?: {
290
+ timestamp?: number;
291
+ ipAddress?: string;
292
+ };
293
+ }
294
+ /**
295
+ * Permission check result
296
+ */
297
+ interface PermissionCheckResult {
298
+ granted: boolean;
299
+ reason?: string;
300
+ grantedBy?: string;
301
+ permissions: Permission[];
302
+ }
303
+
304
+ /**
305
+ * Shared messaging types for Connect and Accelerator
306
+ */
307
+ /**
308
+ * Message event for pub/sub messaging
309
+ */
310
+ interface MessageEvent {
311
+ type: string;
312
+ topic: string;
313
+ data: any;
314
+ senderPublicKey?: string;
315
+ timestamp: number;
316
+ }
317
+ /**
318
+ * Message subscription
319
+ */
320
+ interface Subscription {
321
+ connectionId: string;
322
+ publicKey: string;
323
+ topic: string;
324
+ filters?: any;
325
+ }
326
+ /**
327
+ * WebSocket message types
328
+ */
329
+ type WSMessageType = 'connect' | 'connected' | 'subscribe' | 'subscribed' | 'unsubscribe' | 'publish' | 'event' | 'message' | 'error';
330
+ /**
331
+ * WebSocket message
332
+ */
333
+ interface WSMessage {
334
+ type: WSMessageType;
335
+ topic?: string;
336
+ data?: any;
337
+ publicKey?: string;
338
+ connectionId?: string;
339
+ timestamp?: number;
340
+ }
341
+
342
+ /**
343
+ * Shared zero-knowledge types and circuit constants.
344
+ *
345
+ * Used by both the browser/server builds (where snarkjs handles proof generation
346
+ * and verification) and the workers build (where verification runs through
347
+ * `bn128.wasm` via `src/workers/groth16-verifier.ts`). Keeping these here ensures
348
+ * proof shapes and verification keys stay identical across runtimes.
349
+ */
350
+ /**
351
+ * Groth16 proof structure (BN128).
352
+ *
353
+ * - `pi_a`: G1 point as `[x, y]` or projective `[x, y, z]`.
354
+ * - `pi_b`: G2 point as `[[x0, x1], [y0, y1]]` or projective `[..., [z0, z1]]`.
355
+ * - `pi_c`: G1 point as `[x, y]` or projective `[x, y, z]`.
356
+ *
357
+ * Field elements are decimal-string-encoded BigInts (snarkjs convention).
358
+ */
359
+ interface Groth16Proof {
360
+ pi_a: string[];
361
+ pi_b: string[][];
362
+ pi_c: string[];
363
+ protocol?: string;
364
+ curve?: string;
365
+ }
366
+ /**
367
+ * Groth16 verification key (BN128).
368
+ *
369
+ * Field elements are decimal-string-encoded BigInts (snarkjs convention).
370
+ */
371
+ interface VerificationKey {
372
+ protocol: string;
373
+ curve: string;
374
+ nPublic: number;
375
+ vk_alpha_1: string[];
376
+ vk_beta_2: string[][];
377
+ vk_gamma_2: string[][];
378
+ vk_delta_2: string[][];
379
+ vk_alphabeta_12: string[][][];
380
+ IC: string[][];
381
+ }
382
+ /**
383
+ * Verification key for the `preimagePoK` circuit (Poseidon-commitment
384
+ * proof-of-knowledge with three public signals: commitment, nonce, ecdsaPubHash).
385
+ *
386
+ * Pinned here so worker-side and SDK-side verification agree on the key being
387
+ * checked. If the circuit is recompiled, regenerate this constant and
388
+ * `public/circuits/build/preimagePoK_verification_key.json` together.
389
+ */
390
+ declare const PREIMAGE_POK_VERIFICATION_KEY: VerificationKey;
391
+
392
+ /**
393
+ * WebCrypto subtle-interface resolver.
394
+ *
395
+ * Available natively in every runtime this SDK targets (modern browsers,
396
+ * Node 16+, CF Workers). Centralized here so the runtime check + error
397
+ * message are consistent across every primitive — and so a future polyfill
398
+ * shim has one place to plug in.
399
+ */
400
+ declare function getSubtle(): SubtleCrypto;
401
+
402
+ /**
403
+ * Cryptographically secure random bytes.
404
+ *
405
+ * Wraps `crypto.getRandomValues` with two small affordances every caller
406
+ * needs:
407
+ * - Allocates over a fresh, non-shared `ArrayBuffer` so the returned
408
+ * `Uint8Array` satisfies WebCrypto's `BufferSource` type under strict
409
+ * TS lib settings (where `Uint8Array<ArrayBufferLike>` isn't directly
410
+ * assignable).
411
+ * - Chunks requests larger than 65 KiB, the per-call cap browsers enforce
412
+ * on `getRandomValues`.
413
+ */
414
+ declare function randomBytes(n: number): Uint8Array;
415
+
416
+ /**
417
+ * AES-256-GCM encryption / decryption with raw key bytes.
418
+ *
419
+ * The two functions here are the single canonical AES-GCM call site in the
420
+ * SDK. Earlier copies of this logic existed inline in `DoubleRatchet`, in the
421
+ * storage `ChunkCipher`, and in the passphrase-wrap helpers — they all now
422
+ * route through here so the algorithm, IV size, and tag handling stay in one
423
+ * place.
424
+ *
425
+ * Conventions:
426
+ * - 256-bit keys only. Passing 16- or 24-byte keys throws; callers that
427
+ * need AES-128 should add a dedicated primitive.
428
+ * - 96-bit IVs. WebCrypto's recommended GCM IV length; trades the 4-byte
429
+ * IV economy of 128-bit for compatibility with every spec-compliant
430
+ * implementation.
431
+ * - The 16-byte GCM authentication tag is appended to the ciphertext by
432
+ * WebCrypto's default behavior. Callers don't manage it separately.
433
+ *
434
+ * If a future need arises for AAD (associated data), add an optional `aad`
435
+ * parameter to both functions rather than introducing a parallel primitive.
436
+ */
437
+ /** Length of the AES key in bytes. */
438
+ declare const AES_GCM_KEY_BYTES = 32;
439
+ /** Length of the AES-GCM IV in bytes (WebCrypto-recommended). */
440
+ declare const AES_GCM_IV_BYTES = 12;
441
+ /** Length of the AES-GCM authentication tag in bytes (appended to ciphertext). */
442
+ declare const AES_GCM_TAG_BYTES = 16;
443
+ /**
444
+ * Encrypt `plaintext` under a 32-byte AES-256 key with the supplied 12-byte
445
+ * IV. Returns ciphertext with the 16-byte GCM tag appended (so the output
446
+ * length is `plaintext.length + 16`).
447
+ *
448
+ * The IV must be unique per (key, message). Callers that generate the IV
449
+ * randomly should grab it via `randomBytes(AES_GCM_IV_BYTES)`; callers
450
+ * managing a counter pass their own.
451
+ */
452
+ declare function encryptAesGcm(key: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Promise<Uint8Array>;
453
+ /**
454
+ * Inverse of {@link encryptAesGcm}. Throws if the GCM auth tag fails — this
455
+ * captures "wrong key", "wrong IV", or "ciphertext was tampered with",
456
+ * indistinguishably. Callers that care about distinguishing those should
457
+ * keep separate provenance bookkeeping, not invent a parallel MAC.
458
+ */
459
+ declare function decryptAesGcm(key: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Promise<Uint8Array>;
460
+
461
+ /**
462
+ * Key-derivation primitives — PBKDF2 (for passphrase → key) and HKDF (for
463
+ * symmetric key material → expanded key material).
464
+ *
465
+ * PBKDF2 is the slow, salted KDF the passphrase-wrap helper uses to harden
466
+ * user passwords against offline guessing. HKDF is the fast, deterministic
467
+ * KDF the Double Ratchet uses to derive root + chain + message keys from
468
+ * shared secrets.
469
+ *
470
+ * Both are SHA-256-backed everywhere in this codebase — keep it that way
471
+ * unless there's a concrete reason to diverge.
472
+ */
473
+ /**
474
+ * Iteration count default for PBKDF2 passphrase hardening. Tuned for
475
+ * 2024–2026 hardware: slow enough to deter offline guessing of weak
476
+ * passphrases (~100ms on commodity laptops), fast enough that interactive
477
+ * unlock stays sub-second.
478
+ */
479
+ declare const PBKDF2_DEFAULT_ITERATIONS = 200000;
480
+ /**
481
+ * Derive a 256-bit AES-GCM key from `passphrase + salt` via PBKDF2-SHA256.
482
+ *
483
+ * Returned as raw bytes (not a `CryptoKey`) so callers can hand it straight
484
+ * to {@link encryptAesGcm} — that's the convention everywhere else in the
485
+ * primitive layer. If a future caller needs the non-extractable
486
+ * `CryptoKey` form, add a parallel `deriveCryptoKeyFromPassphrase` variant
487
+ * rather than overloading this one.
488
+ */
489
+ declare function deriveAesKeyFromPassphrase(passphrase: string, salt: Uint8Array, iterations?: number): Promise<Uint8Array>;
490
+ /**
491
+ * HKDF-Expand with SHA-256, returning `lengthBytes` raw bytes of key material.
492
+ *
493
+ * `info` is the per-purpose label (e.g. `"DoubleRatchetMsg"`). Optional
494
+ * `salt` defaults to an empty buffer, matching the way the Double Ratchet
495
+ * code has always used it.
496
+ *
497
+ * Note: this is intentionally a one-shot Extract-then-Expand call rather than
498
+ * exposing Extract and Expand separately — every call site in this codebase
499
+ * wants the same shape, and a richer API can be added later if needed.
500
+ */
501
+ declare function deriveBitsHkdf(inputKeyMaterial: Uint8Array, info: string, lengthBytes: number, salt?: Uint8Array): Promise<Uint8Array>;
502
+
503
+ interface KeyPair {
504
+ privateKey: CryptoKey | null;
505
+ publicKey: CryptoKey;
506
+ }
507
+ interface AuthKeyPair {
508
+ privateKey: CryptoKey | null;
509
+ publicKey: CryptoKey;
510
+ }
511
+ interface DehydratedKeys {
512
+ ecdhPub: string;
513
+ ecdhPriv: string;
514
+ ecdsaPub: string;
515
+ ecdsaPriv: string;
516
+ }
517
+ declare class KeyStore {
518
+ private static instance;
519
+ private _keys;
520
+ private _authKeys;
521
+ private constructor();
522
+ static getInstance(): KeyStore;
523
+ get keys(): Map<string, KeyPair>;
524
+ get authKeys(): Map<string, AuthKeyPair>;
525
+ generateOwnKeyPair(id: string): Promise<KeyPair>;
526
+ storeRemotePublicKeys(id: string, ecdhPublicKey: CryptoKey, ecdsaPublicKey: CryptoKey): Promise<void>;
527
+ hydrateKeyPair(id: string, dehydrated: DehydratedKeys): Promise<void>;
528
+ getKeyPair(id: string): KeyPair | null;
529
+ getAuthKeyPair(id: string): AuthKeyPair | null;
530
+ dehydrateKeyPair(id: string): Promise<DehydratedKeys>;
531
+ getRawEcdsaPublicKey(id: string): Promise<Uint8Array | null>;
532
+ packDehydratedKeys(id: string): Promise<string>;
533
+ hydrateFromPacked(id: string, masterKey: string): Promise<void>;
534
+ }
535
+
536
+ interface CipherMessageHeader {
537
+ dhPub: string;
538
+ prevChainLength: number;
539
+ messageNumber: number;
540
+ sessionId: string;
541
+ sessionType: 'global' | 'specific';
542
+ senderId: string;
543
+ recipientId: string;
544
+ timestamp: number;
545
+ signature: string;
546
+ }
547
+
548
+ interface CipherMessage {
549
+ header: CipherMessageHeader;
550
+ ciphertext: string; // Hex-encoded
551
+ nonce: string; // Hex-encoded
552
+ }
553
+
554
+ // interface RatchetState {
555
+ // clientDhPriv: Uint8Array;
556
+ // clientDhPub: Uint8Array;
557
+ // serverDhPriv: Uint8Array;
558
+ // serverDhPub: Uint8Array;
559
+ // rootKey: Uint8Array | null;
560
+ // sendChainKey: Uint8Array | null;
561
+ // recvChainKey: Uint8Array | null;
562
+ // sendCount: number;
563
+ // recvCount: number;
564
+ // prevChainLength: number;
565
+ // skippedKeys: Map<number, Uint8Array>;
566
+ // }
567
+
568
+ // interface RatchetState {
569
+ // clientDhPriv: CryptoKey | null; // ECDH private key
570
+ // clientDhPub: CryptoKey; //
571
+ // serverDhPriv: CryptoKey | null; // ECDH private key
572
+ // serverDhPub: CryptoKey; // ECDH public key
573
+ // rootKey: CryptoKey | null; // AES-GCM key
574
+ // sendChainKey: CryptoKey | null; // AES-GCM key for sending
575
+ // recvChainKey: CryptoKey | null; // AES-GCM key for receiving
576
+ // sendCount: number; // Number of messages sent
577
+ // recvCount: number; // Number of messages received
578
+ // prevChainLength: number; // Length of the previous chain
579
+ // skippedKeys: Map<number, CryptoKey>; // Skipped keys for messages
580
+ // }
581
+
582
+ interface RatchetState {
583
+ clientDhPriv: CryptoKey | null;
584
+ clientDhPub: CryptoKey ;
585
+ serverDhPriv: CryptoKey | null;
586
+ serverDhPub: CryptoKey ;
587
+ // Symmetric key material (root key + chain keys + skipped message keys).
588
+ // Stored as Uint8Array rather than Node's Buffer so this type works in the
589
+ // browser/Workers builds.
590
+ rootKey: Uint8Array | null;
591
+ sendChainKey: Uint8Array | null;
592
+ recvChainKey: Uint8Array | null;
593
+ sendCount: number;
594
+ recvCount: number;
595
+ prevChainLength: number;
596
+ currentSkippedKeys: Map<number, Uint8Array>;
597
+ oldSkippedMessageKeys: Map<string, { skips: Map<number, Uint8Array>; created: number }>;
598
+ }
599
+
600
+ declare class DoubleRatchet {
601
+ private state;
602
+ private sessionType;
603
+ private maxSkip;
604
+ private overlapPeriod;
605
+ private windowSize;
606
+ constructor(senderId: string, recipientId: string, sessionType: 'global' | 'specific', isClient?: boolean);
607
+ private hkdf;
608
+ initializeSession(isClient?: boolean): Promise<void>;
609
+ private dhRatchet;
610
+ private symmetricRatchet;
611
+ encrypt(plaintext: string, newDhKey: boolean | undefined, senderId: string, recipientId: string, sessionId: string, sessionType: 'global' | 'specific'): Promise<CipherMessage>;
612
+ private cleanOldSkippedKeys;
613
+ decrypt(message: CipherMessage, isClient?: boolean): Promise<string>;
614
+ getState(): RatchetState;
615
+ setState(state: RatchetState): void;
616
+ }
617
+
618
+ /**
619
+ * Per-chunk AES-256-GCM encryption with random keys and IVs.
620
+ *
621
+ * Each chunk of a stored file gets its own freshly generated 256-bit AES key
622
+ * and 96-bit IV. The (key, IV) pair lives in the file manifest, which the
623
+ * application is responsible for storing under whatever access-control regime
624
+ * it uses (in this codebase: the `SharedSpaceClient` writes manifests into
625
+ * ZK-gated multi-user spaces; the shard store itself is open).
626
+ *
627
+ * Per-chunk keys (rather than a per-file DEK) lets future versions revoke or
628
+ * re-share individual chunks without re-encrypting the whole file. The
629
+ * additional manifest size is dominated by the shard hash list anyway.
630
+ *
631
+ * The cipher itself delegates to the canonical `encryptAesGcm` / `decryptAesGcm`
632
+ * primitives — this class only wraps them with key-material generation and
633
+ * the JSON-friendly base64 serialization the manifest needs.
634
+ */
635
+ /**
636
+ * Per-chunk key material, base64-encoded for JSON-friendly storage in the
637
+ * manifest. The IV is unique per chunk; the key is unique per chunk too in
638
+ * this codebase, though that's a stricter property than AES-GCM strictly
639
+ * requires (which is "unique IV per key").
640
+ */
641
+ interface ChunkKeyMaterial {
642
+ /** Base64-encoded 32-byte AES-256 key. */
643
+ key: string;
644
+ /** Base64-encoded 12-byte AES-GCM IV. */
645
+ iv: string;
646
+ }
647
+ declare class ChunkCipher {
648
+ /**
649
+ * Generate a fresh random (key, iv) pair encoded for the manifest.
650
+ * Callers should generate one of these per chunk before calling
651
+ * {@link encryptChunk}.
652
+ */
653
+ generateKeyMaterial(): ChunkKeyMaterial;
654
+ /**
655
+ * Encrypt `plaintext` under the supplied key + IV. Returns the AES-GCM
656
+ * ciphertext with the 16-byte auth tag appended (WebCrypto's default
657
+ * GCM behavior).
658
+ */
659
+ encryptChunk(plaintext: Uint8Array, material: ChunkKeyMaterial): Promise<Uint8Array>;
660
+ /**
661
+ * Inverse of {@link encryptChunk}. Throws if the auth tag fails — that
662
+ * means the ciphertext was tampered with, the key/iv was wrong, or the
663
+ * shards were reassembled incorrectly.
664
+ */
665
+ decryptChunk(ciphertext: Uint8Array, material: ChunkKeyMaterial): Promise<Uint8Array>;
666
+ }
667
+
668
+ /**
669
+ * Passphrase-based wrap / unwrap helpers using WebCrypto.
670
+ *
671
+ * Encrypt a payload with a user-supplied passphrase before persisting it
672
+ * somewhere the server can see (e.g. the manifest blob in a ZK-gated
673
+ * SharedSpace, or chat-key material in a PersonalSpace). The server never
674
+ * sees the passphrase or the plaintext — both wrap and unwrap run entirely
675
+ * client-side.
676
+ *
677
+ * Algorithm (the only one this codebase has ever shipped):
678
+ * - PBKDF2-SHA256 with 200_000 iterations derives a 256-bit AES key from
679
+ * `passphrase || salt`.
680
+ * - AES-256-GCM encrypts the plaintext with a fresh random 12-byte IV.
681
+ * - The auth tag is appended to the ciphertext by WebCrypto's default
682
+ * GCM behaviour, so {@link WrappedPayload.ciphertext} carries it implicitly.
683
+ *
684
+ * The returned {@link WrappedPayload} is JSON-friendly (all base64 fields), so
685
+ * it can be `JSON.stringify`-ed straight into a KV layer.
686
+ */
687
+ /**
688
+ * The persisted, JSON-friendly representation of a passphrase-wrapped blob.
689
+ *
690
+ * The `alg` field is a self-describing marker — if we ever rotate KDF/cipher,
691
+ * unwrap can switch on this value to keep older payloads readable.
692
+ */
693
+ interface WrappedPayload {
694
+ /** Random per-wrap salt for PBKDF2, base64. */
695
+ salt: string;
696
+ /** AES-GCM IV, base64. */
697
+ iv: string;
698
+ /** AES-GCM ciphertext (includes 16-byte GCM auth tag), base64. */
699
+ ciphertext: string;
700
+ /** Algorithm marker so future readers know what they're looking at. */
701
+ alg: "PBKDF2-SHA256/AES-256-GCM";
702
+ /** PBKDF2 iteration count used. */
703
+ iter: number;
704
+ }
705
+ /**
706
+ * Encrypt `plaintext` under a key derived from `passphrase`. The returned
707
+ * payload bundles the salt, IV, and ciphertext so unwrap only needs the
708
+ * passphrase to reverse the operation.
709
+ */
710
+ declare function wrapWithPassphrase(passphrase: string, plaintext: Uint8Array): Promise<WrappedPayload>;
711
+ /**
712
+ * Decrypt a {@link WrappedPayload} produced by {@link wrapWithPassphrase}.
713
+ * Throws if the passphrase is wrong, the payload is tampered with, or the
714
+ * algorithm marker is unrecognised.
715
+ */
716
+ declare function unwrapWithPassphrase(passphrase: string, wrapped: WrappedPayload): Promise<Uint8Array>;
717
+
718
+ declare enum EventCoreEvents {
719
+ CONNECTED = "connected",
720
+ DISCONNECTED = "disconnected",
721
+ RECONNECTING = "reconnecting",
722
+ ERROR = "error",
723
+ DATA_RECEIVED = "data_received",
724
+ DATA_SENT = "data_sent",
725
+ MESSAGE = "message",
726
+ GET_HISTORY = "get_history",
727
+ RECEIVED_HISTORY = "received_history",
728
+ ONLINE = "online",
729
+ OFFLINE = "offline",
730
+ SYNCING = "syncing",
731
+ SYNC_PROGRESS = "sync_progress",
732
+ SYNC_COMPLETE = "sync_complete",
733
+ SYNC_ERROR = "sync_error"
734
+ }
735
+ /**
736
+ * @public
737
+ * EventCore is a static event emitter with singleton functionality for global event handling.
738
+ */
739
+ declare class EventCore {
740
+ private static eventTarget;
741
+ /**
742
+ * `event -> (caller's handler -> the wrapper actually registered on the
743
+ * EventTarget)`. Two levels because one event can have many listeners and
744
+ * {@link off} must remove exactly the wrapper it registered for that caller.
745
+ */
746
+ private static events;
747
+ /**
748
+ * @remarks Listen to an event.
749
+ * @example EventCore.on('event', (e) =\> appLogger.debug(e));
750
+ */
751
+ static on(event: EventCoreEvents, handler: EventListener | CallableFunction): void;
752
+ /**
753
+ * @remarks Stop listening to an event.
754
+ * @example EventCore.off('event', handler);
755
+ */
756
+ static off(event: EventCoreEvents, handler: EventListener | CallableFunction): void;
757
+ /**
758
+ * @remarks Emit an event.
759
+ * @example EventCore.emit('event', data);
760
+ */
761
+ static emit(event: EventCoreEvents, data: any): void;
762
+ }
763
+
764
+ /**
765
+ * An end-to-end-encrypted communication session for one local identity
766
+ * (`myId`) talking to an arbitrary set of peers. Wraps `KeyStore` +
767
+ * per-pair `DoubleRatchet` instances and owns the protocol bookkeeping
768
+ * (handshake reciprocation, dedup, role assignment, broadcast fan-out,
769
+ * recipient filtering) so application code doesn't have to.
770
+ *
771
+ * Transport-agnostic by design: the Session never touches a WebSocket
772
+ * (or HTTP, or anything else). Application code:
773
+ * - calls `session.encrypt(text)` and gets back an array of frames it
774
+ * ships over whatever transport it has;
775
+ * - calls `session.receive(frame)` with each inbound frame and acts on
776
+ * the typed result (plaintext, handshake-done, ignored).
777
+ *
778
+ * Frames are plain JSON objects with the same shape the existing chat
779
+ * protocol uses (`{ keyExchange: {...} }` and `{ cipherMessage: {...} }`),
780
+ * so this can drop into a chat app without changing the wire format.
781
+ */
782
+
783
+ interface SessionOptions {
784
+ /** This client's identifier (e.g. username / DID / public-key string). */
785
+ myId: string;
786
+ }
787
+ /** A handshake frame the application ships to peers (or receives from them). */
788
+ interface KeyExchangeFrame {
789
+ type: "handshake" | "update";
790
+ userId: string;
791
+ /** Base64-encoded JWK of the sender's ECDH public key. */
792
+ ecdhPublicKey: string;
793
+ /** Base64-encoded JWK of the sender's ECDSA public key. */
794
+ ecdsaPublicKey: string;
795
+ }
796
+ /** A ciphertext frame, addressed to exactly one peer. */
797
+ interface CipherFrame {
798
+ cipherMessage: CipherMessage;
799
+ }
800
+ /** Anything inbound the application might hand to `session.receive()`. */
801
+ type IncomingFrame = {
802
+ keyExchange: KeyExchangeFrame;
803
+ } | {
804
+ cipherMessage: CipherMessage;
805
+ } | Record<string, unknown>;
806
+ /** Outcome of feeding one frame through `session.receive()`. */
807
+ type ReceiveResult = {
808
+ kind: "plaintext";
809
+ /** The sender's id (from the cipherMessage header). */
810
+ from: string;
811
+ /** Decrypted message body. */
812
+ text: string;
813
+ /** The original cipherMessage, for callers that want it. */
814
+ cipherMessage: CipherMessage;
815
+ } | {
816
+ kind: "handshake";
817
+ /** The peer whose handshake was just processed. */
818
+ peerId: string;
819
+ /**
820
+ * If non-null, the application MUST send this frame back to the peer
821
+ * (e.g. broadcast on the room WebSocket). It's our reciprocation —
822
+ * giving the peer our public keys so they can verify our messages.
823
+ *
824
+ * The Session dedups internally: we only emit this on the first
825
+ * handshake we process for a given peer in this Session's lifetime.
826
+ */
827
+ outbound: {
828
+ keyExchange: KeyExchangeFrame;
829
+ } | null;
830
+ } | {
831
+ kind: "ignored";
832
+ /** Why we ignored the frame (debugging aid; not for branching on). */
833
+ reason: string;
834
+ };
835
+ declare class EncryptedSession {
836
+ private readonly myId;
837
+ private readonly keyStore;
838
+ /** One DoubleRatchet per peer pair. */
839
+ private readonly ratchets;
840
+ /** Peers we've already sent our keyExchange to in this Session. */
841
+ private readonly sentHandshakeTo;
842
+ /** Resolved when own keys are generated and ready to use. */
843
+ private readonly _ready;
844
+ private _resolveReady;
845
+ private _initialized;
846
+ constructor(opts: SessionOptions);
847
+ /** A promise that resolves once `initialize()` has completed. */
848
+ get ready(): Promise<void>;
849
+ /** Our local identifier. */
850
+ get id(): string;
851
+ /**
852
+ * Generate this client's ECDH + ECDSA keypair (idempotent — safe to call
853
+ * multiple times across reconnects; only the first call generates).
854
+ */
855
+ initialize(): Promise<void>;
856
+ /**
857
+ * Build the keyExchange frame this client should send to peers when it
858
+ * comes online. Application is responsible for actually transmitting it
859
+ * (e.g. ws.send(JSON.stringify(frame))).
860
+ */
861
+ getOwnKeyExchange(): Promise<{
862
+ keyExchange: KeyExchangeFrame;
863
+ }>;
864
+ /**
865
+ * Encrypt a plaintext to every peer we currently have a ratchet for, and
866
+ * return one cipherMessage frame per recipient. The application sends
867
+ * each frame over its transport.
868
+ *
869
+ * If there are no peers yet (or all peers are still mid-handshake), the
870
+ * returned array may be empty — the caller should typically render the
871
+ * outgoing message locally in that case (since there's no one to send
872
+ * it to yet).
873
+ */
874
+ encrypt(plaintext: string): Promise<CipherFrame[]>;
875
+ /**
876
+ * Hand an inbound frame to the session. Returns a typed result the
877
+ * application acts on.
878
+ *
879
+ * - `kind: 'plaintext'` — decrypted; render `result.text`
880
+ * - `kind: 'handshake'` — a peer's keys are now stored, and if
881
+ * `result.outbound` is set, send it
882
+ * - `kind: 'ignored'` — not for us, malformed, duplicate, etc.
883
+ *
884
+ * Calling code should always handle `'ignored'` (no UI side effect).
885
+ */
886
+ receive(frame: IncomingFrame): Promise<ReceiveResult>;
887
+ /** List of peer ids we have an established ratchet for. */
888
+ peers(): string[];
889
+ /** Has the handshake completed for this peer? */
890
+ hasRatchetFor(peerId: string): boolean;
891
+ /**
892
+ * Tear down a peer's ratchet (e.g. when they leave the room). The peer's
893
+ * stored keys remain in `KeyStore` so a fresh handshake can be set up
894
+ * later without re-fetching them, but the ratchet state is dropped.
895
+ */
896
+ forgetPeer(peerId: string): void;
897
+ /** Deterministic per-pair session id — same string on both sides. */
898
+ private _sessionIdFor;
899
+ }
900
+
901
+ /**
902
+ * Pure WebSocket transport — owns the socket lifecycle and nothing else.
903
+ *
904
+ * Hands raw frame strings up (via the `MESSAGE` event) and accepts raw frame
905
+ * strings down (via `send`). Anything frame-shape, encryption, or serialization
906
+ * related belongs in the layer above (e.g. `BroadcastChannel` for client↔server
907
+ * encrypted pipes, `BroadcastChannel` for multi-peer E2E rooms).
908
+ *
909
+ * Responsibilities:
910
+ * - Open / close the WebSocket
911
+ * - Auto-reconnect with linear backoff after an unexpected close
912
+ * - Buffer outbound frames while disconnected (capped queue)
913
+ * - Emit lifecycle events (CONNECTED / DISCONNECTED / RECONNECTING / ERROR)
914
+ * plus raw inbound MESSAGE events
915
+ */
916
+
917
+ interface WSTransportOptions {
918
+ /** WebSocket URL, e.g. `ws://localhost:8787/foo`. */
919
+ url: string;
920
+ /**
921
+ * Optional async provider for a FRESH connection URL, called before each
922
+ * RECONNECT. Use it when the URL carries a single-use/expiring credential
923
+ * (e.g. a one-time WS upgrade ticket): on reconnect the baked-in ticket is
924
+ * already consumed/expired, so without this the reconnect is rejected and the
925
+ * transport burns through its attempts. The initial connect uses `url` as-is.
926
+ */
927
+ urlProvider?: () => string | Promise<string>;
928
+ /**
929
+ * Stable identifier for this connection (a channel / space / personal-space
930
+ * id). Lifecycle + MESSAGE events are namespaced by it on the shared
931
+ * `EventCore` bus so concurrent transports (chat room A, room B, the storage
932
+ * feed, …) never receive each other's frames. Optional — a unique id is
933
+ * generated when omitted; pass a semantic one for clearer debugging.
934
+ */
935
+ id?: string;
936
+ /** Reconnect automatically after unexpected disconnect (default `true`). */
937
+ autoReconnect?: boolean;
938
+ /** Delay between reconnect attempts in ms (default `3000`). */
939
+ reconnectDelay?: number;
940
+ /** Cap on reconnect attempts (default `5`; `0` means unlimited). */
941
+ maxReconnectAttempts?: number;
942
+ /**
943
+ * Max outbound frames buffered while disconnected. Sending past this caps
944
+ * throws synchronously. Default `100`.
945
+ */
946
+ maxQueueSize?: number;
947
+ /**
948
+ * Heartbeat interval in ms. While connected, a `pingFrame` is sent every
949
+ * interval to keep the socket warm (long-lived WebSockets get dropped by CF
950
+ * / intermediaries when idle) and to detect a silently-dead connection.
951
+ * Default `30000`. Set `0` to disable the heartbeat entirely.
952
+ */
953
+ heartbeatInterval?: number;
954
+ /**
955
+ * How long (ms) to wait for a `pongFrame` after sending a ping before
956
+ * treating the connection as dead and forcing a reconnect. Should be well
957
+ * under `heartbeatInterval`. Default `10000`.
958
+ */
959
+ heartbeatTimeout?: number;
960
+ /** Outbound keep-alive frame. Default `"ping"`. */
961
+ pingFrame?: string;
962
+ /**
963
+ * Inbound keep-alive ack. Matching frames are swallowed (never surfaced as a
964
+ * `MESSAGE`) and reset the liveness timer. Must match the server's
965
+ * auto-response. Default `"pong"`.
966
+ */
967
+ pongFrame?: string;
968
+ }
969
+ declare class WSTransport extends EventCore {
970
+ /**
971
+ * Per-instance event scope. `EventCore` is a process-global static emitter,
972
+ * so without namespacing every WSTransport would hear every other one's
973
+ * frames. We prefix each event with this transport's `id` and expose
974
+ * `on`/`off`/`emit` that scope transparently — callers still use the plain
975
+ * `EventCoreEvents` enum and only ever see their own connection's events.
976
+ */
977
+ readonly id: string;
978
+ readonly emit: (event: EventCoreEvents, data: unknown) => void;
979
+ readonly on: (event: EventCoreEvents, handler: EventListener | CallableFunction) => void;
980
+ readonly off: (event: EventCoreEvents, handler: EventListener | CallableFunction) => void;
981
+ private scopedEvent;
982
+ private url;
983
+ private urlProvider?;
984
+ private socket;
985
+ private _isConnected;
986
+ private _isConnecting;
987
+ private autoReconnect;
988
+ private reconnectDelay;
989
+ private maxReconnectAttempts;
990
+ private reconnectAttempts;
991
+ private reconnectTimeout;
992
+ private outboundQueue;
993
+ private maxQueueSize;
994
+ private heartbeatInterval;
995
+ private heartbeatTimeout;
996
+ private pingFrame;
997
+ private pongFrame;
998
+ private heartbeatTimer;
999
+ private pongTimer;
1000
+ constructor(opts: WSTransportOptions);
1001
+ /** Open the socket. Resolves when CONNECTED fires. Rejects on first error. */
1002
+ connect(): Promise<void>;
1003
+ /** Close the socket and stop reconnecting. */
1004
+ disconnect(): void;
1005
+ /**
1006
+ * Send a raw frame. If currently disconnected, the frame is queued and sent
1007
+ * after the next successful reconnect (up to `maxQueueSize`).
1008
+ */
1009
+ send(frame: string): void;
1010
+ isConnected(): boolean;
1011
+ isConnecting(): boolean;
1012
+ queuedFrames(): number;
1013
+ reconnectAttemptCount(): number;
1014
+ /** Begin the keep-alive loop (no-op when disabled). */
1015
+ private startHeartbeat;
1016
+ /** Stop the keep-alive loop and clear any pending liveness deadline. */
1017
+ private stopHeartbeat;
1018
+ /**
1019
+ * Send one ping and arm a liveness deadline. If no `pongFrame` arrives before
1020
+ * `heartbeatTimeout`, the connection is considered dead and we force a
1021
+ * reconnect — this is what catches a silently-dropped TCP connection that
1022
+ * never fired `onclose` (the failure mode that "loses connection with the
1023
+ * Accelerator" on long sessions).
1024
+ */
1025
+ private sendHeartbeat;
1026
+ /**
1027
+ * Tear down a connection we believe is dead and kick off reconnect, without
1028
+ * waiting on the OS/browser to eventually fire `onclose` (which a half-open
1029
+ * socket may never do). We detach the handlers first so the normal close path
1030
+ * can't double-fire the reconnect.
1031
+ */
1032
+ private forceReconnect;
1033
+ private scheduleReconnect;
1034
+ private flushQueue;
1035
+ }
1036
+
1037
+ /**
1038
+ * BroadcastChannel — a turnkey end-to-end encrypted channel for multi-peer
1039
+ * "room"-style apps (e.g. a chat). Wires together:
1040
+ *
1041
+ * - `WSTransport` for socket lifecycle, reconnect, and offline frame queue
1042
+ * - `EncryptedSession` for per-peer Double Ratchet state, handshake
1043
+ * reciprocation, fan-out encryption, and recipient filtering
1044
+ *
1045
+ * The host app only deals with high-level events and method calls; it never
1046
+ * touches `new WebSocket(...)` or builds cipherMessage frames by hand.
1047
+ *
1048
+ * Wire protocol (kept compatible with the existing chat protocol so this
1049
+ * drops into the deployed Accelerator without server changes):
1050
+ *
1051
+ * outbound: `{ keyExchange: {...} }` — produced by us on `announce()`
1052
+ * `{ cipherMessage: {...} }` — produced by `send(text)`,
1053
+ * one frame per peer
1054
+ * arbitrary JSON — sent via `sendRaw(obj)`
1055
+ *
1056
+ * inbound: `{ keyExchange: {...} }` — routed into EncryptedSession;
1057
+ * emits `peer_handshake`
1058
+ * `{ cipherMessage: {...} }` — decrypted; emits `message`
1059
+ * anything else — passes through as `raw_frame`
1060
+ * (the app decides what to do)
1061
+ *
1062
+ * **Event scoping.** BroadcastChannel uses a per-instance
1063
+ * `EventTarget`, so two channels (e.g. two rooms in the same app) don't
1064
+ * cross-contaminate each other's events.
1065
+ *
1066
+ * Note: this class shadows the `BroadcastChannel` Web API global (cross-tab
1067
+ * messaging) only when imported by name. They're unrelated.
1068
+ */
1069
+
1070
+ /**
1071
+ * Event names BroadcastChannel emits. Lifecycle events reuse the
1072
+ * `EventCoreEvents` string values (`connected`, `disconnected`, etc.) so
1073
+ * apps can use shared constants if they want. Channel-specific events are
1074
+ * prefixed `channel:` to avoid colliding with other event systems in the
1075
+ * same app.
1076
+ */
1077
+ declare const BroadcastChannelEvents: {
1078
+ readonly CONNECTED: EventCoreEvents.CONNECTED;
1079
+ readonly DISCONNECTED: EventCoreEvents.DISCONNECTED;
1080
+ readonly RECONNECTING: EventCoreEvents.RECONNECTING;
1081
+ readonly ERROR: EventCoreEvents.ERROR;
1082
+ /** A peer's ratchet is now ready. `detail.peerId` is the peer's id. */
1083
+ readonly PEER_HANDSHAKE: "channel:peer_handshake";
1084
+ /** Decrypted message. `detail` is `{ from, text, cipherMessage }`. */
1085
+ readonly MESSAGE: "channel:message";
1086
+ /** Inbound frame BroadcastChannel didn't recognize. App handles it. */
1087
+ readonly RAW_FRAME: "channel:raw_frame";
1088
+ };
1089
+ type BroadcastChannelEvent = (typeof BroadcastChannelEvents)[keyof typeof BroadcastChannelEvents];
1090
+ interface BroadcastChannelOptions extends WSTransportOptions {
1091
+ /** This client's identifier (username, DID, public-key string, etc.). */
1092
+ myId: string;
1093
+ /**
1094
+ * If true (default false), automatically send our keyExchange as soon as
1095
+ * the socket is connected. For chat-style apps that send a `{name}` frame
1096
+ * first, leave this false and call `announce()` after that handshake.
1097
+ */
1098
+ autoAnnounce?: boolean;
1099
+ }
1100
+ declare class BroadcastChannel {
1101
+ readonly transport: WSTransport;
1102
+ readonly session: EncryptedSession;
1103
+ readonly myId: string;
1104
+ private autoAnnounce;
1105
+ private announced;
1106
+ /** Per-instance EventTarget — events do NOT leak across BroadcastChannels. */
1107
+ private events;
1108
+ constructor(opts: BroadcastChannelOptions);
1109
+ /** Subscribe to a channel event. Handler receives a CustomEvent. */
1110
+ on(event: BroadcastChannelEvent | string, handler: (e: CustomEvent) => void): void;
1111
+ /** Unsubscribe. */
1112
+ off(event: BroadcastChannelEvent | string, handler: (e: CustomEvent) => void): void;
1113
+ private emit;
1114
+ /** Generate our keys (idempotent) and open the WebSocket. */
1115
+ connect(): Promise<void>;
1116
+ /** Close the WebSocket and stop reconnecting. */
1117
+ disconnect(): void;
1118
+ /**
1119
+ * Send our `keyExchange` frame to the room. Idempotent — only sends on the
1120
+ * first call. Call this once your higher-level handshake (e.g. sending a
1121
+ * `{name}` identification frame) has completed.
1122
+ */
1123
+ announce(): Promise<void>;
1124
+ /**
1125
+ * Encrypt `plaintext` to every peer we have a ratchet with, and send each
1126
+ * cipherMessage as its own WS frame. Returns the number of peers we sent to
1127
+ * (0 if no peers are handshaken yet — in which case the caller should
1128
+ * typically render the message locally for the sender and try again later).
1129
+ */
1130
+ send(plaintext: string): Promise<number>;
1131
+ /**
1132
+ * Send an arbitrary JSON-serializable frame as-is. The app uses this for
1133
+ * any frame that isn't channel-managed (e.g. a chat's `{name}`, `{file}`,
1134
+ * or unencrypted `{message}`).
1135
+ */
1136
+ sendRaw(frame: unknown): void;
1137
+ /** List of peer ids we have an established ratchet for. */
1138
+ peers(): string[];
1139
+ /** Drop a peer's ratchet (e.g. when they leave the room). */
1140
+ forgetPeer(peerId: string): void;
1141
+ isConnected(): boolean;
1142
+ private handleInboundFrame;
1143
+ }
1144
+
1145
+ /**
1146
+ * Cloudflare-Workers-compatible Groth16 ZK proof verifier.
1147
+ *
1148
+ * Drives the BN128 WASM module's exported pairing functions directly. Does NOT
1149
+ * use snarkjs/ffjavascript — those depend on runtime features (most notably
1150
+ * `URL.createObjectURL`) that CF Workers don't expose. See ROADMAP / CLAUDE.md.
1151
+ *
1152
+ * Two ways to initialize:
1153
+ * 1. `await initBn128Wasm()` — uses the bundled bn128.wasm (base64-inlined at
1154
+ * build time via `@rollup/plugin-wasm`). Works in any modern JS runtime
1155
+ * that allows runtime `WebAssembly.compile()` — Node, browsers, most
1156
+ * Workers configurations.
1157
+ * 2. `await initBn128Wasm(myWasmModule)` — accepts a pre-compiled
1158
+ * `WebAssembly.Module`. Lets a CF-Workers consumer keep the native pattern
1159
+ * of importing `.wasm` so wrangler precompiles it at deploy time and the
1160
+ * worker never pays for runtime compilation.
1161
+ */
1162
+
1163
+ interface Bn128WasmInstance {
1164
+ instance: WebAssembly.Instance;
1165
+ memory: WebAssembly.Memory;
1166
+ initialPFree: number;
1167
+ }
1168
+ /**
1169
+ * Initialize the BN128 WASM module.
1170
+ *
1171
+ * @param wasmModule Optional pre-compiled `WebAssembly.Module`. If omitted, the
1172
+ * bundled bn128.wasm (base64-inlined by `@rollup/plugin-wasm` at build time)
1173
+ * is used. Passing your own module is the recommended path inside CF Workers,
1174
+ * where wrangler can precompile a `.wasm` import at deploy time and avoid the
1175
+ * runtime compile cost.
1176
+ */
1177
+ declare function initBn128Wasm(wasmModule?: WebAssembly.Module): Promise<Bn128WasmInstance>;
1178
+ /**
1179
+ * Verify a Groth16 proof.
1180
+ *
1181
+ * Returns `false` (rather than throwing) for any structural problem — malformed
1182
+ * proof, out-of-range field elements, off-curve points, or a failed pairing.
1183
+ * Throws only for runtime/WASM faults.
1184
+ */
1185
+ declare function verifyGroth16(wasmInstance: WebAssembly.Instance, memory: WebAssembly.Memory, initialPFree: number, verificationKey: VerificationKey, proof: Groth16Proof, publicSignals: string[]): Promise<boolean>;
1186
+
1187
+ export { AES_GCM_IV_BYTES, AES_GCM_KEY_BYTES, AES_GCM_TAG_BYTES, BroadcastChannel, BroadcastChannelEvents, ChunkCipher, DoubleRatchet, EncryptedSession, EventCore, EventCoreEvents, KeyStore, Message, Message as MessageDefault, PBKDF2_DEFAULT_ITERATIONS, PREIMAGE_POK_VERIFICATION_KEY, Packet, SerializeMessage, WSTransport, decorators, decryptAesGcm, deriveAesKeyFromPassphrase, deriveBitsHkdf, encryptAesGcm, getSubtle, initBn128Wasm, randomBytes, unwrapWithPassphrase, verifyGroth16, wrapWithPassphrase };
1188
+ export type { ACL, ACLEntry, AuthKeyPair, AuthType, Bn128WasmInstance, BroadcastChannelEvent, BroadcastChannelOptions, ChunkKeyMaterial, CipherFrame, CipherMessage, CipherMessageHeader, DehydratedKeypair, DehydratedKeys, ECDHCurve, Groth16Proof, IncomingFrame, KeyExchangeFrame, KeyPair, Keypair, MessageBody, MessageEvent, MessageHeaders, MessageOptions, OAuthProvider, PacketOptions, Participant, Permission, PermissionCheckRequest, PermissionCheckResult, PublicKeyJWK, RatchetState, ReceiveResult, Role, Session, SessionOptions, Subscription, UserIdentity, VerificationKey, WSMessage, WSMessageType, WSTransportOptions, WrappedPayload };