@peerbit/stream-interface 1.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.
@@ -0,0 +1,758 @@
1
+ import {
2
+ variant,
3
+ vec,
4
+ field,
5
+ serialize,
6
+ deserialize,
7
+ fixedArray,
8
+ option,
9
+ } from "@dao-xyz/borsh";
10
+ import { equals } from "uint8arrays";
11
+ import { Uint8ArrayList } from "uint8arraylist";
12
+ import {
13
+ PublicSignKey,
14
+ SignatureWithKey,
15
+ verify,
16
+ randomBytes,
17
+ sha256Base64,
18
+ sha256,
19
+ } from "@peerbit/crypto";
20
+
21
+ /**
22
+ * The default msgID implementation
23
+ * Child class can override this.
24
+ */
25
+ export const getMsgId = async (msg: Uint8ArrayList | Uint8Array) => {
26
+ // first bytes is discriminator,
27
+ // next 32 bytes should be an id
28
+ //return Buffer.from(msg.slice(0, 33)).toString('base64');
29
+
30
+ return sha256Base64(msg.subarray(0, 33)); // base64EncArr(msg, 0, ID_LENGTH + 1);
31
+ };
32
+
33
+ let concatBytes: (arr: Uint8Array[], totalLength: number) => Uint8Array;
34
+ if ((globalThis as any).Buffer) {
35
+ concatBytes = (globalThis as any).Buffer.concat;
36
+ } else {
37
+ concatBytes = (arrays, length) => {
38
+ if (length == null) {
39
+ let length = 0;
40
+ for (const element of arrays) {
41
+ length += element.length;
42
+ }
43
+ }
44
+ const output = new Uint8Array(length);
45
+ let offset = 0;
46
+ for (const arr of arrays) {
47
+ output.set(arr, offset);
48
+ offset += arr.length;
49
+ }
50
+ return output;
51
+ };
52
+ }
53
+
54
+ export const ID_LENGTH = 32;
55
+
56
+ const WEEK_MS = 7 * 24 * 60 * 60 + 1000;
57
+
58
+ @variant(0)
59
+ export class MessageHeader {
60
+ @field({ type: fixedArray("u8", ID_LENGTH) })
61
+ private _id: Uint8Array;
62
+
63
+ @field({ type: "u64" })
64
+ private _timestamp: bigint;
65
+
66
+ @field({ type: "u64" })
67
+ private _expires: bigint;
68
+
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
+ }
74
+
75
+ get id() {
76
+ return this._id;
77
+ }
78
+
79
+ get expires() {
80
+ return this._expires;
81
+ }
82
+
83
+ get timetamp() {
84
+ return this._timestamp;
85
+ }
86
+
87
+ equals(other: MessageHeader) {
88
+ return this._expires === other.expires && equals(this._id, other.id);
89
+ }
90
+
91
+ verify() {
92
+ return this.expires >= +new Date();
93
+ }
94
+ }
95
+
96
+ class PublicKeys {
97
+ @field({ type: vec(PublicSignKey) })
98
+ keys: PublicSignKey[];
99
+ constructor(keys: PublicSignKey[]) {
100
+ this.keys = keys;
101
+ }
102
+ }
103
+
104
+ const SIGNATURES_SIZE_ENCODING = "u8"; // with 7 steps you know everyone in the world?, so u8 *should* suffice
105
+ @variant(0)
106
+ export class Signatures {
107
+ @field({ type: vec(SignatureWithKey, SIGNATURES_SIZE_ENCODING) })
108
+ signatures: SignatureWithKey[];
109
+
110
+ constructor(signatures: SignatureWithKey[] = []) {
111
+ this.signatures = signatures;
112
+ }
113
+
114
+ equals(other: Signatures) {
115
+ return (
116
+ this.signatures.length === other.signatures.length &&
117
+ this.signatures.every((value, i) => other.signatures[i].equals(value))
118
+ );
119
+ }
120
+
121
+ get publicKeys(): PublicSignKey[] {
122
+ return this.signatures.map((x) => x.publicKey);
123
+ }
124
+
125
+ hashPublicKeys(): Promise<string> {
126
+ return sha256Base64(serialize(new PublicKeys(this.publicKeys)));
127
+ }
128
+ }
129
+
130
+ const keyMap: Map<string, PublicSignKey> = new Map();
131
+ interface Signed {
132
+ get signatures(): Signatures;
133
+ }
134
+ interface Suffix {
135
+ getSuffix(iteration: number): Uint8Array | Uint8Array[];
136
+ }
137
+
138
+ const verifyMultiSig = async (
139
+ message: Suffix & Prefixed & Signed,
140
+ expectSignatures: boolean
141
+ ) => {
142
+ const signatures = message.signatures.signatures;
143
+ if (signatures.length === 0) {
144
+ return !expectSignatures;
145
+ }
146
+
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!))) {
160
+ return false;
161
+ }
162
+ }
163
+ return true;
164
+ };
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
+
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 {
205
+ static deserialize(bytes: Uint8ArrayList) {
206
+ if (bytes.get(0) === DATA_VARIANT) {
207
+ // Data
208
+ return DataMessage.deserialize(bytes);
209
+ } else if (bytes.get(0) === HELLO_VARIANT) {
210
+ // heartbeat
211
+ return Hello.deserialize(bytes);
212
+ } else if (bytes.get(0) === GOODBYE_VARIANT) {
213
+ // heartbeat
214
+ return Goodbye.deserialize(bytes);
215
+ } else if (bytes.get(0) === PING_VARIANT) {
216
+ return PingPong.deserialize(bytes);
217
+ }
218
+
219
+ throw new Error("Unsupported");
220
+ }
221
+
222
+ abstract serialize(): Uint8ArrayList | Uint8Array;
223
+ abstract equals(other: Message): boolean;
224
+ abstract verify(expectSignatures: boolean): Promise<boolean>;
225
+ }
226
+
227
+ // I pack data with this message
228
+ const DATA_VARIANT = 0;
229
+ @variant(DATA_VARIANT)
230
+ export class DataMessage extends Message {
231
+ @field({ type: MessageHeader })
232
+ private _header: MessageHeader;
233
+
234
+ @field({ type: vec("string") })
235
+ private _to: string[]; // not signed! TODO should we sign this?
236
+
237
+ @field({ type: Signatures })
238
+ private _signatures: Signatures;
239
+
240
+ @field({ type: Uint8Array })
241
+ private _data: Uint8Array;
242
+
243
+ constructor(properties: {
244
+ header?: MessageHeader;
245
+ to?: string[];
246
+ data: Uint8Array;
247
+ signatures?: Signatures;
248
+ }) {
249
+ super();
250
+ this._data = properties.data;
251
+ this._header = properties.header || new MessageHeader();
252
+ this._to = properties.to || [];
253
+ this._signatures = properties.signatures || new Signatures();
254
+ }
255
+
256
+ get id(): Uint8Array {
257
+ return this._header.id;
258
+ }
259
+
260
+ get signatures(): Signatures {
261
+ return this._signatures;
262
+ }
263
+
264
+ get header(): MessageHeader {
265
+ return this._header;
266
+ }
267
+
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 {
281
+ return this._data;
282
+ }
283
+
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
+ /** Manually ser/der for performance gains */
334
+ serialize() {
335
+ if (this._serialized) {
336
+ return this._serialized;
337
+ }
338
+ return serialize(this);
339
+ }
340
+
341
+ static deserialize(bytes: Uint8ArrayList): DataMessage {
342
+ if (bytes.get(0) !== 0) {
343
+ throw new Error("Unsupported");
344
+ }
345
+ const arr = bytes.subarray();
346
+ const ret = deserialize(arr, DataMessage);
347
+ ret._serialized = arr;
348
+ return ret;
349
+ }
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
+ }
370
+ @variant(0)
371
+ export class NetworkInfo {
372
+ @field({ type: vec("u32", SIGNATURES_SIZE_ENCODING) })
373
+ pingLatencies: number[];
374
+
375
+ constructor(pingLatencies: number[]) {
376
+ this.pingLatencies = pingLatencies;
377
+ }
378
+ }
379
+
380
+ // I send this too all my peers
381
+ const HELLO_VARIANT = 1;
382
+ @variant(HELLO_VARIANT)
383
+ export class Hello extends Message {
384
+ @field({ type: MessageHeader })
385
+ header: MessageHeader;
386
+
387
+ @field({ type: vec("string") })
388
+ multiaddrs: string[];
389
+
390
+ @field({ type: option(Uint8Array) })
391
+ data?: Uint8Array;
392
+
393
+ @field({ type: NetworkInfo })
394
+ networkInfo: NetworkInfo;
395
+
396
+ @field({ type: Signatures })
397
+ signatures: Signatures;
398
+
399
+ constructor(options?: { multiaddrs?: string[]; data?: Uint8Array }) {
400
+ 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([]);
407
+ }
408
+
409
+ get sender(): PublicSignKey {
410
+ return this.signatures.signatures[0].publicKey;
411
+ }
412
+
413
+ serialize() {
414
+ return serialize(this);
415
+ }
416
+ static deserialize(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
+
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
+ );
487
+ }
488
+ return false;
489
+ }
490
+ }
491
+
492
+ // Me or some my peer disconnected
493
+ const GOODBYE_VARIANT = 2;
494
+ @variant(GOODBYE_VARIANT)
495
+ export class Goodbye extends Message {
496
+ @field({ type: MessageHeader })
497
+ header: MessageHeader;
498
+
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;
507
+
508
+ constructor(properties?: {
509
+ header?: MessageHeader;
510
+ data?: Uint8Array;
511
+ early?: boolean;
512
+ }) {
513
+ // disconnected: PeerId | string,
514
+ super();
515
+ this.header = properties?.header || new MessageHeader();
516
+ this.data = properties?.data;
517
+ this.early = properties?.early;
518
+ this.signatures = new Signatures();
519
+ }
520
+
521
+ get sender(): PublicSignKey {
522
+ return this.signatures.signatures[0]!.publicKey;
523
+ }
524
+
525
+ serialize() {
526
+ return serialize(this);
527
+ }
528
+ static deserialize(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 deserialize(bytes: Uint8ArrayList) {
606
+ return deserialize(bytes.subarray(), PingPong);
607
+ }
608
+
609
+ serialize(): 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
+
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;
684
+ }
685
+ return true;
686
+ }
687
+ }
688
+
689
+ // Share connections
690
+ /* const NETWORK_INFO_VARIANT = 3;
691
+ @variant(NETWORK_INFO_VARIANT)
692
+ export class NetworkInfo extends Message {
693
+
694
+ @field({ type: MessageHeader })
695
+ header: MessageHeader;
696
+
697
+ @field({ type: Connections })
698
+ connections: Connections;
699
+
700
+
701
+ @field({ type: Signatures })
702
+ signatures: Signatures
703
+
704
+
705
+ constructor(connections: [string, string][]) {
706
+ super();
707
+ this.header = new MessageHeader();
708
+ this.connections = new Connections(connections);
709
+ this.signatures = new Signatures()
710
+ }
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)
733
+ }
734
+
735
+
736
+ serialize() {
737
+ return serialize(this)
738
+ }
739
+ static deserialize(bytes: Uint8ArrayList): NetworkInfo {
740
+ return deserialize(bytes.subarray(), NetworkInfo)
741
+ }
742
+
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)
753
+ }
754
+ return false;
755
+ }
756
+ }
757
+
758
+ */