@peerbit/stream-interface 1.0.11 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/messages.ts CHANGED
@@ -15,15 +15,23 @@ import {
15
15
  verify,
16
16
  randomBytes,
17
17
  sha256Base64,
18
- sha256
18
+ getPublicKeyFromPeerId
19
19
  } from "@peerbit/crypto";
20
20
 
21
+ import type { PeerId } from "@libp2p/interface/peer-id";
22
+
23
+ export const ID_LENGTH = 32;
24
+
25
+ const WEEK_MS = 7 * 24 * 60 * 60 + 1000;
26
+
27
+ const SIGNATURES_SIZE_ENCODING = "u8"; // with 7 steps you know everyone in the world?, so u8 *should* suffice
28
+
21
29
  /**
22
30
  * The default msgID implementation
23
31
  * Child class can override this.
24
32
  */
25
33
  export const getMsgId = async (msg: Uint8ArrayList | Uint8Array) => {
26
- // first bytes is discriminator,
34
+ // first bytes fis discriminator,
27
35
  // next 32 bytes should be an id
28
36
  //return Buffer.from(msg.slice(0, 33)).toString('base64');
29
37
 
@@ -51,57 +59,107 @@ if ((globalThis as any).Buffer) {
51
59
  };
52
60
  }
53
61
 
