@libp2p/pnet 3.0.27 → 3.0.28-4a6c5d11a

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.
@@ -12,7 +12,26 @@ export declare class BoxMessageStream extends AbstractMultiaddrConnection {
12
12
  private maConn;
13
13
  private inboundXor;
14
14
  private outboundXor;
15
+ private inboundReleased;
16
+ private outboundReleased;
17
+ private readonly onInboundMessage;
15
18
  constructor(init: BoxMessageStreamInit);
19
+ /**
20
+ * A connection can close while still holding bytes it received. Decrypt them
21
+ * into our own read buffer first, so closing does not discard them and the
22
+ * application can still read them
23
+ */
24
+ private drainMaConn;
25
+ /**
26
+ * Return the inbound cipher's slot in the shared wasm memory to the pool and
27
+ * zero the key material it holds. If that fails the slot leaks and the key
28
+ * stays resident.
29
+ *
30
+ * 'end' waits for the read buffer to drain, so a connection left paused with
31
+ * unread bytes never emits it and keeps its slot until the process exits
32
+ */
33
+ private releaseInbound;
34
+ private releaseOutbound;
16
35
  sendClose(options?: AbortOptions): Promise<void>;
17
36
  sendData(data: Uint8ArrayList): SendResult;
18
37
  sendReset(err: Error): void;
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAM3D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,MAAM,EAAE,mBAAmB,CAAA;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,EAAE,UAAU,CAAA;IACvB,GAAG,EAAE,UAAU,CAAA;CAChB;AAED,qBAAa,gBAAiB,SAAQ,2BAA2B;IAC/D,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,WAAW,CAAc;gBAEpB,IAAI,EAAE,oBAAoB;IAyCjC,SAAS,CAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,QAAQ,CAAE,IAAI,EAAE,cAAc,GAAG,UAAU;IAO3C,SAAS,CAAE,GAAG,EAAE,KAAK,GAAG,IAAI;IAI5B,SAAS,IAAK,IAAI;IAIlB,UAAU,IAAK,IAAI;CAGpB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAE,SAAS,EAAE,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,CAyB/H"}
1
+ {"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AAM3D,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAsB,MAAM,mBAAmB,CAAA;AAC9F,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAClE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAsBpD,MAAM,WAAW,oBAAqB,SAAQ,iBAAiB;IAC7D,MAAM,EAAE,mBAAmB,CAAA;IAC3B,UAAU,EAAE,UAAU,CAAA;IACtB,WAAW,EAAE,UAAU,CAAA;IACvB,GAAG,EAAE,UAAU,CAAA;CAChB;AAED,qBAAa,gBAAiB,SAAQ,2BAA2B;IAC/D,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,UAAU,CAAc;IAChC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmC;gBAEvD,IAAI,EAAE,oBAAoB;IAmGvC;;;;OAIG;IACH,OAAO,CAAC,WAAW;IAYnB;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAgBtB,OAAO,CAAC,eAAe;IAcjB,SAAS,CAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAIvD,QAAQ,CAAE,IAAI,EAAE,cAAc,GAAG,UAAU;IAO3C,SAAS,CAAE,GAAG,EAAE,KAAK,GAAG,IAAI;IAI5B,SAAS,IAAK,IAAI;IAIlB,UAAU,IAAK,IAAI;CASpB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAE,SAAS,EAAE,UAAU,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,GAAG,EAAE,UAAU,CAAA;CAAE,CAyB/H"}
@@ -4,10 +4,28 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string';
4
4
  import xsalsa20 from 'xsalsa20';
5
5
  import * as Errors from "./errors.js";
6
6
  import { KEY_LENGTH } from "./key-generator.js";
7
+ const XOR_CHUNK_SIZE = 65536;
8
+ /**
9
+ * The xsalsa20 wasm backend copies each input into linear memory, which starts
10
+ * at 640KiB and is capped at 62.5MiB. Growing it detaches every other cipher's
11
+ * view of it, so keep chunks under the initial size rather than the cap. The
12
+ * cipher resumes where it left off, so the result matches a single update.
13
+ */
14
+ function xorInChunks(xor, input) {
15
+ const output = new Uint8Array(input.byteLength);
16
+ for (let start = 0; start < input.byteLength; start += XOR_CHUNK_SIZE) {
17
+ const end = Math.min(start + XOR_CHUNK_SIZE, input.byteLength);
18
+ xor.update(input.subarray(start, end), output.subarray(start, end));
19
+ }
20
+ return output;
21
+ }
7
22
  export class BoxMessageStream extends AbstractMultiaddrConnection {
8
23
  maConn;
9
24
  inboundXor;
10
25
  outboundXor;
26
+ inboundReleased;
27
+ outboundReleased;
28
+ onInboundMessage;
11
29
  constructor(init) {
12
30
  super({
13
31
  ...init,
@@ -16,22 +34,46 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
16
34
  });
17
35
  this.inboundXor = xsalsa20(init.remoteNonce, init.psk);
18
36
  this.outboundXor = xsalsa20(init.localNonce, init.psk);
37
+ this.inboundReleased = false;
38
+ this.outboundReleased = false;
19
39
  this.maConn = init.maConn;
20
- this.maConn.addEventListener('message', (evt) => {
40
+ this.onInboundMessage = (evt) => {
21
41
  const data = evt.data;
22
- if (data instanceof Uint8Array) {
23
- this.onData(this.inboundXor.update(data));
42
+ // a peer can send data after closing and nothing upstream rejects it
43
+ if (this.inboundReleased) {
44
+ this.log('discarding %d bytes received after the readable end ended', data.byteLength);
45
+ return;
24
46
  }
25
- else {
26
- for (const buf of data) {
27
- this.onData(this.inboundXor.update(buf));
47
+ try {
48
+ if (data instanceof Uint8Array) {
49
+ this.onData(xorInChunks(this.inboundXor, data));
50
+ }
51
+ else {
52
+ for (const buf of data) {
53
+ // onData dispatches synchronously, so the consumer can abort and
54
+ // release the cipher part way through the message
55
+ if (this.inboundReleased) {
56
+ break;
57
+ }
58
+ this.onData(xorInChunks(this.inboundXor, buf));
59
+ }
28
60
  }
29
61
  }
30
- });
62
+ catch (err) {
63
+ // any bytes we fail to process leave the cipher permanently out of
64
+ // step with the remote so tear the connection down
65
+ this.log.error('error decrypting inbound data - %e', err);
66
+ this.abort(err);
67
+ }
68
+ };
69
+ this.maConn.addEventListener('message', this.onInboundMessage);
31
70
  // resume sending when the underlying connection can accept more data
32
71
  this.maConn.addEventListener('drain', () => {
33
72
  this.onMuxerDrain();
34
73
  });
74
+ this.maConn.addEventListener('end', () => {
75
+ this.releaseInbound();
76
+ });
35
77
  this.maConn.addEventListener('close', (evt) => {
36
78
  if (evt.error != null) {
37
79
  if (evt.local) {
@@ -40,11 +82,88 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
40
82
  else {
41
83
  this.onRemoteReset();
42
84
  }
85
+ // an errored connection never delivers what it still holds, so waiting
86
+ // for 'end' would keep the cipher forever
87
+ this.releaseInbound();
43
88
  }
44
89
  else {
90
+ // the two ends have to close at different moments. the write side
91
+ // first, because the drain dispatches to the consumer and a reply
92
+ // would otherwise reach a released cipher and abort a graceful close.
93
+ // the read side after, because closing it while our buffer is still
94
+ // empty makes onData discard everything the drain produces
95
+ this.writeStatus = 'closed';
96
+ this.drainMaConn();
45
97
  this.onTransportClosed();
46
98
  }
99
+ // last, the branches above close the write side first and that is what
100
+ // stops sendData reaching a released cipher
101
+ this.releaseOutbound();
47
102
  });
103
+ // the nonce handshake reads from the connection before this stream exists,
104
+ // so it can already have ended or closed. bytes unwrapped back onto it are
105
+ // dispatched in a microtask, so run after them
106
+ if (this.maConn.readableEnded || this.maConn.status !== 'open') {
107
+ queueMicrotask(() => {
108
+ if (this.maConn.readableEnded) {
109
+ this.releaseInbound();
110
+ }
111
+ if (this.maConn.status !== 'open' && this.maConn.status !== 'closing') {
112
+ this.onTransportClosed();
113
+ this.releaseOutbound();
114
+ }
115
+ });
116
+ }
117
+ }
118
+ /**
119
+ * A connection can close while still holding bytes it received. Decrypt them
120
+ * into our own read buffer first, so closing does not discard them and the
121
+ * application can still read them
122
+ */
123
+ drainMaConn() {
124
+ if (this.maConn.readableEnded) {
125
+ return;
126
+ }
127
+ try {
128
+ this.maConn.resume();
129
+ }
130
+ catch (err) {
131
+ this.log.error('could not drain the connection before closing - %e', err);
132
+ }
133
+ }
134
+ /**
135
+ * Return the inbound cipher's slot in the shared wasm memory to the pool and
136
+ * zero the key material it holds. If that fails the slot leaks and the key
137
+ * stays resident.
138
+ *
139
+ * 'end' waits for the read buffer to drain, so a connection left paused with
140
+ * unread bytes never emits it and keeps its slot until the process exits
141
+ */
142
+ releaseInbound() {
143
+ if (this.inboundReleased) {
144
+ return;
145
+ }
146
+ this.inboundReleased = true;
147
+ try {
148
+ this.inboundXor.finalize();
149
+ }
150
+ catch (err) {
151
+ // xsalsa20 caches a view of the shared wasm memory that any other cipher
152
+ // growing it detaches, and finalize does not refresh it
153
+ this.log.error('could not release the inbound cipher, its wasm slot and key leak - %e', err);
154
+ }
155
+ }
156
+ releaseOutbound() {
157
+ if (this.outboundReleased) {
158
+ return;
159
+ }
160
+ this.outboundReleased = true;
161
+ try {
162
+ this.outboundXor.finalize();
163
+ }
164
+ catch (err) {
165
+ this.log.error('could not release the outbound cipher, its wasm slot and key leak - %e', err);
166
+ }
48
167
  }
49
168
  async sendClose(options) {
50
169
  await this.maConn.close(options);
@@ -52,7 +171,7 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
52
171
  sendData(data) {
53
172
  return {
54
173
  sentBytes: data.byteLength,
55
- canSendMore: this.maConn.send(this.outboundXor.update(data.subarray()))
174
+ canSendMore: this.maConn.send(xorInChunks(this.outboundXor, data.subarray()))
56
175
  };
57
176
  }
58
177
  sendReset(err) {
@@ -62,6 +181,11 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
62
181
  this.maConn.pause();
63
182
  }
64
183
  sendResume() {
184
+ // the connection may have finished delivering everything it had, in which
185
+ // case resuming it throws
186
+ if (this.maConn.readableEnded) {
187
+ return;
188
+ }
65
189
  this.maConn.resume();
66
190
  }
67
191
  }
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libp2p/pnet",
3
- "version": "3.0.27",
3
+ "version": "3.0.28-4a6c5d11a",
4
4
  "description": "Implementation of Connection protection management via a shared secret",
5
5
  "license": "Apache-2.0 OR MIT",
6
6
  "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/pnet#readme",
@@ -44,8 +44,8 @@
44
44
  "doc-check": "aegir doc-check"
45
45
  },
46
46
  "dependencies": {
47
- "@libp2p/interface": "^3.2.5",
48
- "@libp2p/utils": "^7.3.2",
47
+ "@libp2p/interface": "3.3.0-4a6c5d11a",
48
+ "@libp2p/utils": "7.4.0-4a6c5d11a",
49
49
  "uint8arraylist": "^3.0.2",
50
50
  "uint8arrays": "^6.1.1",
51
51
  "xsalsa20": "^1.2.0"
package/src/crypto.ts CHANGED
@@ -4,10 +4,30 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
4
4
  import xsalsa20 from 'xsalsa20'
5
5
  import * as Errors from './errors.ts'
6
6
  import { KEY_LENGTH } from './key-generator.ts'
7
- import type { AbortOptions, MultiaddrConnection } from '@libp2p/interface'
7
+ import type { AbortOptions, MultiaddrConnection, StreamMessageEvent } from '@libp2p/interface'
8
8
  import type { MessageStreamInit, SendResult } from '@libp2p/utils'
9
9
  import type { Uint8ArrayList } from 'uint8arraylist'
10
10
 
11
+ const XOR_CHUNK_SIZE = 65536
12
+
13
+ /**
14
+ * The xsalsa20 wasm backend copies each input into linear memory, which starts
15
+ * at 640KiB and is capped at 62.5MiB. Growing it detaches every other cipher's
16
+ * view of it, so keep chunks under the initial size rather than the cap. The
17
+ * cipher resumes where it left off, so the result matches a single update.
18
+ */
19
+ function xorInChunks (xor: xsalsa20.Xor, input: Uint8Array): Uint8Array {
20
+ const output = new Uint8Array(input.byteLength)
21
+
22
+ for (let start = 0; start < input.byteLength; start += XOR_CHUNK_SIZE) {
23
+ const end = Math.min(start + XOR_CHUNK_SIZE, input.byteLength)
24
+
25
+ xor.update(input.subarray(start, end), output.subarray(start, end))
26
+ }
27
+
28
+ return output
29
+ }
30
+
11
31
  export interface BoxMessageStreamInit extends MessageStreamInit {
12
32
  maConn: MultiaddrConnection
13
33
  localNonce: Uint8Array
@@ -19,6 +39,9 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
19
39
  private maConn: MultiaddrConnection
20
40
  private inboundXor: xsalsa20.Xor
21
41
  private outboundXor: xsalsa20.Xor
42
+ private inboundReleased: boolean
43
+ private outboundReleased: boolean
44
+ private readonly onInboundMessage: (evt: StreamMessageEvent) => void
22
45
 
23
46
  constructor (init: BoxMessageStreamInit) {
24
47
  super({
@@ -29,25 +52,52 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
29
52
 
30
53
  this.inboundXor = xsalsa20(init.remoteNonce, init.psk)
31
54
  this.outboundXor = xsalsa20(init.localNonce, init.psk)
55
+ this.inboundReleased = false
56
+ this.outboundReleased = false
32
57
  this.maConn = init.maConn
33
58
 
34
- this.maConn.addEventListener('message', (evt) => {
59
+ this.onInboundMessage = (evt) => {
35
60
  const data = evt.data
36
61
 
37
- if (data instanceof Uint8Array) {
38
- this.onData(this.inboundXor.update(data))
39
- } else {
40
- for (const buf of data) {
41
- this.onData(this.inboundXor.update(buf))
62
+ // a peer can send data after closing and nothing upstream rejects it
63
+ if (this.inboundReleased) {
64
+ this.log('discarding %d bytes received after the readable end ended', data.byteLength)
65
+ return
66
+ }
67
+
68
+ try {
69
+ if (data instanceof Uint8Array) {
70
+ this.onData(xorInChunks(this.inboundXor, data))
71
+ } else {
72
+ for (const buf of data) {
73
+ // onData dispatches synchronously, so the consumer can abort and
74
+ // release the cipher part way through the message
75
+ if (this.inboundReleased) {
76
+ break
77
+ }
78
+
79
+ this.onData(xorInChunks(this.inboundXor, buf))
80
+ }
42
81
  }
82
+ } catch (err: any) {
83
+ // any bytes we fail to process leave the cipher permanently out of
84
+ // step with the remote so tear the connection down
85
+ this.log.error('error decrypting inbound data - %e', err)
86
+ this.abort(err)
43
87
  }
44
- })
88
+ }
89
+
90
+ this.maConn.addEventListener('message', this.onInboundMessage)
45
91
 
46
92
  // resume sending when the underlying connection can accept more data
47
93
  this.maConn.addEventListener('drain', () => {
48
94
  this.onMuxerDrain()
49
95
  })
50
96
 
97
+ this.maConn.addEventListener('end', () => {
98
+ this.releaseInbound()
99
+ })
100
+
51
101
  this.maConn.addEventListener('close', (evt) => {
52
102
  if (evt.error != null) {
53
103
  if (evt.local) {
@@ -55,10 +105,96 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
55
105
  } else {
56
106
  this.onRemoteReset()
57
107
  }
108
+
109
+ // an errored connection never delivers what it still holds, so waiting
110
+ // for 'end' would keep the cipher forever
111
+ this.releaseInbound()
58
112
  } else {
113
+ // the two ends have to close at different moments. the write side
114
+ // first, because the drain dispatches to the consumer and a reply
115
+ // would otherwise reach a released cipher and abort a graceful close.
116
+ // the read side after, because closing it while our buffer is still
117
+ // empty makes onData discard everything the drain produces
118
+ this.writeStatus = 'closed'
119
+ this.drainMaConn()
59
120
  this.onTransportClosed()
60
121
  }
122
+
123
+ // last, the branches above close the write side first and that is what
124
+ // stops sendData reaching a released cipher
125
+ this.releaseOutbound()
61
126
  })
127
+
128
+ // the nonce handshake reads from the connection before this stream exists,
129
+ // so it can already have ended or closed. bytes unwrapped back onto it are
130
+ // dispatched in a microtask, so run after them
131
+ if (this.maConn.readableEnded || this.maConn.status !== 'open') {
132
+ queueMicrotask(() => {
133
+ if (this.maConn.readableEnded) {
134
+ this.releaseInbound()
135
+ }
136
+
137
+ if (this.maConn.status !== 'open' && this.maConn.status !== 'closing') {
138
+ this.onTransportClosed()
139
+ this.releaseOutbound()
140
+ }
141
+ })
142
+ }
143
+ }
144
+
145
+ /**
146
+ * A connection can close while still holding bytes it received. Decrypt them
147
+ * into our own read buffer first, so closing does not discard them and the
148
+ * application can still read them
149
+ */
150
+ private drainMaConn (): void {
151
+ if (this.maConn.readableEnded) {
152
+ return
153
+ }
154
+
155
+ try {
156
+ this.maConn.resume()
157
+ } catch (err: any) {
158
+ this.log.error('could not drain the connection before closing - %e', err)
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Return the inbound cipher's slot in the shared wasm memory to the pool and
164
+ * zero the key material it holds. If that fails the slot leaks and the key
165
+ * stays resident.
166
+ *
167
+ * 'end' waits for the read buffer to drain, so a connection left paused with
168
+ * unread bytes never emits it and keeps its slot until the process exits
169
+ */
170
+ private releaseInbound (): void {
171
+ if (this.inboundReleased) {
172
+ return
173
+ }
174
+
175
+ this.inboundReleased = true
176
+
177
+ try {
178
+ this.inboundXor.finalize()
179
+ } catch (err: any) {
180
+ // xsalsa20 caches a view of the shared wasm memory that any other cipher
181
+ // growing it detaches, and finalize does not refresh it
182
+ this.log.error('could not release the inbound cipher, its wasm slot and key leak - %e', err)
183
+ }
184
+ }
185
+
186
+ private releaseOutbound (): void {
187
+ if (this.outboundReleased) {
188
+ return
189
+ }
190
+
191
+ this.outboundReleased = true
192
+
193
+ try {
194
+ this.outboundXor.finalize()
195
+ } catch (err: any) {
196
+ this.log.error('could not release the outbound cipher, its wasm slot and key leak - %e', err)
197
+ }
62
198
  }
63
199
 
64
200
  async sendClose (options?: AbortOptions): Promise<void> {
@@ -68,7 +204,7 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
68
204
  sendData (data: Uint8ArrayList): SendResult {
69
205
  return {
70
206
  sentBytes: data.byteLength,
71
- canSendMore: this.maConn.send(this.outboundXor.update(data.subarray()))
207
+ canSendMore: this.maConn.send(xorInChunks(this.outboundXor, data.subarray()))
72
208
  }
73
209
  }
74
210
 
@@ -81,6 +217,12 @@ export class BoxMessageStream extends AbstractMultiaddrConnection {
81
217
  }
82
218
 
83
219
  sendResume (): void {
220
+ // the connection may have finished delivering everything it had, in which
221
+ // case resuming it throws
222
+ if (this.maConn.readableEnded) {
223
+ return
224
+ }
225
+
84
226
  this.maConn.resume()
85
227
  }
86
228
  }
@@ -1,9 +0,0 @@
1
- {
2
- "ProtectorComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorComponents.html",
3
- ".:ProtectorComponents": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorComponents.html",
4
- "ProtectorInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorInit.html",
5
- ".:ProtectorInit": "https://libp2p.github.io/js-libp2p/interfaces/_libp2p_pnet.ProtectorInit.html",
6
- "generateKey": "https://libp2p.github.io/js-libp2p/functions/_libp2p_pnet.generateKey.html",
7
- "preSharedKey": "https://libp2p.github.io/js-libp2p/functions/_libp2p_pnet.preSharedKey.html",
8
- ".:preSharedKey": "https://libp2p.github.io/js-libp2p/functions/_libp2p_pnet.preSharedKey.html"
9
- }