From fed9eece3929ce19f151f5f378e4ff9615262365 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Fri, 9 Aug 2024 15:24:11 +0100 Subject: [PATCH 1/2] fix: make local peer id optional `libp2p@2.x.x` will remove the `localPeer` argument from the `secureInbound` and `secureOutbound` methods of the `ConnectionEncrypter` interface. Unfortunately the `js-libp2p` monorepo has a depdendency on this module so the change cannot be released until this module is compatible... with the unreleased change. This PR tests the first argument to `secureInbound` and `secureOutbound` to ensure that it is actually a `PeerId`. If not it shuffles all the arguments along by one place. This PR can be reverted and the first argument removed once `libp2p@2.x.x` is released. --- src/index.ts | 3 ++- src/noise.ts | 18 ++++++++++++++- test/compliance.spec.ts | 9 ++++++-- test/index.spec.ts | 19 ++++++++++++---- test/noise.spec.ts | 50 ++++++++++++++++++++++++++++++++--------- 5 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/index.ts b/src/index.ts index c4397f0..60725b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,12 @@ import { Noise } from './noise.js' import type { NoiseInit } from './noise.js' import type { NoiseExtensions } from './proto/payload.js' -import type { ComponentLogger, ConnectionEncrypter, Metrics } from '@libp2p/interface' +import type { ComponentLogger, ConnectionEncrypter, Metrics, PeerId } from '@libp2p/interface' export type { ICryptoInterface } from './crypto.js' export { pureJsCrypto } from './crypto/js.js' export interface NoiseComponents { + peerId: PeerId logger: ComponentLogger metrics?: Metrics } diff --git a/src/noise.ts b/src/noise.ts index 6ecfe59..9e13d84 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -1,5 +1,5 @@ import { unmarshalPrivateKey } from '@libp2p/crypto/keys' -import { type MultiaddrConnection, type SecuredConnection, type PeerId, CodeError, type PrivateKey, serviceCapabilities } from '@libp2p/interface' +import { type MultiaddrConnection, type SecuredConnection, type PeerId, CodeError, type PrivateKey, serviceCapabilities, isPeerId } from '@libp2p/interface' import { peerIdFromKeys } from '@libp2p/peer-id' import { decode } from 'it-length-prefixed' import { lpStream, type LengthPrefixedStream } from 'it-length-prefixed-stream' @@ -73,6 +73,14 @@ export class Noise implements INoiseConnection { * @param remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer. */ public async secureOutbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> { + // handle upcoming changes in libp2p@2.x.x + // @see https://github.com/libp2p/js-libp2p/pull/2304 + if (!isPeerId(localPeer)) { + remotePeer = connection as unknown as PeerId + connection = localPeer + localPeer = this.components.peerId + } + const wrappedConnection = lpStream( connection, { @@ -114,6 +122,14 @@ export class Noise implements INoiseConnection { * @param remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades. */ public async secureInbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> { + // handle upcoming changes in libp2p@2.x.x + // @see https://github.com/libp2p/js-libp2p/pull/2304 + if (!isPeerId(localPeer)) { + remotePeer = connection as unknown as PeerId + connection = localPeer + localPeer = this.components.peerId + } + const wrappedConnection = lpStream( connection, { diff --git a/test/compliance.spec.ts b/test/compliance.spec.ts index bbda027..a7aa4eb 100644 --- a/test/compliance.spec.ts +++ b/test/compliance.spec.ts @@ -1,11 +1,16 @@ import tests from '@libp2p/interface-compliance-tests/connection-encryption' import { defaultLogger } from '@libp2p/logger' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { Noise } from '../src/noise.js' +import type { PeerId } from '@libp2p/interface' describe('spec compliance tests', function () { tests({ - async setup () { - return new Noise({ logger: defaultLogger() }) + async setup (opts: { peerId?: PeerId }) { + return new Noise({ + peerId: opts?.peerId ?? await createEd25519PeerId(), + logger: defaultLogger() + }) }, async teardown () {} }) diff --git a/test/index.spec.ts b/test/index.spec.ts index bd18341..777eccc 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -1,4 +1,5 @@ import { defaultLogger } from '@libp2p/logger' +import { createEd25519PeerId } from '@libp2p/peer-id-factory' import { expect } from 'aegir/chai' import { lpStream } from 'it-length-prefixed-stream' import { duplexPair } from 'it-pair/duplex' @@ -18,8 +19,11 @@ function createCounterSpy (): ReturnType { } describe('Index', () => { - it('should expose class with tag and required functions', () => { - const noiseInstance = noise()({ logger: defaultLogger() }) + it('should expose class with tag and required functions', async () => { + const noiseInstance = noise()({ + peerId: await createEd25519PeerId(), + logger: defaultLogger() + }) expect(noiseInstance.protocol).to.equal('/noise') expect(typeof (noiseInstance.secureInbound)).to.equal('function') expect(typeof (noiseInstance.secureOutbound)).to.equal('function') @@ -35,8 +39,15 @@ describe('Index', () => { return counter } } - const noiseInit = new Noise({ logger: defaultLogger(), metrics: metrics as any as Metrics }) - const noiseResp = new Noise({ logger: defaultLogger() }) + const noiseInit = new Noise({ + peerId: await createEd25519PeerId(), + logger: defaultLogger(), + metrics: metrics as any as Metrics + }) + const noiseResp = new Noise({ + peerId: await createEd25519PeerId(), + logger: defaultLogger() + }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ diff --git a/test/noise.spec.ts b/test/noise.spec.ts index 03a1f62..b3e5882 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -28,8 +28,14 @@ describe('Noise', () => { it('should communicate through encrypted streams without noise pipes', async () => { try { - const noiseInit = new Noise({ logger: defaultLogger() }, { staticNoiseKey: undefined, extensions: undefined }) - const noiseResp = new Noise({ logger: defaultLogger() }, { staticNoiseKey: undefined, extensions: undefined }) + const noiseInit = new Noise({ + peerId: localPeer, + logger: defaultLogger() + }, { staticNoiseKey: undefined, extensions: undefined }) + const noiseResp = new Noise({ + peerId: remotePeer, + logger: defaultLogger() + }, { staticNoiseKey: undefined, extensions: undefined }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ @@ -51,8 +57,14 @@ describe('Noise', () => { it('should test large payloads', async function () { this.timeout(10000) try { - const noiseInit = new Noise({ logger: defaultLogger() }, { staticNoiseKey: undefined }) - const noiseResp = new Noise({ logger: defaultLogger() }, { staticNoiseKey: undefined }) + const noiseInit = new Noise({ + peerId: localPeer, + logger: defaultLogger() + }, { staticNoiseKey: undefined }) + const noiseResp = new Noise({ + peerId: remotePeer, + logger: defaultLogger() + }, { staticNoiseKey: undefined }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ @@ -76,9 +88,15 @@ describe('Noise', () => { it('should working without remote peer provided in incoming connection', async () => { try { const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const noiseInit = new Noise({ logger: defaultLogger() }, { staticNoiseKey: staticKeysInitiator.privateKey }) + const noiseInit = new Noise({ + peerId: localPeer, + logger: defaultLogger() + }, { staticNoiseKey: staticKeysInitiator.privateKey }) const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - const noiseResp = new Noise({ logger: defaultLogger() }, { staticNoiseKey: staticKeysResponder.privateKey }) + const noiseResp = new Noise({ + peerId: remotePeer, + logger: defaultLogger() + }, { staticNoiseKey: staticKeysResponder.privateKey }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ @@ -109,10 +127,16 @@ describe('Noise', () => { try { const certhashInit = Buffer.from('certhash data from init') const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const noiseInit = new Noise({ logger: defaultLogger() }, { staticNoiseKey: staticKeysInitiator.privateKey, extensions: { webtransportCerthashes: [certhashInit] } }) + const noiseInit = new Noise({ + peerId: localPeer, + logger: defaultLogger() + }, { staticNoiseKey: staticKeysInitiator.privateKey, extensions: { webtransportCerthashes: [certhashInit] } }) const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() const certhashResp = Buffer.from('certhash data from respon') - const noiseResp = new Noise({ logger: defaultLogger() }, { staticNoiseKey: staticKeysResponder.privateKey, extensions: { webtransportCerthashes: [certhashResp] } }) + const noiseResp = new Noise({ + peerId: remotePeer, + logger: defaultLogger() + }, { staticNoiseKey: staticKeysResponder.privateKey, extensions: { webtransportCerthashes: [certhashResp] } }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ @@ -130,8 +154,14 @@ describe('Noise', () => { it('should accept a prologue', async () => { try { - const noiseInit = new Noise({ logger: defaultLogger() }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) - const noiseResp = new Noise({ logger: defaultLogger() }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) + const noiseInit = new Noise({ + peerId: localPeer, + logger: defaultLogger() + }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) + const noiseResp = new Noise({ + peerId: remotePeer, + logger: defaultLogger() + }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ From dd954a690e452b785daa0da88268c4c439c3dcb6 Mon Sep 17 00:00:00 2001 From: achingbrain Date: Mon, 12 Aug 2024 08:25:54 +0100 Subject: [PATCH 2/2] chore: switch to method overloading --- src/noise.ts | 50 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/src/noise.ts b/src/noise.ts index 9e13d84..b469792 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -72,14 +72,10 @@ export class Noise implements INoiseConnection { * @param connection - streaming iterable duplex that will be encrypted * @param remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer. */ - public async secureOutbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> { - // handle upcoming changes in libp2p@2.x.x - // @see https://github.com/libp2p/js-libp2p/pull/2304 - if (!isPeerId(localPeer)) { - remotePeer = connection as unknown as PeerId - connection = localPeer - localPeer = this.components.peerId - } + public async secureOutbound > = MultiaddrConnection> (connection: Stream, remotePeer?: PeerId): Promise> + public async secureOutbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> + public async secureOutbound > = MultiaddrConnection> (...args: any[]): Promise> { + const { localPeer, connection, remotePeer } = this.parseArgs(args) const wrappedConnection = lpStream( connection, @@ -121,14 +117,10 @@ export class Noise implements INoiseConnection { * @param connection - streaming iterable duplex that will be encrypted. * @param remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades. */ - public async secureInbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> { - // handle upcoming changes in libp2p@2.x.x - // @see https://github.com/libp2p/js-libp2p/pull/2304 - if (!isPeerId(localPeer)) { - remotePeer = connection as unknown as PeerId - connection = localPeer - localPeer = this.components.peerId - } + public async secureInbound > = MultiaddrConnection> (connection: Stream, remotePeer?: PeerId): Promise> + public async secureInbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> + public async secureInbound > = MultiaddrConnection> (...args: any[]): Promise> { + const { localPeer, connection, remotePeer } = this.parseArgs(args) const wrappedConnection = lpStream( connection, @@ -242,4 +234,30 @@ export class Noise implements INoiseConnection { return user } + + /** + * Detect call signature in `libp2p@1.x.x` or `libp2p@2.x.x` style. + * + * TODO: remove this after `libp2p@2.x.x` is released and only support the + * newer style + */ + private parseArgs > = MultiaddrConnection> (args: any[]): { localPeer: PeerId, connection: Stream, remotePeer?: PeerId } { + // if the first argument is a peer id, we're using the libp2p@1.x.x style + if (isPeerId(args[0])) { + return { + localPeer: args[0], + connection: args[1], + remotePeer: args[2] + } + } else { + // handle upcoming changes in libp2p@2.x.x where the first argument is the + // connection and the second is optionally the remote peer + // @see https://github.com/libp2p/js-libp2p/pull/2304 + return { + localPeer: this.components.peerId, + connection: args[0], + remotePeer: args[1] + } + } + } }