54
- export const ID_LENGTH = 32;
62
+ const coerceTo = (tos: (string | PublicSignKey | PeerId)[] | Set<string>) => {
63
+ const toHashes: string[] = [];
64
+ let i = 0;
55
65
 
56
- const WEEK_MS = 7 * 24 * 60 * 60 + 1000;
66
+ for (const to of tos) {
67
+ const hash =
68
+ to instanceof PublicSignKey
69
+ ? to.hashcode()
70
+ : typeof to === "string"
71
+ ? to
72
+ : getPublicKeyFromPeerId(to).hashcode();
57
73
 
58
- @variant(0)
59
- export class MessageHeader {
60
- @field({ type: fixedArray("u8", ID_LENGTH) })
61
- private _id: Uint8Array;
74
+ toHashes[i++] = hash;
75
+ }
76
+ return toHashes;
77
+ };
62
78
 
63
- @field({ type: "u64" })
64
- private _timestamp: bigint;
79
+ export abstract class DeliveryMode {}
65
80
 
66
- @field({ type: "u64" })
67
- private _expires: bigint;
81
+ /**
82
+ * when you just want to deliver at paths, but does not expect acknowledgement
83
+ */
84
+ @variant(0)
85
+ export class SilentDelivery extends DeliveryMode {
86
+ @field({ type: vec("string") })
87
+ to: string[];
68
88
 
69
- constructor(properties?: { expires?: bigint; id?: Uint8Array }) {
70
- this._id = properties?.id || randomBytes(ID_LENGTH);
71
- this._expires = properties?.expires || BigInt(+new Date() + WEEK_MS);
72
- this._timestamp = BigInt(+new Date());
73
- }
89
+ @field({ type: "u8" })
90
+ redundancy: number;
74
91
 
75
- get id() {
76
- return this._id;
92
+ constructor(properties: {
93
+ to: (string | PublicSignKey | PeerId)[] | Set<string>;
94
+ redundancy: number;
95
+ }) {
96
+ super();
97
+ this.to = coerceTo(properties.to);
98
+ this.redundancy = properties.redundancy;
77
99
  }
100
+ }
78
101
 
79
- get expires() {
80
- return this._expires;
81
- }
102
+ /**
103
+ * Deliver and expect acknowledgement
104
+ */
105
+ @variant(1)
106
+ export class AcknowledgeDelivery extends DeliveryMode {
107
+ @field({ type: vec("string") })
108
+ to: string[];
82
109
 
83
- get timetamp() {
84
- return this._timestamp;
110
+ @field({ type: "u8" })
111
+ redundancy: number;
112
+
113
+ constructor(properties: {
114
+ to: (string | PublicSignKey | PeerId)[] | Set<string>;
115
+ redundancy: number;
116
+ }) {
117
+ super();
118
+ this.to = coerceTo(properties.to);
119
+ this.redundancy = properties.redundancy;
85
120
  }
121
+ }
86
122
 
87
- equals(other: MessageHeader) {
88
- return this._expires === other.expires && equals(this._id, other.id);
123
+ /**
124
+ * Deliver but with greedy fanout so that we eventually reach our target
125
+ * Expect acknowledgement
126
+ */
127
+ @variant(2)
128
+ export class SeekDelivery extends DeliveryMode {
129
+ @field({ type: option(vec("string")) })
130
+ to?: string[];
131
+
132
+ @field({ type: "u8" })
133
+ redundancy: number;
134
+
135
+ constructor(properties: {
136
+ to?: (string | PublicSignKey | PeerId)[] | Set<string>;
137
+ redundancy: number;
138
+ }) {
139
+ super();
140
+ this.to = properties.to ? coerceTo(properties.to) : undefined;
141
+ this.redundancy = properties.redundancy;
89
142
  }
143
+ }
90
144
 
91
- verify() {
92
- return this.expires >= +new Date();
145
+ @variant(3)
146
+ export class TracedDelivery extends DeliveryMode {
147
+ @field({ type: vec("string") })
148
+ trace: string[];
149
+
150
+ constructor(trace: string[]) {
151
+ super();
152
+ this.trace = trace;
93
153
  }
94
154
  }
95
155
 
96
- class PublicKeys {
97
- @field({ type: vec(PublicSignKey) })
98
- keys: PublicSignKey[];
99
- constructor(keys: PublicSignKey[]) {
100
- this.keys = keys;
156
+ @variant(4)
157
+ export class AnyWhere extends DeliveryMode {
158
+ constructor() {
159
+ super();
101
160
  }
102
161
  }
103
162
 
104
- const SIGNATURES_SIZE_ENCODING = "u8"; // with 7 steps you know everyone in the world?, so u8 *should* suffice
105
163
  @variant(0)
106
164
  export class Signatures {
107
165
  @field({ type: vec(SignatureWithKey, SIGNATURES_SIZE_ENCODING) })
@@ -121,220 +179,202 @@ export class Signatures {
121
179
  get publicKeys(): PublicSignKey[] {
122
180
  return this.signatures.map((x) => x.publicKey);
123
181
  }
182
+ }
183
+
184
+ abstract class PeerInfo {}
185
+
186
+ @variant(0)
187
+ export class MultiAddrinfo extends PeerInfo {
188
+ @field({ type: vec("string") })
189
+ multiaddrs: string[];
124
190
 
125
- hashPublicKeys(): Promise<string> {
126
- return sha256Base64(serialize(new PublicKeys(this.publicKeys)));
191
+ constructor(multiaddrs: string[]) {
192
+ super();
193
+ this.multiaddrs = multiaddrs;
127
194
  }
128
195
  }
129
196
 
130
- const keyMap: Map<string, PublicSignKey> = new Map();
131
- interface Signed {
132
- get signatures(): Signatures;
197
+ @variant(0)
198
+ export class MessageHeader<T extends DeliveryMode = DeliveryMode> {
199
+ @field({ type: fixedArray("u8", ID_LENGTH) })
200
+ private _id: Uint8Array;
201
+
202
+ @field({ type: "u64" })
203
+ private _timestamp: bigint;
204
+
205
+ @field({ type: "u64" })
206
+ private _expires: bigint;
207
+
208
+ @field({ type: option(PeerInfo) })
209
+ private _origin?: MultiAddrinfo;
210
+
211
+ // Not signed, since we might want to modify it during transit
212
+ @field({ type: option(DeliveryMode) })
213
+ mode: T;
214
+
215
+ // Not signed, since we might want to modify it during transit
216
+ @field({ type: option(Signatures) })
217
+ signatures: Signatures | undefined;
218
+
219
+ constructor(properties: {
220
+ origin?: MultiAddrinfo;
221
+ expires?: bigint;
222
+ id?: Uint8Array;
223
+ mode: T;
224
+ }) {
225
+ this._id = properties?.id || randomBytes(ID_LENGTH);
226
+ this._expires = properties?.expires || BigInt(+new Date() + WEEK_MS);
227
+ this._timestamp = BigInt(+new Date());
228
+ this.signatures = new Signatures();
229
+ this._origin = properties?.origin;
230
+ this.mode = properties.mode;
231
+ }
232
+
233
+ get id() {
234
+ return this._id;
235
+ }
236
+
237
+ get expires() {
238
+ return this._expires;
239
+ }
240
+
241
+ get timetamp() {
242
+ return this._timestamp;
243
+ }
244
+
245
+ get origin(): MultiAddrinfo | undefined {
246
+ return this._origin;
247
+ }
248
+
249
+ equals(other: MessageHeader) {
250
+ return this._expires === other.expires && equals(this._id, other.id);
251
+ }
252
+
253
+ verify() {
254
+ return this.expires >= +new Date();
255
+ }
133
256
  }
134
- interface Suffix {
135
- getSuffix(iteration: number): Uint8Array | Uint8Array[];
257
+
258
+ interface WithHeader {
259
+ header: MessageHeader;
136
260
  }
137
261
 
262
+ const sign = async <T extends WithHeader>(
263
+ obj: T,
264
+ signer: (bytes: Uint8Array) => Promise<SignatureWithKey>
265
+ ): Promise<T> => {
266
+ const mode = obj.header.mode;
267
+ obj.header.mode = undefined as any;
268
+ const signatures = obj.header.signatures;
269
+ obj.header.signatures = undefined;
270
+ const bytes = serialize(obj);
271
+ // reassign properties if some other process expects them
272
+ obj.header.signatures = signatures;
273
+ obj.header.mode = mode;
274
+
275
+ const signature = await signer(bytes);
276
+ obj.header.signatures = new Signatures(
277
+ signatures ? [...signatures.signatures, signature] : [signature]
278
+ );
279
+ obj.header.mode = mode;
280
+ return obj;
281
+ };
282
+
138
283
  const verifyMultiSig = async (
139
- message: Suffix & Prefixed & Signed,
284
+ message: WithHeader,
140
285
  expectSignatures: boolean
141
286
  ) => {
142
- const signatures = message.signatures.signatures;
143
- if (signatures.length === 0) {
287
+ const signatures = message.header.signatures;
288
+ if (!signatures || signatures.signatures.length === 0) {
144
289
  return !expectSignatures;
145
290
  }
291
+ const to = message.header.mode;
292
+ message.header.mode = undefined as any;
293
+ message.header.signatures = undefined;
294
+ const bytes = serialize(message);
295
+ message.header.mode = to;
296
+ message.header.signatures = signatures;
146
297
 
147
- await message.createPrefix();
148
-
149
- const dataGenerator = getMultiSigDataToSignHistory(message, 0);
150
- let done: boolean | undefined = false;
151
- for (const signature of signatures) {
152
- if (done) {
153
- throw new Error(
154
- "Unexpected, the amount of signatures does not match the amount of data verify"
155
- );
156
- }
157
- const data = dataGenerator.next();
158
- done = data.done;
159
- if (!(await verify(signature, data.value!))) {
298
+ for (const signature of signatures.signatures) {
299
+ if (!(await verify(signature, bytes))) {
160
300
  return false;
161
301
  }
162
302
  }
163
303
  return true;
164
304
  };
165
- interface Prefixed {
166
- prefix: Uint8Array;
167
- createPrefix: () => Promise<Uint8Array>;
168
- }
169
-
170
- const emptySignatures = serialize(new Signatures());
171
- function* getMultiSigDataToSignHistory(
172
- message: Suffix & Prefixed & Signed,
173
- from = 0
174
- ): Generator<Uint8Array, undefined, void> {
175
- if (from === 0) {
176
- yield concatBytes(
177
- [message.prefix, emptySignatures],
178
- message.prefix.length + emptySignatures.length
179
- );
180
- }
181
305
 
182
- for (
183
- let i = Math.max(from - 1, 0);
184
- i < message.signatures.signatures.length;
185
- i++
186
- ) {
187
- const bytes = message.getSuffix(i); // TODO make more performant
188
- const concat = [message.prefix];
189
- let len = message.prefix.length;
190
- if (bytes instanceof Uint8Array) {
191
- concat.push(bytes);
192
- len += bytes.byteLength;
193
- } else {
194
- for (const arr of bytes) {
195
- concat.push(arr);
196
- len += arr.byteLength;
197
- }
198
- }
199
- yield concatBytes(concat, len);
200
- }
201
- return;
202
- }
203
-
204
- export abstract class Message {
306
+ export abstract class Message<T extends DeliveryMode = DeliveryMode> {
205
307
  static from(bytes: Uint8ArrayList) {
206
308
  if (bytes.get(0) === DATA_VARIANT) {
207
309
  // Data
208
310
  return DataMessage.from(bytes);
311
+ } else if (bytes.get(0) === ACKNOWLEDGE_VARIANT) {
312
+ return ACK.from(bytes);
209
313
  } else if (bytes.get(0) === HELLO_VARIANT) {
210
- // heartbeat
211
314
  return Hello.from(bytes);
212
315
  } else if (bytes.get(0) === GOODBYE_VARIANT) {
213
- // heartbeat
214
316
  return Goodbye.from(bytes);
215
- } else if (bytes.get(0) === PING_VARIANT) {
216
- return PingPong.from(bytes);
217
317
  }
218
-
219
318
  throw new Error("Unsupported");
220
319
  }
221
320
 
321
+ abstract get header(): MessageHeader<T>;
322
+
323
+ async sign(
324
+ signer: (bytes: Uint8Array) => Promise<SignatureWithKey>
325
+ ): Promise<this> {
326
+ return sign(this, signer);
327
+ }
222
328
  abstract bytes(): Uint8ArrayList | Uint8Array;
223
- abstract equals(other: Message): boolean;
224
- abstract verify(expectSignatures: boolean): Promise<boolean>;
329
+ /* abstract equals(other: Message): boolean; */
330
+ _verified: boolean;
331
+
332
+ async verify(expectSignatures: boolean): Promise<boolean> {
333
+ return this._verified != null
334
+ ? this._verified
335
+ : (this._verified =
336
+ (await this.header.verify()) &&
337
+ (await verifyMultiSig(this, expectSignatures)));
338
+ }
225
339
  }
226
340
 
227
341
  // I pack data with this message
228
342
  const DATA_VARIANT = 0;
343
+
229
344
  @variant(DATA_VARIANT)
230
- export class DataMessage extends Message {
345
+ export class DataMessage<
346
+ T extends SilentDelivery | SeekDelivery | AcknowledgeDelivery | AnyWhere =
347
+ | SilentDelivery
348
+ | SeekDelivery
349
+ | AcknowledgeDelivery
350
+ | AnyWhere
351
+ > extends Message<T> {
231
352
  @field({ type: MessageHeader })
232
- private _header: MessageHeader;
233
-
234
- @field({ type: vec("string") })
235
- private _to: string[]; // not signed! TODO should we sign this?
353
+ private _header: MessageHeader<T>;
236
354
 
237
- @field({ type: Signatures })
238
- private _signatures: Signatures;
239
-
240
- @field({ type: Uint8Array })
241
- private _data: Uint8Array;
355
+ @field({ type: option(Uint8Array) })
356
+ private _data?: Uint8Array;
242
357
 
243
- constructor(properties: {
244
- header?: MessageHeader;
245
- to?: string[];
246
- data: Uint8Array;
247
- signatures?: Signatures;
248
- }) {
358
+ constructor(properties: { header: MessageHeader<T>; data?: Uint8Array }) {
249
359
  super();
250
360
  this._data = properties.data;
251
- this._header = properties.header || new MessageHeader();
252
- this._to = properties.to || [];
253
- this._signatures = properties.signatures || new Signatures();
361
+ this._header = properties.header;
254
362
  }
255
363
 
256
364
  get id(): Uint8Array {
257
365
  return this._header.id;
258
366
  }
259
367
 
260
- get signatures(): Signatures {
261
- return this._signatures;
262
- }
263
-
264
- get header(): MessageHeader {
368
+ get header(): MessageHeader<T> {
265
369
  return this._header;
266
370
  }
267
371
 
268
- get to(): string[] {
269
- return this._to;
270
- }
271
- set to(to: string[]) {
272
- this._serialized = undefined;
273
- this._to = to;
274
- }
275
-
276
- get sender(): PublicSignKey {
277
- return this.signatures.signatures[0].publicKey;
278
- }
279
-
280
- get data(): Uint8Array {
372
+ get data(): Uint8Array | undefined {
281
373
  return this._data;
282
374
  }
283
375
 
284
- _serialized: Uint8Array | undefined;
285
- get serialized(): Uint8Array | undefined {
286
- return this.serialized;
287
- }
288
-
289
- _prefix: Uint8Array | undefined;
290
- get prefix(): Uint8Array {
291
- if (!this._prefix) {
292
- throw new Error("Prefix not created");
293
- }
294
- return this._prefix;
295
- }
296
- async createPrefix(): Promise<Uint8Array> {
297
- if (this._prefix) {
298
- return this._prefix;
299
- }
300
- const headerSer = serialize(this._header);
301
- const hashBytes = await sha256(this.data);
302
- this._prefix = concatBytes(
303
- [new Uint8Array([DATA_VARIANT]), headerSer, hashBytes],
304
- 1 + headerSer.length + hashBytes.length
305
- );
306
- return this._prefix;
307
- }
308
-
309
- getSuffix(iteration: number): Uint8Array {
310
- return serialize(
311
- new Signatures(this.signatures.signatures.slice(0, iteration + 1))
312
- );
313
- }
314
-
315
- async sign(sign: (bytes: Uint8Array) => Promise<SignatureWithKey>) {
316
- this._serialized = undefined; // because we will change this object, so the serialized version will not be applicable anymore
317
- await this.createPrefix();
318
- this.signatures.signatures.push(
319
- await sign(
320
- getMultiSigDataToSignHistory(
321
- this,
322
- this.signatures.signatures.length
323
- ).next().value!
324
- )
325
- );
326
- return this;
327
- }
328
-
329
- async verify(expectSignatures: boolean): Promise<boolean> {
330
- return this._header.verify() && verifyMultiSig(this, expectSignatures);
331
- }
332
-
333
376
  /** Manually ser/der for performance gains */
334
377
  bytes() {
335
- if (this._serialized) {
336
- return this._serialized;
337
- }
338
378
  return serialize(this);
339
379
  }
340
380
 
@@ -344,415 +384,123 @@ export class DataMessage extends Message {
344
384
  }
345
385
  const arr = bytes.subarray();
346
386
  const ret = deserialize(arr, DataMessage);
347
- ret._serialized = arr;
348
387
  return ret;
349
388
  }
350
-
351
- equals(other: Message) {
352
- if (other instanceof DataMessage) {
353
- const a =
354
- equals(this.data, other.data) &&
355
- equals(this.id, other.id) &&
356
- this.to.length === other.to.length;
357
- if (!a) {
358
- return false;
359
- }
360
- for (let i = 0; i < this.to.length; i++) {
361
- if (this.to[i] !== other.to[i]) {
362
- return false;
363
- }
364
- }
365
- return this.signatures.equals(other.signatures);
366
- }
367
- return false;
368
- }
369
389
  }
370
- @variant(0)
371
- export class NetworkInfo {
372
- @field({ type: vec("u32", SIGNATURES_SIZE_ENCODING) })
373
- pingLatencies: number[];
374
390
 
375
- constructor(pingLatencies: number[]) {
376
- this.pingLatencies = pingLatencies;
377
- }
378
- }
391
+ const ACKNOWLEDGE_VARIANT = 1;
379
392
 
380
- // I send this too all my peers
381
- const HELLO_VARIANT = 1;
382
- @variant(HELLO_VARIANT)
383
- export class Hello extends Message {
393
+ @variant(ACKNOWLEDGE_VARIANT)
394
+ export class ACK extends Message {
384
395
  @field({ type: MessageHeader })
385
- header: MessageHeader;
386
-
387
- @field({ type: vec("string") })
388
- multiaddrs: string[];
396
+ header: MessageHeader<TracedDelivery>;
389
397
 
390
- @field({ type: option(Uint8Array) })
391
- data?: Uint8Array;
392
-
393
- @field({ type: NetworkInfo })
394
- networkInfo: NetworkInfo;
398
+ @field({ type: fixedArray("u8", 32) })
399
+ messageIdToAcknowledge: Uint8Array;
395
400
 
396
- @field({ type: Signatures })
397
- signatures: Signatures;
401
+ @field({ type: "u8" })
402
+ seenCounter: number; // Number of times a peer has received the messageIdToAcknowledge before
398
403
 
399
- constructor(options?: { multiaddrs?: string[]; data?: Uint8Array }) {
404
+ constructor(properties: {
405
+ messageIdToAcknowledge: Uint8Array;
406
+ seenCounter: number;
407
+ header: MessageHeader<TracedDelivery>;
408
+ }) {
400
409
  super();
401
- this.header = new MessageHeader();
402
- this.data = options?.data;
403
- this.multiaddrs =
404
- options?.multiaddrs?.filter((x) => !x.includes("/p2p-circuit/")) || []; // don't forward relay addresess (TODO ?)
405
- this.signatures = new Signatures();
406
- this.networkInfo = new NetworkInfo([]);
410
+ this.header = properties.header;
411
+ this.messageIdToAcknowledge = properties.messageIdToAcknowledge;
412
+ this.seenCounter = Math.min(255, properties.seenCounter);
407
413
  }
408
414
 
409
- get sender(): PublicSignKey {
410
- return this.signatures.signatures[0].publicKey;
415
+ get id() {
416
+ return this.header.id;
411
417
  }
412
418
 
413
419
  bytes() {
414
420
  return serialize(this);
415
421
  }
416
- static from(bytes: Uint8ArrayList): Hello {
417
- const result = deserialize(bytes.subarray(), Hello);
418
- if (result.signatures.signatures.length === 0) {
419
- throw new Error("Missing sender on Hello");
420
- }
421
- return result;
422
- }
423
-
424
- _prefix: Uint8Array | undefined;
425
- get prefix(): Uint8Array {
426
- if (!this._prefix) {
427
- throw new Error("Prefix not created");
428
- }
429
- return this._prefix;
430
- }
431
- async createPrefix(): Promise<Uint8Array> {
432
- if (this._prefix) {
433
- return this._prefix;
434
- }
435
- const headerSer = serialize(this.header);
436
- const hashBytes = this.data ? await sha256(this.data) : new Uint8Array();
437
- this._prefix = concatBytes(
438
- [new Uint8Array([HELLO_VARIANT]), headerSer, hashBytes],
439
- 1 + headerSer.length + hashBytes.length
440
- );
441
- return this._prefix;
442
- }
443
-
444
- getSuffix(iteration: number): Uint8Array[] {
445
- return [
446
- serialize(
447
- new NetworkInfo(this.networkInfo.pingLatencies.slice(0, iteration + 1))
448
- ),
449
- serialize(
450
- new Signatures(this.signatures.signatures.slice(0, iteration + 1))
451
- )
452
- ];
453
- }
454
-
455
- async sign(sign: (bytes: Uint8Array) => Promise<SignatureWithKey>) {
456
- await this.createPrefix();
457
- const toSign = getMultiSigDataToSignHistory(
458
- this,
459
- this.signatures.signatures.length
460
- ).next().value!;
461
- this.signatures.signatures.push(await sign(toSign));
462
- return this;
463
- }
464
-
465
- async verify(expectSignatures: boolean): Promise<boolean> {
466
- return (
467
- this.header.verify() &&
468
- this.networkInfo.pingLatencies.length ===
469
- this.signatures.signatures.length - 1 &&
470
- verifyMultiSig(this, expectSignatures)
471
- );
472
- }
473
422
 
474
- equals(other: Message) {
475
- if (other instanceof Hello) {
476
- const dataEquals =
477
- (!!this.data && !!other.data && equals(this.data, other.data)) ||
478
- !this.data === !other.data;
479
- if (!dataEquals) {
480
- return false;
481
- }
482
-
483
- return (
484
- this.header.equals(other.header) &&
485
- this.signatures.equals(other.signatures)
486
- );
423
+ static from(bytes: Uint8ArrayList): ACK {
424
+ const result = deserialize(bytes.subarray(), ACK);
425
+ if (
426
+ !result.header.signatures ||
427
+ result.header.signatures.signatures.length === 0
428
+ ) {
429
+ throw new Error("Missing sender on ACK");
487
430
  }
488
- return false;
431
+ return result;
489
432
  }
490
433
  }
491
434
 
492
- // Me or some my peer disconnected
493
- const GOODBYE_VARIANT = 2;
494
- @variant(GOODBYE_VARIANT)
495
- export class Goodbye extends Message {
435
+ const HELLO_VARIANT = 2;
436
+
437
+ @variant(HELLO_VARIANT)
438
+ export class Hello extends Message {
496
439
  @field({ type: MessageHeader })
497
440
  header: MessageHeader;
498
441
 
499
- @field({ type: "bool" })
500
- early?: boolean; // is early goodbye, I send this to my peers so when I disconnect, they can relay the message for me
501
-
502
- @field({ type: option(Uint8Array) })
503
- data?: Uint8Array; // not signed
504
-
505
- @field({ type: Signatures })
506
- signatures: Signatures;
442
+ @field({ type: vec("string") })
443
+ joined: string[];
507
444
 
508
- constructor(properties?: {
509
- header?: MessageHeader;
510
- data?: Uint8Array;
511
- early?: boolean;
512
- }) {
513
- // disconnected: PeerId | string,
445
+ constructor(properties: { joined: string[] }) {
514
446
  super();
515
- this.header = properties?.header || new MessageHeader();
516
- this.data = properties?.data;
517
- this.early = properties?.early;
518
- this.signatures = new Signatures();
447
+ this.joined = properties.joined;
519
448
  }
520
449
 
521
- get sender(): PublicSignKey {
522
- return this.signatures.signatures[0]!.publicKey;
450
+ get id() {
451
+ return this.header.id;
523
452
  }
524
453
 
525
454
  bytes() {
526
455
  return serialize(this);
527
456
  }
528
- static from(bytes: Uint8ArrayList): Goodbye {
529
- const result = deserialize(bytes.subarray(), Goodbye);
530
- if (result.signatures.signatures.length === 0) {
531
- throw new Error("Missing sender on Goodbye");
532
- }
533
- return result;
534
- }
535
-
536
- _prefix: Uint8Array | undefined;
537
- get prefix(): Uint8Array {
538
- if (!this._prefix) {
539
- throw new Error("Prefix not created");
540
- }
541
- return this._prefix;
542
- }
543
-
544
- async createPrefix(): Promise<Uint8Array> {
545
- if (this._prefix) {
546
- return this._prefix;
547
- }
548
- const headerSer = serialize(this.header);
549
- const hashBytes = this.data ? await sha256(this.data) : new Uint8Array();
550
- this._prefix = concatBytes(
551
- [new Uint8Array([GOODBYE_VARIANT]), headerSer, hashBytes],
552
- 1 + headerSer.length + 1 + hashBytes.length
553
- );
554
- return this._prefix;
555
- }
556
-
557
- getSuffix(iteration: number): Uint8Array {
558
- return serialize(
559
- new Signatures(this.signatures.signatures.slice(0, iteration + 1))
560
- );
561
- }
562
-
563
- async sign(sign: (bytes: Uint8Array) => Promise<SignatureWithKey>) {
564
- await this.createPrefix();
565
- this.signatures.signatures.push(
566
- await sign(
567
- getMultiSigDataToSignHistory(
568
- this,
569
- this.signatures.signatures.length
570
- ).next().value!
571
- )
572
- );
573
- return this;
574
- }
575
-
576
- async verify(expectSignatures: boolean): Promise<boolean> {
577
- return this.header.verify() && verifyMultiSig(this, expectSignatures);
578
- }
579
-
580
- equals(other: Message) {
581
- if (other instanceof Goodbye) {
582
- if (this.early !== other.early) {
583
- return false;
584
- }
585
-
586
- const dataEquals =
587
- (!!this.data && !!other.data && equals(this.data, other.data)) ||
588
- !this.data === !other.data;
589
- if (!dataEquals) {
590
- return false;
591
- }
592
- return (
593
- this.header.equals(other.header) &&
594
- this.signatures.equals(other.signatures)
595
- );
596
- }
597
- return false;
598
- }
599
- }
600
-
601
- const PING_VARIANT = 3;
602
-
603
- @variant(PING_VARIANT)
604
- export abstract class PingPong extends Message {
605
- static from(bytes: Uint8ArrayList) {
606
- return deserialize(bytes.subarray(), PingPong);
607
- }
608
-
609
- bytes(): Uint8ArrayList | Uint8Array {
610
- return serialize(this);
611
- }
612
-
613
- verify(_expectSignatures: boolean): Promise<boolean> {
614
- return Promise.resolve(true);
615
- }
616
-
617
- abstract get pingBytes(): Uint8Array;
618
- }
619
-
620
- @variant(0)
621
- export class Ping extends PingPong {
622
- @field({ type: fixedArray("u8", 32) })
623
- pingBytes: Uint8Array;
624
-
625
- constructor() {
626
- super();
627
- this.pingBytes = randomBytes(32);
628
- }
629
- equals(other: Message) {
630
- if (other instanceof Ping) {
631
- return equals(this.pingBytes, other.pingBytes);
632
- }
633
- return false;
634
- }
635
- }
636
-
637
- @variant(1)
638
- export class Pong extends PingPong {
639
- @field({ type: fixedArray("u8", 32) })
640
- pingBytes: Uint8Array;
641
-
642
- constructor(pingBytes: Uint8Array) {
643
- super();
644
- this.pingBytes = pingBytes;
645
- }
646
-
647
- equals(other: Message) {
648
- if (other instanceof Pong) {
649
- return equals(this.pingBytes, other.pingBytes);
650
- }
651
- return false;
652
- }
653
- }
654
-
655
- @variant(0)
656
- export class Connections {
657
- @field({ type: vec(fixedArray("string", 2)) })
658
- connections: [string, string][];
659
-
660
- constructor(connections: [string, string][]) {
661
- this.connections = connections;
662
- }
663
457
 
664
- equals(other: Connections) {
665
- if (this.connections.length !== other.connections.length) {
666
- return false;
667
- }
668
- for (let i = 0; i < this.connections.length; i++) {
669
- if (this.connections[i].length !== other.connections[i].length) {
670
- return false;
671
- }
672
- const a1 = this.connections[i][0];
673
- const a2 = this.connections[i][1];
674
- const b1 = other.connections[i][0];
675
- const b2 = other.connections[i][1];
676
-
677
- if (a1 === b1 && a2 === b2) {
678
- continue;
679
- }
680
- if (a1 === b2 && a2 === b1) {
681
- continue;
682
- }
683
- return false;
458
+ static from(bytes: Uint8ArrayList): Hello {
459
+ const result = deserialize(bytes.subarray(), Hello);
460
+ if (
461
+ !result.header.signatures ||
462
+ result.header.signatures.signatures.length === 0
463
+ ) {
464
+ throw new Error("Missing sender on Hello");
684
465
  }
685
- return true;
466
+ return result;
686
467
  }
687
468
  }
688
469
 
689
- // Share connections
690
- /* const NETWORK_INFO_VARIANT = 3;
691
- @variant(NETWORK_INFO_VARIANT)
692
- export class NetworkInfo extends Message {
470
+ const GOODBYE_VARIANT = 3;
693
471
 
472
+ @variant(GOODBYE_VARIANT)
473
+ export class Goodbye extends Message {
694
474
  @field({ type: MessageHeader })
695
- header: MessageHeader;
696
-
697
- @field({ type: Connections })
698
- connections: Connections;
699
-
700
-
701
- @field({ type: Signatures })
702
- signatures: Signatures
475
+ header: MessageHeader<SilentDelivery>;
703
476
 
477
+ @field({ type: vec("string") })
478
+ leaving: string[];
704
479
 
705
- constructor(connections: [string, string][]) {
480
+ constructor(properties: {
481
+ leaving: string[];
482
+ header: MessageHeader<SilentDelivery>;
483
+ }) {
706
484
  super();
707
- this.header = new MessageHeader();
708
- this.connections = new Connections(connections);
709
- this.signatures = new Signatures()
485
+ this.header = properties.header;
486
+ this.leaving = properties.leaving;
710
487
  }
711
-
712
- getDataToSign(): Uint8Array {
713
- return this.serialize()
714
- }
715
-
716
- _prefix: Uint8Array | undefined
717
- get prefix(): Uint8Array {
718
- if (this._prefix)
719
- return this._prefix
720
- const header = serialize(this.header);
721
- const connections = serialize(this.connections);
722
- this._prefix = concatBytes([new Uint8Array([NETWORK_INFO_VARIANT]), header, connections], 1 + header.length + connections.length);
723
- return this._prefix;
724
- }
725
-
726
- sign(sign: (bytes: Uint8Array) => SignatureWithKey) {
727
- this.signatures.signatures.push(sign(getMultiSigDataToSignHistory(this, this.signatures.signatures.length).next().value!));
728
- return this;
729
- }
730
-
731
- verify(): boolean {
732
- return this.header.verify() && verifyMultiSig(this)
488
+ get id() {
489
+ return this.header.id;
733
490
  }
734
491
 
735
-
736
- serialize() {
737
- return serialize(this)
738
- }
739
- static from(bytes: Uint8ArrayList): NetworkInfo {
740
- return deserialize(bytes.subarray(), NetworkInfo)
492
+ bytes() {
493
+ return serialize(this);
741
494
  }
742
495
 
743
- equals(other: Message) {
744
- if (other instanceof NetworkInfo) {
745
- if (!equals(this.header.id, other.header.id) || !this.header.equals(other.header)) { // TODO fix uneccessary copy
746
- return false;
747
- }
748
-
749
- if (!this.connections.equals(other.connections)) {
750
- return false;
751
- }
752
- return this.signatures.equals(other.signatures)
496
+ static from(bytes: Uint8ArrayList): Goodbye {
497
+ const result = deserialize(bytes.subarray(), Goodbye);
498
+ if (
499
+ !result.header.signatures ||
500
+ result.header.signatures.signatures.length === 0
501
+ ) {
502
+ throw new Error("Missing sender on Goodbye");
753
503
  }
754
- return false;
504
+ return result;
755
505
  }
756
506
  }
757
-
758
- */