@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.
- package/LICENSE +661 -0
- package/README.md +11 -0
- package/dist/index.cjs +4321 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +115 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +115 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.iife.js +4318 -0
- package/dist/index.iife.js.map +1 -0
- package/dist/index.mjs +4312 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -0
- package/src/authenticator.ts +163 -0
- package/src/chain.ts +522 -0
- package/src/constants.ts +90 -0
- package/src/encoding/gf.ts +190 -0
- package/src/encoding/index.ts +15 -0
- package/src/encoding/polynomial.ts +657 -0
- package/src/error.ts +75 -0
- package/src/incremental-mlkem768.ts +546 -0
- package/src/index.ts +415 -0
- package/src/kdf.ts +34 -0
- package/src/proto/index.ts +1376 -0
- package/src/proto/pq-ratchet-types.ts +195 -0
- package/src/types.ts +81 -0
- package/src/util.ts +61 -0
- package/src/v1/chunked/index.ts +60 -0
- package/src/v1/chunked/message.ts +257 -0
- package/src/v1/chunked/send-ct.ts +352 -0
- package/src/v1/chunked/send-ek.ts +285 -0
- package/src/v1/chunked/serialize.ts +278 -0
- package/src/v1/chunked/states.ts +399 -0
- package/src/v1/index.ts +9 -0
- package/src/v1/unchunked/index.ts +20 -0
- package/src/v1/unchunked/send-ct.ts +231 -0
- package/src/v1/unchunked/send-ek.ts +177 -0
|
@@ -0,0 +1,1376 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright © 2025 Signal Messenger, LLC
|
|
3
|
+
* Copyright © 2026 Parity Technologies
|
|
4
|
+
*
|
|
5
|
+
* Minimal protobuf encoder/decoder for SPQR wire format.
|
|
6
|
+
* Supports varint (wire type 0) and length-delimited (wire type 2).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
PbAuthenticator,
|
|
11
|
+
PbChain,
|
|
12
|
+
PbChainParams,
|
|
13
|
+
PbChunkedState,
|
|
14
|
+
PbChunk,
|
|
15
|
+
PbEpoch,
|
|
16
|
+
PbEpochDirection,
|
|
17
|
+
PbPolynomialDecoder,
|
|
18
|
+
PbPolynomialEncoder,
|
|
19
|
+
PbPqRatchetState,
|
|
20
|
+
PbV1Msg,
|
|
21
|
+
PbV1MsgInner,
|
|
22
|
+
PbV1State,
|
|
23
|
+
PbVersionNegotiation,
|
|
24
|
+
} from "./pq-ratchet-types.js";
|
|
25
|
+
|
|
26
|
+
// ---- Wire types ----
|
|
27
|
+
|
|
28
|
+
const WIRE_VARINT = 0;
|
|
29
|
+
const WIRE_LENGTH_DELIMITED = 2;
|
|
30
|
+
|
|
31
|
+
// ---- Empty defaults for bytes fields ----
|
|
32
|
+
|
|
33
|
+
const EMPTY_BYTES = new Uint8Array(0);
|
|
34
|
+
|
|
35
|
+
// =========================================================================
|
|
36
|
+
// ProtoWriter
|
|
37
|
+
// =========================================================================
|
|
38
|
+
|
|
39
|
+
export class ProtoWriter {
|
|
40
|
+
private readonly parts: Uint8Array[] = [];
|
|
41
|
+
|
|
42
|
+
writeVarint(fieldNumber: number, value: number | bigint): void {
|
|
43
|
+
if (typeof value === "bigint") {
|
|
44
|
+
if (value === 0n) return; // default value, omit
|
|
45
|
+
this.writeTag(fieldNumber, WIRE_VARINT);
|
|
46
|
+
this.writeRawVarint64(value);
|
|
47
|
+
} else {
|
|
48
|
+
if (value === 0) return; // default value, omit
|
|
49
|
+
this.writeTag(fieldNumber, WIRE_VARINT);
|
|
50
|
+
this.writeRawVarint(value);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Write a varint field even when the value is zero (for required fields). */
|
|
55
|
+
writeVarintAlways(fieldNumber: number, value: number | bigint): void {
|
|
56
|
+
if (typeof value === "bigint") {
|
|
57
|
+
this.writeTag(fieldNumber, WIRE_VARINT);
|
|
58
|
+
this.writeRawVarint64(value);
|
|
59
|
+
} else {
|
|
60
|
+
this.writeTag(fieldNumber, WIRE_VARINT);
|
|
61
|
+
this.writeRawVarint(value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
writeBool(fieldNumber: number, value: boolean): void {
|
|
66
|
+
if (!value) return; // default false, omit
|
|
67
|
+
this.writeTag(fieldNumber, WIRE_VARINT);
|
|
68
|
+
this.writeRawVarint(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
writeBytes(fieldNumber: number, data: Uint8Array): void {
|
|
72
|
+
if (data.length === 0) return; // default empty, omit
|
|
73
|
+
this.writeTag(fieldNumber, WIRE_LENGTH_DELIMITED);
|
|
74
|
+
this.writeRawVarint(data.length);
|
|
75
|
+
this.parts.push(new Uint8Array(data));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
writeMessage(fieldNumber: number, writer: ProtoWriter): void {
|
|
79
|
+
const data = writer.finish();
|
|
80
|
+
if (data.length === 0) return;
|
|
81
|
+
this.writeTag(fieldNumber, WIRE_LENGTH_DELIMITED);
|
|
82
|
+
this.writeRawVarint(data.length);
|
|
83
|
+
this.parts.push(data);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** For oneof fields, write even if the sub-message is empty. */
|
|
87
|
+
writeMessageAlways(fieldNumber: number, writer: ProtoWriter): void {
|
|
88
|
+
const data = writer.finish();
|
|
89
|
+
this.writeTag(fieldNumber, WIRE_LENGTH_DELIMITED);
|
|
90
|
+
this.writeRawVarint(data.length);
|
|
91
|
+
if (data.length > 0) this.parts.push(data);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
writeEnum(fieldNumber: number, value: number): void {
|
|
95
|
+
this.writeVarint(fieldNumber, value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
finish(): Uint8Array {
|
|
99
|
+
let total = 0;
|
|
100
|
+
for (const p of this.parts) total += p.length;
|
|
101
|
+
const result = new Uint8Array(total);
|
|
102
|
+
let offset = 0;
|
|
103
|
+
for (const p of this.parts) {
|
|
104
|
+
result.set(p, offset);
|
|
105
|
+
offset += p.length;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private writeTag(fieldNumber: number, wireType: number): void {
|
|
111
|
+
this.writeRawVarint((fieldNumber << 3) | wireType);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private writeRawVarint(value: number): void {
|
|
115
|
+
const buf: number[] = [];
|
|
116
|
+
let v = value >>> 0; // ensure unsigned
|
|
117
|
+
while (v > 0x7f) {
|
|
118
|
+
buf.push((v & 0x7f) | 0x80);
|
|
119
|
+
v >>>= 7;
|
|
120
|
+
}
|
|
121
|
+
buf.push(v & 0x7f);
|
|
122
|
+
this.parts.push(new Uint8Array(buf));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private writeRawVarint64(value: bigint): void {
|
|
126
|
+
const buf: number[] = [];
|
|
127
|
+
let v = value;
|
|
128
|
+
while (v > 0x7fn) {
|
|
129
|
+
buf.push(Number(v & 0x7fn) | 0x80);
|
|
130
|
+
v >>= 7n;
|
|
131
|
+
}
|
|
132
|
+
buf.push(Number(v & 0x7fn));
|
|
133
|
+
this.parts.push(new Uint8Array(buf));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// =========================================================================
|
|
138
|
+
// ProtoReader
|
|
139
|
+
// =========================================================================
|
|
140
|
+
|
|
141
|
+
export class ProtoReader {
|
|
142
|
+
private readonly data: Uint8Array;
|
|
143
|
+
private pos: number;
|
|
144
|
+
|
|
145
|
+
constructor(data: Uint8Array) {
|
|
146
|
+
this.data = data;
|
|
147
|
+
this.pos = 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
get remaining(): number {
|
|
151
|
+
return this.data.length - this.pos;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
get done(): boolean {
|
|
155
|
+
return this.pos >= this.data.length;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
readField(): { fieldNumber: number; wireType: number } | null {
|
|
159
|
+
if (this.pos >= this.data.length) return null;
|
|
160
|
+
const tag = this.readRawVarint();
|
|
161
|
+
return { fieldNumber: tag >>> 3, wireType: tag & 0x7 };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
readVarint(): number {
|
|
165
|
+
return this.readRawVarint();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
readVarint64(): bigint {
|
|
169
|
+
return this.readRawVarint64();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
readBool(): boolean {
|
|
173
|
+
return this.readRawVarint() !== 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
readBytes(): Uint8Array {
|
|
177
|
+
const len = this.readRawVarint();
|
|
178
|
+
const data = this.data.slice(this.pos, this.pos + len);
|
|
179
|
+
this.pos += len;
|
|
180
|
+
return data;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
readMessage(): ProtoReader {
|
|
184
|
+
const data = this.readBytes();
|
|
185
|
+
return new ProtoReader(data);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
readEnum(): number {
|
|
189
|
+
return this.readRawVarint();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
skip(wireType: number): void {
|
|
193
|
+
switch (wireType) {
|
|
194
|
+
case 0: // varint
|
|
195
|
+
this.readRawVarint();
|
|
196
|
+
break;
|
|
197
|
+
case 1: // 64-bit
|
|
198
|
+
this.pos += 8;
|
|
199
|
+
break;
|
|
200
|
+
case 2: {
|
|
201
|
+
// length-delimited
|
|
202
|
+
const len = this.readRawVarint();
|
|
203
|
+
this.pos += len;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
case 5: // 32-bit
|
|
207
|
+
this.pos += 4;
|
|
208
|
+
break;
|
|
209
|
+
default:
|
|
210
|
+
throw new Error(`Unknown wire type: ${wireType}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private readRawVarint(): number {
|
|
215
|
+
let result = 0;
|
|
216
|
+
let shift = 0;
|
|
217
|
+
while (shift < 35) {
|
|
218
|
+
const b = this.data[this.pos++];
|
|
219
|
+
result |= (b & 0x7f) << shift;
|
|
220
|
+
if ((b & 0x80) === 0) return result >>> 0;
|
|
221
|
+
shift += 7;
|
|
222
|
+
}
|
|
223
|
+
throw new Error("Varint too long");
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
private readRawVarint64(): bigint {
|
|
227
|
+
let result = 0n;
|
|
228
|
+
let shift = 0n;
|
|
229
|
+
while (shift < 70n) {
|
|
230
|
+
const b = this.data[this.pos++];
|
|
231
|
+
result |= BigInt(b & 0x7f) << shift;
|
|
232
|
+
if ((b & 0x80) === 0) return result;
|
|
233
|
+
shift += 7n;
|
|
234
|
+
}
|
|
235
|
+
throw new Error("Varint64 too long");
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// =========================================================================
|
|
240
|
+
// ChainParams
|
|
241
|
+
// =========================================================================
|
|
242
|
+
|
|
243
|
+
export function encodeChainParams(msg: PbChainParams): Uint8Array {
|
|
244
|
+
const w = new ProtoWriter();
|
|
245
|
+
w.writeVarint(1, msg.maxJump);
|
|
246
|
+
w.writeVarint(2, msg.maxOooKeys);
|
|
247
|
+
return w.finish();
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function decodeChainParams(data: Uint8Array): PbChainParams {
|
|
251
|
+
const r = new ProtoReader(data);
|
|
252
|
+
let maxJump = 0;
|
|
253
|
+
let maxOooKeys = 0;
|
|
254
|
+
while (!r.done) {
|
|
255
|
+
const field = r.readField();
|
|
256
|
+
if (field === null) break;
|
|
257
|
+
switch (field.fieldNumber) {
|
|
258
|
+
case 1:
|
|
259
|
+
maxJump = r.readVarint();
|
|
260
|
+
break;
|
|
261
|
+
case 2:
|
|
262
|
+
maxOooKeys = r.readVarint();
|
|
263
|
+
break;
|
|
264
|
+
default:
|
|
265
|
+
r.skip(field.wireType);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return { maxJump, maxOooKeys };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// =========================================================================
|
|
272
|
+
// VersionNegotiation
|
|
273
|
+
// =========================================================================
|
|
274
|
+
|
|
275
|
+
export function encodeVersionNegotiation(msg: PbVersionNegotiation): Uint8Array {
|
|
276
|
+
const w = new ProtoWriter();
|
|
277
|
+
w.writeBytes(1, msg.authKey);
|
|
278
|
+
w.writeEnum(2, msg.direction);
|
|
279
|
+
w.writeEnum(3, msg.minVersion);
|
|
280
|
+
if (msg.chainParams !== undefined) {
|
|
281
|
+
const sub = new ProtoWriter();
|
|
282
|
+
sub.writeVarint(1, msg.chainParams.maxJump);
|
|
283
|
+
sub.writeVarint(2, msg.chainParams.maxOooKeys);
|
|
284
|
+
w.writeMessage(4, sub);
|
|
285
|
+
}
|
|
286
|
+
return w.finish();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export function decodeVersionNegotiation(data: Uint8Array): PbVersionNegotiation {
|
|
290
|
+
const r = new ProtoReader(data);
|
|
291
|
+
let authKey: Uint8Array = EMPTY_BYTES;
|
|
292
|
+
let direction = 0;
|
|
293
|
+
let minVersion = 0;
|
|
294
|
+
let chainParams: PbChainParams | undefined;
|
|
295
|
+
while (!r.done) {
|
|
296
|
+
const field = r.readField();
|
|
297
|
+
if (field === null) break;
|
|
298
|
+
switch (field.fieldNumber) {
|
|
299
|
+
case 1:
|
|
300
|
+
authKey = r.readBytes();
|
|
301
|
+
break;
|
|
302
|
+
case 2:
|
|
303
|
+
direction = r.readEnum();
|
|
304
|
+
break;
|
|
305
|
+
case 3:
|
|
306
|
+
minVersion = r.readEnum();
|
|
307
|
+
break;
|
|
308
|
+
case 4:
|
|
309
|
+
chainParams = decodeChainParams(r.readBytes());
|
|
310
|
+
break;
|
|
311
|
+
default:
|
|
312
|
+
r.skip(field.wireType);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return { authKey, direction, minVersion, chainParams };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// =========================================================================
|
|
319
|
+
// EpochDirection
|
|
320
|
+
// =========================================================================
|
|
321
|
+
|
|
322
|
+
function encodeEpochDirection(w: ProtoWriter, msg: PbEpochDirection): void {
|
|
323
|
+
w.writeVarint(1, msg.ctr);
|
|
324
|
+
w.writeBytes(2, msg.next);
|
|
325
|
+
w.writeBytes(3, msg.prev);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function decodeEpochDirection(r: ProtoReader): PbEpochDirection {
|
|
329
|
+
let ctr = 0;
|
|
330
|
+
let next: Uint8Array = EMPTY_BYTES;
|
|
331
|
+
let prev: Uint8Array = EMPTY_BYTES;
|
|
332
|
+
while (!r.done) {
|
|
333
|
+
const field = r.readField();
|
|
334
|
+
if (field === null) break;
|
|
335
|
+
switch (field.fieldNumber) {
|
|
336
|
+
case 1:
|
|
337
|
+
ctr = r.readVarint();
|
|
338
|
+
break;
|
|
339
|
+
case 2:
|
|
340
|
+
next = r.readBytes();
|
|
341
|
+
break;
|
|
342
|
+
case 3:
|
|
343
|
+
prev = r.readBytes();
|
|
344
|
+
break;
|
|
345
|
+
default:
|
|
346
|
+
r.skip(field.wireType);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return { ctr, next, prev };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// =========================================================================
|
|
353
|
+
// Epoch
|
|
354
|
+
// =========================================================================
|
|
355
|
+
|
|
356
|
+
function encodeEpoch(msg: PbEpoch): Uint8Array {
|
|
357
|
+
const w = new ProtoWriter();
|
|
358
|
+
if (msg.send !== undefined) {
|
|
359
|
+
const sub = new ProtoWriter();
|
|
360
|
+
encodeEpochDirection(sub, msg.send);
|
|
361
|
+
w.writeMessage(1, sub);
|
|
362
|
+
}
|
|
363
|
+
if (msg.recv !== undefined) {
|
|
364
|
+
const sub = new ProtoWriter();
|
|
365
|
+
encodeEpochDirection(sub, msg.recv);
|
|
366
|
+
w.writeMessage(2, sub);
|
|
367
|
+
}
|
|
368
|
+
return w.finish();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function decodeEpoch(data: Uint8Array): PbEpoch {
|
|
372
|
+
const r = new ProtoReader(data);
|
|
373
|
+
let send: PbEpochDirection | undefined;
|
|
374
|
+
let recv: PbEpochDirection | undefined;
|
|
375
|
+
while (!r.done) {
|
|
376
|
+
const field = r.readField();
|
|
377
|
+
if (field === null) break;
|
|
378
|
+
switch (field.fieldNumber) {
|
|
379
|
+
case 1:
|
|
380
|
+
send = decodeEpochDirection(r.readMessage());
|
|
381
|
+
break;
|
|
382
|
+
case 2:
|
|
383
|
+
recv = decodeEpochDirection(r.readMessage());
|
|
384
|
+
break;
|
|
385
|
+
default:
|
|
386
|
+
r.skip(field.wireType);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return { send, recv };
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// =========================================================================
|
|
393
|
+
// Chain
|
|
394
|
+
// =========================================================================
|
|
395
|
+
|
|
396
|
+
export function encodeChain(msg: PbChain): Uint8Array {
|
|
397
|
+
const w = new ProtoWriter();
|
|
398
|
+
w.writeEnum(1, msg.direction);
|
|
399
|
+
w.writeVarint(2, msg.currentEpoch);
|
|
400
|
+
for (const link of msg.links) {
|
|
401
|
+
w.writeBytes(3, encodeEpoch(link));
|
|
402
|
+
}
|
|
403
|
+
w.writeBytes(4, msg.nextRoot);
|
|
404
|
+
w.writeVarint(5, msg.sendEpoch);
|
|
405
|
+
if (msg.params !== undefined) {
|
|
406
|
+
const sub = new ProtoWriter();
|
|
407
|
+
sub.writeVarint(1, msg.params.maxJump);
|
|
408
|
+
sub.writeVarint(2, msg.params.maxOooKeys);
|
|
409
|
+
w.writeMessage(6, sub);
|
|
410
|
+
}
|
|
411
|
+
return w.finish();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
export function decodeChain(data: Uint8Array): PbChain {
|
|
415
|
+
const r = new ProtoReader(data);
|
|
416
|
+
let direction = 0;
|
|
417
|
+
let currentEpoch = 0n;
|
|
418
|
+
const links: PbEpoch[] = [];
|
|
419
|
+
let nextRoot: Uint8Array = EMPTY_BYTES;
|
|
420
|
+
let sendEpoch = 0n;
|
|
421
|
+
let params: PbChainParams | undefined;
|
|
422
|
+
while (!r.done) {
|
|
423
|
+
const field = r.readField();
|
|
424
|
+
if (field === null) break;
|
|
425
|
+
switch (field.fieldNumber) {
|
|
426
|
+
case 1:
|
|
427
|
+
direction = r.readEnum();
|
|
428
|
+
break;
|
|
429
|
+
case 2:
|
|
430
|
+
currentEpoch = r.readVarint64();
|
|
431
|
+
break;
|
|
432
|
+
case 3:
|
|
433
|
+
links.push(decodeEpoch(r.readBytes()));
|
|
434
|
+
break;
|
|
435
|
+
case 4:
|
|
436
|
+
nextRoot = r.readBytes();
|
|
437
|
+
break;
|
|
438
|
+
case 5:
|
|
439
|
+
sendEpoch = r.readVarint64();
|
|
440
|
+
break;
|
|
441
|
+
case 6:
|
|
442
|
+
params = decodeChainParams(r.readBytes());
|
|
443
|
+
break;
|
|
444
|
+
default:
|
|
445
|
+
r.skip(field.wireType);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return { direction, currentEpoch, links, nextRoot, sendEpoch, params };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// =========================================================================
|
|
452
|
+
// Authenticator
|
|
453
|
+
// =========================================================================
|
|
454
|
+
|
|
455
|
+
export function encodeAuthenticator(msg: PbAuthenticator): Uint8Array {
|
|
456
|
+
const w = new ProtoWriter();
|
|
457
|
+
w.writeBytes(1, msg.rootKey);
|
|
458
|
+
w.writeBytes(2, msg.macKey);
|
|
459
|
+
return w.finish();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function decodeAuthenticator(data: Uint8Array): PbAuthenticator {
|
|
463
|
+
const r = new ProtoReader(data);
|
|
464
|
+
let rootKey: Uint8Array = EMPTY_BYTES;
|
|
465
|
+
let macKey: Uint8Array = EMPTY_BYTES;
|
|
466
|
+
while (!r.done) {
|
|
467
|
+
const field = r.readField();
|
|
468
|
+
if (field === null) break;
|
|
469
|
+
switch (field.fieldNumber) {
|
|
470
|
+
case 1:
|
|
471
|
+
rootKey = r.readBytes();
|
|
472
|
+
break;
|
|
473
|
+
case 2:
|
|
474
|
+
macKey = r.readBytes();
|
|
475
|
+
break;
|
|
476
|
+
default:
|
|
477
|
+
r.skip(field.wireType);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return { rootKey, macKey };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// =========================================================================
|
|
484
|
+
// PolynomialEncoder
|
|
485
|
+
// =========================================================================
|
|
486
|
+
|
|
487
|
+
export function encodePolynomialEncoder(msg: PbPolynomialEncoder): Uint8Array {
|
|
488
|
+
const w = new ProtoWriter();
|
|
489
|
+
w.writeVarint(1, msg.idx);
|
|
490
|
+
for (const pt of msg.pts) {
|
|
491
|
+
w.writeBytes(2, pt);
|
|
492
|
+
}
|
|
493
|
+
for (const poly of msg.polys) {
|
|
494
|
+
w.writeBytes(3, poly);
|
|
495
|
+
}
|
|
496
|
+
return w.finish();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function decodePolynomialEncoder(data: Uint8Array): PbPolynomialEncoder {
|
|
500
|
+
const r = new ProtoReader(data);
|
|
501
|
+
let idx = 0;
|
|
502
|
+
const pts: Uint8Array[] = [];
|
|
503
|
+
const polys: Uint8Array[] = [];
|
|
504
|
+
while (!r.done) {
|
|
505
|
+
const field = r.readField();
|
|
506
|
+
if (field === null) break;
|
|
507
|
+
switch (field.fieldNumber) {
|
|
508
|
+
case 1:
|
|
509
|
+
idx = r.readVarint();
|
|
510
|
+
break;
|
|
511
|
+
case 2:
|
|
512
|
+
pts.push(r.readBytes());
|
|
513
|
+
break;
|
|
514
|
+
case 3:
|
|
515
|
+
polys.push(r.readBytes());
|
|
516
|
+
break;
|
|
517
|
+
default:
|
|
518
|
+
r.skip(field.wireType);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return { idx, pts, polys };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
// =========================================================================
|
|
525
|
+
// PolynomialDecoder
|
|
526
|
+
// =========================================================================
|
|
527
|
+
|
|
528
|
+
export function encodePolynomialDecoder(msg: PbPolynomialDecoder): Uint8Array {
|
|
529
|
+
const w = new ProtoWriter();
|
|
530
|
+
w.writeVarint(1, msg.ptsNeeded);
|
|
531
|
+
w.writeVarint(2, msg.polys);
|
|
532
|
+
for (const pt of msg.pts) {
|
|
533
|
+
w.writeBytes(3, pt);
|
|
534
|
+
}
|
|
535
|
+
w.writeBool(4, msg.isComplete);
|
|
536
|
+
return w.finish();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export function decodePolynomialDecoder(data: Uint8Array): PbPolynomialDecoder {
|
|
540
|
+
const r = new ProtoReader(data);
|
|
541
|
+
let ptsNeeded = 0;
|
|
542
|
+
let polys = 0;
|
|
543
|
+
const pts: Uint8Array[] = [];
|
|
544
|
+
let isComplete = false;
|
|
545
|
+
while (!r.done) {
|
|
546
|
+
const field = r.readField();
|
|
547
|
+
if (field === null) break;
|
|
548
|
+
switch (field.fieldNumber) {
|
|
549
|
+
case 1:
|
|
550
|
+
ptsNeeded = r.readVarint();
|
|
551
|
+
break;
|
|
552
|
+
case 2:
|
|
553
|
+
polys = r.readVarint();
|
|
554
|
+
break;
|
|
555
|
+
case 3:
|
|
556
|
+
pts.push(r.readBytes());
|
|
557
|
+
break;
|
|
558
|
+
case 4:
|
|
559
|
+
isComplete = r.readBool();
|
|
560
|
+
break;
|
|
561
|
+
default:
|
|
562
|
+
r.skip(field.wireType);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return { ptsNeeded, polys, pts, isComplete };
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// =========================================================================
|
|
569
|
+
// Chunk
|
|
570
|
+
// =========================================================================
|
|
571
|
+
|
|
572
|
+
export function encodeChunk(msg: PbChunk): Uint8Array {
|
|
573
|
+
const w = new ProtoWriter();
|
|
574
|
+
w.writeVarint(1, msg.index);
|
|
575
|
+
w.writeBytes(2, msg.data);
|
|
576
|
+
return w.finish();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function decodeChunk(data: Uint8Array): PbChunk {
|
|
580
|
+
const r = new ProtoReader(data);
|
|
581
|
+
let index = 0;
|
|
582
|
+
let chunkData: Uint8Array = EMPTY_BYTES;
|
|
583
|
+
while (!r.done) {
|
|
584
|
+
const field = r.readField();
|
|
585
|
+
if (field === null) break;
|
|
586
|
+
switch (field.fieldNumber) {
|
|
587
|
+
case 1:
|
|
588
|
+
index = r.readVarint();
|
|
589
|
+
break;
|
|
590
|
+
case 2:
|
|
591
|
+
chunkData = r.readBytes();
|
|
592
|
+
break;
|
|
593
|
+
default:
|
|
594
|
+
r.skip(field.wireType);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return { index, data: chunkData };
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// =========================================================================
|
|
601
|
+
// V1Msg
|
|
602
|
+
// =========================================================================
|
|
603
|
+
|
|
604
|
+
export function encodeV1Msg(msg: PbV1Msg): Uint8Array {
|
|
605
|
+
const w = new ProtoWriter();
|
|
606
|
+
w.writeVarint(1, msg.epoch);
|
|
607
|
+
w.writeVarint(2, msg.index);
|
|
608
|
+
if (msg.innerMsg !== undefined) {
|
|
609
|
+
const inner = msg.innerMsg;
|
|
610
|
+
switch (inner.type) {
|
|
611
|
+
case "hdr": {
|
|
612
|
+
const sub = new ProtoWriter();
|
|
613
|
+
sub.writeVarint(1, inner.chunk.index);
|
|
614
|
+
sub.writeBytes(2, inner.chunk.data);
|
|
615
|
+
w.writeMessage(3, sub);
|
|
616
|
+
break;
|
|
617
|
+
}
|
|
618
|
+
case "ek": {
|
|
619
|
+
const sub = new ProtoWriter();
|
|
620
|
+
sub.writeVarint(1, inner.chunk.index);
|
|
621
|
+
sub.writeBytes(2, inner.chunk.data);
|
|
622
|
+
w.writeMessage(4, sub);
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
case "ekCt1Ack": {
|
|
626
|
+
const sub = new ProtoWriter();
|
|
627
|
+
sub.writeVarint(1, inner.chunk.index);
|
|
628
|
+
sub.writeBytes(2, inner.chunk.data);
|
|
629
|
+
w.writeMessage(5, sub);
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
case "ct1Ack": {
|
|
633
|
+
// Bool field at field 6
|
|
634
|
+
w.writeBool(6, inner.value);
|
|
635
|
+
break;
|
|
636
|
+
}
|
|
637
|
+
case "ct1": {
|
|
638
|
+
const sub = new ProtoWriter();
|
|
639
|
+
sub.writeVarint(1, inner.chunk.index);
|
|
640
|
+
sub.writeBytes(2, inner.chunk.data);
|
|
641
|
+
w.writeMessage(7, sub);
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
case "ct2": {
|
|
645
|
+
const sub = new ProtoWriter();
|
|
646
|
+
sub.writeVarint(1, inner.chunk.index);
|
|
647
|
+
sub.writeBytes(2, inner.chunk.data);
|
|
648
|
+
w.writeMessage(8, sub);
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return w.finish();
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
export function decodeV1Msg(data: Uint8Array): PbV1Msg {
|
|
657
|
+
const r = new ProtoReader(data);
|
|
658
|
+
let epoch = 0n;
|
|
659
|
+
let index = 0;
|
|
660
|
+
let innerMsg: PbV1MsgInner | undefined;
|
|
661
|
+
while (!r.done) {
|
|
662
|
+
const field = r.readField();
|
|
663
|
+
if (field === null) break;
|
|
664
|
+
switch (field.fieldNumber) {
|
|
665
|
+
case 1:
|
|
666
|
+
epoch = r.readVarint64();
|
|
667
|
+
break;
|
|
668
|
+
case 2:
|
|
669
|
+
index = r.readVarint();
|
|
670
|
+
break;
|
|
671
|
+
case 3:
|
|
672
|
+
innerMsg = { type: "hdr", chunk: decodeChunk(r.readBytes()) };
|
|
673
|
+
break;
|
|
674
|
+
case 4:
|
|
675
|
+
innerMsg = { type: "ek", chunk: decodeChunk(r.readBytes()) };
|
|
676
|
+
break;
|
|
677
|
+
case 5:
|
|
678
|
+
innerMsg = { type: "ekCt1Ack", chunk: decodeChunk(r.readBytes()) };
|
|
679
|
+
break;
|
|
680
|
+
case 6:
|
|
681
|
+
innerMsg = { type: "ct1Ack", value: r.readBool() };
|
|
682
|
+
break;
|
|
683
|
+
case 7:
|
|
684
|
+
innerMsg = { type: "ct1", chunk: decodeChunk(r.readBytes()) };
|
|
685
|
+
break;
|
|
686
|
+
case 8:
|
|
687
|
+
innerMsg = { type: "ct2", chunk: decodeChunk(r.readBytes()) };
|
|
688
|
+
break;
|
|
689
|
+
default:
|
|
690
|
+
r.skip(field.wireType);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return { epoch, index, innerMsg };
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// =========================================================================
|
|
697
|
+
// Unchunked state encode/decode helpers
|
|
698
|
+
// =========================================================================
|
|
699
|
+
|
|
700
|
+
function encodeAuthOptional(w: ProtoWriter, fieldNumber: number, auth?: PbAuthenticator): void {
|
|
701
|
+
if (auth !== undefined) {
|
|
702
|
+
const sub = new ProtoWriter();
|
|
703
|
+
sub.writeBytes(1, auth.rootKey);
|
|
704
|
+
sub.writeBytes(2, auth.macKey);
|
|
705
|
+
w.writeMessage(fieldNumber, sub);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function decodeAuthOptional(r: ProtoReader): PbAuthenticator {
|
|
710
|
+
return decodeAuthenticator(r.readBytes());
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Detect whether an unchunked sub-message uses the Rust field layout
|
|
715
|
+
* (epoch at field 1, auth at field 2, data fields shifted +1) or the
|
|
716
|
+
* TS layout (auth at field 1, data fields as-is).
|
|
717
|
+
*
|
|
718
|
+
* Returns the field number offset: 0 for TS layout, 1 for Rust layout.
|
|
719
|
+
* Detection is based on field 1's wire type:
|
|
720
|
+
* - varint (wire type 0) => Rust layout (field 1 = epoch)
|
|
721
|
+
* - length-delimited (wire type 2) => TS layout (field 1 = auth)
|
|
722
|
+
*/
|
|
723
|
+
function detectUcLayout(data: Uint8Array): number {
|
|
724
|
+
if (data.length === 0) return 0;
|
|
725
|
+
// Read the first tag to check wire type
|
|
726
|
+
const firstByte = data[0];
|
|
727
|
+
const wireType = firstByte & 0x7;
|
|
728
|
+
// Varint wire type 0 at field 1 => Rust layout (epoch)
|
|
729
|
+
// Length-delimited wire type 2 at field 1 => TS layout (auth)
|
|
730
|
+
return wireType === WIRE_VARINT ? 1 : 0;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// -- keysUnsampled (unchunked) --
|
|
734
|
+
|
|
735
|
+
function encodeUcKeysUnsampled(msg: { auth?: PbAuthenticator | undefined }): Uint8Array {
|
|
736
|
+
const w = new ProtoWriter();
|
|
737
|
+
encodeAuthOptional(w, 1, msg.auth);
|
|
738
|
+
return w.finish();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function decodeUcKeysUnsampled(data: Uint8Array): { auth?: PbAuthenticator | undefined } {
|
|
742
|
+
const r = new ProtoReader(data);
|
|
743
|
+
const offset = detectUcLayout(data);
|
|
744
|
+
let auth: PbAuthenticator | undefined;
|
|
745
|
+
while (!r.done) {
|
|
746
|
+
const field = r.readField();
|
|
747
|
+
if (field === null) break;
|
|
748
|
+
const fn = field.fieldNumber - offset;
|
|
749
|
+
switch (fn) {
|
|
750
|
+
case 1:
|
|
751
|
+
auth = decodeAuthOptional(r);
|
|
752
|
+
break;
|
|
753
|
+
default:
|
|
754
|
+
r.skip(field.wireType);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return { auth };
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// -- keysSampled (unchunked) --
|
|
761
|
+
|
|
762
|
+
function encodeUcKeysSampled(msg: {
|
|
763
|
+
auth?: PbAuthenticator | undefined;
|
|
764
|
+
ek: Uint8Array;
|
|
765
|
+
dk: Uint8Array;
|
|
766
|
+
hdr: Uint8Array;
|
|
767
|
+
hdrMac: Uint8Array;
|
|
768
|
+
}): Uint8Array {
|
|
769
|
+
const w = new ProtoWriter();
|
|
770
|
+
encodeAuthOptional(w, 1, msg.auth);
|
|
771
|
+
w.writeBytes(2, msg.ek);
|
|
772
|
+
w.writeBytes(3, msg.dk);
|
|
773
|
+
w.writeBytes(4, msg.hdr);
|
|
774
|
+
w.writeBytes(5, msg.hdrMac);
|
|
775
|
+
return w.finish();
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function decodeUcKeysSampled(data: Uint8Array): {
|
|
779
|
+
auth?: PbAuthenticator | undefined;
|
|
780
|
+
ek: Uint8Array;
|
|
781
|
+
dk: Uint8Array;
|
|
782
|
+
hdr: Uint8Array;
|
|
783
|
+
hdrMac: Uint8Array;
|
|
784
|
+
} {
|
|
785
|
+
const r = new ProtoReader(data);
|
|
786
|
+
const offset = detectUcLayout(data);
|
|
787
|
+
let auth: PbAuthenticator | undefined;
|
|
788
|
+
let ek: Uint8Array = EMPTY_BYTES;
|
|
789
|
+
let dk: Uint8Array = EMPTY_BYTES;
|
|
790
|
+
let hdr: Uint8Array = EMPTY_BYTES;
|
|
791
|
+
let hdrMac: Uint8Array = EMPTY_BYTES;
|
|
792
|
+
while (!r.done) {
|
|
793
|
+
const field = r.readField();
|
|
794
|
+
if (field === null) break;
|
|
795
|
+
const fn = field.fieldNumber - offset;
|
|
796
|
+
switch (fn) {
|
|
797
|
+
case 1:
|
|
798
|
+
auth = decodeAuthOptional(r);
|
|
799
|
+
break;
|
|
800
|
+
case 2:
|
|
801
|
+
ek = r.readBytes();
|
|
802
|
+
break;
|
|
803
|
+
case 3:
|
|
804
|
+
dk = r.readBytes();
|
|
805
|
+
break;
|
|
806
|
+
case 4:
|
|
807
|
+
hdr = r.readBytes();
|
|
808
|
+
break;
|
|
809
|
+
case 5:
|
|
810
|
+
hdrMac = r.readBytes();
|
|
811
|
+
break;
|
|
812
|
+
default:
|
|
813
|
+
r.skip(field.wireType);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return { auth, ek, dk, hdr, hdrMac };
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// -- headerSent (unchunked) --
|
|
820
|
+
// NOTE: Rust uses Unchunked.EkSent for Chunked.HeaderSent:
|
|
821
|
+
// Rust: epoch=1, auth=2, dk=3
|
|
822
|
+
// TS: auth=1, ek=2, dk=3
|
|
823
|
+
// The TS type carries ek+dk but Rust's EkSent only has dk.
|
|
824
|
+
// The offset detection handles the epoch field difference.
|
|
825
|
+
|
|
826
|
+
function encodeUcHeaderSent(msg: {
|
|
827
|
+
auth?: PbAuthenticator | undefined;
|
|
828
|
+
ek: Uint8Array;
|
|
829
|
+
dk: Uint8Array;
|
|
830
|
+
}): Uint8Array {
|
|
831
|
+
const w = new ProtoWriter();
|
|
832
|
+
encodeAuthOptional(w, 1, msg.auth);
|
|
833
|
+
w.writeBytes(2, msg.ek);
|
|
834
|
+
w.writeBytes(3, msg.dk);
|
|
835
|
+
return w.finish();
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function decodeUcHeaderSent(data: Uint8Array): {
|
|
839
|
+
auth?: PbAuthenticator | undefined;
|
|
840
|
+
ek: Uint8Array;
|
|
841
|
+
dk: Uint8Array;
|
|
842
|
+
} {
|
|
843
|
+
const r = new ProtoReader(data);
|
|
844
|
+
const offset = detectUcLayout(data);
|
|
845
|
+
let auth: PbAuthenticator | undefined;
|
|
846
|
+
let ek: Uint8Array = EMPTY_BYTES;
|
|
847
|
+
let dk: Uint8Array = EMPTY_BYTES;
|
|
848
|
+
while (!r.done) {
|
|
849
|
+
const field = r.readField();
|
|
850
|
+
if (field === null) break;
|
|
851
|
+
const fn = field.fieldNumber - offset;
|
|
852
|
+
switch (fn) {
|
|
853
|
+
case 1:
|
|
854
|
+
auth = decodeAuthOptional(r);
|
|
855
|
+
break;
|
|
856
|
+
case 2:
|
|
857
|
+
ek = r.readBytes();
|
|
858
|
+
break;
|
|
859
|
+
case 3:
|
|
860
|
+
dk = r.readBytes();
|
|
861
|
+
break;
|
|
862
|
+
default:
|
|
863
|
+
r.skip(field.wireType);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return { auth, ek, dk };
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// -- ct1Received / ekSentCt1Received (unchunked, same shape) --
|
|
870
|
+
|
|
871
|
+
function encodeUcDkCt1(msg: {
|
|
872
|
+
auth?: PbAuthenticator | undefined;
|
|
873
|
+
dk: Uint8Array;
|
|
874
|
+
ct1: Uint8Array;
|
|
875
|
+
}): Uint8Array {
|
|
876
|
+
const w = new ProtoWriter();
|
|
877
|
+
encodeAuthOptional(w, 1, msg.auth);
|
|
878
|
+
w.writeBytes(2, msg.dk);
|
|
879
|
+
w.writeBytes(3, msg.ct1);
|
|
880
|
+
return w.finish();
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function decodeUcDkCt1(data: Uint8Array): {
|
|
884
|
+
auth?: PbAuthenticator | undefined;
|
|
885
|
+
dk: Uint8Array;
|
|
886
|
+
ct1: Uint8Array;
|
|
887
|
+
} {
|
|
888
|
+
const r = new ProtoReader(data);
|
|
889
|
+
const offset = detectUcLayout(data);
|
|
890
|
+
let auth: PbAuthenticator | undefined;
|
|
891
|
+
let dk: Uint8Array = EMPTY_BYTES;
|
|
892
|
+
let ct1: Uint8Array = EMPTY_BYTES;
|
|
893
|
+
while (!r.done) {
|
|
894
|
+
const field = r.readField();
|
|
895
|
+
if (field === null) break;
|
|
896
|
+
const fn = field.fieldNumber - offset;
|
|
897
|
+
switch (fn) {
|
|
898
|
+
case 1:
|
|
899
|
+
auth = decodeAuthOptional(r);
|
|
900
|
+
break;
|
|
901
|
+
case 2:
|
|
902
|
+
dk = r.readBytes();
|
|
903
|
+
break;
|
|
904
|
+
case 3:
|
|
905
|
+
ct1 = r.readBytes();
|
|
906
|
+
break;
|
|
907
|
+
default:
|
|
908
|
+
r.skip(field.wireType);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return { auth, dk, ct1 };
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// -- noHeaderReceived / ct2Sampled (unchunked, auth-only) --
|
|
915
|
+
// Reuses encodeUcKeysUnsampled / decodeUcKeysUnsampled
|
|
916
|
+
|
|
917
|
+
// -- headerReceived (unchunked) --
|
|
918
|
+
// NOTE: Rust Unchunked.HeaderReceived has only {epoch, auth, hdr},
|
|
919
|
+
// while TS carries {auth, hdr, es, ct1, ss}. The offset detection
|
|
920
|
+
// handles the epoch field difference; extra fields (es, ct1, ss)
|
|
921
|
+
// will be at shifted positions for Rust data.
|
|
922
|
+
|
|
923
|
+
function encodeUcHeaderReceived(msg: {
|
|
924
|
+
auth?: PbAuthenticator | undefined;
|
|
925
|
+
hdr: Uint8Array;
|
|
926
|
+
es: Uint8Array;
|
|
927
|
+
ct1: Uint8Array;
|
|
928
|
+
ss: Uint8Array;
|
|
929
|
+
}): Uint8Array {
|
|
930
|
+
const w = new ProtoWriter();
|
|
931
|
+
encodeAuthOptional(w, 1, msg.auth);
|
|
932
|
+
w.writeBytes(2, msg.hdr);
|
|
933
|
+
w.writeBytes(3, msg.es);
|
|
934
|
+
w.writeBytes(4, msg.ct1);
|
|
935
|
+
w.writeBytes(5, msg.ss);
|
|
936
|
+
return w.finish();
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function decodeUcHeaderReceived(data: Uint8Array): {
|
|
940
|
+
auth?: PbAuthenticator | undefined;
|
|
941
|
+
hdr: Uint8Array;
|
|
942
|
+
es: Uint8Array;
|
|
943
|
+
ct1: Uint8Array;
|
|
944
|
+
ss: Uint8Array;
|
|
945
|
+
} {
|
|
946
|
+
const r = new ProtoReader(data);
|
|
947
|
+
const offset = detectUcLayout(data);
|
|
948
|
+
let auth: PbAuthenticator | undefined;
|
|
949
|
+
let hdr: Uint8Array = EMPTY_BYTES;
|
|
950
|
+
let es: Uint8Array = EMPTY_BYTES;
|
|
951
|
+
let ct1: Uint8Array = EMPTY_BYTES;
|
|
952
|
+
let ss: Uint8Array = EMPTY_BYTES;
|
|
953
|
+
while (!r.done) {
|
|
954
|
+
const field = r.readField();
|
|
955
|
+
if (field === null) break;
|
|
956
|
+
const fn = field.fieldNumber - offset;
|
|
957
|
+
switch (fn) {
|
|
958
|
+
case 1:
|
|
959
|
+
auth = decodeAuthOptional(r);
|
|
960
|
+
break;
|
|
961
|
+
case 2:
|
|
962
|
+
hdr = r.readBytes();
|
|
963
|
+
break;
|
|
964
|
+
case 3:
|
|
965
|
+
es = r.readBytes();
|
|
966
|
+
break;
|
|
967
|
+
case 4:
|
|
968
|
+
ct1 = r.readBytes();
|
|
969
|
+
break;
|
|
970
|
+
case 5:
|
|
971
|
+
ss = r.readBytes();
|
|
972
|
+
break;
|
|
973
|
+
default:
|
|
974
|
+
r.skip(field.wireType);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return { auth, hdr, es, ct1, ss };
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// -- ct1Sampled / ct1Acknowledged (unchunked, auth+hdr+es+ct1) --
|
|
981
|
+
|
|
982
|
+
function encodeUcAuthHdrEsCt1(msg: {
|
|
983
|
+
auth?: PbAuthenticator | undefined;
|
|
984
|
+
hdr: Uint8Array;
|
|
985
|
+
es: Uint8Array;
|
|
986
|
+
ct1: Uint8Array;
|
|
987
|
+
}): Uint8Array {
|
|
988
|
+
const w = new ProtoWriter();
|
|
989
|
+
encodeAuthOptional(w, 1, msg.auth);
|
|
990
|
+
w.writeBytes(2, msg.hdr);
|
|
991
|
+
w.writeBytes(3, msg.es);
|
|
992
|
+
w.writeBytes(4, msg.ct1);
|
|
993
|
+
return w.finish();
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
function decodeUcAuthHdrEsCt1(data: Uint8Array): {
|
|
997
|
+
auth?: PbAuthenticator | undefined;
|
|
998
|
+
hdr: Uint8Array;
|
|
999
|
+
es: Uint8Array;
|
|
1000
|
+
ct1: Uint8Array;
|
|
1001
|
+
} {
|
|
1002
|
+
const r = new ProtoReader(data);
|
|
1003
|
+
const offset = detectUcLayout(data);
|
|
1004
|
+
let auth: PbAuthenticator | undefined;
|
|
1005
|
+
let hdr: Uint8Array = EMPTY_BYTES;
|
|
1006
|
+
let es: Uint8Array = EMPTY_BYTES;
|
|
1007
|
+
let ct1: Uint8Array = EMPTY_BYTES;
|
|
1008
|
+
while (!r.done) {
|
|
1009
|
+
const field = r.readField();
|
|
1010
|
+
if (field === null) break;
|
|
1011
|
+
const fn = field.fieldNumber - offset;
|
|
1012
|
+
switch (fn) {
|
|
1013
|
+
case 1:
|
|
1014
|
+
auth = decodeAuthOptional(r);
|
|
1015
|
+
break;
|
|
1016
|
+
case 2:
|
|
1017
|
+
hdr = r.readBytes();
|
|
1018
|
+
break;
|
|
1019
|
+
case 3:
|
|
1020
|
+
es = r.readBytes();
|
|
1021
|
+
break;
|
|
1022
|
+
case 4:
|
|
1023
|
+
ct1 = r.readBytes();
|
|
1024
|
+
break;
|
|
1025
|
+
default:
|
|
1026
|
+
r.skip(field.wireType);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return { auth, hdr, es, ct1 };
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// -- ekReceivedCt1Sampled (unchunked, auth+hdr+es+ek+ct1) --
|
|
1033
|
+
|
|
1034
|
+
function encodeUcEkReceivedCt1Sampled(msg: {
|
|
1035
|
+
auth?: PbAuthenticator | undefined;
|
|
1036
|
+
hdr: Uint8Array;
|
|
1037
|
+
es: Uint8Array;
|
|
1038
|
+
ek: Uint8Array;
|
|
1039
|
+
ct1: Uint8Array;
|
|
1040
|
+
}): Uint8Array {
|
|
1041
|
+
const w = new ProtoWriter();
|
|
1042
|
+
encodeAuthOptional(w, 1, msg.auth);
|
|
1043
|
+
w.writeBytes(2, msg.hdr);
|
|
1044
|
+
w.writeBytes(3, msg.es);
|
|
1045
|
+
w.writeBytes(4, msg.ek);
|
|
1046
|
+
w.writeBytes(5, msg.ct1);
|
|
1047
|
+
return w.finish();
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
function decodeUcEkReceivedCt1Sampled(data: Uint8Array): {
|
|
1051
|
+
auth?: PbAuthenticator | undefined;
|
|
1052
|
+
hdr: Uint8Array;
|
|
1053
|
+
es: Uint8Array;
|
|
1054
|
+
ek: Uint8Array;
|
|
1055
|
+
ct1: Uint8Array;
|
|
1056
|
+
} {
|
|
1057
|
+
const r = new ProtoReader(data);
|
|
1058
|
+
const offset = detectUcLayout(data);
|
|
1059
|
+
let auth: PbAuthenticator | undefined;
|
|
1060
|
+
let hdr: Uint8Array = EMPTY_BYTES;
|
|
1061
|
+
let es: Uint8Array = EMPTY_BYTES;
|
|
1062
|
+
let ek: Uint8Array = EMPTY_BYTES;
|
|
1063
|
+
let ct1: Uint8Array = EMPTY_BYTES;
|
|
1064
|
+
while (!r.done) {
|
|
1065
|
+
const field = r.readField();
|
|
1066
|
+
if (field === null) break;
|
|
1067
|
+
const fn = field.fieldNumber - offset;
|
|
1068
|
+
switch (fn) {
|
|
1069
|
+
case 1:
|
|
1070
|
+
auth = decodeAuthOptional(r);
|
|
1071
|
+
break;
|
|
1072
|
+
case 2:
|
|
1073
|
+
hdr = r.readBytes();
|
|
1074
|
+
break;
|
|
1075
|
+
case 3:
|
|
1076
|
+
es = r.readBytes();
|
|
1077
|
+
break;
|
|
1078
|
+
case 4:
|
|
1079
|
+
ek = r.readBytes();
|
|
1080
|
+
break;
|
|
1081
|
+
case 5:
|
|
1082
|
+
ct1 = r.readBytes();
|
|
1083
|
+
break;
|
|
1084
|
+
default:
|
|
1085
|
+
r.skip(field.wireType);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return { auth, hdr, es, ek, ct1 };
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
// =========================================================================
|
|
1092
|
+
// Chunked state (wraps unchunked + encoder/decoder)
|
|
1093
|
+
// =========================================================================
|
|
1094
|
+
|
|
1095
|
+
/**
|
|
1096
|
+
* Encode a PbChunkedState into a sub-message for V1State.
|
|
1097
|
+
* The field number in V1State determines which variant this is:
|
|
1098
|
+
* 1=keysUnsampled, 2=keysSampled, ..., 11=ct2Sampled
|
|
1099
|
+
*
|
|
1100
|
+
* Each chunked state has:
|
|
1101
|
+
* field 1 = unchunked data
|
|
1102
|
+
* field 2 = encoder (if present)
|
|
1103
|
+
* field 3 = decoder (if present)
|
|
1104
|
+
*/
|
|
1105
|
+
function encodeChunkedStateInner(state: PbChunkedState): { fieldNumber: number; data: Uint8Array } {
|
|
1106
|
+
const w = new ProtoWriter();
|
|
1107
|
+
|
|
1108
|
+
switch (state.type) {
|
|
1109
|
+
case "keysUnsampled": {
|
|
1110
|
+
w.writeBytes(1, encodeUcKeysUnsampled(state.uc));
|
|
1111
|
+
return { fieldNumber: 1, data: w.finish() };
|
|
1112
|
+
}
|
|
1113
|
+
case "keysSampled": {
|
|
1114
|
+
w.writeBytes(1, encodeUcKeysSampled(state.uc));
|
|
1115
|
+
w.writeBytes(2, encodePolynomialEncoder(state.sendingHdr));
|
|
1116
|
+
return { fieldNumber: 2, data: w.finish() };
|
|
1117
|
+
}
|
|
1118
|
+
case "headerSent": {
|
|
1119
|
+
w.writeBytes(1, encodeUcHeaderSent(state.uc));
|
|
1120
|
+
w.writeBytes(2, encodePolynomialEncoder(state.sendingEk));
|
|
1121
|
+
w.writeBytes(3, encodePolynomialDecoder(state.receivingCt1));
|
|
1122
|
+
return { fieldNumber: 3, data: w.finish() };
|
|
1123
|
+
}
|
|
1124
|
+
case "ct1Received": {
|
|
1125
|
+
w.writeBytes(1, encodeUcDkCt1(state.uc));
|
|
1126
|
+
w.writeBytes(2, encodePolynomialEncoder(state.sendingEk));
|
|
1127
|
+
return { fieldNumber: 4, data: w.finish() };
|
|
1128
|
+
}
|
|
1129
|
+
case "ekSentCt1Received": {
|
|
1130
|
+
w.writeBytes(1, encodeUcDkCt1(state.uc));
|
|
1131
|
+
w.writeBytes(3, encodePolynomialDecoder(state.receivingCt2));
|
|
1132
|
+
return { fieldNumber: 5, data: w.finish() };
|
|
1133
|
+
}
|
|
1134
|
+
case "noHeaderReceived": {
|
|
1135
|
+
w.writeBytes(1, encodeUcKeysUnsampled(state.uc));
|
|
1136
|
+
w.writeBytes(2, encodePolynomialDecoder(state.receivingHdr));
|
|
1137
|
+
return { fieldNumber: 6, data: w.finish() };
|
|
1138
|
+
}
|
|
1139
|
+
case "headerReceived": {
|
|
1140
|
+
w.writeBytes(1, encodeUcHeaderReceived(state.uc));
|
|
1141
|
+
w.writeBytes(2, encodePolynomialDecoder(state.receivingEk));
|
|
1142
|
+
return { fieldNumber: 7, data: w.finish() };
|
|
1143
|
+
}
|
|
1144
|
+
case "ct1Sampled": {
|
|
1145
|
+
w.writeBytes(1, encodeUcAuthHdrEsCt1(state.uc));
|
|
1146
|
+
w.writeBytes(2, encodePolynomialEncoder(state.sendingCt1));
|
|
1147
|
+
w.writeBytes(3, encodePolynomialDecoder(state.receivingEk));
|
|
1148
|
+
return { fieldNumber: 8, data: w.finish() };
|
|
1149
|
+
}
|
|
1150
|
+
case "ekReceivedCt1Sampled": {
|
|
1151
|
+
w.writeBytes(1, encodeUcEkReceivedCt1Sampled(state.uc));
|
|
1152
|
+
w.writeBytes(2, encodePolynomialEncoder(state.sendingCt1));
|
|
1153
|
+
return { fieldNumber: 9, data: w.finish() };
|
|
1154
|
+
}
|
|
1155
|
+
case "ct1Acknowledged": {
|
|
1156
|
+
w.writeBytes(1, encodeUcAuthHdrEsCt1(state.uc));
|
|
1157
|
+
w.writeBytes(2, encodePolynomialDecoder(state.receivingEk));
|
|
1158
|
+
return { fieldNumber: 10, data: w.finish() };
|
|
1159
|
+
}
|
|
1160
|
+
case "ct2Sampled": {
|
|
1161
|
+
w.writeBytes(1, encodeUcKeysUnsampled(state.uc));
|
|
1162
|
+
w.writeBytes(2, encodePolynomialEncoder(state.sendingCt2));
|
|
1163
|
+
return { fieldNumber: 11, data: w.finish() };
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
interface ChunkedDecodeResult {
|
|
1169
|
+
uc: Uint8Array;
|
|
1170
|
+
encoder?: Uint8Array | undefined;
|
|
1171
|
+
decoder?: Uint8Array | undefined;
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
function decodeChunkedRaw(data: Uint8Array): ChunkedDecodeResult {
|
|
1175
|
+
const r = new ProtoReader(data);
|
|
1176
|
+
let uc: Uint8Array = EMPTY_BYTES;
|
|
1177
|
+
let encoder: Uint8Array | undefined;
|
|
1178
|
+
let decoder: Uint8Array | undefined;
|
|
1179
|
+
while (!r.done) {
|
|
1180
|
+
const field = r.readField();
|
|
1181
|
+
if (field === null) break;
|
|
1182
|
+
switch (field.fieldNumber) {
|
|
1183
|
+
case 1:
|
|
1184
|
+
uc = r.readBytes();
|
|
1185
|
+
break;
|
|
1186
|
+
case 2:
|
|
1187
|
+
encoder = r.readBytes();
|
|
1188
|
+
break;
|
|
1189
|
+
case 3:
|
|
1190
|
+
decoder = r.readBytes();
|
|
1191
|
+
break;
|
|
1192
|
+
default:
|
|
1193
|
+
r.skip(field.wireType);
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
return { uc, encoder, decoder };
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
function requireField(data: Uint8Array | undefined, name: string): Uint8Array {
|
|
1200
|
+
if (data === undefined) {
|
|
1201
|
+
throw new Error(`Protobuf: missing required field '${name}'`);
|
|
1202
|
+
}
|
|
1203
|
+
return data;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
function decodeChunkedState(fieldNumber: number, data: Uint8Array): PbChunkedState {
|
|
1207
|
+
const raw = decodeChunkedRaw(data);
|
|
1208
|
+
|
|
1209
|
+
switch (fieldNumber) {
|
|
1210
|
+
case 1:
|
|
1211
|
+
return { type: "keysUnsampled", uc: decodeUcKeysUnsampled(raw.uc) };
|
|
1212
|
+
case 2:
|
|
1213
|
+
return {
|
|
1214
|
+
type: "keysSampled",
|
|
1215
|
+
uc: decodeUcKeysSampled(raw.uc),
|
|
1216
|
+
sendingHdr: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
|
|
1217
|
+
};
|
|
1218
|
+
case 3:
|
|
1219
|
+
return {
|
|
1220
|
+
type: "headerSent",
|
|
1221
|
+
uc: decodeUcHeaderSent(raw.uc),
|
|
1222
|
+
sendingEk: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
|
|
1223
|
+
receivingCt1: decodePolynomialDecoder(requireField(raw.decoder, "decoder")),
|
|
1224
|
+
};
|
|
1225
|
+
case 4:
|
|
1226
|
+
return {
|
|
1227
|
+
type: "ct1Received",
|
|
1228
|
+
uc: decodeUcDkCt1(raw.uc),
|
|
1229
|
+
sendingEk: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
|
|
1230
|
+
};
|
|
1231
|
+
case 5:
|
|
1232
|
+
return {
|
|
1233
|
+
type: "ekSentCt1Received",
|
|
1234
|
+
uc: decodeUcDkCt1(raw.uc),
|
|
1235
|
+
receivingCt2: decodePolynomialDecoder(requireField(raw.decoder, "decoder")),
|
|
1236
|
+
};
|
|
1237
|
+
case 6:
|
|
1238
|
+
return {
|
|
1239
|
+
type: "noHeaderReceived",
|
|
1240
|
+
uc: decodeUcKeysUnsampled(raw.uc),
|
|
1241
|
+
receivingHdr: decodePolynomialDecoder(requireField(raw.encoder, "encoder")),
|
|
1242
|
+
};
|
|
1243
|
+
case 7:
|
|
1244
|
+
return {
|
|
1245
|
+
type: "headerReceived",
|
|
1246
|
+
uc: decodeUcHeaderReceived(raw.uc),
|
|
1247
|
+
receivingEk: decodePolynomialDecoder(requireField(raw.encoder, "encoder")),
|
|
1248
|
+
};
|
|
1249
|
+
case 8:
|
|
1250
|
+
return {
|
|
1251
|
+
type: "ct1Sampled",
|
|
1252
|
+
uc: decodeUcAuthHdrEsCt1(raw.uc),
|
|
1253
|
+
sendingCt1: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
|
|
1254
|
+
receivingEk: decodePolynomialDecoder(requireField(raw.decoder, "decoder")),
|
|
1255
|
+
};
|
|
1256
|
+
case 9:
|
|
1257
|
+
return {
|
|
1258
|
+
type: "ekReceivedCt1Sampled",
|
|
1259
|
+
uc: decodeUcEkReceivedCt1Sampled(raw.uc),
|
|
1260
|
+
sendingCt1: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
|
|
1261
|
+
};
|
|
1262
|
+
case 10:
|
|
1263
|
+
return {
|
|
1264
|
+
type: "ct1Acknowledged",
|
|
1265
|
+
uc: decodeUcAuthHdrEsCt1(raw.uc),
|
|
1266
|
+
receivingEk: decodePolynomialDecoder(requireField(raw.encoder, "encoder")),
|
|
1267
|
+
};
|
|
1268
|
+
case 11:
|
|
1269
|
+
return {
|
|
1270
|
+
type: "ct2Sampled",
|
|
1271
|
+
uc: decodeUcKeysUnsampled(raw.uc),
|
|
1272
|
+
sendingCt2: decodePolynomialEncoder(requireField(raw.encoder, "encoder")),
|
|
1273
|
+
};
|
|
1274
|
+
default:
|
|
1275
|
+
throw new Error(`Unknown chunked state field number: ${fieldNumber}`);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
// =========================================================================
|
|
1280
|
+
// V1State
|
|
1281
|
+
// =========================================================================
|
|
1282
|
+
|
|
1283
|
+
function encodeV1State(msg: PbV1State): Uint8Array {
|
|
1284
|
+
if (msg.innerState === undefined) return EMPTY_BYTES;
|
|
1285
|
+
const { fieldNumber, data } = encodeChunkedStateInner(msg.innerState);
|
|
1286
|
+
const w = new ProtoWriter();
|
|
1287
|
+
// Write as length-delimited sub-message at the correct oneof field number
|
|
1288
|
+
w.writeBytes(fieldNumber, data);
|
|
1289
|
+
// Field 12: epoch (used for deserialization round-trip)
|
|
1290
|
+
if (msg.epoch !== undefined) {
|
|
1291
|
+
w.writeVarint(12, msg.epoch);
|
|
1292
|
+
}
|
|
1293
|
+
return w.finish();
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function decodeV1State(data: Uint8Array): PbV1State {
|
|
1297
|
+
const r = new ProtoReader(data);
|
|
1298
|
+
let innerState: PbChunkedState | undefined;
|
|
1299
|
+
let epoch: bigint | undefined;
|
|
1300
|
+
while (!r.done) {
|
|
1301
|
+
const field = r.readField();
|
|
1302
|
+
if (field === null) break;
|
|
1303
|
+
if (field.fieldNumber >= 1 && field.fieldNumber <= 11) {
|
|
1304
|
+
innerState = decodeChunkedState(field.fieldNumber, r.readBytes());
|
|
1305
|
+
} else if (field.fieldNumber === 12) {
|
|
1306
|
+
epoch = r.readVarint64();
|
|
1307
|
+
} else {
|
|
1308
|
+
r.skip(field.wireType);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
return { innerState, epoch };
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
// =========================================================================
|
|
1315
|
+
// PqRatchetState (top-level)
|
|
1316
|
+
// =========================================================================
|
|
1317
|
+
|
|
1318
|
+
export function encodePqRatchetState(msg: PbPqRatchetState): Uint8Array {
|
|
1319
|
+
const w = new ProtoWriter();
|
|
1320
|
+
if (msg.versionNegotiation !== undefined) {
|
|
1321
|
+
w.writeBytes(1, encodeVersionNegotiation(msg.versionNegotiation));
|
|
1322
|
+
}
|
|
1323
|
+
if (msg.chain !== undefined) {
|
|
1324
|
+
w.writeBytes(2, encodeChain(msg.chain));
|
|
1325
|
+
}
|
|
1326
|
+
if (msg.v1 !== undefined) {
|
|
1327
|
+
w.writeBytes(3, encodeV1State(msg.v1));
|
|
1328
|
+
}
|
|
1329
|
+
return w.finish();
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
export function decodePqRatchetState(data: Uint8Array): PbPqRatchetState {
|
|
1333
|
+
const r = new ProtoReader(data);
|
|
1334
|
+
let versionNegotiation: PbVersionNegotiation | undefined;
|
|
1335
|
+
let chain: PbChain | undefined;
|
|
1336
|
+
let v1: PbV1State | undefined;
|
|
1337
|
+
while (!r.done) {
|
|
1338
|
+
const field = r.readField();
|
|
1339
|
+
if (field === null) break;
|
|
1340
|
+
switch (field.fieldNumber) {
|
|
1341
|
+
case 1:
|
|
1342
|
+
versionNegotiation = decodeVersionNegotiation(r.readBytes());
|
|
1343
|
+
break;
|
|
1344
|
+
case 2:
|
|
1345
|
+
chain = decodeChain(r.readBytes());
|
|
1346
|
+
break;
|
|
1347
|
+
case 3:
|
|
1348
|
+
v1 = decodeV1State(r.readBytes());
|
|
1349
|
+
break;
|
|
1350
|
+
default:
|
|
1351
|
+
r.skip(field.wireType);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
return { versionNegotiation, chain, v1 };
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// =========================================================================
|
|
1358
|
+
// Re-exports
|
|
1359
|
+
// =========================================================================
|
|
1360
|
+
|
|
1361
|
+
export type {
|
|
1362
|
+
PbAuthenticator,
|
|
1363
|
+
PbChain,
|
|
1364
|
+
PbChainParams,
|
|
1365
|
+
PbChunk,
|
|
1366
|
+
PbChunkedState,
|
|
1367
|
+
PbEpoch,
|
|
1368
|
+
PbEpochDirection,
|
|
1369
|
+
PbPolynomialDecoder,
|
|
1370
|
+
PbPolynomialEncoder,
|
|
1371
|
+
PbPqRatchetState,
|
|
1372
|
+
PbV1Msg,
|
|
1373
|
+
PbV1MsgInner,
|
|
1374
|
+
PbV1State,
|
|
1375
|
+
PbVersionNegotiation,
|
|
1376
|
+
} from "./pq-ratchet-types.js";
|