@bcts/spqr 1.0.0-alpha.21

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,4318 @@
1
+ var bctsSpqr = (function(exports, _noble_hashes_hkdf_js, _noble_hashes_sha2_js, _noble_hashes_hmac_js, _noble_hashes_sha3_js, _noble_hashes_utils_js, _noble_post_quantum__crystals_js, _noble_post_quantum_ml_kem_js) {
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ //#region src/kdf.ts
6
+ /**
7
+ * Copyright © 2025 Signal Messenger, LLC
8
+ * Copyright © 2026 Parity Technologies
9
+ *
10
+ * KDF wrappers for SPQR (HKDF-SHA256 and HMAC-SHA256).
11
+ */
12
+ /**
13
+ * HKDF-SHA256 key derivation.
14
+ * @param ikm Input key material
15
+ * @param salt Salt (use ZERO_SALT for empty)
16
+ * @param info Context info string or bytes
17
+ * @param length Output length in bytes
18
+ */
19
+ function hkdfSha256(ikm, salt, info, length) {
20
+ return (0, _noble_hashes_hkdf_js.hkdf)(_noble_hashes_sha2_js.sha256, ikm, salt, typeof info === "string" ? new TextEncoder().encode(info) : info, length);
21
+ }
22
+ /**
23
+ * HMAC-SHA256 computation.
24
+ */
25
+ function hmacSha256(key, data) {
26
+ return (0, _noble_hashes_hmac_js.hmac)(_noble_hashes_sha2_js.sha256, key, data);
27
+ }
28
+
29
+ //#endregion
30
+ //#region src/util.ts
31
+ /**
32
+ * Copyright © 2025 Signal Messenger, LLC
33
+ * Copyright © 2026 Parity Technologies
34
+ *
35
+ * Low-level utility functions for SPQR.
36
+ */
37
+ /**
38
+ * Constant-time comparison of two byte arrays.
39
+ * Returns true if they are equal, false otherwise.
40
+ *
41
+ * Uses a XOR accumulator to avoid early-exit timing leaks.
42
+ *
43
+ * Limitation: Unlike Rust (#[inline(never)] + black_box), JavaScript
44
+ * cannot fully prevent JIT optimizations. In Node.js environments,
45
+ * callers requiring stronger guarantees should use
46
+ * crypto.timingSafeEqual directly.
47
+ */
48
+ function constantTimeEqual(a, b) {
49
+ if (a.length !== b.length) return false;
50
+ let diff = 0;
51
+ for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
52
+ return diff === 0;
53
+ }
54
+ /** Convert a bigint to 8-byte big-endian Uint8Array */
55
+ function bigintToBE8(value) {
56
+ const buf = new Uint8Array(8);
57
+ new DataView(buf.buffer).setBigUint64(0, value, false);
58
+ return buf;
59
+ }
60
+ /** Convert a number to 4-byte big-endian Uint8Array */
61
+ function uint32ToBE4(value) {
62
+ const buf = new Uint8Array(4);
63
+ new DataView(buf.buffer).setUint32(0, value, false);
64
+ return buf;
65
+ }
66
+ /** Concatenate multiple Uint8Arrays */
67
+ function concat(...arrays) {
68
+ let total = 0;
69
+ for (const a of arrays) total += a.length;
70
+ const result = new Uint8Array(total);
71
+ let offset = 0;
72
+ for (const a of arrays) {
73
+ result.set(a, offset);
74
+ offset += a.length;
75
+ }
76
+ return result;
77
+ }
78
+
79
+ //#endregion
80
+ //#region src/constants.ts
81
+ /** HMAC-SHA256 MAC size */
82
+ const MAC_SIZE = 32;
83
+ /** Default max key index jump */
84
+ const DEFAULT_MAX_JUMP = 25e3;
85
+ /** Default max out-of-order keys stored */
86
+ const DEFAULT_MAX_OOO_KEYS = 2e3;
87
+ /** Number of epochs to keep prior to the current send epoch */
88
+ const EPOCHS_TO_KEEP_PRIOR_TO_SEND_EPOCH = 1;
89
+ /** Size of a key entry in KeyHistory: 4 bytes (index BE32) + 32 bytes (key) */
90
+ const KEY_ENTRY_SIZE = 36;
91
+ /** Chain initialization label (NOTE: two spaces before "Start") */
92
+ const LABEL_CHAIN_START = "Signal PQ Ratchet V1 Chain Start";
93
+ /** Chain epoch advancement label */
94
+ const LABEL_CHAIN_ADD_EPOCH = "Signal PQ Ratchet V1 Chain Add Epoch";
95
+ /** Per-message key derivation label */
96
+ const LABEL_CHAIN_NEXT = "Signal PQ Ratchet V1 Chain Next";
97
+ /** Authenticator key ratchet label */
98
+ const LABEL_AUTH_UPDATE = "Signal_PQCKA_V1_MLKEM768:Authenticator Update";
99
+ /** Ciphertext MAC label */
100
+ const LABEL_CT_MAC = "Signal_PQCKA_V1_MLKEM768:ciphertext";
101
+ /** Header MAC label */
102
+ const LABEL_HDR_MAC = "Signal_PQCKA_V1_MLKEM768:ekheader";
103
+ /** Epoch secret derivation label */
104
+ const LABEL_SCKA_KEY = "Signal_PQCKA_V1_MLKEM768:SCKA Key";
105
+ /** 32-byte zero salt used in HKDF */
106
+ const ZERO_SALT = new Uint8Array(32);
107
+
108
+ //#endregion
109
+ //#region src/incremental-mlkem768.ts
110
+ /**
111
+ * Copyright © 2025 Signal Messenger, LLC
112
+ * Copyright © 2026 Parity Technologies
113
+ *
114
+ * True incremental ML-KEM-768 implementation.
115
+ *
116
+ * Implements the libcrux-compatible incremental encapsulation split:
117
+ *
118
+ * - generate(): Splits the ML-KEM-768 public key into:
119
+ * hdr (pk1, 64 bytes) = rho(32) + H(ek)(32) -- where H = SHA3-256
120
+ * ek (pk2, 1152 bytes) = ByteEncode12(tHat) -- the NTT vector
121
+ *
122
+ * - encaps1(hdr, rng): Uses only rho and H(ek) from the header to produce
123
+ * a REAL ct1 (960 bytes), shared secret (32 bytes), and
124
+ * encapsulation state (2080 bytes) for encaps2.
125
+ *
126
+ * - encaps2(ek, es): Completes the encapsulation using the tHat from pk2
127
+ * and the stored NTT randomness. Returns ct2 (128 bytes).
128
+ *
129
+ * - decaps(dk, ct1, ct2): Standard ML-KEM-768 decapsulation.
130
+ *
131
+ * Wire-compatible with Signal's Rust libcrux incremental implementation.
132
+ */
133
+ const N = 256;
134
+ const Q = 3329;
135
+ const F = 3303;
136
+ const ROOT_OF_UNITY = 17;
137
+ const K = 3;
138
+ const ETA1 = 2;
139
+ const ETA2 = 2;
140
+ const DU = 10;
141
+ const DV = 4;
142
+ /** Size of the public key header: rho(32) + H(ek)(32) */
143
+ const HEADER_SIZE = 64;
144
+ /** Size of the encapsulation key: ByteEncode12(tHat) = 3 * 384 = 1152 bytes */
145
+ const EK_SIZE = 1152;
146
+ /** Size of the first ciphertext fragment (ct[0..960]) */
147
+ const CT1_SIZE = 960;
148
+ /** Size of the second ciphertext fragment (ct[960..1088]) */
149
+ const CT2_SIZE = 128;
150
+ /** Standard ML-KEM-768 full public key size */
151
+ const FULL_PK_SIZE = 1184;
152
+ /** Standard ML-KEM-768 full ciphertext size */
153
+ const FULL_CT_SIZE = 1088;
154
+ /** Size of the keygen seed */
155
+ const KEYGEN_SEED_SIZE = 64;
156
+ /** Size of the encapsulation randomness (message m) */
157
+ const ENCAPS_SEED_SIZE = 32;
158
+ /**
159
+ * Size of the encapsulation state:
160
+ * r_as_ntt: K(3) * N(256) * 2 = 1536 bytes
161
+ * error2: N(256) * 2 = 512 bytes
162
+ * randomness: 32 bytes
163
+ * Total: 2080 bytes
164
+ */
165
+ const ES_SIZE = 2080;
166
+ const { mod, nttZetas, NTT, bitsCoder } = (0, _noble_post_quantum__crystals_js.genCrystals)({
167
+ N,
168
+ Q,
169
+ F,
170
+ ROOT_OF_UNITY,
171
+ newPoly: (n) => new Uint16Array(n),
172
+ brvBits: 7,
173
+ isKyber: true
174
+ });
175
+ function polyAdd(a, b) {
176
+ for (let i = 0; i < N; i++) a[i] = mod(a[i] + b[i]);
177
+ }
178
+ function BaseCaseMultiply(a0, a1, b0, b1, zeta) {
179
+ return {
180
+ c0: mod(a1 * b1 * zeta + a0 * b0),
181
+ c1: mod(a0 * b1 + a1 * b0)
182
+ };
183
+ }
184
+ function MultiplyNTTs(f, g) {
185
+ for (let i = 0; i < N / 2; i++) {
186
+ let z = nttZetas[64 + (i >> 1)];
187
+ if ((i & 1) !== 0) z = -z;
188
+ const { c0, c1 } = BaseCaseMultiply(f[2 * i + 0], f[2 * i + 1], g[2 * i + 0], g[2 * i + 1], z);
189
+ f[2 * i + 0] = c0;
190
+ f[2 * i + 1] = c1;
191
+ }
192
+ return f;
193
+ }
194
+ function SampleNTT(xof) {
195
+ const r = new Uint16Array(N);
196
+ for (let j = 0; j < N;) {
197
+ const b = xof();
198
+ if (b.length % 3 !== 0) throw new Error("SampleNTT: unaligned block");
199
+ for (let i = 0; j < N && i + 3 <= b.length; i += 3) {
200
+ const d1 = (b[i + 0] >> 0 | b[i + 1] << 8) & 4095;
201
+ const d2 = (b[i + 1] >> 4 | b[i + 2] << 4) & 4095;
202
+ if (d1 < Q) r[j++] = d1;
203
+ if (j < N && d2 < Q) r[j++] = d2;
204
+ }
205
+ }
206
+ return r;
207
+ }
208
+ function sampleCBD(seed, nonce, eta) {
209
+ const len = eta * N / 4;
210
+ const buf = _noble_hashes_sha3_js.shake256.create({ dkLen: len }).update(seed).update(new Uint8Array([nonce])).digest();
211
+ const r = new Uint16Array(N);
212
+ const b32 = (0, _noble_hashes_utils_js.u32)(buf);
213
+ let bitLen = 0;
214
+ let p = 0;
215
+ let bb = 0;
216
+ let t0 = 0;
217
+ for (let b of b32) for (let j = 0; j < 32; j++) {
218
+ bb += b & 1;
219
+ b >>= 1;
220
+ bitLen += 1;
221
+ if (bitLen === eta) {
222
+ t0 = bb;
223
+ bb = 0;
224
+ } else if (bitLen === 2 * eta) {
225
+ r[p++] = mod(t0 - bb);
226
+ bb = 0;
227
+ bitLen = 0;
228
+ }
229
+ }
230
+ if (bitLen !== 0) throw new Error(`sampleCBD: leftover bits: ${bitLen}`);
231
+ return r;
232
+ }
233
+ const compress = (d) => {
234
+ if (d >= 12) return {
235
+ encode: (i) => i,
236
+ decode: (i) => i
237
+ };
238
+ const a = 2 ** (d - 1);
239
+ return {
240
+ encode: (i) => ((i << d) + Q / 2) / Q,
241
+ decode: (i) => i * Q + a >>> d
242
+ };
243
+ };
244
+ const polyCoder = (d) => bitsCoder(d, compress(d));
245
+ const poly12 = polyCoder(12);
246
+ const polyDU = polyCoder(DU);
247
+ const polyDV = polyCoder(DV);
248
+ const poly1 = polyCoder(1);
249
+ /**
250
+ * Encode the encapsulation state as 2080 bytes:
251
+ * r_as_ntt: K polys, each 256 coefficients as uint16 LE = 1536 bytes
252
+ * error2: 1 poly, 256 coefficients as uint16 LE = 512 bytes
253
+ * randomness: 32 bytes (the message m)
254
+ */
255
+ function encodeState(rHat, e2, m) {
256
+ const state = new Uint8Array(ES_SIZE);
257
+ let offset = 0;
258
+ for (let k = 0; k < K; k++) {
259
+ const poly = rHat[k];
260
+ for (let i = 0; i < N; i++) {
261
+ const val = poly[i];
262
+ state[offset++] = val & 255;
263
+ state[offset++] = val >> 8 & 255;
264
+ }
265
+ }
266
+ for (let i = 0; i < N; i++) {
267
+ const val = e2[i];
268
+ state[offset++] = val & 255;
269
+ state[offset++] = val >> 8 & 255;
270
+ }
271
+ state.set(m, offset);
272
+ return state;
273
+ }
274
+ /**
275
+ * Decode the encapsulation state from 2080 bytes.
276
+ * Includes the Issue 1275 endianness workaround.
277
+ */
278
+ function decodeState(state) {
279
+ const st = fixIssue1275(state) ?? state;
280
+ let offset = 0;
281
+ const rHat = [];
282
+ for (let k = 0; k < K; k++) {
283
+ const poly = new Uint16Array(N);
284
+ for (let i = 0; i < N; i++) {
285
+ poly[i] = st[offset] | st[offset + 1] << 8;
286
+ offset += 2;
287
+ }
288
+ rHat.push(poly);
289
+ }
290
+ const e2 = new Uint16Array(N);
291
+ for (let i = 0; i < N; i++) {
292
+ e2[i] = st[offset] | st[offset + 1] << 8;
293
+ offset += 2;
294
+ }
295
+ return {
296
+ rHat,
297
+ e2,
298
+ m: st.slice(offset, offset + 32)
299
+ };
300
+ }
301
+ /**
302
+ * Port of Rust's potentially_fix_state_incorrectly_encoded_by_libcrux_issue_1275.
303
+ *
304
+ * Due to https://github.com/cryspen/libcrux/issues/1275, the encapsulation
305
+ * state may contain error2 coefficients with wrong endianness.
306
+ *
307
+ * Error2 values should be in [-2, 2] (ETA2=2 for ML-KEM-768).
308
+ * As uint16 LE, valid values are: 0x0000, 0x0001, 0x0002, 0xFFFF (-1), 0xFFFE (-2).
309
+ * Bad-endian equivalents: 0x0100, 0x0200, 0xFEFF.
310
+ *
311
+ * Returns a fixed copy if endianness is wrong, or null if state is OK.
312
+ */
313
+ function fixIssue1275(es) {
314
+ const E2_START = K * N * 2;
315
+ const E2_END = E2_START + N * 2;
316
+ for (let i = E2_START; i < E2_END; i += 2) {
317
+ const val = es[i] | es[i + 1] << 8;
318
+ if (val === 0 || val === 65535) continue;
319
+ if (val === 1 || val === 2 || val === 65534) return null;
320
+ if (val === 256 || val === 512 || val === 65279) return flipEndianness(es);
321
+ return null;
322
+ }
323
+ return null;
324
+ }
325
+ /**
326
+ * Flip the endianness of all i16 values in the encapsulation state.
327
+ * The last 32 bytes (randomness) are NOT flipped.
328
+ */
329
+ function flipEndianness(es) {
330
+ const fixed = new Uint8Array(es);
331
+ const coeffEnd = es.length - 32;
332
+ for (let i = 0; i < coeffEnd; i += 2) {
333
+ const tmp = fixed[i];
334
+ fixed[i] = fixed[i + 1];
335
+ fixed[i + 1] = tmp;
336
+ }
337
+ return fixed;
338
+ }
339
+ /**
340
+ * Generate an ML-KEM-768 keypair and split the public key.
341
+ *
342
+ * The ML-KEM-768 public key (1184 bytes) is:
343
+ * tHat = pk[0..1152] (ByteEncode12 of NTT vector)
344
+ * rho = pk[1152..1184] (32-byte seed)
345
+ *
346
+ * The incremental split produces:
347
+ * hdr (pk1) = rho(32) + SHA3-256(pk)(32) = 64 bytes
348
+ * ek (pk2) = tHat(1152) = ByteEncode12(tHat) = 1152 bytes
349
+ *
350
+ * @param rng - Random byte generator; must provide at least 64 bytes
351
+ * @returns Split key material
352
+ */
353
+ function generate(rng) {
354
+ const seed = rng(KEYGEN_SEED_SIZE);
355
+ const { publicKey, secretKey } = _noble_post_quantum_ml_kem_js.ml_kem768.keygen(seed);
356
+ const tHat = publicKey.slice(0, EK_SIZE);
357
+ const rho = publicKey.slice(EK_SIZE);
358
+ const hEk = (0, _noble_hashes_sha3_js.sha3_256)(publicKey);
359
+ const hdr = new Uint8Array(HEADER_SIZE);
360
+ hdr.set(rho, 0);
361
+ hdr.set(hEk, 32);
362
+ return {
363
+ hdr,
364
+ ek: tHat,
365
+ dk: secretKey
366
+ };
367
+ }
368
+ /**
369
+ * Phase 1 of true incremental encapsulation.
370
+ *
371
+ * Uses only rho and H(ek) from the 64-byte header to produce:
372
+ * - REAL ct1 (960 bytes): compress_du(u) where u = NTT^-1(A^T * rHat) + e1
373
+ * - REAL shared secret: K-hat from SHA3-512(m || H(ek))
374
+ * - Encapsulation state (2080 bytes): r_as_ntt + error2 + m
375
+ *
376
+ * @param hdr - The 64-byte header: rho(32) + H(ek)(32)
377
+ * @param rng - Random byte generator
378
+ * @returns Real ct1, encapsulation state, and real shared secret
379
+ */
380
+ function encaps1(hdr, rng) {
381
+ const rho = hdr.slice(0, 32);
382
+ const hEk = hdr.slice(32, 64);
383
+ const m = rng(ENCAPS_SEED_SIZE);
384
+ const kr = _noble_hashes_sha3_js.sha3_512.create().update(m).update(hEk).digest();
385
+ const kHat = kr.slice(0, 32);
386
+ const r = kr.slice(32, 64);
387
+ const rHat = [];
388
+ for (let i = 0; i < K; i++) rHat.push(NTT.encode(sampleCBD(r, i, ETA1)));
389
+ const x = (0, _noble_post_quantum__crystals_js.XOF128)(rho);
390
+ const u = [];
391
+ for (let i = 0; i < K; i++) {
392
+ const e1 = sampleCBD(r, K + i, ETA2);
393
+ const tmp = new Uint16Array(N);
394
+ for (let j = 0; j < K; j++) polyAdd(tmp, MultiplyNTTs(SampleNTT(x.get(i, j)), rHat[j].slice()));
395
+ polyAdd(e1, NTT.decode(tmp));
396
+ u.push(e1);
397
+ }
398
+ x.clean();
399
+ const ct1 = new Uint8Array(CT1_SIZE);
400
+ for (let i = 0; i < K; i++) {
401
+ const encoded = polyDU.encode(u[i]);
402
+ ct1.set(encoded, i * polyDU.bytesLen);
403
+ }
404
+ return {
405
+ ct1,
406
+ es: encodeState(rHat, sampleCBD(r, 2 * K, ETA2), m),
407
+ sharedSecret: kHat
408
+ };
409
+ }
410
+ /**
411
+ * Phase 2 of true incremental encapsulation.
412
+ *
413
+ * Uses the tHat from pk2 (ek) and the stored NTT randomness to compute ct2.
414
+ *
415
+ * @param ek - The 1152-byte encapsulation key (ByteEncode12(tHat))
416
+ * @param es - The 2080-byte encapsulation state from encaps1
417
+ * @returns ct2 (128 bytes) ONLY
418
+ */
419
+ function encaps2(ek, es) {
420
+ const { rHat, e2, m } = decodeState(es);
421
+ const tHat = [];
422
+ for (let i = 0; i < K; i++) {
423
+ const slice = ek.subarray(i * poly12.bytesLen, (i + 1) * poly12.bytesLen);
424
+ tHat.push(poly12.decode(slice));
425
+ }
426
+ const tmp = new Uint16Array(N);
427
+ for (let i = 0; i < K; i++) polyAdd(tmp, MultiplyNTTs(tHat[i].slice(), rHat[i].slice()));
428
+ const v = NTT.decode(tmp);
429
+ polyAdd(v, e2);
430
+ polyAdd(v, poly1.decode(m));
431
+ return polyDV.encode(v);
432
+ }
433
+ /**
434
+ * Decapsulate a split ciphertext using the decapsulation key.
435
+ *
436
+ * Concatenates ct1 (960 bytes) and ct2 (128 bytes) into a standard
437
+ * 1088-byte ML-KEM-768 ciphertext, then performs standard decapsulation.
438
+ *
439
+ * @param dk - The 2400-byte decapsulation (secret) key
440
+ * @param ct1 - First ciphertext fragment (960 bytes)
441
+ * @param ct2 - Second ciphertext fragment (128 bytes)
442
+ * @returns The 32-byte shared secret
443
+ */
444
+ function decaps(dk, ct1, ct2) {
445
+ const ct = new Uint8Array(FULL_CT_SIZE);
446
+ ct.set(ct1.subarray(0, CT1_SIZE), 0);
447
+ ct.set(ct2.subarray(0, CT2_SIZE), CT1_SIZE);
448
+ return _noble_post_quantum_ml_kem_js.ml_kem768.decapsulate(ct, dk);
449
+ }
450
+ /**
451
+ * Check whether an encapsulation key is consistent with a header.
452
+ *
453
+ * Reconstructs the full public key from pk2 (tHat) and pk1[0..32] (rho),
454
+ * computes SHA3-256 of the full pk, and compares with H(ek) in the header.
455
+ *
456
+ * @param ek - Encapsulation key (1152 bytes = ByteEncode12(tHat))
457
+ * @param hdr - Header (64 bytes = rho(32) + H(ek)(32))
458
+ * @returns true if ek is consistent with the header
459
+ */
460
+ function ekMatchesHeader(ek, hdr) {
461
+ if (hdr.length < HEADER_SIZE || ek.length < EK_SIZE) return false;
462
+ const rho = hdr.slice(0, 32);
463
+ const expectedHash = hdr.slice(32, 64);
464
+ const pk = new Uint8Array(FULL_PK_SIZE);
465
+ pk.set(ek.subarray(0, EK_SIZE), 0);
466
+ pk.set(rho, EK_SIZE);
467
+ const actualHash = (0, _noble_hashes_sha3_js.sha3_256)(pk);
468
+ if (actualHash.length !== expectedHash.length) return false;
469
+ let diff = 0;
470
+ for (let i = 0; i < actualHash.length; i++) diff |= actualHash[i] ^ expectedHash[i];
471
+ return diff === 0;
472
+ }
473
+
474
+ //#endregion
475
+ //#region src/error.ts
476
+ /**
477
+ * Copyright © 2025 Signal Messenger, LLC
478
+ * Copyright © 2026 Parity Technologies
479
+ *
480
+ * Error types for the SPQR protocol.
481
+ */
482
+ var SpqrError = class extends Error {
483
+ constructor(message, code, detail) {
484
+ super(message);
485
+ this.code = code;
486
+ this.detail = detail;
487
+ this.name = "SpqrError";
488
+ }
489
+ };
490
+ let SpqrErrorCode = /* @__PURE__ */ function(SpqrErrorCode) {
491
+ SpqrErrorCode["StateDecode"] = "STATE_DECODE";
492
+ SpqrErrorCode["NotImplemented"] = "NOT_IMPLEMENTED";
493
+ SpqrErrorCode["MsgDecode"] = "MSG_DECODE";
494
+ SpqrErrorCode["MacVerifyFailed"] = "MAC_VERIFY_FAILED";
495
+ SpqrErrorCode["EpochOutOfRange"] = "EPOCH_OUT_OF_RANGE";
496
+ SpqrErrorCode["EncodingDecoding"] = "ENCODING_DECODING";
497
+ SpqrErrorCode["Serialization"] = "SERIALIZATION";
498
+ SpqrErrorCode["VersionMismatch"] = "VERSION_MISMATCH";
499
+ SpqrErrorCode["MinimumVersion"] = "MINIMUM_VERSION";
500
+ SpqrErrorCode["KeyJump"] = "KEY_JUMP";
501
+ SpqrErrorCode["KeyTrimmed"] = "KEY_TRIMMED";
502
+ SpqrErrorCode["KeyAlreadyRequested"] = "KEY_ALREADY_REQUESTED";
503
+ SpqrErrorCode["ErroneousDataReceived"] = "ERRONEOUS_DATA_RECEIVED";
504
+ SpqrErrorCode["SendKeyEpochDecreased"] = "SEND_KEY_EPOCH_DECREASED";
505
+ SpqrErrorCode["InvalidParams"] = "INVALID_PARAMS";
506
+ SpqrErrorCode["ChainNotAvailable"] = "CHAIN_NOT_AVAILABLE";
507
+ return SpqrErrorCode;
508
+ }({});
509
+ var AuthenticatorError = class extends Error {
510
+ constructor(message, code) {
511
+ super(message);
512
+ this.code = code;
513
+ this.name = "AuthenticatorError";
514
+ }
515
+ };
516
+
517
+ //#endregion
518
+ //#region src/v1/unchunked/send-ct.ts
519
+ const SCKA_KEY_LABEL$1 = new TextEncoder().encode(LABEL_SCKA_KEY);
520
+ /**
521
+ * Derive the epoch secret from the KEM shared secret.
522
+ *
523
+ * HKDF-SHA256(ikm=sharedSecret, salt=ZERO_SALT,
524
+ * info=LABEL_SCKA_KEY || epoch_be8, length=32)
525
+ *
526
+ * Matches Rust: info = [b"Signal_PQCKA_V1_MLKEM768:SCKA Key", epoch.to_be_bytes()].concat()
527
+ */
528
+ function deriveEpochSecret$1(epoch, sharedSecret) {
529
+ return {
530
+ epoch,
531
+ secret: hkdfSha256(sharedSecret, ZERO_SALT, concat(SCKA_KEY_LABEL$1, bigintToBE8(epoch)), 32)
532
+ };
533
+ }
534
+ /**
535
+ * Waiting to receive the header from the send_ek peer.
536
+ */
537
+ var NoHeaderReceived$1 = class {
538
+ constructor(epoch, auth) {
539
+ this.epoch = epoch;
540
+ this.auth = auth;
541
+ }
542
+ /**
543
+ * Receive the header and verify its MAC.
544
+ *
545
+ * @param epoch - The epoch for this exchange (must match current epoch)
546
+ * @param hdr - The 64-byte public key header
547
+ * @param mac - The 32-byte HMAC-SHA256 MAC over the header
548
+ * @returns Next state
549
+ * @throws {AuthenticatorError} If the header MAC is invalid
550
+ */
551
+ recvHeader(epoch, hdr, mac) {
552
+ this.auth.verifyHdr(epoch, hdr, mac);
553
+ return new HeaderReceived$1(this.epoch, this.auth, hdr);
554
+ }
555
+ };
556
+ /**
557
+ * The header has been received and verified. Ready to produce ct1.
558
+ *
559
+ * In the true incremental ML-KEM approach, sendCt1 performs encaps1
560
+ * using only the header (rho + H(ek)), producing REAL ct1 and shared
561
+ * secret. The epoch secret is derived here.
562
+ */
563
+ var HeaderReceived$1 = class {
564
+ constructor(epoch, auth, hdr) {
565
+ this.epoch = epoch;
566
+ this.auth = auth;
567
+ this.hdr = hdr;
568
+ }
569
+ /**
570
+ * Generate encapsulation randomness and produce REAL ct1.
571
+ *
572
+ * Performs encaps1 using the header to produce:
573
+ * - Real ct1 (960 bytes)
574
+ * - Real shared secret -> epoch secret
575
+ * - Encapsulation state for later encaps2
576
+ *
577
+ * The authenticator is updated with the derived epoch secret.
578
+ *
579
+ * @param rng - Random byte generator
580
+ * @returns [nextState, real_ct1, epochSecret]
581
+ */
582
+ sendCt1(rng) {
583
+ const { ct1, es, sharedSecret } = encaps1(this.hdr, rng);
584
+ const epochSecret = deriveEpochSecret$1(this.epoch, sharedSecret);
585
+ const auth = this.auth.clone();
586
+ auth.update(this.epoch, epochSecret.secret);
587
+ return [
588
+ new Ct1Sent(this.epoch, auth, this.hdr, es, ct1),
589
+ ct1,
590
+ epochSecret
591
+ ];
592
+ }
593
+ };
594
+ /**
595
+ * Real ct1 has been produced. Waiting for the encapsulation key.
596
+ *
597
+ * Stores hdr, es(2080), and ct1(960) for the encaps2 phase.
598
+ */
599
+ var Ct1Sent = class {
600
+ constructor(epoch, auth, hdr, es, ct1) {
601
+ this.epoch = epoch;
602
+ this.auth = auth;
603
+ this.hdr = hdr;
604
+ this.es = es;
605
+ this.ct1 = ct1;
606
+ }
607
+ /**
608
+ * Receive the encapsulation key and validate it against the header.
609
+ *
610
+ * In the true incremental approach, this simply validates and stores
611
+ * the ek for later use in sendCt2. No encapsulation happens here.
612
+ *
613
+ * @param ek - The 1152-byte encapsulation key from the send_ek peer
614
+ * @returns Next state
615
+ * @throws {SpqrError} If the ek does not match the header
616
+ */
617
+ recvEk(ek) {
618
+ if (!ekMatchesHeader(ek, this.hdr)) throw new SpqrError("Encapsulation key does not match header", SpqrErrorCode.ErroneousDataReceived);
619
+ return new Ct1SentEkReceived(this.epoch, this.auth, this.es, ek, this.ct1);
620
+ }
621
+ };
622
+ /**
623
+ * The encapsulation key has been received and validated.
624
+ * Ready to send ct2.
625
+ *
626
+ * Stores es(2080), ek(1152), and ct1(960) for encaps2 + MAC.
627
+ */
628
+ var Ct1SentEkReceived = class {
629
+ constructor(epoch, auth, es, ek, ct1) {
630
+ this.epoch = epoch;
631
+ this.auth = auth;
632
+ this.es = es;
633
+ this.ek = ek;
634
+ this.ct1 = ct1;
635
+ }
636
+ /**
637
+ * Produce ct2 by calling encaps2, then MAC over ct1 || ct2.
638
+ *
639
+ * @returns Result with next state, ct2, and MAC
640
+ */
641
+ sendCt2() {
642
+ const ct2 = encaps2(this.ek, this.es);
643
+ const fullCt = concat(this.ct1, ct2);
644
+ const mac = this.auth.macCt(this.epoch, fullCt);
645
+ return {
646
+ state: new Ct2Sent(this.epoch, this.auth),
647
+ ct2,
648
+ mac
649
+ };
650
+ }
651
+ };
652
+ /**
653
+ * Terminal state for this epoch's send_ct exchange.
654
+ *
655
+ * The caller is responsible for creating the next epoch's
656
+ * send_ek::KeysUnsampled state from the epoch and auth.
657
+ */
658
+ var Ct2Sent = class {
659
+ constructor(epoch, auth) {
660
+ this.epoch = epoch;
661
+ this.auth = auth;
662
+ }
663
+ /** The next epoch for the send_ek side */
664
+ get nextEpoch() {
665
+ return this.epoch + 1n;
666
+ }
667
+ };
668
+
669
+ //#endregion
670
+ //#region src/encoding/gf.ts
671
+ /**
672
+ * Copyright © 2025 Signal Messenger, LLC
673
+ * Copyright © 2026 Parity Technologies
674
+ *
675
+ * GF(2^16) Galois field arithmetic.
676
+ * Ported from the Rust SPQR implementation.
677
+ *
678
+ * Primitive polynomial: x^16 + x^12 + x^3 + x + 1 (0x1100b)
679
+ */
680
+ /** Primitive polynomial for GF(2^16): x^16 + x^12 + x^3 + x + 1 */
681
+ const POLY = 69643;
682
+ /**
683
+ * Build one entry of the reduction table.
684
+ *
685
+ * Given a single byte `a` that appears in positions [16..23] or [24..31] of
686
+ * a 32-bit product, compute the 16-bit XOR mask needed to reduce those bits
687
+ * modulo POLY.
688
+ */
689
+ function reduceFromByte(a) {
690
+ let byte = a;
691
+ let out = 0;
692
+ for (let i = 7; i >= 0; i--) if ((1 << i & byte) !== 0) {
693
+ out ^= POLY << i;
694
+ byte ^= POLY << i >>> 16 & 255;
695
+ }
696
+ return out & 65535;
697
+ }
698
+ /** Precomputed 256-entry lookup table for polynomial reduction. */
699
+ const REDUCE_BYTES = /* @__PURE__ */ (() => {
700
+ const table = new Uint16Array(256);
701
+ for (let i = 0; i < 256; i++) table[i] = reduceFromByte(i);
702
+ return table;
703
+ })();
704
+ /**
705
+ * Polynomial multiplication in GF(2)[x] (no reduction).
706
+ *
707
+ * Both `a` and `b` must be in the range [0, 0xffff].
708
+ * The result may be up to 31 bits wide.
709
+ */
710
+ function polyMul(a, b) {
711
+ let acc = 0;
712
+ for (let shift = 0; shift < 16; shift++) if ((b & 1 << shift) !== 0) acc ^= a << shift;
713
+ return acc >>> 0;
714
+ }
715
+ /**
716
+ * Reduce a 32-bit polynomial product modulo POLY, yielding a 16-bit result.
717
+ *
718
+ * Uses the precomputed REDUCE_BYTES table to process the top two bytes.
719
+ */
720
+ function polyReduce(v) {
721
+ let r = v >>> 0;
722
+ r ^= REDUCE_BYTES[r >>> 24 & 255] << 8;
723
+ r ^= REDUCE_BYTES[r >>> 16 & 255];
724
+ return r & 65535;
725
+ }
726
+ /**
727
+ * Full GF(2^16) multiplication of two raw u16 values.
728
+ * Returns a u16 result.
729
+ */
730
+ function mulRaw(a, b) {
731
+ return polyReduce(polyMul(a, b));
732
+ }
733
+ /**
734
+ * An element of GF(2^16).
735
+ *
736
+ * The `value` field holds a 16-bit unsigned integer in [0, 65535].
737
+ */
738
+ var GF16 = class GF16 {
739
+ value;
740
+ constructor(value) {
741
+ this.value = value & 65535;
742
+ }
743
+ static ZERO = new GF16(0);
744
+ static ONE = new GF16(1);
745
+ /** Addition in GF(2^n) is XOR. */
746
+ add(other) {
747
+ return new GF16(this.value ^ other.value);
748
+ }
749
+ /** Subtraction in GF(2^n) is the same as addition (XOR). */
750
+ sub(other) {
751
+ return this.add(other);
752
+ }
753
+ /** Multiplication in GF(2^16) using polynomial long-multiplication + reduction. */
754
+ mul(other) {
755
+ return new GF16(mulRaw(this.value, other.value));
756
+ }
757
+ /**
758
+ * Division in GF(2^16).
759
+ *
760
+ * Computes `this / other` via Fermat's little theorem:
761
+ * other^(-1) = other^(2^16 - 2)
762
+ *
763
+ * The loop accumulates the inverse through repeated squaring:
764
+ * After 15 iterations (i = 1..15):
765
+ * out = this * other^(2^16 - 2) = this * other^(-1)
766
+ *
767
+ * Throws if `other` is zero.
768
+ */
769
+ div(other) {
770
+ if (other.value === 0) throw new Error("GF16: division by zero");
771
+ let sqVal = mulRaw(other.value, other.value);
772
+ let outVal = this.value;
773
+ for (let i = 1; i < 16; i++) {
774
+ const newSqVal = mulRaw(sqVal, sqVal);
775
+ const newOutVal = mulRaw(sqVal, outVal);
776
+ sqVal = newSqVal;
777
+ outVal = newOutVal;
778
+ }
779
+ return new GF16(outVal);
780
+ }
781
+ equals(other) {
782
+ return this.value === other.value;
783
+ }
784
+ toString() {
785
+ return `GF16(0x${this.value.toString(16).padStart(4, "0")})`;
786
+ }
787
+ };
788
+ /**
789
+ * Multiply every element of `into` by `a` in-place.
790
+ *
791
+ * This is the TypeScript equivalent of Rust's `parallel_mult` which benefits
792
+ * from SIMD on native platforms. Here we just iterate.
793
+ */
794
+ function parallelMult(a, into) {
795
+ const av = a.value;
796
+ for (let i = 0; i < into.length; i++) into[i] = new GF16(mulRaw(av, into[i].value));
797
+ }
798
+
799
+ //#endregion
800
+ //#region src/encoding/polynomial.ts
801
+ /**
802
+ * Copyright © 2025 Signal Messenger, LLC
803
+ * Copyright © 2026 Parity Technologies
804
+ *
805
+ * Polynomial erasure coding over GF(2^16).
806
+ * Ported from the Rust SPQR implementation.
807
+ *
808
+ * The encoder splits a message into 16 parallel polynomials and can produce
809
+ * an unlimited number of coded chunks. Any sufficient subset of chunks
810
+ * allows the decoder to reconstruct the original message via Lagrange
811
+ * interpolation.
812
+ */
813
+ var PolynomialError = class extends Error {
814
+ constructor(message) {
815
+ super(message);
816
+ this.name = "PolynomialError";
817
+ }
818
+ };
819
+ const NUM_POLYS = 16;
820
+ const CHUNK_DATA_SIZE$1 = 32;
821
+ const MAX_MESSAGE_LENGTH = 65536 * NUM_POLYS;
822
+ /**
823
+ * A polynomial over GF(2^16) in coefficient form.
824
+ *
825
+ * Coefficients are stored in little-endian order:
826
+ * coefficients[0] = constant term (x^0)
827
+ * coefficients[1] = linear term (x^1)
828
+ * ...
829
+ */
830
+ var Poly = class Poly {
831
+ coefficients;
832
+ constructor(coefficients) {
833
+ this.coefficients = coefficients;
834
+ }
835
+ /** Create a zero polynomial of a given length. */
836
+ static zeros(len) {
837
+ const coeffs = new Array(len);
838
+ for (let i = 0; i < len; i++) coeffs[i] = GF16.ZERO;
839
+ return new Poly(coeffs);
840
+ }
841
+ /** Number of coefficients. */
842
+ get length() {
843
+ return this.coefficients.length;
844
+ }
845
+ /**
846
+ * Evaluate the polynomial at a given point using a divide-and-conquer
847
+ * approach for computing powers of x, then a dot product.
848
+ *
849
+ * xs[0] = 1, xs[1] = x, xs[i] = xs[floor(i/2)] * xs[floor(i/2) + (i%2)]
850
+ */
851
+ computeAt(x) {
852
+ const n = this.coefficients.length;
853
+ if (n === 0) return GF16.ZERO;
854
+ if (n === 1) return this.coefficients[0];
855
+ const xs = new Array(n);
856
+ xs[0] = GF16.ONE;
857
+ if (n > 1) xs[1] = x;
858
+ for (let i = 2; i < n; i++) {
859
+ const half = i >>> 1;
860
+ const rem = i & 1;
861
+ xs[i] = xs[half].mul(xs[half + rem]);
862
+ }
863
+ let result = GF16.ZERO;
864
+ for (let i = 0; i < n; i++) result = result.add(this.coefficients[i].mul(xs[i]));
865
+ return result;
866
+ }
867
+ /** Add another polynomial to this one in-place. */
868
+ addAssign(other) {
869
+ while (this.coefficients.length < other.coefficients.length) this.coefficients.push(GF16.ZERO);
870
+ for (let i = 0; i < other.coefficients.length; i++) this.coefficients[i] = this.coefficients[i].add(other.coefficients[i]);
871
+ }
872
+ /** Multiply all coefficients by a scalar in-place. */
873
+ multAssign(m) {
874
+ parallelMult(m, this.coefficients);
875
+ }
876
+ /** Serialize coefficients as big-endian u16 pairs. */
877
+ serialize() {
878
+ const out = new Uint8Array(this.coefficients.length * 2);
879
+ for (let i = 0; i < this.coefficients.length; i++) {
880
+ const v = this.coefficients[i].value;
881
+ out[i * 2] = v >>> 8 & 255;
882
+ out[i * 2 + 1] = v & 255;
883
+ }
884
+ return out;
885
+ }
886
+ /** Deserialize from big-endian u16 pairs. */
887
+ static deserialize(data) {
888
+ if (data.length % 2 !== 0) throw new PolynomialError("Poly data length must be even");
889
+ const n = data.length / 2;
890
+ const coeffs = new Array(n);
891
+ for (let i = 0; i < n; i++) coeffs[i] = new GF16(data[i * 2] << 8 | data[i * 2 + 1]);
892
+ return new Poly(coeffs);
893
+ }
894
+ /**
895
+ * Lagrange interpolation over a set of points.
896
+ *
897
+ * Given N points (x_i, y_i), produces the unique polynomial of degree < N
898
+ * passing through all of them.
899
+ */
900
+ static lagrangeInterpolate(pts) {
901
+ const n = pts.length;
902
+ if (n === 0) return new Poly([]);
903
+ let product = new Poly([pts[0].x, GF16.ONE]);
904
+ for (let i = 1; i < n; i++) product = polyMultiply(product, new Poly([pts[i].x, GF16.ONE]));
905
+ const result = Poly.zeros(n);
906
+ for (let i = 0; i < n; i++) {
907
+ const basis = syntheticDivide(product, pts[i].x);
908
+ const denom = basis.computeAt(pts[i].x);
909
+ const scale = pts[i].y.div(denom);
910
+ const scaled = clonePoly(basis);
911
+ scaled.multAssign(scale);
912
+ result.addAssign(scaled);
913
+ }
914
+ return result;
915
+ }
916
+ };
917
+ /** Multiply two polynomials (convolution over GF(2^16)). */
918
+ function polyMultiply(a, b) {
919
+ if (a.length === 0 || b.length === 0) return new Poly([]);
920
+ const result = Poly.zeros(a.length + b.length - 1);
921
+ for (let i = 0; i < a.length; i++) for (let j = 0; j < b.length; j++) result.coefficients[i + j] = result.coefficients[i + j].add(a.coefficients[i].mul(b.coefficients[j]));
922
+ return result;
923
+ }
924
+ /**
925
+ * Synthetic division: divide poly by (x - root) = (x + root) in GF(2^n).
926
+ *
927
+ * Returns the quotient polynomial (degree one less than input).
928
+ */
929
+ function syntheticDivide(poly, root) {
930
+ const n = poly.length;
931
+ if (n <= 1) return new Poly([]);
932
+ const quotient = new Array(n - 1);
933
+ let carry = GF16.ZERO;
934
+ for (let i = n - 1; i >= 1; i--) {
935
+ const coeff = poly.coefficients[i].add(carry);
936
+ quotient[i - 1] = coeff;
937
+ carry = coeff.mul(root);
938
+ }
939
+ return new Poly(quotient);
940
+ }
941
+ /** Clone a polynomial (shallow copy of coefficient array). */
942
+ function clonePoly(p) {
943
+ return new Poly(p.coefficients.slice());
944
+ }
945
+ var SortedPtSet = class SortedPtSet {
946
+ pts = [];
947
+ get length() {
948
+ return this.pts.length;
949
+ }
950
+ /** Insert a point, maintaining sorted order. Returns false if duplicate x. */
951
+ insert(pt) {
952
+ const idx = this.findIndex(pt.x.value);
953
+ if (idx < this.pts.length && this.pts[idx].x.value === pt.x.value) return false;
954
+ this.pts.splice(idx, 0, pt);
955
+ return true;
956
+ }
957
+ /** Binary search for the insertion point of a given x value. */
958
+ findIndex(xVal) {
959
+ let lo = 0;
960
+ let hi = this.pts.length;
961
+ while (lo < hi) {
962
+ const mid = lo + hi >>> 1;
963
+ if (this.pts[mid].x.value < xVal) lo = mid + 1;
964
+ else hi = mid;
965
+ }
966
+ return lo;
967
+ }
968
+ /** Look up a point by x value. Returns undefined if not found. */
969
+ findByX(xVal) {
970
+ const idx = this.findIndex(xVal);
971
+ if (idx < this.pts.length && this.pts[idx].x.value === xVal) return this.pts[idx];
972
+ }
973
+ /** Return all points as an array (for interpolation). */
974
+ toArray() {
975
+ return this.pts.slice();
976
+ }
977
+ /** Serialize all points as big-endian u16 pairs (x then y). */
978
+ serialize() {
979
+ const out = new Uint8Array(this.pts.length * 4);
980
+ for (let i = 0; i < this.pts.length; i++) {
981
+ const pt = this.pts[i];
982
+ out[i * 4] = pt.x.value >>> 8 & 255;
983
+ out[i * 4 + 1] = pt.x.value & 255;
984
+ out[i * 4 + 2] = pt.y.value >>> 8 & 255;
985
+ out[i * 4 + 3] = pt.y.value & 255;
986
+ }
987
+ return out;
988
+ }
989
+ /** Deserialize from big-endian u16 pairs (x then y). */
990
+ static deserialize(data) {
991
+ if (data.length % 4 !== 0) throw new PolynomialError("SortedPtSet data length must be multiple of 4");
992
+ const set = new SortedPtSet();
993
+ const n = data.length / 4;
994
+ for (let i = 0; i < n; i++) {
995
+ const x = new GF16(data[i * 4] << 8 | data[i * 4 + 1]);
996
+ const y = new GF16(data[i * 4 + 2] << 8 | data[i * 4 + 3]);
997
+ set.insert({
998
+ x,
999
+ y
1000
+ });
1001
+ }
1002
+ return set;
1003
+ }
1004
+ };
1005
+ var PolyEncoder = class PolyEncoder {
1006
+ idx;
1007
+ state;
1008
+ constructor(idx, state) {
1009
+ this.idx = idx;
1010
+ this.state = state;
1011
+ }
1012
+ /**
1013
+ * Create an encoder from a message byte array.
1014
+ *
1015
+ * The message is split into 16 interleaved streams of GF16 values.
1016
+ * Each pair of consecutive bytes becomes one GF16 element. If the message
1017
+ * length is odd, it is padded with a zero byte.
1018
+ */
1019
+ static encodeBytes(msg) {
1020
+ if (msg.length % 2 !== 0) throw new PolynomialError("Message length must be even");
1021
+ if (msg.length > MAX_MESSAGE_LENGTH) throw new PolynomialError("Message length exceeds maximum");
1022
+ const totalValues = msg.length / 2;
1023
+ const points = new Array(NUM_POLYS);
1024
+ for (let p = 0; p < NUM_POLYS; p++) points[p] = [];
1025
+ for (let i = 0; i < totalValues; i++) {
1026
+ const poly = i % NUM_POLYS;
1027
+ const value = msg[i * 2] << 8 | msg[i * 2 + 1];
1028
+ points[poly].push(new GF16(value));
1029
+ }
1030
+ return new PolyEncoder(0, {
1031
+ kind: "points",
1032
+ points
1033
+ });
1034
+ }
1035
+ /** Return the next chunk and advance the index. */
1036
+ nextChunk() {
1037
+ const chunk = this.chunkAt(this.idx);
1038
+ this.idx++;
1039
+ return chunk;
1040
+ }
1041
+ /** Compute the chunk at a specific index. */
1042
+ chunkAt(idx) {
1043
+ const data = new Uint8Array(CHUNK_DATA_SIZE$1);
1044
+ for (let i = 0; i < NUM_POLYS; i++) {
1045
+ const totalIdx = idx * NUM_POLYS + i;
1046
+ const poly = totalIdx % NUM_POLYS;
1047
+ const polyIdx = Math.floor(totalIdx / NUM_POLYS);
1048
+ const val = this.pointAt(poly, polyIdx);
1049
+ data[i * 2] = val.value >>> 8 & 255;
1050
+ data[i * 2 + 1] = val.value & 255;
1051
+ }
1052
+ return {
1053
+ index: idx & 65535,
1054
+ data
1055
+ };
1056
+ }
1057
+ /**
1058
+ * Get the GF16 value for polynomial `poly` at index `idx`.
1059
+ *
1060
+ * If we are in Points state and the index is within range, return directly.
1061
+ * Otherwise, convert to Polys state and evaluate.
1062
+ */
1063
+ pointAt(poly, idx) {
1064
+ if (this.state.kind === "points") {
1065
+ const pts = this.state.points[poly];
1066
+ if (idx < pts.length) return pts[idx];
1067
+ this.convertToPolys();
1068
+ }
1069
+ return this.state.polys[poly].computeAt(new GF16(idx));
1070
+ }
1071
+ /** Convert from Points state to Polys state via Lagrange interpolation. */
1072
+ convertToPolys() {
1073
+ if (this.state.kind === "polys") return;
1074
+ const { points } = this.state;
1075
+ const polys = new Array(NUM_POLYS);
1076
+ for (let p = 0; p < NUM_POLYS; p++) {
1077
+ const pts = points[p].map((y, i) => ({
1078
+ x: new GF16(i),
1079
+ y
1080
+ }));
1081
+ polys[p] = Poly.lagrangeInterpolate(pts);
1082
+ }
1083
+ this.state = {
1084
+ kind: "polys",
1085
+ polys
1086
+ };
1087
+ }
1088
+ /** Serialize encoder state for protobuf transport. */
1089
+ toProto() {
1090
+ if (this.state.kind === "points") {
1091
+ const pts = this.state.points.map((arr) => {
1092
+ const buf = new Uint8Array(arr.length * 2);
1093
+ for (let i = 0; i < arr.length; i++) {
1094
+ buf[i * 2] = arr[i].value >>> 8 & 255;
1095
+ buf[i * 2 + 1] = arr[i].value & 255;
1096
+ }
1097
+ return buf;
1098
+ });
1099
+ return {
1100
+ idx: this.idx,
1101
+ pts,
1102
+ polys: []
1103
+ };
1104
+ }
1105
+ const polys = this.state.polys.map((p) => p.serialize());
1106
+ return {
1107
+ idx: this.idx,
1108
+ pts: [],
1109
+ polys
1110
+ };
1111
+ }
1112
+ /** Restore encoder from protobuf data. */
1113
+ static fromProto(data) {
1114
+ if (data.polys.length > 0) {
1115
+ const polys = data.polys.map((buf) => Poly.deserialize(buf));
1116
+ return new PolyEncoder(data.idx, {
1117
+ kind: "polys",
1118
+ polys
1119
+ });
1120
+ }
1121
+ const points = data.pts.map((buf) => {
1122
+ const arr = [];
1123
+ for (let i = 0; i < buf.length; i += 2) arr.push(new GF16(buf[i] << 8 | buf[i + 1]));
1124
+ return arr;
1125
+ });
1126
+ return new PolyEncoder(data.idx, {
1127
+ kind: "points",
1128
+ points
1129
+ });
1130
+ }
1131
+ };
1132
+ var PolyDecoder = class PolyDecoder {
1133
+ /** Total number of GF16 values needed (= message byte length / 2, rounded up). */
1134
+ ptsNeeded;
1135
+ pts;
1136
+ _isComplete;
1137
+ constructor(ptsNeeded, pts, isComplete) {
1138
+ this.ptsNeeded = ptsNeeded;
1139
+ this.pts = pts;
1140
+ this._isComplete = isComplete;
1141
+ }
1142
+ /**
1143
+ * Create a decoder for a message of `lenBytes` bytes.
1144
+ *
1145
+ * The caller must know the original message length to know when enough
1146
+ * chunks have been received.
1147
+ */
1148
+ static create(lenBytes) {
1149
+ if (lenBytes % 2 !== 0) throw new PolynomialError("Message length must be even");
1150
+ const ptsNeeded = lenBytes / 2;
1151
+ const pts = new Array(NUM_POLYS);
1152
+ for (let i = 0; i < NUM_POLYS; i++) pts[i] = new SortedPtSet();
1153
+ return new PolyDecoder(ptsNeeded, pts, false);
1154
+ }
1155
+ /** Whether all polynomial sets have enough points to decode. */
1156
+ get isComplete() {
1157
+ return this._isComplete;
1158
+ }
1159
+ /**
1160
+ * Number of points necessary for polynomial `poly`.
1161
+ *
1162
+ * The total points (ptsNeeded) are distributed across 16 polynomials
1163
+ * round-robin, so some may need one more point than others.
1164
+ */
1165
+ necessaryPoints(poly) {
1166
+ const base = Math.floor(this.ptsNeeded / NUM_POLYS);
1167
+ return poly < this.ptsNeeded % NUM_POLYS ? base + 1 : base;
1168
+ }
1169
+ /** Add a chunk to the decoder. */
1170
+ addChunk(chunk) {
1171
+ if (this._isComplete) return;
1172
+ for (let i = 0; i < NUM_POLYS; i++) {
1173
+ const totalIdx = chunk.index * NUM_POLYS + i;
1174
+ const poly = totalIdx % NUM_POLYS;
1175
+ const polyIdx = Math.floor(totalIdx / NUM_POLYS);
1176
+ const needed = this.necessaryPoints(poly);
1177
+ if (this.pts[poly].length >= needed) continue;
1178
+ const y = new GF16(chunk.data[i * 2] << 8 | chunk.data[i * 2 + 1]);
1179
+ const pt = {
1180
+ x: new GF16(polyIdx),
1181
+ y
1182
+ };
1183
+ this.pts[poly].insert(pt);
1184
+ }
1185
+ this._isComplete = this.checkComplete();
1186
+ }
1187
+ /** Check whether all 16 polynomial sets have enough points. */
1188
+ checkComplete() {
1189
+ for (let p = 0; p < NUM_POLYS; p++) if (this.pts[p].length < this.necessaryPoints(p)) return false;
1190
+ return true;
1191
+ }
1192
+ /**
1193
+ * Attempt to decode the message.
1194
+ *
1195
+ * Returns the decoded byte array if enough chunks have been received,
1196
+ * or null if more chunks are needed.
1197
+ */
1198
+ decodedMessage() {
1199
+ if (!this._isComplete) return null;
1200
+ const result = new Uint8Array(this.ptsNeeded * 2);
1201
+ for (let i = 0; i < this.ptsNeeded; i++) {
1202
+ const poly = i % NUM_POLYS;
1203
+ const xVal = Math.floor(i / NUM_POLYS);
1204
+ let val;
1205
+ const direct = this.pts[poly].findByX(xVal);
1206
+ if (direct !== void 0) val = direct.y;
1207
+ else {
1208
+ const allPts = this.pts[poly].toArray();
1209
+ val = Poly.lagrangeInterpolate(allPts).computeAt(new GF16(xVal));
1210
+ }
1211
+ result[i * 2] = val.value >>> 8 & 255;
1212
+ result[i * 2 + 1] = val.value & 255;
1213
+ }
1214
+ return result;
1215
+ }
1216
+ /** Serialize decoder state for protobuf transport. */
1217
+ toProto() {
1218
+ return {
1219
+ ptsNeeded: this.ptsNeeded,
1220
+ polys: NUM_POLYS,
1221
+ pts: this.pts.map((s) => s.serialize()),
1222
+ isComplete: this._isComplete
1223
+ };
1224
+ }
1225
+ /** Restore decoder from protobuf data. */
1226
+ static fromProto(data) {
1227
+ const pts = data.pts.map((buf) => SortedPtSet.deserialize(buf));
1228
+ while (pts.length < NUM_POLYS) pts.push(new SortedPtSet());
1229
+ return new PolyDecoder(data.ptsNeeded, pts, data.isComplete);
1230
+ }
1231
+ };
1232
+
1233
+ //#endregion
1234
+ //#region src/authenticator.ts
1235
+ /**
1236
+ * Copyright © 2025 Signal Messenger, LLC
1237
+ * Copyright © 2026 Parity Technologies
1238
+ *
1239
+ * SPQR Authenticator -- HMAC-SHA256 MAC for KEM exchanges.
1240
+ *
1241
+ * Ported from Signal's spqr crate: authenticator.rs
1242
+ *
1243
+ * The Authenticator produces and verifies MACs over ciphertext and header
1244
+ * data using HMAC-SHA256. The internal rootKey and macKey are updated
1245
+ * via HKDF at each epoch transition.
1246
+ *
1247
+ * All info strings and data formats MUST match the Rust implementation exactly.
1248
+ */
1249
+ const enc$1 = new TextEncoder();
1250
+ const AUTH_UPDATE_INFO = enc$1.encode(LABEL_AUTH_UPDATE);
1251
+ const CT_MAC_PREFIX = enc$1.encode(LABEL_CT_MAC);
1252
+ const HDR_MAC_PREFIX = enc$1.encode(LABEL_HDR_MAC);
1253
+ /**
1254
+ * Authenticator manages root_key and mac_key state for producing
1255
+ * and verifying MACs over KEM ciphertext and headers.
1256
+ *
1257
+ * The update operation uses HKDF:
1258
+ * IKM = [rootKey || key]
1259
+ * Salt = ZERO_SALT (32 zeros)
1260
+ * Info = LABEL_AUTH_UPDATE + epoch_be8
1261
+ * Output: 64 bytes -> [0..32] = new rootKey, [32..64] = new macKey
1262
+ *
1263
+ * MAC operations use HMAC-SHA256:
1264
+ * Key = macKey
1265
+ * Data = [label_prefix || epoch_be8 || payload]
1266
+ */
1267
+ var Authenticator = class Authenticator {
1268
+ rootKey;
1269
+ macKey;
1270
+ constructor(rootKey, macKey) {
1271
+ this.rootKey = rootKey;
1272
+ this.macKey = macKey;
1273
+ }
1274
+ /**
1275
+ * Create a new Authenticator from a root key and initial epoch.
1276
+ * Matches Rust: `Authenticator::new(root_key, ep)`
1277
+ *
1278
+ * Initializes with zero keys, then immediately updates with
1279
+ * the provided root key and epoch.
1280
+ */
1281
+ static create(rootKey, epoch) {
1282
+ const auth = new Authenticator(new Uint8Array(32), new Uint8Array(32));
1283
+ auth.update(epoch, rootKey);
1284
+ return auth;
1285
+ }
1286
+ /**
1287
+ * Update the authenticator with a new epoch and key material.
1288
+ *
1289
+ * HKDF(IKM=[rootKey||key], salt=ZERO_SALT, info=[label||epoch_be8], length=64)
1290
+ *
1291
+ * Output split: rootKey = [0..32], macKey = [32..64]
1292
+ */
1293
+ update(epoch, key) {
1294
+ const derived = hkdfSha256(concat(this.rootKey, key), ZERO_SALT, concat(AUTH_UPDATE_INFO, bigintToBE8(epoch)), 64);
1295
+ this.rootKey = derived.slice(0, 32);
1296
+ this.macKey = derived.slice(32, 64);
1297
+ }
1298
+ /**
1299
+ * Compute MAC over ciphertext.
1300
+ *
1301
+ * HMAC-SHA256(macKey, [LABEL_CT_MAC || epoch_be8 || ct])
1302
+ */
1303
+ macCt(epoch, ct) {
1304
+ const data = concat(CT_MAC_PREFIX, bigintToBE8(epoch), ct);
1305
+ return hmacSha256(this.macKey, data);
1306
+ }
1307
+ /**
1308
+ * Verify ciphertext MAC (constant-time comparison).
1309
+ * Throws AuthenticatorError if the MAC does not match.
1310
+ */
1311
+ verifyCt(epoch, ct, expectedMac) {
1312
+ if (!constantTimeEqual(this.macCt(epoch, ct), expectedMac)) throw new AuthenticatorError("Ciphertext MAC is invalid", "INVALID_CT_MAC");
1313
+ }
1314
+ /**
1315
+ * Compute MAC over header (encapsulation key header).
1316
+ *
1317
+ * HMAC-SHA256(macKey, [LABEL_HDR_MAC || epoch_be8 || hdr])
1318
+ */
1319
+ macHdr(epoch, hdr) {
1320
+ const data = concat(HDR_MAC_PREFIX, bigintToBE8(epoch), hdr);
1321
+ return hmacSha256(this.macKey, data);
1322
+ }
1323
+ /**
1324
+ * Verify header MAC (constant-time comparison).
1325
+ * Throws AuthenticatorError if the MAC does not match.
1326
+ */
1327
+ verifyHdr(epoch, hdr, expectedMac) {
1328
+ if (!constantTimeEqual(this.macHdr(epoch, hdr), expectedMac)) throw new AuthenticatorError("Encapsulation key MAC is invalid", "INVALID_HDR_MAC");
1329
+ }
1330
+ /**
1331
+ * Deep clone this authenticator.
1332
+ */
1333
+ clone() {
1334
+ return new Authenticator(Uint8Array.from(this.rootKey), Uint8Array.from(this.macKey));
1335
+ }
1336
+ /**
1337
+ * Serialize to protobuf representation.
1338
+ * Matches Rust Authenticator::into_pb().
1339
+ */
1340
+ toProto() {
1341
+ return {
1342
+ rootKey: Uint8Array.from(this.rootKey),
1343
+ macKey: Uint8Array.from(this.macKey)
1344
+ };
1345
+ }
1346
+ /**
1347
+ * Deserialize from protobuf representation.
1348
+ * Matches Rust Authenticator::from_pb().
1349
+ */
1350
+ static fromProto(pb) {
1351
+ return new Authenticator(Uint8Array.from(pb.rootKey), Uint8Array.from(pb.macKey));
1352
+ }
1353
+ };
1354
+
1355
+ //#endregion
1356
+ //#region src/v1/chunked/send-ek.ts
1357
+ /**
1358
+ * Initial chunked send_ek state. No keypair generated yet.
1359
+ * Wraps unchunked.KeysUnsampled.
1360
+ */
1361
+ var KeysUnsampled$1 = class {
1362
+ constructor(uc) {
1363
+ this.uc = uc;
1364
+ }
1365
+ get epoch() {
1366
+ return this.uc.epoch;
1367
+ }
1368
+ /**
1369
+ * Generate keypair, produce header + MAC, encode into PolyEncoder,
1370
+ * and return the first header chunk.
1371
+ */
1372
+ sendHdrChunk(rng) {
1373
+ const [headerSent, hdr, mac] = this.uc.sendHeader(rng);
1374
+ const hdrPayload = concat(hdr, mac);
1375
+ const sendingHdr = PolyEncoder.encodeBytes(hdrPayload);
1376
+ const chunk = sendingHdr.nextChunk();
1377
+ return [new KeysSampled(headerSent, sendingHdr), chunk];
1378
+ }
1379
+ };
1380
+ /**
1381
+ * Header has been generated and encoding started. Still sending header chunks.
1382
+ * Can also begin receiving ct1 chunks.
1383
+ */
1384
+ var KeysSampled = class {
1385
+ constructor(uc, sendingHdr) {
1386
+ this.uc = uc;
1387
+ this.sendingHdr = sendingHdr;
1388
+ }
1389
+ get epoch() {
1390
+ return this.uc.epoch;
1391
+ }
1392
+ /** Produce the next header chunk. */
1393
+ sendHdrChunk() {
1394
+ const chunk = this.sendingHdr.nextChunk();
1395
+ return [this, chunk];
1396
+ }
1397
+ /**
1398
+ * Receive a ct1 chunk from the send_ct peer.
1399
+ * This triggers sending the encapsulation key.
1400
+ */
1401
+ recvCt1Chunk(epoch, chunk) {
1402
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1403
+ const receivingCt1 = PolyDecoder.create(CT1_SIZE);
1404
+ receivingCt1.addChunk(chunk);
1405
+ const [ekSent, ek] = this.uc.sendEk();
1406
+ return new HeaderSent$1(ekSent, PolyEncoder.encodeBytes(ek), receivingCt1);
1407
+ }
1408
+ };
1409
+ /**
1410
+ * Sending ek chunks while receiving ct1 chunks.
1411
+ */
1412
+ var HeaderSent$1 = class {
1413
+ constructor(uc, sendingEk, receivingCt1) {
1414
+ this.uc = uc;
1415
+ this.sendingEk = sendingEk;
1416
+ this.receivingCt1 = receivingCt1;
1417
+ }
1418
+ get epoch() {
1419
+ return this.uc.epoch;
1420
+ }
1421
+ /** Produce the next ek chunk. */
1422
+ sendEkChunk() {
1423
+ const chunk = this.sendingEk.nextChunk();
1424
+ return [this, chunk];
1425
+ }
1426
+ /** Receive a ct1 chunk. Returns done when ct1 is fully decoded. */
1427
+ recvCt1Chunk(epoch, chunk) {
1428
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1429
+ this.receivingCt1.addChunk(chunk);
1430
+ const decoded = this.receivingCt1.decodedMessage();
1431
+ if (decoded !== null) return {
1432
+ done: true,
1433
+ state: new Ct1Received(this.uc.recvCt1(decoded), this.sendingEk)
1434
+ };
1435
+ return {
1436
+ done: false,
1437
+ state: this
1438
+ };
1439
+ }
1440
+ };
1441
+ /**
1442
+ * ct1 has been fully decoded. Still sending ek chunks.
1443
+ * Can begin receiving ct2 chunks.
1444
+ */
1445
+ var Ct1Received = class {
1446
+ constructor(uc, sendingEk) {
1447
+ this.uc = uc;
1448
+ this.sendingEk = sendingEk;
1449
+ }
1450
+ get epoch() {
1451
+ return this.uc.epoch;
1452
+ }
1453
+ /** Produce the next ek chunk. */
1454
+ sendEkChunk() {
1455
+ const chunk = this.sendingEk.nextChunk();
1456
+ return [this, chunk];
1457
+ }
1458
+ /**
1459
+ * Receive a ct2 chunk. Creates the ct2 decoder.
1460
+ * ct2 payload is CT2_SIZE + MAC_SIZE = 160 bytes
1461
+ * (carries ct2 + mac).
1462
+ */
1463
+ recvCt2Chunk(epoch, chunk) {
1464
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1465
+ const receivingCt2 = PolyDecoder.create(CT2_SIZE + MAC_SIZE);
1466
+ receivingCt2.addChunk(chunk);
1467
+ return new EkSentCt1Received$1(this.uc, receivingCt2);
1468
+ }
1469
+ };
1470
+ /**
1471
+ * Both ek sending and ct1 receiving are done. Now receiving ct2 chunks.
1472
+ * When ct2 is fully decoded, extract ct2 and mac, then perform decapsulation
1473
+ * to derive the epoch secret.
1474
+ */
1475
+ var EkSentCt1Received$1 = class {
1476
+ constructor(uc, receivingCt2) {
1477
+ this.uc = uc;
1478
+ this.receivingCt2 = receivingCt2;
1479
+ }
1480
+ get epoch() {
1481
+ return this.uc.epoch;
1482
+ }
1483
+ /**
1484
+ * Receive a ct2 chunk. Returns done when ct2 is fully decoded and
1485
+ * epoch secret is derived.
1486
+ */
1487
+ recvCt2Chunk(epoch, chunk) {
1488
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1489
+ this.receivingCt2.addChunk(chunk);
1490
+ const decoded = this.receivingCt2.decodedMessage();
1491
+ if (decoded !== null) {
1492
+ const ct2 = decoded.slice(0, CT2_SIZE);
1493
+ const mac = decoded.slice(CT2_SIZE);
1494
+ const result = this.uc.recvCt2(ct2, mac);
1495
+ const nextUcSendCt = new NoHeaderReceived$1(result.nextEpoch, result.auth);
1496
+ const receivingHdr = PolyDecoder.create(HEADER_SIZE + MAC_SIZE);
1497
+ return {
1498
+ done: true,
1499
+ state: createSendCtNoHeaderReceived(nextUcSendCt, receivingHdr),
1500
+ epochSecret: result.epochSecret
1501
+ };
1502
+ }
1503
+ return {
1504
+ done: false,
1505
+ state: this
1506
+ };
1507
+ }
1508
+ };
1509
+ /** @internal Set by the chunked index module to break circular imports. */
1510
+ let createSendCtNoHeaderReceived;
1511
+ /** @internal Called by the index module to wire up the factory. */
1512
+ function _setCreateSendCtNoHeaderReceived(factory) {
1513
+ createSendCtNoHeaderReceived = factory;
1514
+ }
1515
+
1516
+ //#endregion
1517
+ //#region src/v1/unchunked/send-ek.ts
1518
+ const SCKA_KEY_LABEL = new TextEncoder().encode(LABEL_SCKA_KEY);
1519
+ /**
1520
+ * Derive the epoch secret from the KEM shared secret.
1521
+ *
1522
+ * HKDF-SHA256(ikm=sharedSecret, salt=ZERO_SALT,
1523
+ * info=LABEL_SCKA_KEY || epoch_be8, length=32)
1524
+ *
1525
+ * Matches Rust: info = [b"Signal_PQCKA_V1_MLKEM768:SCKA Key", epoch.to_be_bytes()].concat()
1526
+ */
1527
+ function deriveEpochSecret(epoch, sharedSecret) {
1528
+ return {
1529
+ epoch,
1530
+ secret: hkdfSha256(sharedSecret, ZERO_SALT, concat(SCKA_KEY_LABEL, bigintToBE8(epoch)), 32)
1531
+ };
1532
+ }
1533
+ /**
1534
+ * Initial send_ek state. No keypair has been generated yet.
1535
+ */
1536
+ var KeysUnsampled = class {
1537
+ constructor(epoch, auth) {
1538
+ this.epoch = epoch;
1539
+ this.auth = auth;
1540
+ }
1541
+ /**
1542
+ * Generate an ML-KEM-768 keypair and produce the header + MAC.
1543
+ *
1544
+ * @param rng - Random byte generator
1545
+ * @returns [nextState, hdr, hdrMac]
1546
+ */
1547
+ sendHeader(rng) {
1548
+ const keys = generate(rng);
1549
+ const mac = this.auth.macHdr(this.epoch, keys.hdr);
1550
+ return [
1551
+ new HeaderSent(this.epoch, this.auth, keys.ek, keys.dk),
1552
+ keys.hdr,
1553
+ mac
1554
+ ];
1555
+ }
1556
+ };
1557
+ /**
1558
+ * The header has been sent to the peer. Ready to send the encapsulation key.
1559
+ */
1560
+ var HeaderSent = class {
1561
+ constructor(epoch, auth, ek, dk) {
1562
+ this.epoch = epoch;
1563
+ this.auth = auth;
1564
+ this.ek = ek;
1565
+ this.dk = dk;
1566
+ }
1567
+ /**
1568
+ * Produce the encapsulation key to send to the peer.
1569
+ *
1570
+ * @returns [nextState, ek]
1571
+ */
1572
+ sendEk() {
1573
+ return [new EkSent(this.epoch, this.auth, this.dk), this.ek];
1574
+ }
1575
+ };
1576
+ /**
1577
+ * Both the header and encapsulation key have been sent.
1578
+ * Waiting to receive ct1 from the send_ct peer.
1579
+ */
1580
+ var EkSent = class {
1581
+ constructor(epoch, auth, dk) {
1582
+ this.epoch = epoch;
1583
+ this.auth = auth;
1584
+ this.dk = dk;
1585
+ }
1586
+ /**
1587
+ * Receive the first ciphertext fragment from the peer.
1588
+ *
1589
+ * @param ct1 - The 960-byte first ciphertext fragment
1590
+ * @returns Next state
1591
+ */
1592
+ recvCt1(ct1) {
1593
+ return new EkSentCt1Received(this.epoch, this.auth, this.dk, ct1);
1594
+ }
1595
+ };
1596
+ /**
1597
+ * ct1 has been received. Waiting for ct2 to complete decapsulation.
1598
+ */
1599
+ var EkSentCt1Received = class {
1600
+ constructor(epoch, auth, dk, ct1) {
1601
+ this.epoch = epoch;
1602
+ this.auth = auth;
1603
+ this.dk = dk;
1604
+ this.ct1 = ct1;
1605
+ }
1606
+ /**
1607
+ * Receive ct2 and MAC, perform decapsulation, verify the MAC,
1608
+ * and derive the epoch secret.
1609
+ *
1610
+ * The caller constructs the next send_ct::NoHeaderReceived state from
1611
+ * the returned nextEpoch and auth.
1612
+ *
1613
+ * @param ct2 - The 128-byte second ciphertext fragment
1614
+ * @param mac - The 32-byte HMAC-SHA256 MAC over the full ciphertext
1615
+ * @returns Result containing next epoch, updated auth, and epoch secret
1616
+ * @throws {AuthenticatorError} If the ciphertext MAC is invalid
1617
+ */
1618
+ recvCt2(ct2, mac) {
1619
+ const sharedSecret = decaps(this.dk, this.ct1, ct2);
1620
+ const epochSecret = deriveEpochSecret(this.epoch, sharedSecret);
1621
+ const auth = this.auth.clone();
1622
+ auth.update(this.epoch, epochSecret.secret);
1623
+ const fullCt = concat(this.ct1, ct2);
1624
+ auth.verifyCt(this.epoch, fullCt, mac);
1625
+ return {
1626
+ nextEpoch: this.epoch + 1n,
1627
+ auth,
1628
+ epochSecret
1629
+ };
1630
+ }
1631
+ };
1632
+
1633
+ //#endregion
1634
+ //#region src/v1/chunked/send-ct.ts
1635
+ /**
1636
+ * Copyright © 2025 Signal Messenger, LLC
1637
+ * Copyright © 2026 Parity Technologies
1638
+ *
1639
+ * Chunked send_ct state machine for SPQR V1.
1640
+ *
1641
+ * Wraps the unchunked send_ct states with PolyEncoder/PolyDecoder erasure
1642
+ * coding, enabling chunk-by-chunk data transfer.
1643
+ *
1644
+ * True incremental ML-KEM (Phase 9):
1645
+ * - send_ct produces REAL ct1 chunks from the start.
1646
+ * - ct2 payload carries only ct2(128) + mac(32) = 160 bytes.
1647
+ * - Epoch secret is derived in sendCt1 (when encaps1 is performed).
1648
+ *
1649
+ * States:
1650
+ * NoHeaderReceived -- recvHdrChunk(epoch, chunk) --> NoHeaderReceived | HeaderReceived
1651
+ * HeaderReceived -- sendCt1Chunk(rng) --> [Ct1Sampled, Chunk, EpochSecret]
1652
+ * Ct1Sampled -- sendCt1Chunk() --> [Ct1Sampled, Chunk]
1653
+ * -- recvEkChunk(epoch, chunk, ct1Ack) --> 4 outcomes
1654
+ * EkReceivedCt1Sampled -- sendCt1Chunk() --> [EkReceivedCt1Sampled, Chunk]
1655
+ * -- recvCt1Ack(epoch) --> Ct2Sampled
1656
+ * Ct1Acknowledged -- recvEkChunk(epoch, chunk) --> Ct1Acknowledged | Ct2Sampled
1657
+ * Ct2Sampled -- sendCt2Chunk() --> [Ct2Sampled, Chunk]
1658
+ * -- recvNextEpoch(epoch) --> sendEk.KeysUnsampled
1659
+ */
1660
+ /**
1661
+ * Waiting to receive header chunks from the send_ek peer.
1662
+ */
1663
+ var NoHeaderReceived = class NoHeaderReceived {
1664
+ constructor(uc, receivingHdr) {
1665
+ this.uc = uc;
1666
+ this.receivingHdr = receivingHdr;
1667
+ }
1668
+ get epoch() {
1669
+ return this.uc.epoch;
1670
+ }
1671
+ /**
1672
+ * Create the initial NoHeaderReceived from an auth key.
1673
+ */
1674
+ static create(authKey) {
1675
+ const auth = Authenticator.create(authKey, 1n);
1676
+ return new NoHeaderReceived(new NoHeaderReceived$1(1n, auth), PolyDecoder.create(HEADER_SIZE + MAC_SIZE));
1677
+ }
1678
+ /** Receive a header chunk. Returns done when header is fully decoded. */
1679
+ recvHdrChunk(epoch, chunk) {
1680
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1681
+ this.receivingHdr.addChunk(chunk);
1682
+ const decoded = this.receivingHdr.decodedMessage();
1683
+ if (decoded !== null) {
1684
+ const hdr = decoded.slice(0, HEADER_SIZE);
1685
+ const mac = decoded.slice(HEADER_SIZE);
1686
+ return {
1687
+ done: true,
1688
+ state: new HeaderReceived(this.uc.recvHeader(epoch, hdr, mac), PolyDecoder.create(EK_SIZE))
1689
+ };
1690
+ }
1691
+ return {
1692
+ done: false,
1693
+ state: this
1694
+ };
1695
+ }
1696
+ };
1697
+ /**
1698
+ * Header has been received and verified. Ready to produce ct1 chunks.
1699
+ */
1700
+ var HeaderReceived = class {
1701
+ constructor(uc, receivingEk) {
1702
+ this.uc = uc;
1703
+ this.receivingEk = receivingEk;
1704
+ }
1705
+ get epoch() {
1706
+ return this.uc.epoch;
1707
+ }
1708
+ /**
1709
+ * Generate encapsulation randomness, produce REAL ct1, encode it,
1710
+ * and return the first ct1 chunk.
1711
+ *
1712
+ * Returns [Ct1Sampled, Chunk, EpochSecret] -- real epoch secret.
1713
+ */
1714
+ sendCt1Chunk(rng) {
1715
+ const [ct1Sent, realCt1, epochSecret] = this.uc.sendCt1(rng);
1716
+ const sendingCt1 = PolyEncoder.encodeBytes(realCt1);
1717
+ const chunk = sendingCt1.nextChunk();
1718
+ return [
1719
+ new Ct1Sampled(ct1Sent, sendingCt1, this.receivingEk),
1720
+ chunk,
1721
+ epochSecret
1722
+ ];
1723
+ }
1724
+ };
1725
+ /**
1726
+ * Real ct1 has been produced and encoding started.
1727
+ * Sending ct1 chunks while receiving ek chunks.
1728
+ */
1729
+ var Ct1Sampled = class {
1730
+ constructor(uc, sendingCt1, receivingEk) {
1731
+ this.uc = uc;
1732
+ this.sendingCt1 = sendingCt1;
1733
+ this.receivingEk = receivingEk;
1734
+ }
1735
+ get epoch() {
1736
+ return this.uc.epoch;
1737
+ }
1738
+ /** Produce the next ct1 chunk. */
1739
+ sendCt1Chunk() {
1740
+ const chunk = this.sendingCt1.nextChunk();
1741
+ return [this, chunk];
1742
+ }
1743
+ /**
1744
+ * Receive an ek chunk, with optional ct1 acknowledgement from peer.
1745
+ *
1746
+ * Four possible outcomes depending on whether ek is complete and
1747
+ * whether the peer acknowledged ct1:
1748
+ * - Both: done (Ct2Sampled + epochSecret)
1749
+ * - ek complete, no ack: stillSending (EkReceivedCt1Sampled + epochSecret)
1750
+ * - ek incomplete, ack: stillReceiving (Ct1Acknowledged)
1751
+ * - Neither: stillReceivingStillSending (Ct1Sampled)
1752
+ */
1753
+ recvEkChunk(epoch, chunk, ct1Ack) {
1754
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1755
+ this.receivingEk.addChunk(chunk);
1756
+ const ekDecoded = this.receivingEk.decodedMessage();
1757
+ if (ekDecoded !== null && ct1Ack) {
1758
+ const { state: ct2SentUc, ct2, mac } = this.uc.recvEk(ekDecoded).sendCt2();
1759
+ const ct2Payload = concat(ct2, mac);
1760
+ return {
1761
+ tag: "done",
1762
+ state: new Ct2Sampled(ct2SentUc, PolyEncoder.encodeBytes(ct2Payload)),
1763
+ epochSecret: null
1764
+ };
1765
+ }
1766
+ if (ekDecoded !== null && !ct1Ack) return {
1767
+ tag: "stillSending",
1768
+ state: new EkReceivedCt1Sampled(this.uc.recvEk(ekDecoded), this.sendingCt1),
1769
+ epochSecret: null
1770
+ };
1771
+ if (ekDecoded === null && ct1Ack) return {
1772
+ tag: "stillReceiving",
1773
+ state: new Ct1Acknowledged(this.uc, this.receivingEk)
1774
+ };
1775
+ return {
1776
+ tag: "stillReceivingStillSending",
1777
+ state: this
1778
+ };
1779
+ }
1780
+ };
1781
+ /**
1782
+ * ek has been received and validated.
1783
+ * Still sending ct1 chunks, waiting for ct1 acknowledgement.
1784
+ */
1785
+ var EkReceivedCt1Sampled = class {
1786
+ constructor(uc, sendingCt1) {
1787
+ this.uc = uc;
1788
+ this.sendingCt1 = sendingCt1;
1789
+ }
1790
+ get epoch() {
1791
+ return this.uc.epoch;
1792
+ }
1793
+ /** Produce the next ct1 chunk. */
1794
+ sendCt1Chunk() {
1795
+ const chunk = this.sendingCt1.nextChunk();
1796
+ return [this, chunk];
1797
+ }
1798
+ /** Peer has acknowledged ct1. Produce ct2 and encode it. */
1799
+ recvCt1Ack(epoch) {
1800
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1801
+ const { state: ct2SentUc, ct2, mac } = this.uc.sendCt2();
1802
+ const ct2Payload = concat(ct2, mac);
1803
+ return new Ct2Sampled(ct2SentUc, PolyEncoder.encodeBytes(ct2Payload));
1804
+ }
1805
+ };
1806
+ /**
1807
+ * ct1 has been acknowledged by the peer. Still receiving ek chunks.
1808
+ * When ek arrives, validate it and produce ct2.
1809
+ */
1810
+ var Ct1Acknowledged = class {
1811
+ constructor(uc, receivingEk) {
1812
+ this.uc = uc;
1813
+ this.receivingEk = receivingEk;
1814
+ }
1815
+ get epoch() {
1816
+ return this.uc.epoch;
1817
+ }
1818
+ /** Receive an ek chunk. Returns done when ek is fully decoded. */
1819
+ recvEkChunk(epoch, chunk) {
1820
+ if (epoch !== this.uc.epoch) throw new SpqrError(`Epoch mismatch: expected ${this.uc.epoch}, got ${epoch}`, SpqrErrorCode.EpochOutOfRange);
1821
+ this.receivingEk.addChunk(chunk);
1822
+ const decoded = this.receivingEk.decodedMessage();
1823
+ if (decoded !== null) {
1824
+ const { state: ct2SentUc, ct2, mac } = this.uc.recvEk(decoded).sendCt2();
1825
+ const ct2Payload = concat(ct2, mac);
1826
+ return {
1827
+ done: true,
1828
+ state: new Ct2Sampled(ct2SentUc, PolyEncoder.encodeBytes(ct2Payload)),
1829
+ epochSecret: null
1830
+ };
1831
+ }
1832
+ return {
1833
+ done: false,
1834
+ state: this
1835
+ };
1836
+ }
1837
+ };
1838
+ /**
1839
+ * ct2 payload (ct2 + mac) has been encoded. Sending ct2 chunks.
1840
+ * Terminal for this epoch once all chunks sent and next epoch begins.
1841
+ */
1842
+ var Ct2Sampled = class {
1843
+ constructor(uc, sendingCt2) {
1844
+ this.uc = uc;
1845
+ this.sendingCt2 = sendingCt2;
1846
+ }
1847
+ get epoch() {
1848
+ return this.uc.epoch;
1849
+ }
1850
+ /** Produce the next ct2 chunk. */
1851
+ sendCt2Chunk() {
1852
+ const chunk = this.sendingCt2.nextChunk();
1853
+ return [this, chunk];
1854
+ }
1855
+ /**
1856
+ * Transition to the next epoch's send_ek KeysUnsampled.
1857
+ */
1858
+ recvNextEpoch(_epoch) {
1859
+ const nextEpoch = this.uc.nextEpoch;
1860
+ const nextUc = new KeysUnsampled(nextEpoch, this.uc.auth);
1861
+ return new KeysUnsampled$1(nextUc);
1862
+ }
1863
+ };
1864
+
1865
+ //#endregion
1866
+ //#region src/v1/chunked/states.ts
1867
+ function getEpoch(s) {
1868
+ return s.state.epoch;
1869
+ }
1870
+ /**
1871
+ * Initialize Alice (send_ek side) at epoch 1.
1872
+ */
1873
+ function initA(authKey) {
1874
+ const auth = Authenticator.create(authKey, 1n);
1875
+ const ucState = new KeysUnsampled(1n, auth);
1876
+ return {
1877
+ tag: "keysUnsampled",
1878
+ state: new KeysUnsampled$1(ucState)
1879
+ };
1880
+ }
1881
+ /**
1882
+ * Initialize Bob (send_ct side) at epoch 1.
1883
+ */
1884
+ function initB(authKey) {
1885
+ return {
1886
+ tag: "noHeaderReceived",
1887
+ state: NoHeaderReceived.create(authKey)
1888
+ };
1889
+ }
1890
+ /**
1891
+ * Produce the next outgoing message from the current state.
1892
+ */
1893
+ function send$1(current, rng) {
1894
+ const epoch = getEpoch(current);
1895
+ switch (current.tag) {
1896
+ case "keysUnsampled": {
1897
+ const [next, chunk] = current.state.sendHdrChunk(rng);
1898
+ return {
1899
+ msg: {
1900
+ epoch,
1901
+ payload: {
1902
+ type: "hdr",
1903
+ chunk
1904
+ }
1905
+ },
1906
+ key: null,
1907
+ state: {
1908
+ tag: "keysSampled",
1909
+ state: next
1910
+ }
1911
+ };
1912
+ }
1913
+ case "keysSampled": {
1914
+ const [next, chunk] = current.state.sendHdrChunk();
1915
+ return {
1916
+ msg: {
1917
+ epoch,
1918
+ payload: {
1919
+ type: "hdr",
1920
+ chunk
1921
+ }
1922
+ },
1923
+ key: null,
1924
+ state: {
1925
+ tag: "keysSampled",
1926
+ state: next
1927
+ }
1928
+ };
1929
+ }
1930
+ case "headerSent": {
1931
+ const [next, chunk] = current.state.sendEkChunk();
1932
+ return {
1933
+ msg: {
1934
+ epoch,
1935
+ payload: {
1936
+ type: "ek",
1937
+ chunk
1938
+ }
1939
+ },
1940
+ key: null,
1941
+ state: {
1942
+ tag: "headerSent",
1943
+ state: next
1944
+ }
1945
+ };
1946
+ }
1947
+ case "ct1Received": {
1948
+ const [next, chunk] = current.state.sendEkChunk();
1949
+ return {
1950
+ msg: {
1951
+ epoch,
1952
+ payload: {
1953
+ type: "ekCt1Ack",
1954
+ chunk
1955
+ }
1956
+ },
1957
+ key: null,
1958
+ state: {
1959
+ tag: "ct1Received",
1960
+ state: next
1961
+ }
1962
+ };
1963
+ }
1964
+ case "ekSentCt1Received": return {
1965
+ msg: {
1966
+ epoch,
1967
+ payload: { type: "ct1Ack" }
1968
+ },
1969
+ key: null,
1970
+ state: current
1971
+ };
1972
+ case "noHeaderReceived": return {
1973
+ msg: {
1974
+ epoch,
1975
+ payload: { type: "none" }
1976
+ },
1977
+ key: null,
1978
+ state: current
1979
+ };
1980
+ case "headerReceived": {
1981
+ const [next, chunk, epochSecret] = current.state.sendCt1Chunk(rng);
1982
+ return {
1983
+ msg: {
1984
+ epoch,
1985
+ payload: {
1986
+ type: "ct1",
1987
+ chunk
1988
+ }
1989
+ },
1990
+ key: epochSecret,
1991
+ state: {
1992
+ tag: "ct1Sampled",
1993
+ state: next
1994
+ }
1995
+ };
1996
+ }
1997
+ case "ct1Sampled": {
1998
+ const [next, chunk] = current.state.sendCt1Chunk();
1999
+ return {
2000
+ msg: {
2001
+ epoch,
2002
+ payload: {
2003
+ type: "ct1",
2004
+ chunk
2005
+ }
2006
+ },
2007
+ key: null,
2008
+ state: {
2009
+ tag: "ct1Sampled",
2010
+ state: next
2011
+ }
2012
+ };
2013
+ }
2014
+ case "ekReceivedCt1Sampled": {
2015
+ const [next, chunk] = current.state.sendCt1Chunk();
2016
+ return {
2017
+ msg: {
2018
+ epoch,
2019
+ payload: {
2020
+ type: "ct1",
2021
+ chunk
2022
+ }
2023
+ },
2024
+ key: null,
2025
+ state: {
2026
+ tag: "ekReceivedCt1Sampled",
2027
+ state: next
2028
+ }
2029
+ };
2030
+ }
2031
+ case "ct1Acknowledged": return {
2032
+ msg: {
2033
+ epoch,
2034
+ payload: { type: "none" }
2035
+ },
2036
+ key: null,
2037
+ state: current
2038
+ };
2039
+ case "ct2Sampled": {
2040
+ const [next, chunk] = current.state.sendCt2Chunk();
2041
+ return {
2042
+ msg: {
2043
+ epoch,
2044
+ payload: {
2045
+ type: "ct2",
2046
+ chunk
2047
+ }
2048
+ },
2049
+ key: null,
2050
+ state: {
2051
+ tag: "ct2Sampled",
2052
+ state: next
2053
+ }
2054
+ };
2055
+ }
2056
+ }
2057
+ }
2058
+ /**
2059
+ * Process an incoming message and transition the state.
2060
+ */
2061
+ function recv$1(current, msg) {
2062
+ const stateEpoch = getEpoch(current);
2063
+ if (msg.epoch < stateEpoch) return {
2064
+ key: null,
2065
+ state: current
2066
+ };
2067
+ if (msg.epoch > stateEpoch) {
2068
+ if (current.tag === "ct2Sampled" && msg.epoch === stateEpoch + 1n) return {
2069
+ key: null,
2070
+ state: {
2071
+ tag: "keysUnsampled",
2072
+ state: current.state.recvNextEpoch(msg.epoch)
2073
+ }
2074
+ };
2075
+ throw new SpqrError(`Epoch too far ahead: state=${stateEpoch}, msg=${msg.epoch}`, SpqrErrorCode.EpochOutOfRange);
2076
+ }
2077
+ const payload = msg.payload;
2078
+ switch (current.tag) {
2079
+ case "keysUnsampled": return {
2080
+ key: null,
2081
+ state: current
2082
+ };
2083
+ case "keysSampled":
2084
+ if (payload.type === "ct1") return {
2085
+ key: null,
2086
+ state: {
2087
+ tag: "headerSent",
2088
+ state: current.state.recvCt1Chunk(msg.epoch, payload.chunk)
2089
+ }
2090
+ };
2091
+ return {
2092
+ key: null,
2093
+ state: current
2094
+ };
2095
+ case "headerSent":
2096
+ if (payload.type === "ct1") {
2097
+ const result = current.state.recvCt1Chunk(msg.epoch, payload.chunk);
2098
+ if (result.done) return {
2099
+ key: null,
2100
+ state: {
2101
+ tag: "ct1Received",
2102
+ state: result.state
2103
+ }
2104
+ };
2105
+ return {
2106
+ key: null,
2107
+ state: {
2108
+ tag: "headerSent",
2109
+ state: result.state
2110
+ }
2111
+ };
2112
+ }
2113
+ return {
2114
+ key: null,
2115
+ state: current
2116
+ };
2117
+ case "ct1Received":
2118
+ if (payload.type === "ct2") return {
2119
+ key: null,
2120
+ state: {
2121
+ tag: "ekSentCt1Received",
2122
+ state: current.state.recvCt2Chunk(msg.epoch, payload.chunk)
2123
+ }
2124
+ };
2125
+ return {
2126
+ key: null,
2127
+ state: current
2128
+ };
2129
+ case "ekSentCt1Received":
2130
+ if (payload.type === "ct2") {
2131
+ const result = current.state.recvCt2Chunk(msg.epoch, payload.chunk);
2132
+ if (result.done) return {
2133
+ key: result.epochSecret,
2134
+ state: {
2135
+ tag: "noHeaderReceived",
2136
+ state: result.state
2137
+ }
2138
+ };
2139
+ return {
2140
+ key: null,
2141
+ state: {
2142
+ tag: "ekSentCt1Received",
2143
+ state: result.state
2144
+ }
2145
+ };
2146
+ }
2147
+ return {
2148
+ key: null,
2149
+ state: current
2150
+ };
2151
+ case "noHeaderReceived":
2152
+ if (payload.type === "hdr") {
2153
+ const result = current.state.recvHdrChunk(msg.epoch, payload.chunk);
2154
+ if (result.done) return {
2155
+ key: null,
2156
+ state: {
2157
+ tag: "headerReceived",
2158
+ state: result.state
2159
+ }
2160
+ };
2161
+ return {
2162
+ key: null,
2163
+ state: {
2164
+ tag: "noHeaderReceived",
2165
+ state: result.state
2166
+ }
2167
+ };
2168
+ }
2169
+ return {
2170
+ key: null,
2171
+ state: current
2172
+ };
2173
+ case "headerReceived": return {
2174
+ key: null,
2175
+ state: current
2176
+ };
2177
+ case "ct1Sampled":
2178
+ if (payload.type === "ek") return mapCt1SampledResult(current.state.recvEkChunk(msg.epoch, payload.chunk, false));
2179
+ if (payload.type === "ekCt1Ack") return mapCt1SampledResult(current.state.recvEkChunk(msg.epoch, payload.chunk, true));
2180
+ return {
2181
+ key: null,
2182
+ state: current
2183
+ };
2184
+ case "ekReceivedCt1Sampled":
2185
+ if (payload.type === "ct1Ack" || payload.type === "ekCt1Ack") return {
2186
+ key: null,
2187
+ state: {
2188
+ tag: "ct2Sampled",
2189
+ state: current.state.recvCt1Ack(msg.epoch)
2190
+ }
2191
+ };
2192
+ return {
2193
+ key: null,
2194
+ state: current
2195
+ };
2196
+ case "ct1Acknowledged":
2197
+ if (payload.type === "ek" || payload.type === "ekCt1Ack") {
2198
+ const chunk = payload.type === "ek" ? payload.chunk : payload.chunk;
2199
+ const result = current.state.recvEkChunk(msg.epoch, chunk);
2200
+ if (result.done) return {
2201
+ key: result.epochSecret,
2202
+ state: {
2203
+ tag: "ct2Sampled",
2204
+ state: result.state
2205
+ }
2206
+ };
2207
+ return {
2208
+ key: null,
2209
+ state: {
2210
+ tag: "ct1Acknowledged",
2211
+ state: result.state
2212
+ }
2213
+ };
2214
+ }
2215
+ return {
2216
+ key: null,
2217
+ state: current
2218
+ };
2219
+ case "ct2Sampled": return {
2220
+ key: null,
2221
+ state: current
2222
+ };
2223
+ }
2224
+ }
2225
+ function mapCt1SampledResult(result) {
2226
+ switch (result.tag) {
2227
+ case "done": return {
2228
+ key: result.epochSecret,
2229
+ state: {
2230
+ tag: "ct2Sampled",
2231
+ state: result.state
2232
+ }
2233
+ };
2234
+ case "stillSending": return {
2235
+ key: result.epochSecret,
2236
+ state: {
2237
+ tag: "ekReceivedCt1Sampled",
2238
+ state: result.state
2239
+ }
2240
+ };
2241
+ case "stillReceiving": return {
2242
+ key: null,
2243
+ state: {
2244
+ tag: "ct1Acknowledged",
2245
+ state: result.state
2246
+ }
2247
+ };
2248
+ case "stillReceivingStillSending": return {
2249
+ key: null,
2250
+ state: {
2251
+ tag: "ct1Sampled",
2252
+ state: result.state
2253
+ }
2254
+ };
2255
+ }
2256
+ }
2257
+
2258
+ //#endregion
2259
+ //#region src/v1/chunked/message.ts
2260
+ var MessageType = /* @__PURE__ */ function(MessageType) {
2261
+ MessageType[MessageType["None"] = 0] = "None";
2262
+ MessageType[MessageType["Hdr"] = 1] = "Hdr";
2263
+ MessageType[MessageType["Ek"] = 2] = "Ek";
2264
+ MessageType[MessageType["EkCt1Ack"] = 3] = "EkCt1Ack";
2265
+ MessageType[MessageType["Ct1Ack"] = 4] = "Ct1Ack";
2266
+ MessageType[MessageType["Ct1"] = 5] = "Ct1";
2267
+ MessageType[MessageType["Ct2"] = 6] = "Ct2";
2268
+ return MessageType;
2269
+ }(MessageType || {});
2270
+ /**
2271
+ * Encode a bigint as LEB128 varint into the output array.
2272
+ */
2273
+ function encodeVarint(value, into) {
2274
+ let v = value;
2275
+ if (v < 0n) v = 0n;
2276
+ do {
2277
+ let byte = Number(v & 127n);
2278
+ v >>= 7n;
2279
+ if (v > 0n) byte |= 128;
2280
+ into.push(byte);
2281
+ } while (v > 0n);
2282
+ }
2283
+ /**
2284
+ * Decode a LEB128 varint from a Uint8Array at the given offset.
2285
+ * Updates offset.offset in place.
2286
+ */
2287
+ function decodeVarint(from, at) {
2288
+ let result = 0n;
2289
+ let shift = 0n;
2290
+ while (shift < 70n) {
2291
+ if (at.offset >= from.length) throw new Error("Varint: unexpected end of data");
2292
+ const byte = from[at.offset++];
2293
+ result |= BigInt(byte & 127) << shift;
2294
+ if ((byte & 128) === 0) return result;
2295
+ shift += 7n;
2296
+ }
2297
+ throw new Error("Varint: too many bytes");
2298
+ }
2299
+ /**
2300
+ * Encode a number (u32) as LEB128 varint into the output array.
2301
+ */
2302
+ function encodeVarint32(value, into) {
2303
+ let v = value >>> 0;
2304
+ do {
2305
+ let byte = v & 127;
2306
+ v >>>= 7;
2307
+ if (v > 0) byte |= 128;
2308
+ into.push(byte);
2309
+ } while (v > 0);
2310
+ }
2311
+ /**
2312
+ * Decode a LEB128 varint as a u32 number.
2313
+ */
2314
+ function decodeVarint32(from, at) {
2315
+ let result = 0;
2316
+ let shift = 0;
2317
+ while (shift < 35) {
2318
+ if (at.offset >= from.length) throw new Error("Varint32: unexpected end of data");
2319
+ const byte = from[at.offset++];
2320
+ result |= (byte & 127) << shift;
2321
+ if ((byte & 128) === 0) return result >>> 0;
2322
+ shift += 7;
2323
+ }
2324
+ throw new Error("Varint32: too many bytes");
2325
+ }
2326
+ const CHUNK_DATA_SIZE = 32;
2327
+ /** Encode a chunk (index varint + 32 data bytes) into the output array. */
2328
+ function encodeChunk(chunk, into) {
2329
+ encodeVarint32(chunk.index, into);
2330
+ for (let i = 0; i < CHUNK_DATA_SIZE; i++) into.push(chunk.data[i]);
2331
+ }
2332
+ /** Decode a chunk from a Uint8Array at the given offset. */
2333
+ function decodeChunk(from, at) {
2334
+ const index = decodeVarint32(from, at);
2335
+ if (at.offset + CHUNK_DATA_SIZE > from.length || index > 65535) throw new Error("Chunk: invalid chunk (data too short or index exceeds u16)");
2336
+ const data = from.slice(at.offset, at.offset + CHUNK_DATA_SIZE);
2337
+ at.offset += CHUNK_DATA_SIZE;
2338
+ return {
2339
+ index,
2340
+ data
2341
+ };
2342
+ }
2343
+ /** Protocol version */
2344
+ const V1 = 1;
2345
+ /**
2346
+ * Serialize a Message with a given sequence index into binary wire format.
2347
+ */
2348
+ function serializeMessage(msg, index) {
2349
+ const out = [];
2350
+ out.push(V1);
2351
+ encodeVarint(msg.epoch, out);
2352
+ encodeVarint32(index, out);
2353
+ const payload = msg.payload;
2354
+ switch (payload.type) {
2355
+ case "none":
2356
+ out.push(MessageType.None);
2357
+ break;
2358
+ case "hdr":
2359
+ out.push(MessageType.Hdr);
2360
+ encodeChunk(payload.chunk, out);
2361
+ break;
2362
+ case "ek":
2363
+ out.push(MessageType.Ek);
2364
+ encodeChunk(payload.chunk, out);
2365
+ break;
2366
+ case "ekCt1Ack":
2367
+ out.push(MessageType.EkCt1Ack);
2368
+ encodeChunk(payload.chunk, out);
2369
+ break;
2370
+ case "ct1Ack":
2371
+ out.push(MessageType.Ct1Ack);
2372
+ break;
2373
+ case "ct1":
2374
+ out.push(MessageType.Ct1);
2375
+ encodeChunk(payload.chunk, out);
2376
+ break;
2377
+ case "ct2":
2378
+ out.push(MessageType.Ct2);
2379
+ encodeChunk(payload.chunk, out);
2380
+ break;
2381
+ }
2382
+ return new Uint8Array(out);
2383
+ }
2384
+ /**
2385
+ * Deserialize a Message from binary wire format.
2386
+ */
2387
+ function deserializeMessage(from) {
2388
+ const at = { offset: 0 };
2389
+ if (at.offset >= from.length) throw new Error("Message: empty data");
2390
+ const version = from[at.offset++];
2391
+ if (version !== V1) throw new Error(`Message: unsupported version ${version}`);
2392
+ const epoch = decodeVarint(from, at);
2393
+ if (epoch === 0n) throw new Error("Message: epoch must be > 0");
2394
+ const index = decodeVarint32(from, at);
2395
+ if (at.offset >= from.length) throw new Error("Message: missing message type");
2396
+ const msgType = from[at.offset++];
2397
+ let payload;
2398
+ switch (msgType) {
2399
+ case MessageType.None:
2400
+ payload = { type: "none" };
2401
+ break;
2402
+ case MessageType.Hdr:
2403
+ payload = {
2404
+ type: "hdr",
2405
+ chunk: decodeChunk(from, at)
2406
+ };
2407
+ break;
2408
+ case MessageType.Ek:
2409
+ payload = {
2410
+ type: "ek",
2411
+ chunk: decodeChunk(from, at)
2412
+ };
2413
+ break;
2414
+ case MessageType.EkCt1Ack:
2415
+ payload = {
2416
+ type: "ekCt1Ack",
2417
+ chunk: decodeChunk(from, at)
2418
+ };
2419
+ break;
2420
+ case MessageType.Ct1Ack:
2421
+ payload = { type: "ct1Ack" };
2422
+ break;
2423
+ case MessageType.Ct1:
2424
+ payload = {
2425
+ type: "ct1",
2426
+ chunk: decodeChunk(from, at)
2427
+ };
2428
+ break;
2429
+ case MessageType.Ct2:
2430
+ payload = {
2431
+ type: "ct2",
2432
+ chunk: decodeChunk(from, at)
2433
+ };
2434
+ break;
2435
+ default: throw new Error(`Message: unknown message type ${msgType}`);
2436
+ }
2437
+ return {
2438
+ msg: {
2439
+ epoch,
2440
+ payload
2441
+ },
2442
+ index,
2443
+ bytesRead: at.offset
2444
+ };
2445
+ }
2446
+
2447
+ //#endregion
2448
+ //#region src/v1/chunked/index.ts
2449
+ /**
2450
+ * Copyright © 2025 Signal Messenger, LLC
2451
+ * Copyright © 2026 Parity Technologies
2452
+ *
2453
+ * Chunked state machine for SPQR V1.
2454
+ *
2455
+ * Provides erasure-coded chunk-by-chunk data transfer wrapping the
2456
+ * unchunked V1 state machine.
2457
+ */
2458
+ _setCreateSendCtNoHeaderReceived((uc, receivingHdr) => new NoHeaderReceived(uc, receivingHdr));
2459
+
2460
+ //#endregion
2461
+ //#region src/v1/chunked/serialize.ts
2462
+ /**
2463
+ * Serialize a runtime States object into PbV1State for protobuf encoding.
2464
+ * Stores the epoch as field 12 so it can be recovered on deserialization.
2465
+ */
2466
+ function statesToPb(s) {
2467
+ const epoch = s.state.epoch;
2468
+ return {
2469
+ innerState: chunkedStateToPb(s),
2470
+ epoch
2471
+ };
2472
+ }
2473
+ function chunkedStateToPb(s) {
2474
+ switch (s.tag) {
2475
+ case "keysUnsampled": return {
2476
+ type: "keysUnsampled",
2477
+ uc: { auth: s.state.uc.auth.toProto() }
2478
+ };
2479
+ case "keysSampled": {
2480
+ const st = s.state;
2481
+ return {
2482
+ type: "keysSampled",
2483
+ uc: {
2484
+ auth: st.uc.auth.toProto(),
2485
+ ek: Uint8Array.from(st.uc.ek),
2486
+ dk: Uint8Array.from(st.uc.dk),
2487
+ hdr: new Uint8Array(0),
2488
+ hdrMac: new Uint8Array(0)
2489
+ },
2490
+ sendingHdr: st.sendingHdr.toProto()
2491
+ };
2492
+ }
2493
+ case "headerSent": {
2494
+ const st = s.state;
2495
+ return {
2496
+ type: "headerSent",
2497
+ uc: {
2498
+ auth: st.uc.auth.toProto(),
2499
+ ek: new Uint8Array(0),
2500
+ dk: Uint8Array.from(st.uc.dk)
2501
+ },
2502
+ sendingEk: st.sendingEk.toProto(),
2503
+ receivingCt1: st.receivingCt1.toProto()
2504
+ };
2505
+ }
2506
+ case "ct1Received": {
2507
+ const st = s.state;
2508
+ return {
2509
+ type: "ct1Received",
2510
+ uc: {
2511
+ auth: st.uc.auth.toProto(),
2512
+ dk: Uint8Array.from(st.uc.dk),
2513
+ ct1: Uint8Array.from(st.uc.ct1)
2514
+ },
2515
+ sendingEk: st.sendingEk.toProto()
2516
+ };
2517
+ }
2518
+ case "ekSentCt1Received": {
2519
+ const st = s.state;
2520
+ return {
2521
+ type: "ekSentCt1Received",
2522
+ uc: {
2523
+ auth: st.uc.auth.toProto(),
2524
+ dk: Uint8Array.from(st.uc.dk),
2525
+ ct1: Uint8Array.from(st.uc.ct1)
2526
+ },
2527
+ receivingCt2: st.receivingCt2.toProto()
2528
+ };
2529
+ }
2530
+ case "noHeaderReceived": {
2531
+ const st = s.state;
2532
+ return {
2533
+ type: "noHeaderReceived",
2534
+ uc: { auth: st.uc.auth.toProto() },
2535
+ receivingHdr: st.receivingHdr.toProto()
2536
+ };
2537
+ }
2538
+ case "headerReceived": {
2539
+ const st = s.state;
2540
+ return {
2541
+ type: "headerReceived",
2542
+ uc: {
2543
+ auth: st.uc.auth.toProto(),
2544
+ hdr: Uint8Array.from(st.uc.hdr),
2545
+ es: new Uint8Array(0),
2546
+ ct1: new Uint8Array(0),
2547
+ ss: new Uint8Array(0)
2548
+ },
2549
+ receivingEk: st.receivingEk.toProto()
2550
+ };
2551
+ }
2552
+ case "ct1Sampled": {
2553
+ const st = s.state;
2554
+ return {
2555
+ type: "ct1Sampled",
2556
+ uc: {
2557
+ auth: st.uc.auth.toProto(),
2558
+ hdr: Uint8Array.from(st.uc.hdr),
2559
+ es: Uint8Array.from(st.uc.es),
2560
+ ct1: Uint8Array.from(st.uc.ct1)
2561
+ },
2562
+ sendingCt1: st.sendingCt1.toProto(),
2563
+ receivingEk: st.receivingEk.toProto()
2564
+ };
2565
+ }
2566
+ case "ekReceivedCt1Sampled": {
2567
+ const st = s.state;
2568
+ return {
2569
+ type: "ekReceivedCt1Sampled",
2570
+ uc: {
2571
+ auth: st.uc.auth.toProto(),
2572
+ hdr: new Uint8Array(0),
2573
+ es: Uint8Array.from(st.uc.es),
2574
+ ek: Uint8Array.from(st.uc.ek),
2575
+ ct1: Uint8Array.from(st.uc.ct1)
2576
+ },
2577
+ sendingCt1: st.sendingCt1.toProto()
2578
+ };
2579
+ }
2580
+ case "ct1Acknowledged": {
2581
+ const st = s.state;
2582
+ return {
2583
+ type: "ct1Acknowledged",
2584
+ uc: {
2585
+ auth: st.uc.auth.toProto(),
2586
+ hdr: Uint8Array.from(st.uc.hdr),
2587
+ es: Uint8Array.from(st.uc.es),
2588
+ ct1: Uint8Array.from(st.uc.ct1)
2589
+ },
2590
+ receivingEk: st.receivingEk.toProto()
2591
+ };
2592
+ }
2593
+ case "ct2Sampled": {
2594
+ const st = s.state;
2595
+ return {
2596
+ type: "ct2Sampled",
2597
+ uc: { auth: st.uc.auth.toProto() },
2598
+ sendingCt2: st.sendingCt2.toProto()
2599
+ };
2600
+ }
2601
+ }
2602
+ }
2603
+ /**
2604
+ * Deserialize a PbV1State back into a runtime States object.
2605
+ *
2606
+ * @param pb - The protobuf V1 state
2607
+ * @returns The reconstructed runtime States
2608
+ */
2609
+ function statesFromPb(pb) {
2610
+ if (pb.innerState === void 0) throw new Error("PbV1State has no innerState");
2611
+ const epoch = pb.epoch ?? 1n;
2612
+ return chunkedStateFromPb(pb.innerState, epoch);
2613
+ }
2614
+ function chunkedStateFromPb(cs, epoch) {
2615
+ switch (cs.type) {
2616
+ case "keysUnsampled": {
2617
+ const auth = authFromPb(cs.uc.auth);
2618
+ const ucState = new KeysUnsampled(epoch, auth);
2619
+ return {
2620
+ tag: "keysUnsampled",
2621
+ state: new KeysUnsampled$1(ucState)
2622
+ };
2623
+ }
2624
+ case "keysSampled": {
2625
+ const auth = authFromPb(cs.uc.auth);
2626
+ const ucState = new HeaderSent(epoch, auth, cs.uc.ek, cs.uc.dk);
2627
+ const encoder = PolyEncoder.fromProto(cs.sendingHdr);
2628
+ return {
2629
+ tag: "keysSampled",
2630
+ state: new KeysSampled(ucState, encoder)
2631
+ };
2632
+ }
2633
+ case "headerSent": {
2634
+ const auth = authFromPb(cs.uc.auth);
2635
+ const ucState = new EkSent(epoch, auth, cs.uc.dk);
2636
+ const encoder = PolyEncoder.fromProto(cs.sendingEk);
2637
+ const decoder = PolyDecoder.fromProto(cs.receivingCt1);
2638
+ return {
2639
+ tag: "headerSent",
2640
+ state: new HeaderSent$1(ucState, encoder, decoder)
2641
+ };
2642
+ }
2643
+ case "ct1Received": {
2644
+ const auth = authFromPb(cs.uc.auth);
2645
+ const ucState = new EkSentCt1Received(epoch, auth, cs.uc.dk, cs.uc.ct1);
2646
+ const encoder = PolyEncoder.fromProto(cs.sendingEk);
2647
+ return {
2648
+ tag: "ct1Received",
2649
+ state: new Ct1Received(ucState, encoder)
2650
+ };
2651
+ }
2652
+ case "ekSentCt1Received": {
2653
+ const auth = authFromPb(cs.uc.auth);
2654
+ const ucState = new EkSentCt1Received(epoch, auth, cs.uc.dk, cs.uc.ct1);
2655
+ const decoder = PolyDecoder.fromProto(cs.receivingCt2);
2656
+ return {
2657
+ tag: "ekSentCt1Received",
2658
+ state: new EkSentCt1Received$1(ucState, decoder)
2659
+ };
2660
+ }
2661
+ case "noHeaderReceived": {
2662
+ const auth = authFromPb(cs.uc.auth);
2663
+ const ucState = new NoHeaderReceived$1(epoch, auth);
2664
+ const decoder = PolyDecoder.fromProto(cs.receivingHdr);
2665
+ return {
2666
+ tag: "noHeaderReceived",
2667
+ state: new NoHeaderReceived(ucState, decoder)
2668
+ };
2669
+ }
2670
+ case "headerReceived": {
2671
+ const auth = authFromPb(cs.uc.auth);
2672
+ const ucState = new HeaderReceived$1(epoch, auth, cs.uc.hdr);
2673
+ const decoder = PolyDecoder.fromProto(cs.receivingEk);
2674
+ return {
2675
+ tag: "headerReceived",
2676
+ state: new HeaderReceived(ucState, decoder)
2677
+ };
2678
+ }
2679
+ case "ct1Sampled": {
2680
+ const auth = authFromPb(cs.uc.auth);
2681
+ const ucState = new Ct1Sent(epoch, auth, cs.uc.hdr, cs.uc.es, cs.uc.ct1);
2682
+ const encoder = PolyEncoder.fromProto(cs.sendingCt1);
2683
+ const decoder = PolyDecoder.fromProto(cs.receivingEk);
2684
+ return {
2685
+ tag: "ct1Sampled",
2686
+ state: new Ct1Sampled(ucState, encoder, decoder)
2687
+ };
2688
+ }
2689
+ case "ekReceivedCt1Sampled": {
2690
+ const auth = authFromPb(cs.uc.auth);
2691
+ const ucState = new Ct1SentEkReceived(epoch, auth, cs.uc.es, cs.uc.ek, cs.uc.ct1);
2692
+ const encoder = PolyEncoder.fromProto(cs.sendingCt1);
2693
+ return {
2694
+ tag: "ekReceivedCt1Sampled",
2695
+ state: new EkReceivedCt1Sampled(ucState, encoder)
2696
+ };
2697
+ }
2698
+ case "ct1Acknowledged": {
2699
+ const auth = authFromPb(cs.uc.auth);
2700
+ const ucState = new Ct1Sent(epoch, auth, cs.uc.hdr, cs.uc.es, cs.uc.ct1);
2701
+ const decoder = PolyDecoder.fromProto(cs.receivingEk);
2702
+ return {
2703
+ tag: "ct1Acknowledged",
2704
+ state: new Ct1Acknowledged(ucState, decoder)
2705
+ };
2706
+ }
2707
+ case "ct2Sampled": {
2708
+ const auth = authFromPb(cs.uc.auth);
2709
+ const ucState = new Ct2Sent(epoch, auth);
2710
+ const encoder = PolyEncoder.fromProto(cs.sendingCt2);
2711
+ return {
2712
+ tag: "ct2Sampled",
2713
+ state: new Ct2Sampled(ucState, encoder)
2714
+ };
2715
+ }
2716
+ }
2717
+ }
2718
+ function authFromPb(pb) {
2719
+ if (pb === void 0) return Authenticator.fromProto({
2720
+ rootKey: new Uint8Array(32),
2721
+ macKey: new Uint8Array(32)
2722
+ });
2723
+ return Authenticator.fromProto(pb);
2724
+ }
2725
+
2726
+ //#endregion
2727
+ //#region src/types.ts
2728
+ /** Protocol version */
2729
+ let Version = /* @__PURE__ */ function(Version) {
2730
+ Version[Version["V0"] = 0] = "V0";
2731
+ Version[Version["V1"] = 1] = "V1";
2732
+ return Version;
2733
+ }({});
2734
+ /** Communication direction */
2735
+ let Direction = /* @__PURE__ */ function(Direction) {
2736
+ Direction[Direction["A2B"] = 0] = "A2B";
2737
+ Direction[Direction["B2A"] = 1] = "B2A";
2738
+ return Direction;
2739
+ }({});
2740
+
2741
+ //#endregion
2742
+ //#region src/chain.ts
2743
+ /**
2744
+ * Copyright © 2025 Signal Messenger, LLC
2745
+ * Copyright © 2026 Parity Technologies
2746
+ *
2747
+ * SPQR Symmetric Chain -- epoch-based key derivation with forward/backward secrecy.
2748
+ *
2749
+ * Ported from Signal's spqr crate: chain.rs
2750
+ *
2751
+ * The Chain manages send/receive keys across epochs. Each epoch has two
2752
+ * directional chains (A2B and B2A). Keys are derived using HKDF-SHA256
2753
+ * with specific info strings that MUST match the Rust implementation
2754
+ * exactly (including the double space in "Chain Start").
2755
+ */
2756
+ const enc = new TextEncoder();
2757
+ const CHAIN_START_INFO = enc.encode(LABEL_CHAIN_START);
2758
+ const CHAIN_NEXT_INFO = enc.encode(LABEL_CHAIN_NEXT);
2759
+ const CHAIN_ADD_EPOCH_INFO = enc.encode(LABEL_CHAIN_ADD_EPOCH);
2760
+ function resolveMaxJump(params) {
2761
+ return params.maxJump > 0 ? params.maxJump : DEFAULT_MAX_JUMP;
2762
+ }
2763
+ function resolveMaxOooKeys(params) {
2764
+ return params.maxOooKeys > 0 ? params.maxOooKeys : DEFAULT_MAX_OOO_KEYS;
2765
+ }
2766
+ function trimSize(params) {
2767
+ const maxOoo = resolveMaxOooKeys(params);
2768
+ return Math.floor(maxOoo * 11 / 10) + 1;
2769
+ }
2770
+ function switchDirection(d) {
2771
+ return d === Direction.A2B ? Direction.B2A : Direction.A2B;
2772
+ }
2773
+ /**
2774
+ * Stores out-of-order keys as packed [index_be32 (4 bytes)][key (32 bytes)] entries.
2775
+ * Matches Rust KeyHistory data layout exactly.
2776
+ */
2777
+ var KeyHistory = class {
2778
+ data;
2779
+ length;
2780
+ constructor(data) {
2781
+ this.data = data !== void 0 ? Uint8Array.from(data) : new Uint8Array(0);
2782
+ this.length = this.data.length;
2783
+ }
2784
+ add(index, key, _params) {
2785
+ const entry = new Uint8Array(KEY_ENTRY_SIZE);
2786
+ new DataView(entry.buffer).setUint32(0, index, false);
2787
+ entry.set(key, 4);
2788
+ const newData = new Uint8Array(this.length + KEY_ENTRY_SIZE);
2789
+ newData.set(this.data.subarray(0, this.length));
2790
+ newData.set(entry, this.length);
2791
+ this.data = newData;
2792
+ this.length += KEY_ENTRY_SIZE;
2793
+ }
2794
+ gc(currentKey, params) {
2795
+ const maxOoo = resolveMaxOooKeys(params);
2796
+ if (this.length >= trimSize(params) * KEY_ENTRY_SIZE) {
2797
+ if (currentKey < maxOoo) throw new Error("KeyHistory.gc: currentKey < maxOooKeys (corrupted state)");
2798
+ const trimHorizon = currentKey - maxOoo;
2799
+ let i = 0;
2800
+ while (i < this.length) if (trimHorizon > new DataView(this.data.buffer, this.data.byteOffset + i, 4).getUint32(0, false)) this.removeAt(i);
2801
+ else i += KEY_ENTRY_SIZE;
2802
+ }
2803
+ }
2804
+ clear() {
2805
+ this.data = new Uint8Array(0);
2806
+ this.length = 0;
2807
+ }
2808
+ get(at, currentCtr, params) {
2809
+ if (at + resolveMaxOooKeys(params) < currentCtr) throw new SpqrError(`Key trimmed: ${at}`, SpqrErrorCode.KeyTrimmed);
2810
+ const want = new Uint8Array(4);
2811
+ new DataView(want.buffer).setUint32(0, at, false);
2812
+ for (let i = 0; i < this.length; i += KEY_ENTRY_SIZE) if (this.data[i] === want[0] && this.data[i + 1] === want[1] && this.data[i + 2] === want[2] && this.data[i + 3] === want[3]) {
2813
+ const key = this.data.slice(i + 4, i + KEY_ENTRY_SIZE);
2814
+ this.removeAt(i);
2815
+ return key;
2816
+ }
2817
+ throw new SpqrError(`Key already requested: ${at}`, SpqrErrorCode.KeyAlreadyRequested);
2818
+ }
2819
+ removeAt(index) {
2820
+ if (index + KEY_ENTRY_SIZE < this.length) {
2821
+ const lastStart = this.length - KEY_ENTRY_SIZE;
2822
+ this.data.copyWithin(index, lastStart, this.length);
2823
+ }
2824
+ this.length -= KEY_ENTRY_SIZE;
2825
+ }
2826
+ /** Serialize to raw bytes for protobuf storage */
2827
+ serialize() {
2828
+ return this.data.slice(0, this.length);
2829
+ }
2830
+ };
2831
+ /**
2832
+ * One directional chain within an epoch (send or recv).
2833
+ * Manages a ratcheting key derivation with HKDF.
2834
+ */
2835
+ var ChainEpochDirection = class ChainEpochDirection {
2836
+ ctr;
2837
+ next;
2838
+ prev;
2839
+ constructor(key, ctr, prev) {
2840
+ this.ctr = ctr ?? 0;
2841
+ this.next = Uint8Array.from(key);
2842
+ this.prev = new KeyHistory(prev);
2843
+ }
2844
+ /**
2845
+ * Derive the next key in the chain.
2846
+ * Returns [index, key].
2847
+ */
2848
+ nextKey() {
2849
+ this.ctr += 1;
2850
+ const info = concat(uint32ToBE4(this.ctr), CHAIN_NEXT_INFO);
2851
+ const gen = hkdfSha256(this.next, ZERO_SALT, info, 64);
2852
+ this.next = gen.slice(0, 32);
2853
+ const key = gen.slice(32, 64);
2854
+ return [this.ctr, key];
2855
+ }
2856
+ /**
2857
+ * Internal: derive next key without consuming it publicly.
2858
+ * Used for skipping ahead to build out-of-order key history.
2859
+ */
2860
+ static nextKeyInternal(next, ctr) {
2861
+ ctr += 1;
2862
+ const gen = hkdfSha256(next, ZERO_SALT, concat(uint32ToBE4(ctr), CHAIN_NEXT_INFO), 64);
2863
+ return {
2864
+ next: gen.slice(0, 32),
2865
+ ctr,
2866
+ index: ctr,
2867
+ key: gen.slice(32, 64)
2868
+ };
2869
+ }
2870
+ /**
2871
+ * Get the key at a specific counter position.
2872
+ * Supports out-of-order access via KeyHistory.
2873
+ */
2874
+ key(at, params) {
2875
+ const maxJump = resolveMaxJump(params);
2876
+ const maxOoo = resolveMaxOooKeys(params);
2877
+ if (at > this.ctr) {
2878
+ if (at - this.ctr > maxJump) throw new SpqrError(`Key jump: ${this.ctr} - ${at}`, SpqrErrorCode.KeyJump);
2879
+ } else if (at < this.ctr) return this.prev.get(at, this.ctr, params);
2880
+ else throw new SpqrError(`Key already requested: ${at}`, SpqrErrorCode.KeyAlreadyRequested);
2881
+ if (at > this.ctr + maxOoo) this.prev.clear();
2882
+ while (at > this.ctr + 1) {
2883
+ const result = ChainEpochDirection.nextKeyInternal(this.next, this.ctr);
2884
+ this.next = result.next;
2885
+ this.ctr = result.ctr;
2886
+ if (this.ctr + maxOoo >= at) this.prev.add(result.index, result.key, params);
2887
+ }
2888
+ this.prev.gc(this.ctr, params);
2889
+ const result = ChainEpochDirection.nextKeyInternal(this.next, this.ctr);
2890
+ this.next = result.next;
2891
+ this.ctr = result.ctr;
2892
+ return result.key;
2893
+ }
2894
+ clearNext() {
2895
+ this.next = new Uint8Array(0);
2896
+ }
2897
+ /** Serialize to protobuf EpochDirection */
2898
+ toProto() {
2899
+ return {
2900
+ ctr: this.ctr,
2901
+ next: Uint8Array.from(this.next),
2902
+ prev: this.prev.serialize()
2903
+ };
2904
+ }
2905
+ static fromProto(pb) {
2906
+ return new ChainEpochDirection(pb.next, pb.ctr, pb.prev);
2907
+ }
2908
+ };
2909
+ /**
2910
+ * The main Chain manages send/receive keys across all epochs.
2911
+ *
2912
+ * Port of Rust's Chain struct from chain.rs.
2913
+ * Uses bigint for epoch values (matching Rust u64).
2914
+ */
2915
+ var Chain = class Chain {
2916
+ dir;
2917
+ currentEpoch;
2918
+ sendEpoch;
2919
+ links;
2920
+ nextRoot;
2921
+ params;
2922
+ constructor(dir, currentEpoch, sendEpoch, links, nextRoot, params) {
2923
+ this.dir = dir;
2924
+ this.currentEpoch = currentEpoch;
2925
+ this.sendEpoch = sendEpoch;
2926
+ this.links = links;
2927
+ this.nextRoot = nextRoot;
2928
+ this.params = params;
2929
+ }
2930
+ /**
2931
+ * Create a new chain from an initial key and direction.
2932
+ * HKDF info: "Signal PQ Ratchet V1 Chain Start" (TWO spaces before Start!)
2933
+ *
2934
+ * The 96-byte HKDF output is split as:
2935
+ * [0..32]: nextRoot
2936
+ * [32..64]: A2B chain seed
2937
+ * [64..96]: B2A chain seed
2938
+ *
2939
+ * Direction determines which half is send vs recv.
2940
+ */
2941
+ static create(initialKey, dir, params = {
2942
+ maxJump: DEFAULT_MAX_JUMP,
2943
+ maxOooKeys: DEFAULT_MAX_OOO_KEYS
2944
+ }) {
2945
+ const gen = hkdfSha256(initialKey, ZERO_SALT, CHAIN_START_INFO, 96);
2946
+ const switchedDir = switchDirection(dir);
2947
+ const sendKey = cedForDirection(gen, dir);
2948
+ const recvKey = cedForDirection(gen, switchedDir);
2949
+ return new Chain(dir, 0n, 0n, [{
2950
+ send: new ChainEpochDirection(sendKey),
2951
+ recv: new ChainEpochDirection(recvKey)
2952
+ }], gen.slice(0, 32), params);
2953
+ }
2954
+ /**
2955
+ * Add a new epoch to the chain with a shared secret.
2956
+ * HKDF info: "Signal PQ Ratchet V1 Chain Add Epoch"
2957
+ *
2958
+ * Salt = current nextRoot, IKM = epochSecret.secret
2959
+ */
2960
+ addEpoch(epochSecret) {
2961
+ if (epochSecret.epoch !== this.currentEpoch + 1n) throw new SpqrError(`Expected epoch ${this.currentEpoch + 1n}, got ${epochSecret.epoch}`, SpqrErrorCode.EpochOutOfRange);
2962
+ const gen = hkdfSha256(epochSecret.secret, this.nextRoot, CHAIN_ADD_EPOCH_INFO, 96);
2963
+ this.currentEpoch = epochSecret.epoch;
2964
+ this.nextRoot = gen.slice(0, 32);
2965
+ const sendKey = cedForDirection(gen, this.dir);
2966
+ const recvKey = cedForDirection(gen, switchDirection(this.dir));
2967
+ this.links.push({
2968
+ send: new ChainEpochDirection(sendKey),
2969
+ recv: new ChainEpochDirection(recvKey)
2970
+ });
2971
+ }
2972
+ epochIdx(epoch) {
2973
+ if (epoch > this.currentEpoch) throw new SpqrError(`Epoch not in valid range: ${epoch}`, SpqrErrorCode.EpochOutOfRange);
2974
+ const back = Number(this.currentEpoch - epoch);
2975
+ if (back >= this.links.length) throw new SpqrError(`Epoch not in valid range: ${epoch}`, SpqrErrorCode.EpochOutOfRange);
2976
+ return this.links.length - 1 - back;
2977
+ }
2978
+ /**
2979
+ * Get the next send key for a given epoch.
2980
+ * Returns [index, key].
2981
+ */
2982
+ sendKey(epoch) {
2983
+ if (epoch < this.sendEpoch) throw new SpqrError(`Send key epoch decreased (${this.sendEpoch} -> ${epoch})`, SpqrErrorCode.SendKeyEpochDecreased);
2984
+ let epochIndex = this.epochIdx(epoch);
2985
+ if (this.sendEpoch !== epoch) {
2986
+ this.sendEpoch = epoch;
2987
+ while (epochIndex > EPOCHS_TO_KEEP_PRIOR_TO_SEND_EPOCH) {
2988
+ this.links.shift();
2989
+ epochIndex -= 1;
2990
+ }
2991
+ for (let i = 0; i < epochIndex; i++) this.links[i].send.clearNext();
2992
+ }
2993
+ return this.links[epochIndex].send.nextKey();
2994
+ }
2995
+ /**
2996
+ * Get a receive key for a given epoch and counter index.
2997
+ */
2998
+ recvKey(epoch, index) {
2999
+ const epochIndex = this.epochIdx(epoch);
3000
+ return this.links[epochIndex].recv.key(index, this.params);
3001
+ }
3002
+ /**
3003
+ * Serialize the chain to its protobuf representation.
3004
+ * Matches Rust Chain::into_pb().
3005
+ */
3006
+ toProto() {
3007
+ return {
3008
+ direction: this.dir,
3009
+ currentEpoch: this.currentEpoch,
3010
+ sendEpoch: this.sendEpoch,
3011
+ nextRoot: Uint8Array.from(this.nextRoot),
3012
+ links: this.links.map((link) => ({
3013
+ send: link.send.toProto(),
3014
+ recv: link.recv.toProto()
3015
+ })),
3016
+ params: chainParamsToProto(this.params)
3017
+ };
3018
+ }
3019
+ /**
3020
+ * Deserialize a chain from its protobuf representation.
3021
+ * Matches Rust Chain::from_pb().
3022
+ */
3023
+ static fromProto(pb) {
3024
+ const links = pb.links.map((link) => ({
3025
+ send: ChainEpochDirection.fromProto(link.send ?? {
3026
+ ctr: 0,
3027
+ next: new Uint8Array(0),
3028
+ prev: new Uint8Array(0)
3029
+ }),
3030
+ recv: ChainEpochDirection.fromProto(link.recv ?? {
3031
+ ctr: 0,
3032
+ next: new Uint8Array(0),
3033
+ prev: new Uint8Array(0)
3034
+ })
3035
+ }));
3036
+ const params = chainParamsFromProto(pb.params);
3037
+ return new Chain(pb.direction, pb.currentEpoch, pb.sendEpoch, links, Uint8Array.from(pb.nextRoot), params);
3038
+ }
3039
+ };
3040
+ /**
3041
+ * Select the chain key for a given direction from a 96-byte HKDF output.
3042
+ * A2B uses bytes [32..64], B2A uses bytes [64..96].
3043
+ */
3044
+ function cedForDirection(gen, dir) {
3045
+ return dir === Direction.A2B ? gen.slice(32, 64) : gen.slice(64, 96);
3046
+ }
3047
+ /**
3048
+ * Convert ChainParams to protobuf format.
3049
+ * Default values are stored as 0 (proto3 convention).
3050
+ */
3051
+ function chainParamsToProto(params) {
3052
+ return {
3053
+ maxJump: params.maxJump === DEFAULT_MAX_JUMP ? 0 : params.maxJump,
3054
+ maxOooKeys: params.maxOooKeys === DEFAULT_MAX_OOO_KEYS ? 0 : params.maxOooKeys
3055
+ };
3056
+ }
3057
+ /**
3058
+ * Convert protobuf ChainParams to runtime ChainParams.
3059
+ * Zero values are interpreted as defaults.
3060
+ */
3061
+ function chainParamsFromProto(pb) {
3062
+ if (pb === void 0) return {
3063
+ maxJump: DEFAULT_MAX_JUMP,
3064
+ maxOooKeys: DEFAULT_MAX_OOO_KEYS
3065
+ };
3066
+ return {
3067
+ maxJump: pb.maxJump > 0 ? pb.maxJump : DEFAULT_MAX_JUMP,
3068
+ maxOooKeys: pb.maxOooKeys > 0 ? pb.maxOooKeys : DEFAULT_MAX_OOO_KEYS
3069
+ };
3070
+ }
3071
+
3072
+ //#endregion
3073
+ //#region src/proto/index.ts
3074
+ const WIRE_VARINT = 0;
3075
+ const WIRE_LENGTH_DELIMITED = 2;
3076
+ const EMPTY_BYTES = new Uint8Array(0);
3077
+ var ProtoWriter = class {
3078
+ parts = [];
3079
+ writeVarint(fieldNumber, value) {
3080
+ if (typeof value === "bigint") {
3081
+ if (value === 0n) return;
3082
+ this.writeTag(fieldNumber, WIRE_VARINT);
3083
+ this.writeRawVarint64(value);
3084
+ } else {
3085
+ if (value === 0) return;
3086
+ this.writeTag(fieldNumber, WIRE_VARINT);
3087
+ this.writeRawVarint(value);
3088
+ }
3089
+ }
3090
+ /** Write a varint field even when the value is zero (for required fields). */
3091
+ writeVarintAlways(fieldNumber, value) {
3092
+ if (typeof value === "bigint") {
3093
+ this.writeTag(fieldNumber, WIRE_VARINT);
3094
+ this.writeRawVarint64(value);
3095
+ } else {
3096
+ this.writeTag(fieldNumber, WIRE_VARINT);
3097
+ this.writeRawVarint(value);
3098
+ }
3099
+ }
3100
+ writeBool(fieldNumber, value) {
3101
+ if (!value) return;
3102
+ this.writeTag(fieldNumber, WIRE_VARINT);
3103
+ this.writeRawVarint(1);
3104
+ }
3105
+ writeBytes(fieldNumber, data) {
3106
+ if (data.length === 0) return;
3107
+ this.writeTag(fieldNumber, WIRE_LENGTH_DELIMITED);
3108
+ this.writeRawVarint(data.length);
3109
+ this.parts.push(new Uint8Array(data));
3110
+ }
3111
+ writeMessage(fieldNumber, writer) {
3112
+ const data = writer.finish();
3113
+ if (data.length === 0) return;
3114
+ this.writeTag(fieldNumber, WIRE_LENGTH_DELIMITED);
3115
+ this.writeRawVarint(data.length);
3116
+ this.parts.push(data);
3117
+ }
3118
+ /** For oneof fields, write even if the sub-message is empty. */
3119
+ writeMessageAlways(fieldNumber, writer) {
3120
+ const data = writer.finish();
3121
+ this.writeTag(fieldNumber, WIRE_LENGTH_DELIMITED);
3122
+ this.writeRawVarint(data.length);
3123
+ if (data.length > 0) this.parts.push(data);
3124
+ }
3125
+ writeEnum(fieldNumber, value) {
3126
+ this.writeVarint(fieldNumber, value);
3127
+ }
3128
+ finish() {
3129
+ let total = 0;
3130
+ for (const p of this.parts) total += p.length;
3131
+ const result = new Uint8Array(total);
3132
+ let offset = 0;
3133
+ for (const p of this.parts) {
3134
+ result.set(p, offset);
3135
+ offset += p.length;
3136
+ }
3137
+ return result;
3138
+ }
3139
+ writeTag(fieldNumber, wireType) {
3140
+ this.writeRawVarint(fieldNumber << 3 | wireType);
3141
+ }
3142
+ writeRawVarint(value) {
3143
+ const buf = [];
3144
+ let v = value >>> 0;
3145
+ while (v > 127) {
3146
+ buf.push(v & 127 | 128);
3147
+ v >>>= 7;
3148
+ }
3149
+ buf.push(v & 127);
3150
+ this.parts.push(new Uint8Array(buf));
3151
+ }
3152
+ writeRawVarint64(value) {
3153
+ const buf = [];
3154
+ let v = value;
3155
+ while (v > 127n) {
3156
+ buf.push(Number(v & 127n) | 128);
3157
+ v >>= 7n;
3158
+ }
3159
+ buf.push(Number(v & 127n));
3160
+ this.parts.push(new Uint8Array(buf));
3161
+ }
3162
+ };
3163
+ var ProtoReader = class ProtoReader {
3164
+ data;
3165
+ pos;
3166
+ constructor(data) {
3167
+ this.data = data;
3168
+ this.pos = 0;
3169
+ }
3170
+ get remaining() {
3171
+ return this.data.length - this.pos;
3172
+ }
3173
+ get done() {
3174
+ return this.pos >= this.data.length;
3175
+ }
3176
+ readField() {
3177
+ if (this.pos >= this.data.length) return null;
3178
+ const tag = this.readRawVarint();
3179
+ return {
3180
+ fieldNumber: tag >>> 3,
3181
+ wireType: tag & 7
3182
+ };
3183
+ }
3184
+ readVarint() {
3185
+ return this.readRawVarint();
3186
+ }
3187
+ readVarint64() {
3188
+ return this.readRawVarint64();
3189
+ }
3190
+ readBool() {
3191
+ return this.readRawVarint() !== 0;
3192
+ }
3193
+ readBytes() {
3194
+ const len = this.readRawVarint();
3195
+ const data = this.data.slice(this.pos, this.pos + len);
3196
+ this.pos += len;
3197
+ return data;
3198
+ }
3199
+ readMessage() {
3200
+ return new ProtoReader(this.readBytes());
3201
+ }
3202
+ readEnum() {
3203
+ return this.readRawVarint();
3204
+ }
3205
+ skip(wireType) {
3206
+ switch (wireType) {
3207
+ case 0:
3208
+ this.readRawVarint();
3209
+ break;
3210
+ case 1:
3211
+ this.pos += 8;
3212
+ break;
3213
+ case 2: {
3214
+ const len = this.readRawVarint();
3215
+ this.pos += len;
3216
+ break;
3217
+ }
3218
+ case 5:
3219
+ this.pos += 4;
3220
+ break;
3221
+ default: throw new Error(`Unknown wire type: ${wireType}`);
3222
+ }
3223
+ }
3224
+ readRawVarint() {
3225
+ let result = 0;
3226
+ let shift = 0;
3227
+ while (shift < 35) {
3228
+ const b = this.data[this.pos++];
3229
+ result |= (b & 127) << shift;
3230
+ if ((b & 128) === 0) return result >>> 0;
3231
+ shift += 7;
3232
+ }
3233
+ throw new Error("Varint too long");
3234
+ }
3235
+ readRawVarint64() {
3236
+ let result = 0n;
3237
+ let shift = 0n;
3238
+ while (shift < 70n) {
3239
+ const b = this.data[this.pos++];
3240
+ result |= BigInt(b & 127) << shift;
3241
+ if ((b & 128) === 0) return result;
3242
+ shift += 7n;
3243
+ }
3244
+ throw new Error("Varint64 too long");
3245
+ }
3246
+ };
3247
+ function decodeChainParams(data) {
3248
+ const r = new ProtoReader(data);
3249
+ let maxJump = 0;
3250
+ let maxOooKeys = 0;
3251
+ while (!r.done) {
3252
+ const field = r.readField();
3253
+ if (field === null) break;
3254
+ switch (field.fieldNumber) {
3255
+ case 1:
3256
+ maxJump = r.readVarint();
3257
+ break;
3258
+ case 2:
3259
+ maxOooKeys = r.readVarint();
3260
+ break;
3261
+ default: r.skip(field.wireType);
3262
+ }
3263
+ }
3264
+ return {
3265
+ maxJump,
3266
+ maxOooKeys
3267
+ };
3268
+ }
3269
+ function encodeVersionNegotiation(msg) {
3270
+ const w = new ProtoWriter();
3271
+ w.writeBytes(1, msg.authKey);
3272
+ w.writeEnum(2, msg.direction);
3273
+ w.writeEnum(3, msg.minVersion);
3274
+ if (msg.chainParams !== void 0) {
3275
+ const sub = new ProtoWriter();
3276
+ sub.writeVarint(1, msg.chainParams.maxJump);
3277
+ sub.writeVarint(2, msg.chainParams.maxOooKeys);
3278
+ w.writeMessage(4, sub);
3279
+ }
3280
+ return w.finish();
3281
+ }
3282
+ function decodeVersionNegotiation(data) {
3283
+ const r = new ProtoReader(data);
3284
+ let authKey = EMPTY_BYTES;
3285
+ let direction = 0;
3286
+ let minVersion = 0;
3287
+ let chainParams;
3288
+ while (!r.done) {
3289
+ const field = r.readField();
3290
+ if (field === null) break;
3291
+ switch (field.fieldNumber) {
3292
+ case 1:
3293
+ authKey = r.readBytes();
3294
+ break;
3295
+ case 2:
3296
+ direction = r.readEnum();
3297
+ break;
3298
+ case 3:
3299
+ minVersion = r.readEnum();
3300
+ break;
3301
+ case 4:
3302
+ chainParams = decodeChainParams(r.readBytes());
3303
+ break;
3304
+ default: r.skip(field.wireType);
3305
+ }
3306
+ }
3307
+ return {
3308
+ authKey,
3309
+ direction,
3310
+ minVersion,
3311
+ chainParams
3312
+ };
3313
+ }
3314
+ function encodeEpochDirection(w, msg) {
3315
+ w.writeVarint(1, msg.ctr);
3316
+ w.writeBytes(2, msg.next);
3317
+ w.writeBytes(3, msg.prev);
3318
+ }
3319
+ function decodeEpochDirection(r) {
3320
+ let ctr = 0;
3321
+ let next = EMPTY_BYTES;
3322
+ let prev = EMPTY_BYTES;
3323
+ while (!r.done) {
3324
+ const field = r.readField();
3325
+ if (field === null) break;
3326
+ switch (field.fieldNumber) {
3327
+ case 1:
3328
+ ctr = r.readVarint();
3329
+ break;
3330
+ case 2:
3331
+ next = r.readBytes();
3332
+ break;
3333
+ case 3:
3334
+ prev = r.readBytes();
3335
+ break;
3336
+ default: r.skip(field.wireType);
3337
+ }
3338
+ }
3339
+ return {
3340
+ ctr,
3341
+ next,
3342
+ prev
3343
+ };
3344
+ }
3345
+ function encodeEpoch(msg) {
3346
+ const w = new ProtoWriter();
3347
+ if (msg.send !== void 0) {
3348
+ const sub = new ProtoWriter();
3349
+ encodeEpochDirection(sub, msg.send);
3350
+ w.writeMessage(1, sub);
3351
+ }
3352
+ if (msg.recv !== void 0) {
3353
+ const sub = new ProtoWriter();
3354
+ encodeEpochDirection(sub, msg.recv);
3355
+ w.writeMessage(2, sub);
3356
+ }
3357
+ return w.finish();
3358
+ }
3359
+ function decodeEpoch(data) {
3360
+ const r = new ProtoReader(data);
3361
+ let send;
3362
+ let recv;
3363
+ while (!r.done) {
3364
+ const field = r.readField();
3365
+ if (field === null) break;
3366
+ switch (field.fieldNumber) {
3367
+ case 1:
3368
+ send = decodeEpochDirection(r.readMessage());
3369
+ break;
3370
+ case 2:
3371
+ recv = decodeEpochDirection(r.readMessage());
3372
+ break;
3373
+ default: r.skip(field.wireType);
3374
+ }
3375
+ }
3376
+ return {
3377
+ send,
3378
+ recv
3379
+ };
3380
+ }
3381
+ function encodeChain(msg) {
3382
+ const w = new ProtoWriter();
3383
+ w.writeEnum(1, msg.direction);
3384
+ w.writeVarint(2, msg.currentEpoch);
3385
+ for (const link of msg.links) w.writeBytes(3, encodeEpoch(link));
3386
+ w.writeBytes(4, msg.nextRoot);
3387
+ w.writeVarint(5, msg.sendEpoch);
3388
+ if (msg.params !== void 0) {
3389
+ const sub = new ProtoWriter();
3390
+ sub.writeVarint(1, msg.params.maxJump);
3391
+ sub.writeVarint(2, msg.params.maxOooKeys);
3392
+ w.writeMessage(6, sub);
3393
+ }
3394
+ return w.finish();
3395
+ }
3396
+ function decodeChain(data) {
3397
+ const r = new ProtoReader(data);
3398
+ let direction = 0;
3399
+ let currentEpoch = 0n;
3400
+ const links = [];
3401
+ let nextRoot = EMPTY_BYTES;
3402
+ let sendEpoch = 0n;
3403
+ let params;
3404
+ while (!r.done) {
3405
+ const field = r.readField();
3406
+ if (field === null) break;
3407
+ switch (field.fieldNumber) {
3408
+ case 1:
3409
+ direction = r.readEnum();
3410
+ break;
3411
+ case 2:
3412
+ currentEpoch = r.readVarint64();
3413
+ break;
3414
+ case 3:
3415
+ links.push(decodeEpoch(r.readBytes()));
3416
+ break;
3417
+ case 4:
3418
+ nextRoot = r.readBytes();
3419
+ break;
3420
+ case 5:
3421
+ sendEpoch = r.readVarint64();
3422
+ break;
3423
+ case 6:
3424
+ params = decodeChainParams(r.readBytes());
3425
+ break;
3426
+ default: r.skip(field.wireType);
3427
+ }
3428
+ }
3429
+ return {
3430
+ direction,
3431
+ currentEpoch,
3432
+ links,
3433
+ nextRoot,
3434
+ sendEpoch,
3435
+ params
3436
+ };
3437
+ }
3438
+ function decodeAuthenticator(data) {
3439
+ const r = new ProtoReader(data);
3440
+ let rootKey = EMPTY_BYTES;
3441
+ let macKey = EMPTY_BYTES;
3442
+ while (!r.done) {
3443
+ const field = r.readField();
3444
+ if (field === null) break;
3445
+ switch (field.fieldNumber) {
3446
+ case 1:
3447
+ rootKey = r.readBytes();
3448
+ break;
3449
+ case 2:
3450
+ macKey = r.readBytes();
3451
+ break;
3452
+ default: r.skip(field.wireType);
3453
+ }
3454
+ }
3455
+ return {
3456
+ rootKey,
3457
+ macKey
3458
+ };
3459
+ }
3460
+ function encodePolynomialEncoder(msg) {
3461
+ const w = new ProtoWriter();
3462
+ w.writeVarint(1, msg.idx);
3463
+ for (const pt of msg.pts) w.writeBytes(2, pt);
3464
+ for (const poly of msg.polys) w.writeBytes(3, poly);
3465
+ return w.finish();
3466
+ }
3467
+ function decodePolynomialEncoder(data) {
3468
+ const r = new ProtoReader(data);
3469
+ let idx = 0;
3470
+ const pts = [];
3471
+ const polys = [];
3472
+ while (!r.done) {
3473
+ const field = r.readField();
3474
+ if (field === null) break;
3475
+ switch (field.fieldNumber) {
3476
+ case 1:
3477
+ idx = r.readVarint();
3478
+ break;
3479
+ case 2:
3480
+ pts.push(r.readBytes());
3481
+ break;
3482
+ case 3:
3483
+ polys.push(r.readBytes());
3484
+ break;
3485
+ default: r.skip(field.wireType);
3486
+ }
3487
+ }
3488
+ return {
3489
+ idx,
3490
+ pts,
3491
+ polys
3492
+ };
3493
+ }
3494
+ function encodePolynomialDecoder(msg) {
3495
+ const w = new ProtoWriter();
3496
+ w.writeVarint(1, msg.ptsNeeded);
3497
+ w.writeVarint(2, msg.polys);
3498
+ for (const pt of msg.pts) w.writeBytes(3, pt);
3499
+ w.writeBool(4, msg.isComplete);
3500
+ return w.finish();
3501
+ }
3502
+ function decodePolynomialDecoder(data) {
3503
+ const r = new ProtoReader(data);
3504
+ let ptsNeeded = 0;
3505
+ let polys = 0;
3506
+ const pts = [];
3507
+ let isComplete = false;
3508
+ while (!r.done) {
3509
+ const field = r.readField();
3510
+ if (field === null) break;
3511
+ switch (field.fieldNumber) {
3512
+ case 1:
3513
+ ptsNeeded = r.readVarint();
3514
+ break;
3515
+ case 2:
3516
+ polys = r.readVarint();
3517
+ break;
3518
+ case 3:
3519
+ pts.push(r.readBytes());
3520
+ break;
3521
+ case 4:
3522
+ isComplete = r.readBool();
3523
+ break;
3524
+ default: r.skip(field.wireType);
3525
+ }
3526
+ }
3527
+ return {
3528
+ ptsNeeded,
3529
+ polys,
3530
+ pts,
3531
+ isComplete
3532
+ };
3533
+ }
3534
+ function encodeAuthOptional(w, fieldNumber, auth) {
3535
+ if (auth !== void 0) {
3536
+ const sub = new ProtoWriter();
3537
+ sub.writeBytes(1, auth.rootKey);
3538
+ sub.writeBytes(2, auth.macKey);
3539
+ w.writeMessage(fieldNumber, sub);
3540
+ }
3541
+ }
3542
+ function decodeAuthOptional(r) {
3543
+ return decodeAuthenticator(r.readBytes());
3544
+ }
3545
+ /**
3546
+ * Detect whether an unchunked sub-message uses the Rust field layout
3547
+ * (epoch at field 1, auth at field 2, data fields shifted +1) or the
3548
+ * TS layout (auth at field 1, data fields as-is).
3549
+ *
3550
+ * Returns the field number offset: 0 for TS layout, 1 for Rust layout.
3551
+ * Detection is based on field 1's wire type:
3552
+ * - varint (wire type 0) => Rust layout (field 1 = epoch)
3553
+ * - length-delimited (wire type 2) => TS layout (field 1 = auth)
3554
+ */
3555
+ function detectUcLayout(data) {
3556
+ if (data.length === 0) return 0;
3557
+ return (data[0] & 7) === WIRE_VARINT ? 1 : 0;
3558
+ }
3559
+ function encodeUcKeysUnsampled(msg) {
3560
+ const w = new ProtoWriter();
3561
+ encodeAuthOptional(w, 1, msg.auth);
3562
+ return w.finish();
3563
+ }
3564
+ function decodeUcKeysUnsampled(data) {
3565
+ const r = new ProtoReader(data);
3566
+ const offset = detectUcLayout(data);
3567
+ let auth;
3568
+ while (!r.done) {
3569
+ const field = r.readField();
3570
+ if (field === null) break;
3571
+ switch (field.fieldNumber - offset) {
3572
+ case 1:
3573
+ auth = decodeAuthOptional(r);
3574
+ break;
3575
+ default: r.skip(field.wireType);
3576
+ }
3577
+ }
3578
+ return { auth };
3579
+ }
3580
+ function encodeUcKeysSampled(msg) {
3581
+ const w = new ProtoWriter();
3582
+ encodeAuthOptional(w, 1, msg.auth);
3583
+ w.writeBytes(2, msg.ek);
3584
+ w.writeBytes(3, msg.dk);
3585
+ w.writeBytes(4, msg.hdr);
3586
+ w.writeBytes(5, msg.hdrMac);
3587
+ return w.finish();
3588
+ }
3589
+ function decodeUcKeysSampled(data) {
3590
+ const r = new ProtoReader(data);
3591
+ const offset = detectUcLayout(data);
3592
+ let auth;
3593
+ let ek = EMPTY_BYTES;
3594
+ let dk = EMPTY_BYTES;
3595
+ let hdr = EMPTY_BYTES;
3596
+ let hdrMac = EMPTY_BYTES;
3597
+ while (!r.done) {
3598
+ const field = r.readField();
3599
+ if (field === null) break;
3600
+ switch (field.fieldNumber - offset) {
3601
+ case 1:
3602
+ auth = decodeAuthOptional(r);
3603
+ break;
3604
+ case 2:
3605
+ ek = r.readBytes();
3606
+ break;
3607
+ case 3:
3608
+ dk = r.readBytes();
3609
+ break;
3610
+ case 4:
3611
+ hdr = r.readBytes();
3612
+ break;
3613
+ case 5:
3614
+ hdrMac = r.readBytes();
3615
+ break;
3616
+ default: r.skip(field.wireType);
3617
+ }
3618
+ }
3619
+ return {
3620
+ auth,
3621
+ ek,
3622
+ dk,
3623
+ hdr,
3624
+ hdrMac
3625
+ };
3626
+ }
3627
+ function encodeUcHeaderSent(msg) {
3628
+ const w = new ProtoWriter();
3629
+ encodeAuthOptional(w, 1, msg.auth);
3630
+ w.writeBytes(2, msg.ek);
3631
+ w.writeBytes(3, msg.dk);
3632
+ return w.finish();
3633
+ }
3634
+ function decodeUcHeaderSent(data) {
3635
+ const r = new ProtoReader(data);
3636
+ const offset = detectUcLayout(data);
3637
+ let auth;
3638
+ let ek = EMPTY_BYTES;
3639
+ let dk = EMPTY_BYTES;
3640
+ while (!r.done) {
3641
+ const field = r.readField();
3642
+ if (field === null) break;
3643
+ switch (field.fieldNumber - offset) {
3644
+ case 1:
3645
+ auth = decodeAuthOptional(r);
3646
+ break;
3647
+ case 2:
3648
+ ek = r.readBytes();
3649
+ break;
3650
+ case 3:
3651
+ dk = r.readBytes();
3652
+ break;
3653
+ default: r.skip(field.wireType);
3654
+ }
3655
+ }
3656
+ return {
3657
+ auth,
3658
+ ek,
3659
+ dk
3660
+ };
3661
+ }
3662
+ function encodeUcDkCt1(msg) {
3663
+ const w = new ProtoWriter();
3664
+ encodeAuthOptional(w, 1, msg.auth);
3665
+ w.writeBytes(2, msg.dk);
3666
+ w.writeBytes(3, msg.ct1);
3667
+ return w.finish();
3668
+ }
3669
+ function decodeUcDkCt1(data) {
3670
+ const r = new ProtoReader(data);
3671
+ const offset = detectUcLayout(data);
3672
+ let auth;
3673
+ let dk = EMPTY_BYTES;
3674
+ let ct1 = EMPTY_BYTES;
3675
+ while (!r.done) {
3676
+ const field = r.readField();
3677
+ if (field === null) break;
3678
+ switch (field.fieldNumber - offset) {
3679
+ case 1:
3680
+ auth = decodeAuthOptional(r);
3681
+ break;
3682
+ case 2:
3683
+ dk = r.readBytes();
3684
+ break;
3685
+ case 3:
3686
+ ct1 = r.readBytes();
3687
+ break;
3688
+ default: r.skip(field.wireType);
3689
+ }
3690
+ }
3691
+ return {
3692
+ auth,
3693
+ dk,
3694
+ ct1
3695
+ };
3696
+ }
3697
+ function encodeUcHeaderReceived(msg) {
3698
+ const w = new ProtoWriter();
3699
+ encodeAuthOptional(w, 1, msg.auth);
3700
+ w.writeBytes(2, msg.hdr);
3701
+ w.writeBytes(3, msg.es);
3702
+ w.writeBytes(4, msg.ct1);
3703
+ w.writeBytes(5, msg.ss);
3704
+ return w.finish();
3705
+ }
3706
+ function decodeUcHeaderReceived(data) {
3707
+ const r = new ProtoReader(data);
3708
+ const offset = detectUcLayout(data);
3709
+ let auth;
3710
+ let hdr = EMPTY_BYTES;
3711
+ let es = EMPTY_BYTES;
3712
+ let ct1 = EMPTY_BYTES;
3713
+ let ss = EMPTY_BYTES;
3714
+ while (!r.done) {
3715
+ const field = r.readField();
3716
+ if (field === null) break;
3717
+ switch (field.fieldNumber - offset) {
3718
+ case 1:
3719
+ auth = decodeAuthOptional(r);
3720
+ break;
3721
+ case 2:
3722
+ hdr = r.readBytes();
3723
+ break;
3724
+ case 3:
3725
+ es = r.readBytes();
3726
+ break;
3727
+ case 4:
3728
+ ct1 = r.readBytes();
3729
+ break;
3730
+ case 5:
3731
+ ss = r.readBytes();
3732
+ break;
3733
+ default: r.skip(field.wireType);
3734
+ }
3735
+ }
3736
+ return {
3737
+ auth,
3738
+ hdr,
3739
+ es,
3740
+ ct1,
3741
+ ss
3742
+ };
3743
+ }
3744
+ function encodeUcAuthHdrEsCt1(msg) {
3745
+ const w = new ProtoWriter();
3746
+ encodeAuthOptional(w, 1, msg.auth);
3747
+ w.writeBytes(2, msg.hdr);
3748
+ w.writeBytes(3, msg.es);
3749
+ w.writeBytes(4, msg.ct1);
3750
+ return w.finish();
3751
+ }
3752
+ function decodeUcAuthHdrEsCt1(data) {
3753
+ const r = new ProtoReader(data);
3754
+ const offset = detectUcLayout(data);
3755
+ let auth;
3756
+ let hdr = EMPTY_BYTES;
3757
+ let es = EMPTY_BYTES;
3758
+ let ct1 = EMPTY_BYTES;
3759
+ while (!r.done) {
3760
+ const field = r.readField();
3761
+ if (field === null) break;
3762
+ switch (field.fieldNumber - offset) {
3763
+ case 1:
3764
+ auth = decodeAuthOptional(r);
3765
+ break;
3766
+ case 2:
3767
+ hdr = r.readBytes();
3768
+ break;
3769
+ case 3:
3770
+ es = r.readBytes();
3771
+ break;
3772
+ case 4:
3773
+ ct1 = r.readBytes();
3774
+ break;
3775
+ default: r.skip(field.wireType);
3776
+ }
3777
+ }
3778
+ return {
3779
+ auth,
3780
+ hdr,
3781
+ es,
3782
+ ct1
3783
+ };
3784
+ }
3785
+ function encodeUcEkReceivedCt1Sampled(msg) {
3786
+ const w = new ProtoWriter();
3787
+ encodeAuthOptional(w, 1, msg.auth);
3788
+ w.writeBytes(2, msg.hdr);
3789
+ w.writeBytes(3, msg.es);
3790
+ w.writeBytes(4, msg.ek);
3791
+ w.writeBytes(5, msg.ct1);
3792
+ return w.finish();
3793
+ }
3794
+ function decodeUcEkReceivedCt1Sampled(data) {
3795
+ const r = new ProtoReader(data);
3796
+ const offset = detectUcLayout(data);
3797
+ let auth;
3798
+ let hdr = EMPTY_BYTES;
3799
+ let es = EMPTY_BYTES;
3800
+ let ek = EMPTY_BYTES;
3801
+ let ct1 = EMPTY_BYTES;
3802
+ while (!r.done) {
3803
+ const field = r.readField();
3804
+ if (field === null) break;
3805
+ switch (field.fieldNumber - offset) {
3806
+ case 1:
3807
+ auth = decodeAuthOptional(r);
3808
+ break;
3809
+ case 2:
3810
+ hdr = r.readBytes();
3811
+ break;
3812
+ case 3:
3813
+ es = r.readBytes();
3814
+ break;
3815
+ case 4:
3816
+ ek = r.readBytes();
3817
+ break;
3818
+ case 5:
3819
+ ct1 = r.readBytes();
3820
+ break;
3821
+ default: r.skip(field.wireType);
3822
+ }
3823
+ }
3824
+ return {
3825
+ auth,
3826
+ hdr,
3827
+ es,
3828
+ ek,
3829
+ ct1
3830
+ };
3831
+ }
3832
+ /**
3833
+ * Encode a PbChunkedState into a sub-message for V1State.
3834
+ * The field number in V1State determines which variant this is:
3835
+ * 1=keysUnsampled, 2=keysSampled, ..., 11=ct2Sampled
3836
+ *
3837
+ * Each chunked state has:
3838
+ * field 1 = unchunked data
3839
+ * field 2 = encoder (if present)
3840
+ * field 3 = decoder (if present)
3841
+ */
3842
+ function encodeChunkedStateInner(state) {
3843
+ const w = new ProtoWriter();
3844
+ switch (state.type) {
3845
+ case "keysUnsampled":
3846
+ w.writeBytes(1, encodeUcKeysUnsampled(state.uc));
3847
+ return {
3848
+ fieldNumber: 1,
3849
+ data: w.finish()
3850
+ };
3851
+ case "keysSampled":
3852
+ w.writeBytes(1, encodeUcKeysSampled(state.uc));
3853
+ w.writeBytes(2, encodePolynomialEncoder(state.sendingHdr));
3854
+ return {
3855
+ fieldNumber: 2,
3856
+ data: w.finish()
3857
+ };
3858
+ case "headerSent":
3859
+ w.writeBytes(1, encodeUcHeaderSent(state.uc));
3860
+ w.writeBytes(2, encodePolynomialEncoder(state.sendingEk));
3861
+ w.writeBytes(3, encodePolynomialDecoder(state.receivingCt1));
3862
+ return {
3863
+ fieldNumber: 3,
3864
+ data: w.finish()
3865
+ };
3866
+ case "ct1Received":
3867
+ w.writeBytes(1, encodeUcDkCt1(state.uc));
3868
+ w.writeBytes(2, encodePolynomialEncoder(state.sendingEk));
3869
+ return {
3870
+ fieldNumber: 4,
3871
+ data: w.finish()
3872
+ };
3873
+ case "ekSentCt1Received":
3874
+ w.writeBytes(1, encodeUcDkCt1(state.uc));
3875
+ w.writeBytes(3, encodePolynomialDecoder(state.receivingCt2));
3876
+ return {
3877
+ fieldNumber: 5,
3878
+ data: w.finish()
3879
+ };
3880
+ case "noHeaderReceived":
3881
+ w.writeBytes(1, encodeUcKeysUnsampled(state.uc));
3882
+ w.writeBytes(2, encodePolynomialDecoder(state.receivingHdr));
3883
+ return {
3884
+ fieldNumber: 6,
3885
+ data: w.finish()
3886
+ };
3887
+ case "headerReceived":
3888
+ w.writeBytes(1, encodeUcHeaderReceived(state.uc));
3889
+ w.writeBytes(2, encodePolynomialDecoder(state.receivingEk));
3890
+ return {
3891
+ fieldNumber: 7,
3892
+ data: w.finish()
3893
+ };
3894
+ case "ct1Sampled":
3895
+ w.writeBytes(1, encodeUcAuthHdrEsCt1(state.uc));
3896
+ w.writeBytes(2, encodePolynomialEncoder(state.sendingCt1));
3897
+ w.writeBytes(3, encodePolynomialDecoder(state.receivingEk));
3898
+ return {
3899
+ fieldNumber: 8,
3900
+ data: w.finish()
3901
+ };
3902
+ case "ekReceivedCt1Sampled":
3903
+ w.writeBytes(1, encodeUcEkReceivedCt1Sampled(state.uc));
3904
+ w.writeBytes(2, encodePolynomialEncoder(state.sendingCt1));
3905
+ return {
3906
+ fieldNumber: 9,
3907
+ data: w.finish()
3908
+ };
3909
+ case "ct1Acknowledged":
3910
+ w.writeBytes(1, encodeUcAuthHdrEsCt1(state.uc));
3911
+ w.writeBytes(2, encodePolynomialDecoder(state.receivingEk));
3912
+ return {
3913
+ fieldNumber: 10,
3914
+ data: w.finish()
3915
+ };
3916
+ case "ct2Sampled":
3917
+ w.writeBytes(1, encodeUcKeysUnsampled(state.uc));
3918
+ w.writeBytes(2, encodePolynomialEncoder(state.sendingCt2));
3919
+ return {
3920
+ fieldNumber: 11,
3921
+ data: w.finish()
3922
+ };
3923
+ }
3924
+ }
3925
+ function decodeChunkedRaw(data) {
3926
+ const r = new ProtoReader(data);
3927
+ let uc = EMPTY_BYTES;
3928
+ let encoder;
3929
+ let decoder;
3930
+ while (!r.done) {
3931
+ const field = r.readField();
3932
+ if (field === null) break;
3933
+ switch (field.fieldNumber) {
3934
+ case 1:
3935
+ uc = r.readBytes();
3936
+ break;
3937
+ case 2:
3938
+ encoder = r.readBytes();
3939
+ break;
3940
+ case 3:
3941
+ decoder = r.readBytes();
3942
+ break;
3943
+ default: r.skip(field.wireType);
3944
+ }
3945
+ }
3946
+ return {
3947
+ uc,
3948
+ encoder,
3949
+ decoder
3950
+ };
3951
+ }
3952
+ function requireField(data, name) {
3953
+ if (data === void 0) throw new Error(`Protobuf: missing required field '${name}'`);
3954
+ return data;
3955
+ }
3956
+ function decodeChunkedState(fieldNumber, data) {
3957
+ const raw = decodeChunkedRaw(data);
3958
+ switch (fieldNumber) {
3959
+ case 1: return {
3960
+ type: "keysUnsampled",
3961
+ uc: decodeUcKeysUnsampled(raw.uc)
3962
+ };
3963
+ case 2: return {
3964
+ type: "keysSampled",
3965
+ uc: decodeUcKeysSampled(raw.uc),
3966
+ sendingHdr: decodePolynomialEncoder(requireField(raw.encoder, "encoder"))
3967
+ };
3968
+ case 3: return {
3969
+ type: "headerSent",
3970
+ uc: decodeUcHeaderSent(raw.uc),
3971
+ sendingEk: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
3972
+ receivingCt1: decodePolynomialDecoder(requireField(raw.decoder, "decoder"))
3973
+ };
3974
+ case 4: return {
3975
+ type: "ct1Received",
3976
+ uc: decodeUcDkCt1(raw.uc),
3977
+ sendingEk: decodePolynomialEncoder(requireField(raw.encoder, "encoder"))
3978
+ };
3979
+ case 5: return {
3980
+ type: "ekSentCt1Received",
3981
+ uc: decodeUcDkCt1(raw.uc),
3982
+ receivingCt2: decodePolynomialDecoder(requireField(raw.decoder, "decoder"))
3983
+ };
3984
+ case 6: return {
3985
+ type: "noHeaderReceived",
3986
+ uc: decodeUcKeysUnsampled(raw.uc),
3987
+ receivingHdr: decodePolynomialDecoder(requireField(raw.encoder, "encoder"))
3988
+ };
3989
+ case 7: return {
3990
+ type: "headerReceived",
3991
+ uc: decodeUcHeaderReceived(raw.uc),
3992
+ receivingEk: decodePolynomialDecoder(requireField(raw.encoder, "encoder"))
3993
+ };
3994
+ case 8: return {
3995
+ type: "ct1Sampled",
3996
+ uc: decodeUcAuthHdrEsCt1(raw.uc),
3997
+ sendingCt1: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
3998
+ receivingEk: decodePolynomialDecoder(requireField(raw.decoder, "decoder"))
3999
+ };
4000
+ case 9: return {
4001
+ type: "ekReceivedCt1Sampled",
4002
+ uc: decodeUcEkReceivedCt1Sampled(raw.uc),
4003
+ sendingCt1: decodePolynomialEncoder(requireField(raw.encoder, "encoder"))
4004
+ };
4005
+ case 10: return {
4006
+ type: "ct1Acknowledged",
4007
+ uc: decodeUcAuthHdrEsCt1(raw.uc),
4008
+ receivingEk: decodePolynomialDecoder(requireField(raw.encoder, "encoder"))
4009
+ };
4010
+ case 11: return {
4011
+ type: "ct2Sampled",
4012
+ uc: decodeUcKeysUnsampled(raw.uc),
4013
+ sendingCt2: decodePolynomialEncoder(requireField(raw.encoder, "encoder"))
4014
+ };
4015
+ default: throw new Error(`Unknown chunked state field number: ${fieldNumber}`);
4016
+ }
4017
+ }
4018
+ function encodeV1State(msg) {
4019
+ if (msg.innerState === void 0) return EMPTY_BYTES;
4020
+ const { fieldNumber, data } = encodeChunkedStateInner(msg.innerState);
4021
+ const w = new ProtoWriter();
4022
+ w.writeBytes(fieldNumber, data);
4023
+ if (msg.epoch !== void 0) w.writeVarint(12, msg.epoch);
4024
+ return w.finish();
4025
+ }
4026
+ function decodeV1State(data) {
4027
+ const r = new ProtoReader(data);
4028
+ let innerState;
4029
+ let epoch;
4030
+ while (!r.done) {
4031
+ const field = r.readField();
4032
+ if (field === null) break;
4033
+ if (field.fieldNumber >= 1 && field.fieldNumber <= 11) innerState = decodeChunkedState(field.fieldNumber, r.readBytes());
4034
+ else if (field.fieldNumber === 12) epoch = r.readVarint64();
4035
+ else r.skip(field.wireType);
4036
+ }
4037
+ return {
4038
+ innerState,
4039
+ epoch
4040
+ };
4041
+ }
4042
+ function encodePqRatchetState(msg) {
4043
+ const w = new ProtoWriter();
4044
+ if (msg.versionNegotiation !== void 0) w.writeBytes(1, encodeVersionNegotiation(msg.versionNegotiation));
4045
+ if (msg.chain !== void 0) w.writeBytes(2, encodeChain(msg.chain));
4046
+ if (msg.v1 !== void 0) w.writeBytes(3, encodeV1State(msg.v1));
4047
+ return w.finish();
4048
+ }
4049
+ function decodePqRatchetState(data) {
4050
+ const r = new ProtoReader(data);
4051
+ let versionNegotiation;
4052
+ let chain;
4053
+ let v1;
4054
+ while (!r.done) {
4055
+ const field = r.readField();
4056
+ if (field === null) break;
4057
+ switch (field.fieldNumber) {
4058
+ case 1:
4059
+ versionNegotiation = decodeVersionNegotiation(r.readBytes());
4060
+ break;
4061
+ case 2:
4062
+ chain = decodeChain(r.readBytes());
4063
+ break;
4064
+ case 3:
4065
+ v1 = decodeV1State(r.readBytes());
4066
+ break;
4067
+ default: r.skip(field.wireType);
4068
+ }
4069
+ }
4070
+ return {
4071
+ versionNegotiation,
4072
+ chain,
4073
+ v1
4074
+ };
4075
+ }
4076
+
4077
+ //#endregion
4078
+ //#region src/index.ts
4079
+ /**
4080
+ * Copyright © 2025 Signal Messenger, LLC
4081
+ * Copyright © 2026 Parity Technologies
4082
+ *
4083
+ * Top-level public API for the SPQR protocol.
4084
+ *
4085
+ * Matches Signal's Rust `lib.rs` interface. All state is serialized as
4086
+ * opaque protobuf bytes (Uint8Array) so that callers never need to touch
4087
+ * internal types.
4088
+ *
4089
+ * Exported functions:
4090
+ * - emptyState() -> empty serialized state (V0)
4091
+ * - initialState(p) -> create initial serialized state
4092
+ * - send(state, rng) -> produce next message + advance state
4093
+ * - recv(state, msg) -> consume incoming message + advance state
4094
+ * - currentVersion(s) -> inspect version negotiation status
4095
+ */
4096
+ /**
4097
+ * Return an empty (V0) serialized state.
4098
+ */
4099
+ function emptyState() {
4100
+ return new Uint8Array(0);
4101
+ }
4102
+ /**
4103
+ * Create an initial serialized state from parameters.
4104
+ *
4105
+ * For V0, returns an empty state. For V1+, initializes the inner V1
4106
+ * state machine and version negotiation.
4107
+ */
4108
+ function initialState(params) {
4109
+ if (params.version === Version.V0) return emptyState();
4110
+ const inner = initInner(params.version, params.direction, params.authKey);
4111
+ return encodePqRatchetState({
4112
+ versionNegotiation: {
4113
+ authKey: Uint8Array.from(params.authKey),
4114
+ direction: params.direction,
4115
+ minVersion: params.minVersion,
4116
+ chainParams: {
4117
+ maxJump: params.chainParams.maxJump,
4118
+ maxOooKeys: params.chainParams.maxOooKeys
4119
+ }
4120
+ },
4121
+ chain: void 0,
4122
+ v1: inner
4123
+ });
4124
+ }
4125
+ /**
4126
+ * Produce the next outgoing message from the current state.
4127
+ *
4128
+ * Returns the updated state, serialized message, and optional message key.
4129
+ */
4130
+ function send(state, rng) {
4131
+ if (state.length === 0) return {
4132
+ state: new Uint8Array(0),
4133
+ msg: new Uint8Array(0),
4134
+ key: null
4135
+ };
4136
+ const statePb = decodePqRatchetState(state);
4137
+ if (statePb.v1 === void 0) return {
4138
+ state: new Uint8Array(0),
4139
+ msg: new Uint8Array(0),
4140
+ key: null
4141
+ };
4142
+ const sendResult = send$1(statesFromPb(statePb.v1), rng);
4143
+ let chain;
4144
+ if (statePb.chain !== void 0) chain = Chain.fromProto(statePb.chain);
4145
+ else if (statePb.versionNegotiation !== void 0) {
4146
+ const vn = statePb.versionNegotiation;
4147
+ if (vn.minVersion > Version.V0) chain = chainFromVersionNegotiation(vn);
4148
+ } else throw new SpqrError("Chain not available and no version negotiation", SpqrErrorCode.ChainNotAvailable);
4149
+ let index;
4150
+ let msgKey;
4151
+ let chainPb;
4152
+ if (chain === void 0) {
4153
+ if (sendResult.key !== null) throw new SpqrError("Unexpected epoch secret without chain", SpqrErrorCode.ChainNotAvailable);
4154
+ index = 0;
4155
+ msgKey = new Uint8Array(0);
4156
+ chainPb = void 0;
4157
+ } else {
4158
+ if (sendResult.key !== null) chain.addEpoch(sendResult.key);
4159
+ const msgEpoch = sendResult.msg.epoch - 1n;
4160
+ const [sendIndex, sendKey] = chain.sendKey(msgEpoch);
4161
+ index = sendIndex;
4162
+ msgKey = sendKey;
4163
+ chainPb = chain.toProto();
4164
+ }
4165
+ const serializedMsg = serializeMessage(sendResult.msg, index);
4166
+ const v1Pb = statesToPb(sendResult.state);
4167
+ return {
4168
+ state: encodePqRatchetState({
4169
+ versionNegotiation: statePb.versionNegotiation,
4170
+ chain: chainPb,
4171
+ v1: v1Pb
4172
+ }),
4173
+ msg: serializedMsg,
4174
+ key: msgKey.length === 0 ? null : msgKey
4175
+ };
4176
+ }
4177
+ /**
4178
+ * Process an incoming message and transition the state.
4179
+ *
4180
+ * Returns the updated state and optional message key.
4181
+ */
4182
+ function recv(state, msg) {
4183
+ if (state.length === 0 && msg.length === 0) return {
4184
+ state: new Uint8Array(0),
4185
+ key: null
4186
+ };
4187
+ const prenegotiatedPb = state.length === 0 ? {
4188
+ v1: void 0,
4189
+ chain: void 0,
4190
+ versionNegotiation: void 0
4191
+ } : decodePqRatchetState(state);
4192
+ const msgVer = msgVersion(msg);
4193
+ if (msgVer === void 0) return {
4194
+ state: Uint8Array.from(state),
4195
+ key: null
4196
+ };
4197
+ const stateVer = stateVersion(prenegotiatedPb);
4198
+ let statePb;
4199
+ if (msgVer >= stateVer) statePb = prenegotiatedPb;
4200
+ else {
4201
+ const vn = prenegotiatedPb.versionNegotiation;
4202
+ if (vn === void 0) throw new SpqrError(`Version mismatch: state=${stateVer}, msg=${msgVer}, no negotiation available`, SpqrErrorCode.VersionMismatch);
4203
+ if (msgVer < vn.minVersion) throw new SpqrError(`Minimum version not met: min=${vn.minVersion}, msg=${msgVer}`, SpqrErrorCode.MinimumVersion);
4204
+ statePb = {
4205
+ v1: initInner(msgVer, vn.direction, Uint8Array.from(vn.authKey)),
4206
+ versionNegotiation: void 0,
4207
+ chain: chainFrom(prenegotiatedPb.chain, vn)
4208
+ };
4209
+ }
4210
+ if (statePb.v1 === void 0) return {
4211
+ state: new Uint8Array(0),
4212
+ key: null
4213
+ };
4214
+ const { msg: sckaMsg, index } = deserializeMessage(msg);
4215
+ const recvResult = recv$1(statesFromPb(statePb.v1), sckaMsg);
4216
+ const msgKeyEpoch = sckaMsg.epoch - 1n;
4217
+ const chainObj = chainFromState(statePb.chain, statePb.versionNegotiation);
4218
+ if (recvResult.key !== null) chainObj.addEpoch(recvResult.key);
4219
+ let msgKey;
4220
+ if (msgKeyEpoch === 0n && index === 0) msgKey = new Uint8Array(0);
4221
+ else msgKey = chainObj.recvKey(msgKeyEpoch, index);
4222
+ const v1Pb = statesToPb(recvResult.state);
4223
+ return {
4224
+ state: encodePqRatchetState({
4225
+ versionNegotiation: void 0,
4226
+ chain: chainObj.toProto(),
4227
+ v1: v1Pb
4228
+ }),
4229
+ key: msgKey.length === 0 ? null : msgKey
4230
+ };
4231
+ }
4232
+ /**
4233
+ * Inspect the current version negotiation status of a serialized state.
4234
+ */
4235
+ function currentVersion(state) {
4236
+ if (state.length === 0) return {
4237
+ type: "negotiation_complete",
4238
+ version: Version.V0
4239
+ };
4240
+ const statePb = decodePqRatchetState(state);
4241
+ const version = statePb.v1 !== void 0 ? Version.V1 : Version.V0;
4242
+ if (statePb.versionNegotiation !== void 0) return {
4243
+ type: "still_negotiating",
4244
+ version,
4245
+ minVersion: statePb.versionNegotiation.minVersion
4246
+ };
4247
+ return {
4248
+ type: "negotiation_complete",
4249
+ version
4250
+ };
4251
+ }
4252
+ /**
4253
+ * Initialize the V1 inner state based on version and direction.
4254
+ */
4255
+ function initInner(version, direction, authKey) {
4256
+ if (version === Version.V0) return;
4257
+ let states;
4258
+ if (direction === Direction.A2B) states = initA(authKey);
4259
+ else states = initB(authKey);
4260
+ return statesToPb(states);
4261
+ }
4262
+ /**
4263
+ * Extract version from a serialized message.
4264
+ * Empty msg -> V0. msg[0]: 0 -> V0, 1 -> V1, else undefined.
4265
+ */
4266
+ function msgVersion(msg) {
4267
+ if (msg.length === 0) return Version.V0;
4268
+ const v = msg[0];
4269
+ if (v === 0) return Version.V0;
4270
+ if (v === 1) return Version.V1;
4271
+ }
4272
+ /**
4273
+ * Extract version from the decoded state.
4274
+ * No v1 inner -> V0. Has v1 inner -> V1.
4275
+ */
4276
+ function stateVersion(state) {
4277
+ return state.v1 !== void 0 ? Version.V1 : Version.V0;
4278
+ }
4279
+ /**
4280
+ * Create a Chain from version negotiation parameters.
4281
+ */
4282
+ function chainFromVersionNegotiation(vn) {
4283
+ const chainParams = vn.chainParams ?? {
4284
+ maxJump: 25e3,
4285
+ maxOooKeys: 2e3
4286
+ };
4287
+ return Chain.create(Uint8Array.from(vn.authKey), vn.direction, chainParams);
4288
+ }
4289
+ /**
4290
+ * Get or create a Chain from the existing chain proto and version negotiation.
4291
+ * Prefers existing chain, falls back to creating from version negotiation.
4292
+ */
4293
+ function chainFrom(chainPb, vn) {
4294
+ if (chainPb !== void 0) return chainPb;
4295
+ if (vn !== void 0) return chainFromVersionNegotiation(vn).toProto();
4296
+ }
4297
+ /**
4298
+ * Get a Chain object from state, creating from vn if needed.
4299
+ */
4300
+ function chainFromState(chainPb, vn) {
4301
+ if (chainPb !== void 0) return Chain.fromProto(chainPb);
4302
+ if (vn !== void 0) return chainFromVersionNegotiation(vn);
4303
+ throw new SpqrError("Chain not available and no version negotiation", SpqrErrorCode.ChainNotAvailable);
4304
+ }
4305
+
4306
+ //#endregion
4307
+ exports.Direction = Direction;
4308
+ exports.SpqrError = SpqrError;
4309
+ exports.SpqrErrorCode = SpqrErrorCode;
4310
+ exports.Version = Version;
4311
+ exports.currentVersion = currentVersion;
4312
+ exports.emptyState = emptyState;
4313
+ exports.initialState = initialState;
4314
+ exports.recv = recv;
4315
+ exports.send = send;
4316
+ return exports;
4317
+ })({}, nobleHashesHkdf, nobleHashesSha2, nobleHashesHmac, nobleHashesSha3, nobleHashesUtils, noblePostQuantumCrystals, noblePostQuantumMlKem);
4318
+ //# sourceMappingURL=index.iife.js.map