@libp2p/noise 1.0.0-049bfa0fa

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.
Files changed (90) hide show
  1. package/README.md +95 -0
  2. package/dist/index.min.js +23 -0
  3. package/dist/index.min.js.map +7 -0
  4. package/dist/src/constants.d.ts +5 -0
  5. package/dist/src/constants.d.ts.map +1 -0
  6. package/dist/src/constants.js +5 -0
  7. package/dist/src/constants.js.map +1 -0
  8. package/dist/src/crypto/index.browser.d.ts +2 -0
  9. package/dist/src/crypto/index.browser.d.ts.map +1 -0
  10. package/dist/src/crypto/index.browser.js +3 -0
  11. package/dist/src/crypto/index.browser.js.map +1 -0
  12. package/dist/src/crypto/index.d.ts +3 -0
  13. package/dist/src/crypto/index.d.ts.map +1 -0
  14. package/dist/src/crypto/index.js +188 -0
  15. package/dist/src/crypto/index.js.map +1 -0
  16. package/dist/src/crypto/js.d.ts +3 -0
  17. package/dist/src/crypto/js.d.ts.map +1 -0
  18. package/dist/src/crypto/js.js +43 -0
  19. package/dist/src/crypto/js.js.map +1 -0
  20. package/dist/src/crypto.d.ts +14 -0
  21. package/dist/src/crypto.d.ts.map +1 -0
  22. package/dist/src/crypto.js +11 -0
  23. package/dist/src/crypto.js.map +1 -0
  24. package/dist/src/encoder.d.ts +10 -0
  25. package/dist/src/encoder.d.ts.map +1 -0
  26. package/dist/src/encoder.js +22 -0
  27. package/dist/src/encoder.js.map +1 -0
  28. package/dist/src/errors.d.ts +6 -0
  29. package/dist/src/errors.d.ts.map +1 -0
  30. package/dist/src/errors.js +9 -0
  31. package/dist/src/errors.js.map +1 -0
  32. package/dist/src/index.d.ts +52 -0
  33. package/dist/src/index.d.ts.map +1 -0
  34. package/dist/src/index.js +43 -0
  35. package/dist/src/index.js.map +1 -0
  36. package/dist/src/logger.d.ts +10 -0
  37. package/dist/src/logger.d.ts.map +1 -0
  38. package/dist/src/logger.js +56 -0
  39. package/dist/src/logger.js.map +1 -0
  40. package/dist/src/metrics.d.ts +4 -0
  41. package/dist/src/metrics.d.ts.map +1 -0
  42. package/dist/src/metrics.js +20 -0
  43. package/dist/src/metrics.js.map +1 -0
  44. package/dist/src/noise.d.ts +58 -0
  45. package/dist/src/noise.d.ts.map +1 -0
  46. package/dist/src/noise.js +174 -0
  47. package/dist/src/noise.js.map +1 -0
  48. package/dist/src/nonce.d.ts +17 -0
  49. package/dist/src/nonce.d.ts.map +1 -0
  50. package/dist/src/nonce.js +42 -0
  51. package/dist/src/nonce.js.map +1 -0
  52. package/dist/src/performHandshake.d.ts +5 -0
  53. package/dist/src/performHandshake.d.ts.map +1 -0
  54. package/dist/src/performHandshake.js +69 -0
  55. package/dist/src/performHandshake.js.map +1 -0
  56. package/dist/src/proto/payload.d.ts +22 -0
  57. package/dist/src/proto/payload.d.ts.map +1 -0
  58. package/dist/src/proto/payload.js +133 -0
  59. package/dist/src/proto/payload.js.map +1 -0
  60. package/dist/src/protocol.d.ts +71 -0
  61. package/dist/src/protocol.d.ts.map +1 -0
  62. package/dist/src/protocol.js +257 -0
  63. package/dist/src/protocol.js.map +1 -0
  64. package/dist/src/types.d.ts +98 -0
  65. package/dist/src/types.d.ts.map +1 -0
  66. package/dist/src/types.js +2 -0
  67. package/dist/src/types.js.map +1 -0
  68. package/dist/src/utils.d.ts +11 -0
  69. package/dist/src/utils.d.ts.map +1 -0
  70. package/dist/src/utils.js +176 -0
  71. package/dist/src/utils.js.map +1 -0
  72. package/package.json +94 -0
  73. package/src/constants.ts +5 -0
  74. package/src/crypto/index.browser.ts +3 -0
  75. package/src/crypto/index.ts +219 -0
  76. package/src/crypto/js.ts +56 -0
  77. package/src/crypto.ts +27 -0
  78. package/src/encoder.ts +24 -0
  79. package/src/errors.ts +10 -0
  80. package/src/index.ts +58 -0
  81. package/src/logger.ts +65 -0
  82. package/src/metrics.ts +32 -0
  83. package/src/noise.ts +237 -0
  84. package/src/nonce.ts +49 -0
  85. package/src/performHandshake.ts +91 -0
  86. package/src/proto/payload.proto +12 -0
  87. package/src/proto/payload.ts +170 -0
  88. package/src/protocol.ts +313 -0
  89. package/src/types.ts +105 -0
  90. package/src/utils.ts +221 -0
