From 5f92b50e88fbe8406236c479d3e710ab4d1c4421 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Tue, 13 Aug 2024 14:21:51 +0100 Subject: [PATCH] fix: make local peer id optional (#440) `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 dependency 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 | 40 ++++++++++++++++++++++++++++++--- test/compliance.spec.ts | 9 ++++++-- test/index.spec.ts | 19 ++++++++++++---- test/noise.spec.ts | 50 ++++++++++++++++++++++++++++++++--------- 5 files changed, 101 insertions(+), 20 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..b469792 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' @@ -72,7 +72,11 @@ 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> { + 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, { @@ -113,7 +117,11 @@ 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> { + 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, { @@ -226,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] + } + } + } } 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([