@peerbit/stream-interface 1.0.11 → 2.0.0
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/lib/esm/index.d.ts +10 -1
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/messages.d.ts +100 -92
- package/lib/esm/messages.js +286 -481
- package/lib/esm/messages.js.map +1 -1
- package/package.json +3 -4
- package/src/index.ts +12 -1
- package/src/messages.ts +300 -552
package/src/messages.ts
CHANGED
|
@@ -15,15 +15,23 @@ import {
|
|
|
15
15
|
verify,
|
|
16
16
|
randomBytes,
|
|
17
17
|
sha256Base64,
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
62
|
+
const coerceTo = (tos: (string | PublicSignKey | PeerId)[] | Set<string>) => {
|
|
63
|
+
const toHashes: string[] = [];
|
|
64
|
+
let i = 0;
|
|
55
65
|
|
|
56
|
-
const
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
toHashes[i++] = hash;
|
|
75
|
+
}
|
|
76
|
+
return toHashes;
|
|
77
|
+
};
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
private _timestamp: bigint;
|
|
79
|
+
export abstract class DeliveryMode {}
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
88
|
-
|
|
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
|
-
|
|
92
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
126
|
-
|
|
191
|
+
constructor(multiaddrs: string[]) {
|
|
192
|
+
super();
|
|
193
|
+
this.multiaddrs = multiaddrs;
|
|
127
194
|
}
|
|
128
195
|
}
|
|
129
196
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
135
|
-
|
|
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:
|
|
284
|
+
message: WithHeader,
|
|
140
285
|
expectSignatures: boolean
|
|
141
286
|
) => {
|
|
142
|
-
const signatures = message.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
238
|
-
private
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
376
|
-
this.pingLatencies = pingLatencies;
|
|
377
|
-
}
|
|
378
|
-
}
|
|
391
|
+
const ACKNOWLEDGE_VARIANT = 1;
|
|
379
392
|
|
|
380
|
-
|
|
381
|
-
|
|
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:
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
@field({ type: NetworkInfo })
|
|
394
|
-
networkInfo: NetworkInfo;
|
|
398
|
+
@field({ type: fixedArray("u8", 32) })
|
|
399
|
+
messageIdToAcknowledge: Uint8Array;
|
|
395
400
|
|
|
396
|
-
@field({ type:
|
|
397
|
-
|
|
401
|
+
@field({ type: "u8" })
|
|
402
|
+
seenCounter: number; // Number of times a peer has received the messageIdToAcknowledge before
|
|
398
403
|
|
|
399
|
-
constructor(
|
|
404
|
+
constructor(properties: {
|
|
405
|
+
messageIdToAcknowledge: Uint8Array;
|
|
406
|
+
seenCounter: number;
|
|
407
|
+
header: MessageHeader<TracedDelivery>;
|
|
408
|
+
}) {
|
|
400
409
|
super();
|
|
401
|
-
this.header =
|
|
402
|
-
this.
|
|
403
|
-
this.
|
|
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
|
|
410
|
-
return this.
|
|
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
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
|
431
|
+
return result;
|
|
489
432
|
}
|
|
490
433
|
}
|
|
491
434
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
@variant(
|
|
495
|
-
export class
|
|
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: "
|
|
500
|
-
|
|
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.
|
|
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
|
|
522
|
-
return this.
|
|
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
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
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
|
|
466
|
+
return result;
|
|
686
467
|
}
|
|
687
468
|
}
|
|
688
469
|
|
|
689
|
-
|
|
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(
|
|
480
|
+
constructor(properties: {
|
|
481
|
+
leaving: string[];
|
|
482
|
+
header: MessageHeader<SilentDelivery>;
|
|
483
|
+
}) {
|
|
706
484
|
super();
|
|
707
|
-
this.header =
|
|
708
|
-
this.
|
|
709
|
-
this.signatures = new Signatures()
|
|
485
|
+
this.header = properties.header;
|
|
486
|
+
this.leaving = properties.leaving;
|
|
710
487
|
}
|
|
711
|
-
|
|
712
|
-
|
|
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
|
-
|
|
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
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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
|
|
504
|
+
return result;
|
|
755
505
|
}
|
|
756
506
|
}
|
|
757
|
-
|
|
758
|
-
*/
|