@@ -0,0 +1,313 @@
1
+ import { Uint8ArrayList } from 'uint8arraylist'
2
+ import { fromString as uint8ArrayFromString } from 'uint8arrays'
3
+ import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
4
+ import { InvalidCryptoExchangeError } from './errors.js'
5
+ import { Nonce } from './nonce.js'
6
+ import type { ICipherState, ISymmetricState, IHandshakeState, KeyPair, ICrypto } from './types.js'
7
+
8
+ // Code in this file is a direct translation of a subset of the noise protocol https://noiseprotocol.org/noise.html,
9
+ // agnostic to libp2p's usage of noise
10
+
11
+ export const ZEROLEN = uint8ArrayAlloc(0)
12
+
13
+ interface ICipherStateWithKey extends ICipherState {
14
+ k: Uint8Array
15
+ }
16
+
17
+ export class CipherState implements ICipherState {
18
+ public k?: Uint8Array
19
+ public n: Nonce
20
+ private readonly crypto: ICrypto
21
+
22
+ constructor (crypto: ICrypto, k: Uint8Array | undefined = undefined, n = 0) {
23
+ this.crypto = crypto
24
+ this.k = k
25
+ this.n = new Nonce(n)
26
+ }
27
+
28
+ public hasKey (): this is ICipherStateWithKey {
29
+ return Boolean(this.k)
30
+ }
31
+
32
+ public encryptWithAd (ad: Uint8Array, plaintext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList {
33
+ if (!this.hasKey()) {
34
+ return plaintext
35
+ }
36
+
37
+ this.n.assertValue()
38
+ const e = this.crypto.encrypt(plaintext, this.n.getBytes(), ad, this.k)
39
+ this.n.increment()
40
+
41
+ return e
42
+ }
43
+
44
+ public decryptWithAd (ad: Uint8Array, ciphertext: Uint8Array | Uint8ArrayList, dst?: Uint8Array): Uint8Array | Uint8ArrayList {
45
+ if (!this.hasKey()) {
46
+ return ciphertext
47
+ }
48
+
49
+ this.n.assertValue()
50
+ const plaintext = this.crypto.decrypt(ciphertext, this.n.getBytes(), ad, this.k, dst)
51
+ this.n.increment()
52
+
53
+ return plaintext
54
+ }
55
+ }
56
+
57
+ export class SymmetricState implements ISymmetricState {
58
+ public cs: CipherState
59
+ public ck: Uint8Array
60
+ public h: Uint8Array
61
+ private readonly crypto: ICrypto
62
+
63
+ constructor (crypto: ICrypto, protocolName: string) {
64
+ this.crypto = crypto
65
+
66
+ const protocolNameBytes = uint8ArrayFromString(protocolName, 'utf-8')
67
+ this.h = hashProtocolName(crypto, protocolNameBytes)
68
+
69
+ this.ck = this.h
70
+ this.cs = new CipherState(crypto)
71
+ }
72
+
73
+ public mixKey (ikm: Uint8Array): void {
74
+ const [ck, tempK] = this.crypto.hkdf(this.ck, ikm)
75
+ this.ck = ck
76
+ this.cs = new CipherState(this.crypto, tempK)
77
+ }
78
+
79
+ public mixHash (data: Uint8Array | Uint8ArrayList): void {
80
+ this.h = this.crypto.hash(new Uint8ArrayList(this.h, data))
81
+ }
82
+
83
+ public encryptAndHash (plaintext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList {
84
+ const ciphertext = this.cs.encryptWithAd(this.h, plaintext)
85
+ this.mixHash(ciphertext)
86
+ return ciphertext
87
+ }
88
+
89
+ public decryptAndHash (ciphertext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList {
90
+ const plaintext = this.cs.decryptWithAd(this.h, ciphertext)
91
+ this.mixHash(ciphertext)
92
+ return plaintext
93
+ }
94
+
95
+ public split (): [CipherState, CipherState] {
96
+ const [tempK1, tempK2] = this.crypto.hkdf(this.ck, ZEROLEN)
97
+ return [new CipherState(this.crypto, tempK1), new CipherState(this.crypto, tempK2)]
98
+ }
99
+ }
100
+
101
+ // const MESSAGE_PATTERNS = ['e', 's', 'ee', 'es', 'se', 'ss'] as const
102
+ // type MessagePattern = Array<typeof MESSAGE_PATTERNS[number]>
103
+
104
+ export interface HandshakeStateInit {
105
+ crypto: ICrypto
106
+ protocolName: string
107
+ initiator: boolean
108
+ prologue: Uint8Array
109
+ s?: KeyPair
110
+ e?: KeyPair
111
+ rs?: Uint8Array | Uint8ArrayList
112
+ re?: Uint8Array | Uint8ArrayList
113
+ }
114
+
115
+ export abstract class AbstractHandshakeState implements IHandshakeState {
116
+ public ss: SymmetricState
117
+ public s?: KeyPair
118
+ public e?: KeyPair
119
+ public rs?: Uint8Array | Uint8ArrayList
120
+ public re?: Uint8Array | Uint8ArrayList
121
+ public initiator: boolean
122
+ protected readonly crypto: ICrypto
123
+
124
+ constructor (init: HandshakeStateInit) {
125
+ const { crypto, protocolName, prologue, initiator, s, e, rs, re } = init
126
+ this.crypto = crypto
127
+ this.ss = new SymmetricState(crypto, protocolName)
128
+ this.ss.mixHash(prologue)
129
+ this.initiator = initiator
130
+ this.s = s
131
+ this.e = e
132
+ this.rs = rs
133
+ this.re = re
134
+ }
135
+
136
+ protected writeE (): Uint8Array {
137
+ if (this.e) {
138
+ throw new Error('ephemeral keypair is already set')
139
+ }
140
+ const e = this.crypto.generateKeypair()
141
+ this.ss.mixHash(e.publicKey)
142
+ this.e = e
143
+ return e.publicKey
144
+ }
145
+
146
+ protected writeS (): Uint8Array | Uint8ArrayList {
147
+ if (!this.s) {
148
+ throw new Error('static keypair is not set')
149
+ }
150
+ return this.ss.encryptAndHash(this.s.publicKey)
151
+ }
152
+
153
+ protected writeEE (): void {
154
+ if (!this.e) {
155
+ throw new Error('ephemeral keypair is not set')
156
+ }
157
+ if (!this.re) {
158
+ throw new Error('remote ephemeral public key is not set')
159
+ }
160
+ this.ss.mixKey(this.crypto.dh(this.e, this.re))
161
+ }
162
+
163
+ protected writeES (): void {
164
+ if (this.initiator) {
165
+ if (!this.e) {
166
+ throw new Error('ephemeral keypair is not set')
167
+ }
168
+ if (!this.rs) {
169
+ throw new Error('remote static public key is not set')
170
+ }
171
+ this.ss.mixKey(this.crypto.dh(this.e, this.rs))
172
+ } else {
173
+ if (!this.s) {
174
+ throw new Error('static keypair is not set')
175
+ }
176
+ if (!this.re) {
177
+ throw new Error('remote ephemeral public key is not set')
178
+ }
179
+ this.ss.mixKey(this.crypto.dh(this.s, this.re))
180
+ }
181
+ }
182
+
183
+ protected writeSE (): void {
184
+ if (this.initiator) {
185
+ if (!this.s) {
186
+ throw new Error('static keypair is not set')
187
+ }
188
+ if (!this.re) {
189
+ throw new Error('remote ephemeral public key is not set')
190
+ }
191
+ this.ss.mixKey(this.crypto.dh(this.s, this.re))
192
+ } else {
193
+ if (!this.e) {
194
+ throw new Error('ephemeral keypair is not set')
195
+ }
196
+ if (!this.rs) {
197
+ throw new Error('remote static public key is not set')
198
+ }
199
+ this.ss.mixKey(this.crypto.dh(this.e, this.rs))
200
+ }
201
+ }
202
+
203
+ protected readE (message: Uint8ArrayList, offset = 0): void {
204
+ if (this.re) {
205
+ throw new Error('remote ephemeral public key is already set')
206
+ }
207
+ if (message.byteLength < offset + 32) {
208
+ throw new Error('message is not long enough')
209
+ }
210
+ this.re = message.sublist(offset, offset + 32)
211
+ this.ss.mixHash(this.re)
212
+ }
213
+
214
+ protected readS (message: Uint8ArrayList, offset = 0): number {
215
+ if (this.rs) {
216
+ throw new Error('remote static public key is already set')
217
+ }
218
+ const cipherLength = 32 + (this.ss.cs.hasKey() ? 16 : 0)
219
+ if (message.byteLength < offset + cipherLength) {
220
+ throw new Error('message is not long enough')
221
+ }
222
+ const temp = message.sublist(offset, offset + cipherLength)
223
+ this.rs = this.ss.decryptAndHash(temp)
224
+ return cipherLength
225
+ }
226
+
227
+ protected readEE (): void {
228
+ this.writeEE()
229
+ }
230
+
231
+ protected readES (): void {
232
+ this.writeES()
233
+ }
234
+
235
+ protected readSE (): void {
236
+ this.writeSE()
237
+ }
238
+ }
239
+
240
+ /**
241
+ * A IHandshakeState that's optimized for the XX pattern
242
+ */
243
+ export class XXHandshakeState extends AbstractHandshakeState {
244
+ // e
245
+ writeMessageA (payload: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList {
246
+ return new Uint8ArrayList(this.writeE(), this.ss.encryptAndHash(payload))
247
+ }
248
+
249
+ // e, ee, s, es
250
+ writeMessageB (payload: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList {
251
+ const e = this.writeE()
252
+ this.writeEE()
253
+ const encS = this.writeS()
254
+ this.writeES()
255
+
256
+ return new Uint8ArrayList(e, encS, this.ss.encryptAndHash(payload))
257
+ }
258
+
259
+ // s, se
260
+ writeMessageC (payload: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList {
261
+ const encS = this.writeS()
262
+ this.writeSE()
263
+
264
+ return new Uint8ArrayList(encS, this.ss.encryptAndHash(payload))
265
+ }
266
+
267
+ // e
268
+ readMessageA (message: Uint8ArrayList): Uint8Array | Uint8ArrayList {
269
+ try {
270
+ this.readE(message)
271
+
272
+ return this.ss.decryptAndHash(message.sublist(32))
273
+ } catch (e) {
274
+ throw new InvalidCryptoExchangeError(`handshake stage 0 validation fail: ${(e as Error).message}`)
275
+ }
276
+ }
277
+
278
+ // e, ee, s, es
279
+ readMessageB (message: Uint8ArrayList): Uint8Array | Uint8ArrayList {
280
+ try {
281
+ this.readE(message)
282
+ this.readEE()
283
+ const consumed = this.readS(message, 32)
284
+ this.readES()
285
+
286
+ return this.ss.decryptAndHash(message.sublist(32 + consumed))
287
+ } catch (e) {
288
+ throw new InvalidCryptoExchangeError(`handshake stage 1 validation fail: ${(e as Error).message}`)
289
+ }
290
+ }
291
+
292
+ // s, se
293
+ readMessageC (message: Uint8ArrayList): Uint8Array | Uint8ArrayList {
294
+ try {
295
+ const consumed = this.readS(message)
296
+ this.readSE()
297
+
298
+ return this.ss.decryptAndHash(message.sublist(consumed))
299
+ } catch (e) {
300
+ throw new InvalidCryptoExchangeError(`handshake stage 2 validation fail: ${(e as Error).message}`)
301
+ }
302
+ }
303
+ }
304
+
305
+ function hashProtocolName (crypto: ICrypto, protocolName: Uint8Array): Uint8Array {
306
+ if (protocolName.length <= 32) {
307
+ const h = uint8ArrayAlloc(32)
308
+ h.set(protocolName)
309
+ return h
310
+ } else {
311
+ return crypto.hash(protocolName)
312
+ }
313
+ }
package/src/types.ts ADDED
@@ -0,0 +1,105 @@
1
+ import type { Nonce } from './nonce.js'
2
+ import type { NoiseExtensions, NoiseHandshakePayload } from './proto/payload.js'
3
+ import type { ConnectionEncrypter, Logger, PrivateKey, PublicKey } from '@libp2p/interface'
4
+ import type { LengthPrefixedStream } from '@libp2p/utils'
5
+ import type { Uint8ArrayList } from 'uint8arraylist'
6
+
7
+ /**
8
+ * Crypto functions defined by the noise protocol, abstracted from the
9
+ * underlying implementations
10
+ */
11
+ export interface ICrypto {
12
+ generateKeypair(): KeyPair
13
+ dh(keypair: KeyPair, publicKey: Uint8Array | Uint8ArrayList): Uint8Array
14
+ encrypt(plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array): Uint8ArrayList | Uint8Array
15
+ decrypt(ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array, dst?: Uint8Array): Uint8ArrayList | Uint8Array
16
+ hash(data: Uint8Array | Uint8ArrayList): Uint8Array
17
+ hkdf(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array, Uint8Array]
18
+ }
19
+
20
+ export interface HandshakeParams {
21
+ log: Logger
22
+ connection: LengthPrefixedStream
23
+ crypto: ICrypto
24
+ privateKey: PrivateKey
25
+ prologue: Uint8Array
26
+ /** static keypair */
27
+ s: KeyPair
28
+ remoteIdentityKey?: PublicKey
29
+ extensions?: NoiseExtensions
30
+ }
31
+
32
+ export interface HandshakeResult {
33
+ payload: NoiseHandshakePayload
34
+ encrypt (plaintext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList
35
+ decrypt (ciphertext: Uint8Array | Uint8ArrayList, dst?: Uint8Array): Uint8Array | Uint8ArrayList
36
+ }
37
+
38
+ /**
39
+ * A CipherState object contains k and n variables, which it uses to encrypt and
40
+ * decrypt ciphertext.
41
+ *
42
+ * During the handshake phase each party has a single CipherState, but during
43
+ * the transport phase each party has two CipherState objects: one for sending,
44
+ * and one for receiving.
45
+ */
46
+ export interface ICipherState {
47
+ /**
48
+ * A cipher key of 32 bytes (which may be empty). Empty is a special value
49
+ * which indicates k has not yet been initialized.
50
+ */
51
+ k?: Uint8Array
52
+ /**
53
+ * An 8-byte (64-bit) unsigned integer nonce.
54
+ *
55
+ * For performance reasons, the nonce is represented as a Nonce object
56
+ * The nonce is treated as a uint64, even though the underlying `number` only
57
+ * has 52 safely-available bits.
58
+ */
59
+ n: Nonce
60
+ }
61
+
62
+ /**
63
+ * A SymmetricState object contains a CipherState plus ck and h variables. It is
64
+ * so-named because it encapsulates all the "symmetric crypto" used by Noise.
65
+ *
66
+ * During the handshake phase each party has a single SymmetricState, which can
67
+ * be deleted once the handshake is finished.
68
+ */
69
+ export interface ISymmetricState {
70
+ cs: ICipherState
71
+ /** A chaining key of 32 bytes. */
72
+ ck: Uint8Array
73
+ /** A hash output of 32 bytes. */
74
+ h: Uint8Array
75
+ }
76
+
77
+ /**
78
+ * A HandshakeState object contains a SymmetricState plus DH variables (s, e,
79
+ * rs, re) and a variable representing the handshake pattern.
80
+ *
81
+ * During the handshake phase each party has a single HandshakeState, which can
82
+ * be deleted once the handshake is finished.
83
+ */
84
+ export interface IHandshakeState {
85
+ ss: ISymmetricState
86
+ /** The local static key pair */
87
+ s?: KeyPair
88
+ /** The local ephemeral key pair */
89
+ e?: KeyPair
90
+ /** The remote party's static public key */
91
+ rs?: Uint8Array | Uint8ArrayList
92
+ /** The remote party's ephemeral public key */
93
+ re?: Uint8Array | Uint8ArrayList
94
+ }
95
+
96
+ export interface KeyPair {
97
+ publicKey: Uint8Array
98
+ privateKey: Uint8Array
99
+ }
100
+
101
+ export interface INoiseExtensions {
102
+ webtransportCerthashes: Uint8Array[]
103
+ }
104
+
105
+ export interface INoiseConnection extends ConnectionEncrypter<INoiseExtensions> { }
package/src/utils.ts ADDED
@@ -0,0 +1,221 @@
1
+ import { publicKeyFromProtobuf, publicKeyToProtobuf } from '@libp2p/crypto/keys'
2
+ import { StreamMessageEvent, UnexpectedPeerError } from '@libp2p/interface'
3
+ import { AbstractMessageStream, LengthPrefixedDecoder } from '@libp2p/utils'
4
+ import { Uint8ArrayList } from 'uint8arraylist'
5
+ import { concat as uint8ArrayConcat } from 'uint8arrays/concat'
6
+ import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
7
+ import { CHACHA_TAG_LENGTH, NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG } from './constants.ts'
8
+ import { uint16BEEncode, uint16BEDecode } from './encoder.ts'
9
+ import { NoiseHandshakePayload } from './proto/payload.js'
10
+ import type { MetricsRegistry } from './metrics.ts'
11
+ import type { NoiseExtensions } from './proto/payload.js'
12
+ import type { HandshakeResult } from './types.ts'
13
+ import type { AbortOptions, MessageStream, PrivateKey, PublicKey, StreamCloseEvent } from '@libp2p/interface'
14
+ import type { SendResult } from '@libp2p/utils'
15
+
16
+ export async function createHandshakePayload (
17
+ privateKey: PrivateKey,
18
+ staticPublicKey: Uint8Array | Uint8ArrayList,
19
+ extensions?: NoiseExtensions
20
+ ): Promise<Uint8Array | Uint8ArrayList> {
21
+ const identitySig = await privateKey.sign(getSignaturePayload(staticPublicKey))
22
+
23
+ return NoiseHandshakePayload.encode({
24
+ identityKey: publicKeyToProtobuf(privateKey.publicKey),
25
+ identitySig,
26
+ extensions
27
+ })
28
+ }
29
+
30
+ export async function decodeHandshakePayload (
31
+ payloadBytes: Uint8Array | Uint8ArrayList,
32
+ remoteStaticKey?: Uint8Array | Uint8ArrayList,
33
+ remoteIdentityKey?: PublicKey
34
+ ): Promise<NoiseHandshakePayload> {
35
+ try {
36
+ const payload = NoiseHandshakePayload.decode(payloadBytes)
37
+ const publicKey = publicKeyFromProtobuf(payload.identityKey)
38
+
39
+ if (remoteIdentityKey?.equals(publicKey) === false) {
40
+ throw new Error(`Payload identity key ${publicKey} does not match expected remote identity key ${remoteIdentityKey}`)
41
+ }
42
+
43
+ if (!remoteStaticKey) {
44
+ throw new Error('Remote static does not exist')
45
+ }
46
+
47
+ const signaturePayload = getSignaturePayload(remoteStaticKey)
48
+
49
+ if (!(await publicKey.verify(signaturePayload, payload.identitySig))) {
50
+ throw new Error('Invalid payload signature')
51
+ }
52
+
53
+ return payload
54
+ } catch (e) {
55
+ throw new UnexpectedPeerError((e as Error).message)
56
+ }
57
+ }
58
+
59
+ export function getSignaturePayload (publicKey: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList {
60
+ const prefix = uint8ArrayFromString('noise-libp2p-static-key:')
61
+
62
+ if (publicKey instanceof Uint8Array) {
63
+ return uint8ArrayConcat([prefix, publicKey], prefix.length + publicKey.length)
64
+ }
65
+
66
+ publicKey.prepend(prefix)
67
+
68
+ return publicKey
69
+ }
70
+
71
+ class EncryptedMessageStream extends AbstractMessageStream {
72
+ private stream: MessageStream
73
+ private handshake: HandshakeResult
74
+ private metrics?: MetricsRegistry
75
+ private decoder: LengthPrefixedDecoder
76
+
77
+ constructor (stream: MessageStream, handshake: HandshakeResult, metrics?: MetricsRegistry) {
78
+ super({
79
+ log: stream.log,
80
+ inactivityTimeout: stream.inactivityTimeout,
81
+ maxReadBufferLength: stream.maxReadBufferLength,
82
+ direction: stream.direction
83
+ })
84
+
85
+ this.stream = stream
86
+ this.handshake = handshake
87
+ this.metrics = metrics
88
+ this.decoder = new LengthPrefixedDecoder({
89
+ lengthDecoder: uint16BEDecode,
90
+ maxBufferSize: 16 * 1024 * 1024,
91
+ encodingLength: () => 2
92
+ })
93
+
94
+ const noiseOnMessageDecrypt = (evt: StreamMessageEvent): void => {
95
+ try {
96
+ for (const buf of this.decoder.decode(evt.data)) {
97
+ this.onData(this.decrypt(buf))
98
+ }
99
+ } catch (err: any) {
100
+ this.abort(err)
101
+ }
102
+ }
103
+ this.stream.addEventListener('message', noiseOnMessageDecrypt)
104
+
105
+ const noiseOnClose = (evt: StreamCloseEvent): void => {
106
+ if (evt.error != null) {
107
+ if (evt.local === true) {
108
+ this.abort(evt.error)
109
+ } else {
110
+ this.onRemoteReset()
111
+ }
112
+ } else {
113
+ this.onTransportClosed()
114
+ }
115
+ }
116
+ this.stream.addEventListener('close', noiseOnClose)
117
+
118
+ const noiseOnDrain = (): void => {
119
+ this.safeDispatchEvent('drain')
120
+ }
121
+ this.stream.addEventListener('drain', noiseOnDrain)
122
+
123
+ const noiseOnRemoteCloseWrite = (): void => {
124
+ this.onRemoteCloseWrite()
125
+ }
126
+ this.stream.addEventListener('remoteCloseWrite', noiseOnRemoteCloseWrite)
127
+ }
128
+
129
+ encrypt (chunk: Uint8Array | Uint8ArrayList): Uint8ArrayList {
130
+ const output = new Uint8ArrayList()
131
+
132
+ for (let i = 0; i < chunk.byteLength; i += NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG) {
133
+ let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG
134
+ if (end > chunk.byteLength) {
135
+ end = chunk.byteLength
136
+ }
137
+
138
+ let data: Uint8Array | Uint8ArrayList
139
+
140
+ if (chunk instanceof Uint8Array) {
141
+ data = this.handshake.encrypt(chunk.subarray(i, end))
142
+ } else {
143
+ data = this.handshake.encrypt(chunk.sublist(i, end))
144
+ }
145
+
146
+ this.metrics?.encryptedPackets.increment()
147
+
148
+ output.append(uint16BEEncode(data.byteLength))
149
+ output.append(data)
150
+ }
151
+
152
+ return output
153
+ }
154
+
155
+ decrypt (chunk: Uint8Array | Uint8ArrayList): Uint8ArrayList {
156
+ const output = new Uint8ArrayList()
157
+
158
+ for (let i = 0; i < chunk.byteLength; i += NOISE_MSG_MAX_LENGTH_BYTES) {
159
+ let end = i + NOISE_MSG_MAX_LENGTH_BYTES
160
+ if (end > chunk.byteLength) {
161
+ end = chunk.byteLength
162
+ }
163
+
164
+ if (end - CHACHA_TAG_LENGTH < i) {
165
+ throw new Error('Invalid chunk')
166
+ }
167
+
168
+ let encrypted: Uint8Array | Uint8ArrayList
169
+
170
+ if (chunk instanceof Uint8Array) {
171
+ encrypted = chunk.subarray(i, end)
172
+ } else {
173
+ encrypted = chunk.sublist(i, end)
174
+ }
175
+
176
+ // memory allocation is not cheap so reuse the encrypted Uint8Array
177
+ // see https://github.com/ChainSafe/js-libp2p-noise/pull/242#issue-1422126164
178
+ // this is ok because chacha20 reads bytes one by one and don't reread after that
179
+ // it's also tested in https://github.com/ChainSafe/as-chacha20poly1305/pull/1/files#diff-25252846b58979dcaf4e41d47b3eadd7e4f335e7fb98da6c049b1f9cd011f381R48
180
+ const dst = chunk.subarray(i, end - CHACHA_TAG_LENGTH)
181
+ try {
182
+ const plaintext = this.handshake.decrypt(encrypted, dst)
183
+ this.metrics?.decryptedPackets.increment()
184
+
185
+ output.append(plaintext)
186
+ } catch (e) {
187
+ this.metrics?.decryptErrors.increment()
188
+ throw e
189
+ }
190
+ }
191
+
192
+ return output
193
+ }
194
+
195
+ close (options?: AbortOptions): Promise<void> {
196
+ return this.stream.close(options)
197
+ }
198
+
199
+ sendPause (): void {
200
+ this.stream.pause()
201
+ }
202
+
203
+ sendResume (): void {
204
+ this.stream.resume()
205
+ }
206
+
207
+ sendReset (err: Error): void {
208
+ this.stream.abort(err)
209
+ }
210
+
211
+ sendData (data: Uint8ArrayList): SendResult {
212
+ return {
213
+ sentBytes: data.byteLength,
214
+ canSendMore: this.stream.send(this.encrypt(data))
215
+ }
216
+ }
217
+ }
218
+
219
+ export function toMessageStream (connection: MessageStream, handshake: HandshakeResult, metrics?: MetricsRegistry): MessageStream {
220
+ return new EncryptedMessageStream(connection, handshake, metrics)
221
+ }