Skip to content

Commit

Permalink
fix: make local peer id optional (#440)
Browse files Browse the repository at this point in the history
`[email protected]` 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
`[email protected]` is released.
  • Loading branch information
achingbrain authored Aug 13, 2024
1 parent 0a349b6 commit 5f92b50
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 20 deletions.
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -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
}
Expand Down
40 changes: 37 additions & 3 deletions src/noise.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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 <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise<SecuredConnection<Stream, NoiseExtensions>> {
public async secureOutbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (connection: Stream, remotePeer?: PeerId): Promise<SecuredConnection<Stream, NoiseExtensions>>
public async secureOutbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise<SecuredConnection<Stream, NoiseExtensions>>
public async secureOutbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (...args: any[]): Promise<SecuredConnection<Stream, NoiseExtensions>> {
const { localPeer, connection, remotePeer } = this.parseArgs<Stream>(args)

const wrappedConnection = lpStream(
connection,
{
Expand Down Expand Up @@ -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 <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise<SecuredConnection<Stream, NoiseExtensions>> {
public async secureInbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (connection: Stream, remotePeer?: PeerId): Promise<SecuredConnection<Stream, NoiseExtensions>>
public async secureInbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise<SecuredConnection<Stream, NoiseExtensions>>
public async secureInbound <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (...args: any[]): Promise<SecuredConnection<Stream, NoiseExtensions>> {
const { localPeer, connection, remotePeer } = this.parseArgs<Stream>(args)

const wrappedConnection = lpStream(
connection,
{
Expand Down Expand Up @@ -226,4 +234,30 @@ export class Noise implements INoiseConnection {

return user
}

/**
* Detect call signature in `[email protected]` or `[email protected]` style.
*
* TODO: remove this after `[email protected]` is released and only support the
* newer style
*/
private parseArgs <Stream extends Duplex<AsyncGenerator<Uint8Array | Uint8ArrayList>> = MultiaddrConnection> (args: any[]): { localPeer: PeerId, connection: Stream, remotePeer?: PeerId } {
// if the first argument is a peer id, we're using the [email protected] style
if (isPeerId(args[0])) {
return {
localPeer: args[0],
connection: args[1],
remotePeer: args[2]
}
} else {
// handle upcoming changes in [email protected] 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]
}
}
}
}
9 changes: 7 additions & 2 deletions test/compliance.spec.ts
Original file line number Diff line number Diff line change
@@ -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 () {}
})
Expand Down
19 changes: 15 additions & 4 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -18,8 +19,11 @@ function createCounterSpy (): ReturnType<typeof sinon.spy> {
}

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')
Expand All @@ -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<Uint8Array | Uint8ArrayList>()
const [outbound, inbound] = await Promise.all([
Expand Down
50 changes: 40 additions & 10 deletions test/noise.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uint8Array | Uint8ArrayList>()
const [outbound, inbound] = await Promise.all([
Expand 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<Uint8Array | Uint8ArrayList>()
const [outbound, inbound] = await Promise.all([
Expand 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<Uint8Array | Uint8ArrayList>()
const [outbound, inbound] = await Promise.all([
Expand Down Expand Up @@ -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<Uint8Array | Uint8ArrayList>()
const [outbound, inbound] = await Promise.all([
Expand 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<Uint8Array | Uint8ArrayList>()
const [outbound, inbound] = await Promise.all([
Expand Down

0 comments on commit 5f92b50

Please sign in to comment.