Skip to content

Commit 6b79387

Browse files
authored
feat!: update to libp2p v3 api (#618)
Refactors code to conform to the `libp2p@3.x.x` API. - Encrypted streams are now EventTargets - Consuming incoming data is now done synchronously for reduced latency BREAKING CHANGE: Must be used with `libp2p@3.x.x`, it cannot be used with earlier versions
1 parent bcd22e2 commit 6b79387

File tree

12 files changed

+282
-218
lines changed

12 files changed

+282
-218
lines changed

package.json

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -151,44 +151,40 @@
151151
},
152152
"dependencies": {
153153
"@chainsafe/as-chacha20poly1305": "^0.1.0",
154-
"@chainsafe/as-sha256": "^1.0.0",
155-
"@libp2p/crypto": "^5.0.0",
156-
"@libp2p/interface": "^2.9.0",
157-
"@libp2p/peer-id": "^5.0.0",
158-
"@noble/ciphers": "^1.1.3",
159-
"@noble/curves": "^1.1.0",
160-
"@noble/hashes": "^1.3.1",
161-
"it-length-prefixed": "^10.0.1",
162-
"it-length-prefixed-stream": "^2.0.1",
163-
"it-pair": "^2.0.6",
164-
"it-pipe": "^3.0.1",
165-
"it-stream-types": "^2.0.1",
166-
"protons-runtime": "^5.5.0",
167-
"uint8arraylist": "^2.4.3",
168-
"uint8arrays": "^5.0.0",
154+
"@chainsafe/as-sha256": "^1.2.0",
155+
"@libp2p/crypto": "^5.1.9",
156+
"@libp2p/interface": "^3.0.0",
157+
"@libp2p/peer-id": "^6.0.0",
158+
"@libp2p/utils": "^7.0.0",
159+
"@noble/ciphers": "^2.0.1",
160+
"@noble/curves": "^2.0.1",
161+
"@noble/hashes": "^2.0.1",
162+
"protons-runtime": "^5.6.0",
163+
"uint8arraylist": "^2.4.8",
164+
"uint8arrays": "^5.1.0",
169165
"wherearewe": "^2.0.1"
170166
},
171167
"devDependencies": {
172-
"@chainsafe/libp2p-yamux": "^7.0.0",
173-
"@libp2p/daemon-client": "^9.0.0",
174-
"@libp2p/daemon-server": "^8.0.0",
175-
"@libp2p/interface-compliance-tests": "^6.0.0",
176-
"@libp2p/interop": "^13.0.0",
177-
"@libp2p/logger": "^5.0.0",
178-
"@libp2p/tcp": "^10.0.0",
179-
"@multiformats/multiaddr": "^12.1.0",
180-
"@types/sinon": "^17.0.1",
181-
"aegir": "^47.0.18",
168+
"@chainsafe/libp2p-yamux": "^8.0.0",
169+
"@libp2p/daemon-client": "^10.0.0",
170+
"@libp2p/daemon-server": "^9.0.0",
171+
"@libp2p/interface-compliance-tests": "^7.0.0",
172+
"@libp2p/interop": "^14.0.0",
173+
"@libp2p/logger": "^6.0.0",
174+
"@libp2p/tcp": "^11.0.0",
175+
"@multiformats/multiaddr": "^13.0.1",
176+
"@types/sinon": "^17.0.4",
177+
"aegir": "^47.0.22",
182178
"benchmark": "^2.1.4",
183-
"execa": "^9.3.0",
184-
"go-libp2p": "^1.0.3",
179+
"execa": "^9.6.0",
180+
"go-libp2p": "^1.6.0",
185181
"iso-random-stream": "^2.0.2",
186-
"it-byte-stream": "^2.0.1",
187-
"libp2p": "^2.0.0",
188-
"mkdirp": "^3.0.0",
189-
"multiformats": "^13.2.2",
190-
"p-defer": "^4.0.0",
191-
"protons": "^7.6.0",
182+
"it-pair": "^2.0.6",
183+
"libp2p": "^3.0.0",
184+
"mkdirp": "^3.0.1",
185+
"multiformats": "^13.4.0",
186+
"p-defer": "^4.0.1",
187+
"protons": "^7.7.0",
192188
"sinon": "^21.0.0",
193189
"sinon-ts": "^2.0.0"
194190
},

src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export const NOISE_MSG_MAX_LENGTH_BYTES = 65535
22
export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16
33

44
export const DUMP_SESSION_KEYS = Boolean(globalThis.process?.env?.DUMP_SESSION_KEYS)
5+
export const CHACHA_TAG_LENGTH = 16

src/crypto/js.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { chacha20poly1305 } from '@noble/ciphers/chacha'
2-
import { x25519 } from '@noble/curves/ed25519'
3-
import { extract, expand } from '@noble/hashes/hkdf'
4-
import { sha256 } from '@noble/hashes/sha256'
1+
import { chacha20poly1305 } from '@noble/ciphers/chacha.js'
2+
import { x25519 } from '@noble/curves/ed25519.js'
3+
import { extract, expand } from '@noble/hashes/hkdf.js'
4+
import { sha256 } from '@noble/hashes/sha2.js'
55
import type { ICryptoInterface } from '../crypto.js'
66
import type { KeyPair } from '../types.js'
77
import type { Uint8ArrayList } from 'uint8arraylist'
@@ -24,7 +24,7 @@ export const pureJsCrypto: ICryptoInterface = {
2424
},
2525

2626
generateX25519KeyPair (): KeyPair {
27-
const secretKey = x25519.utils.randomPrivateKey()
27+
const secretKey = x25519.utils.randomSecretKey()
2828
const publicKey = x25519.getPublicKey(secretKey)
2929

3030
return {

src/encoder.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { allocUnsafe as uint8ArrayAllocUnsafe } from 'uint8arrays/alloc'
2-
import type { LengthDecoderFunction } from 'it-length-prefixed'
32
import type { Uint8ArrayList } from 'uint8arraylist'
43

54
export const uint16BEEncode = (value: number): Uint8Array => {
@@ -10,7 +9,7 @@ export const uint16BEEncode = (value: number): Uint8Array => {
109
}
1110
uint16BEEncode.bytes = 2
1211

13-
export const uint16BEDecode: LengthDecoderFunction = (data: Uint8Array | Uint8ArrayList): number => {
12+
export const uint16BEDecode = (data: Uint8Array | Uint8ArrayList): number => {
1413
if (data.length < 2) { throw RangeError('Could not decode int16BE') }
1514

1615
if (data instanceof Uint8Array) {

src/noise.ts

Lines changed: 29 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
import { publicKeyFromProtobuf } from '@libp2p/crypto/keys'
22
import { InvalidCryptoExchangeError, serviceCapabilities } from '@libp2p/interface'
33
import { peerIdFromPublicKey } from '@libp2p/peer-id'
4-
import { decode } from 'it-length-prefixed'
5-
import { lpStream } from 'it-length-prefixed-stream'
6-
import { duplexPair } from 'it-pair/duplex'
7-
import { pipe } from 'it-pipe'
4+
import { lpStream } from '@libp2p/utils'
85
import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
96
import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants.js'
107
import { defaultCrypto } from './crypto/index.js'
118
import { wrapCrypto } from './crypto.js'
129
import { uint16BEDecode, uint16BEEncode } from './encoder.js'
1310
import { registerMetrics } from './metrics.js'
1411
import { performHandshakeInitiator, performHandshakeResponder } from './performHandshake.js'
15-
import { decryptStream, encryptStream } from './streaming.js'
12+
import { toMessageStream } from './utils.ts'
1613
import type { ICryptoInterface } from './crypto.js'
1714
import type { NoiseComponents } from './index.js'
1815
import type { MetricsRegistry } from './metrics.js'
19-
import type { HandshakeResult, ICrypto, INoiseConnection, KeyPair } from './types.js'
20-
import type { MultiaddrConnection, SecuredConnection, PrivateKey, PublicKey, StreamMuxerFactory, SecureConnectionOptions } from '@libp2p/interface'
21-
import type { LengthPrefixedStream } from 'it-length-prefixed-stream'
22-
import type { Duplex } from 'it-stream-types'
23-
import type { Uint8ArrayList } from 'uint8arraylist'
16+
import type { HandshakeResult, ICrypto, INoiseConnection, INoiseExtensions, KeyPair } from './types.js'
17+
import type { SecuredConnection, PrivateKey, PublicKey, StreamMuxerFactory, SecureConnectionOptions, Logger, MessageStream } from '@libp2p/interface'
18+
import type { LengthPrefixedStream } from '@libp2p/utils'
2419

2520
export interface NoiseExtensions {
2621
webtransportCerthashes: Uint8Array[]
@@ -45,12 +40,14 @@ export class Noise implements INoiseConnection {
4540
private readonly extensions?: NoiseExtensions
4641
private readonly metrics?: MetricsRegistry
4742
private readonly components: NoiseComponents
43+
private readonly log: Logger
4844

4945
constructor (components: NoiseComponents, init: NoiseInit = {}) {
5046
const { staticNoiseKey, extensions, crypto, prologueBytes } = init
5147
const { metrics } = components
5248

5349
this.components = components
50+
this.log = components.logger.forComponent('libp2p:noise')
5451
const _crypto = crypto ?? defaultCrypto
5552
this.crypto = wrapCrypto(_crypto)
5653
this.extensions = {
@@ -83,31 +80,25 @@ export class Noise implements INoiseConnection {
8380
* @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer
8481
* @param options.signal - Used to abort the operation
8582
*/
86-
public async secureOutbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (connection: Stream, options?: SecureConnectionOptions): Promise<SecuredConnection<Stream, NoiseExtensions>> {
87-
const wrappedConnection = lpStream(
88-
connection,
89-
{
90-
lengthEncoder: uint16BEEncode,
91-
lengthDecoder: uint16BEDecode,
92-
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
93-
}
94-
)
83+
async secureOutbound (connection: MessageStream, options?: SecureConnectionOptions): Promise<SecuredConnection<INoiseExtensions>> {
84+
const log = connection.log?.newScope('noise') ?? this.log
85+
const wrappedConnection = lpStream(connection, {
86+
lengthEncoder: uint16BEEncode,
87+
lengthDecoder: uint16BEDecode,
88+
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
89+
})
9590

9691
const handshake = await this.performHandshakeInitiator(
9792
wrappedConnection,
9893
this.components.privateKey,
94+
log,
9995
options?.remotePeer?.publicKey,
10096
options
10197
)
102-
const conn = await this.createSecureConnection(wrappedConnection, handshake)
103-
104-
connection.source = conn.source
105-
connection.sink = conn.sink
106-
10798
const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey)
10899

109100
return {
110-
conn: connection,
101+
connection: toMessageStream(wrappedConnection.unwrap(), handshake, this.metrics),
111102
remoteExtensions: handshake.payload.extensions,
112103
remotePeer: peerIdFromPublicKey(publicKey),
113104
streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers)
@@ -144,31 +135,25 @@ export class Noise implements INoiseConnection {
144135
* @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer
145136
* @param options.signal - Used to abort the operation
146137
*/
147-
public async secureInbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (connection: Stream, options?: SecureConnectionOptions): Promise<SecuredConnection<Stream, NoiseExtensions>> {
148-
const wrappedConnection = lpStream(
149-
connection,
150-
{
151-
lengthEncoder: uint16BEEncode,
152-
lengthDecoder: uint16BEDecode,
153-
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
154-
}
155-
)
138+
async secureInbound (connection: MessageStream, options?: SecureConnectionOptions): Promise<SecuredConnection<INoiseExtensions>> {
139+
const log = connection.log?.newScope('noise') ?? this.log
140+
const wrappedConnection = lpStream(connection, {
141+
lengthEncoder: uint16BEEncode,
142+
lengthDecoder: uint16BEDecode,
143+
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
144+
})
156145

157146
const handshake = await this.performHandshakeResponder(
158147
wrappedConnection,
159148
this.components.privateKey,
149+
log,
160150
options?.remotePeer?.publicKey,
161151
options
162152
)
163-
const conn = await this.createSecureConnection(wrappedConnection, handshake)
164-
165-
connection.source = conn.source
166-
connection.sink = conn.sink
167-
168153
const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey)
169154

170155
return {
171-
conn: connection,
156+
connection: toMessageStream(wrappedConnection.unwrap(), handshake, this.metrics),
172157
remoteExtensions: handshake.payload.extensions,
173158
remotePeer: peerIdFromPublicKey(publicKey),
174159
streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers)
@@ -182,6 +167,7 @@ export class Noise implements INoiseConnection {
182167
connection: LengthPrefixedStream,
183168
// TODO: pass private key in noise constructor via Components
184169
privateKey: PrivateKey,
170+
log: Logger,
185171
remoteIdentityKey?: PublicKey,
186172
options?: SecureConnectionOptions
187173
): Promise<HandshakeResult> {
@@ -193,7 +179,7 @@ export class Noise implements INoiseConnection {
193179
connection,
194180
privateKey,
195181
remoteIdentityKey,
196-
log: this.components.logger.forComponent('libp2p:noise:xxhandshake'),
182+
log: log.newScope('xxhandshake'),
197183
crypto: this.crypto,
198184
prologue: this.prologue,
199185
s: this.staticKey,
@@ -218,6 +204,7 @@ export class Noise implements INoiseConnection {
218204
private async performHandshakeResponder (
219205
connection: LengthPrefixedStream,
220206
privateKey: PrivateKey,
207+
log: Logger,
221208
remoteIdentityKey?: PublicKey,
222209
options?: SecureConnectionOptions
223210
): Promise<HandshakeResult> {
@@ -229,7 +216,7 @@ export class Noise implements INoiseConnection {
229216
connection,
230217
privateKey,
231218
remoteIdentityKey,
232-
log: this.components.logger.forComponent('libp2p:noise:xxhandshake'),
219+
log: log.newScope('xxhandshake'),
233220
crypto: this.crypto,
234221
prologue: this.prologue,
235222
s: this.staticKey,
@@ -247,24 +234,4 @@ export class Noise implements INoiseConnection {
247234

248235
return result
249236
}
250-
251-
private async createSecureConnection (
252-
connection: LengthPrefixedStream<Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>>>,
253-
handshake: HandshakeResult
254-
): Promise<Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>>> {
255-
// Create encryption box/unbox wrapper
256-
const [secure, user] = duplexPair<Uint8Array | Uint8ArrayList>()
257-
const network = connection.unwrap()
258-
259-
await pipe(
260-
secure, // write to wrapper
261-
encryptStream(handshake, this.metrics), // encrypt data + prefix with message length
262-
network, // send to the remote peer
263-
(source) => decode(source, { lengthDecoder: uint16BEDecode }), // read message length prefix
264-
decryptStream(handshake, this.metrics), // decrypt the incoming data
265-
secure // pipe to the wrapper
266-
)
267-
268-
return user
269-
}
270237
}

src/streaming.ts

Lines changed: 0 additions & 67 deletions
This file was deleted.

0 commit comments

Comments
 (0)