Skip to content

Commit

Permalink
feat: use libp2p component logger
Browse files Browse the repository at this point in the history
Refactors code to use the component logger from libp2p to allow more
flexible logging patterns.

Nb. adds a `NoiseComponents` interface separate from `NoiseInit` that
contains the `Metrics` instance - this is consistent with every other
libp2p module.

Refs: https://github.com/libp2p/js-libp2p/issue/2105
Refs: libp2p/js-libp2p#2198
Refs: https://github.com/libp2p/js-libp2p/issue/378
  • Loading branch information
achingbrain committed Nov 29, 2023
1 parent e4796ed commit bc7c158
Show file tree
Hide file tree
Showing 17 changed files with 126 additions and 104 deletions.
16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,9 @@
"dependencies": {
"@chainsafe/as-chacha20poly1305": "^0.1.0",
"@chainsafe/as-sha256": "^0.4.1",
"@libp2p/crypto": "next",
"@libp2p/interface": "next",
"@libp2p/logger": "next",
"@libp2p/peer-id": "next",
"@libp2p/crypto": "^3.0.0",
"@libp2p/interface": "^1.0.0",
"@libp2p/peer-id": "^4.0.0",
"@noble/ciphers": "^0.4.0",
"@noble/curves": "^1.1.0",
"@noble/hashes": "^1.3.1",
Expand All @@ -88,13 +87,14 @@
"wherearewe": "^2.0.1"
},
"devDependencies": {
"@chainsafe/libp2p-yamux": "^5.0.0",
"@chainsafe/libp2p-yamux": "^6.0.0",
"@libp2p/daemon-client": "^7.0.0",
"@libp2p/daemon-server": "^6.0.0",
"@libp2p/interface-compliance-tests": "next",
"@libp2p/interface-compliance-tests": "^5.0.0",
"@libp2p/interop": "^9.0.0",
"@libp2p/peer-id-factory": "next",
"@libp2p/tcp": "8.0.13-4a474d54d",
"@libp2p/logger": "^4.0.0",
"@libp2p/peer-id-factory": "^3.0.9",
"@libp2p/tcp": "^9.0.0",
"@multiformats/multiaddr": "^12.1.0",
"@types/sinon": "^17.0.1",
"aegir": "^40.0.8",
Expand Down
2 changes: 1 addition & 1 deletion src/@types/handshake-interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { NoiseSession } from './handshake.js'
import type { NoiseExtensions } from '../proto/payload.js'
import type { PeerId } from '@libp2p/interface/peer-id'
import type { PeerId } from '@libp2p/interface'
import type { Uint8ArrayList } from 'uint8arraylist'

export interface IHandshake {
Expand Down
2 changes: 1 addition & 1 deletion src/@types/libp2p.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { bytes32 } from './basic.js'
import type { NoiseExtensions } from '../proto/payload.js'
import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter'
import type { ConnectionEncrypter } from '@libp2p/interface'

export interface KeyPair {
publicKey: bytes32
Expand Down
21 changes: 21 additions & 0 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export class UnexpectedPeerError extends Error {
public code: string

constructor (message = 'Unexpected Peer') {
super(message)
this.code = UnexpectedPeerError.code
}

static readonly code = 'ERR_UNEXPECTED_PEER'
}

export class InvalidCryptoExchangeError extends Error {
public code: string

constructor (message = 'Invalid crypto exchange') {
super(message)
this.code = InvalidCryptoExchangeError.code
}

static readonly code = 'ERR_INVALID_CRYPTO_EXCHANGE'
}
53 changes: 28 additions & 25 deletions src/handshake-xx.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { InvalidCryptoExchangeError, UnexpectedPeerError } from '@libp2p/interface/errors'
import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
import { decode0, decode1, decode2, encode0, encode1, encode2 } from './encoder.js'
import { InvalidCryptoExchangeError, UnexpectedPeerError } from './errors.js'
import { XX } from './handshakes/xx.js'
import {
logger,
logLocalStaticKeys,
logLocalEphemeralKeys,
logRemoteEphemeralKey,
Expand All @@ -20,8 +19,9 @@ import type { IHandshake } from './@types/handshake-interface.js'
import type { CipherState, NoiseSession } from './@types/handshake.js'
import type { KeyPair } from './@types/libp2p.js'
import type { ICryptoInterface } from './crypto.js'
import type { NoiseComponents } from './index.js'
import type { NoiseExtensions } from './proto/payload.js'
import type { PeerId } from '@libp2p/interface/peer-id'
import type { Logger, PeerId } from '@libp2p/interface'
import type { LengthPrefixedStream } from 'it-length-prefixed-stream'
import type { Uint8ArrayList } from 'uint8arraylist'

Expand All @@ -37,8 +37,10 @@ export class XXHandshake implements IHandshake {
protected staticKeypair: KeyPair

private readonly prologue: bytes32
private readonly log: Logger

constructor (
components: NoiseComponents,
isInitiator: boolean,
payload: bytes,
prologue: bytes32,
Expand All @@ -48,6 +50,7 @@ export class XXHandshake implements IHandshake {
remotePeer?: PeerId,
handshake?: XX
) {
this.log = components.logger.forComponent('libp2p:noise:xxhandshake')
this.isInitiator = isInitiator
this.payload = payload
this.prologue = prologue
Expand All @@ -56,45 +59,45 @@ export class XXHandshake implements IHandshake {
if (remotePeer) {
this.remotePeer = remotePeer
}
this.xx = handshake ?? new XX(crypto)
this.xx = handshake ?? new XX(components, crypto)
this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair)
}

// stage 0
public async propose (): Promise<void> {
logLocalStaticKeys(this.session.hs.s)
logLocalStaticKeys(this.session.hs.s, this.log)
if (this.isInitiator) {
logger.trace('Stage 0 - Initiator starting to send first message.')
this.log.trace('Stage 0 - Initiator starting to send first message.')
const messageBuffer = this.xx.sendMessage(this.session, uint8ArrayAlloc(0))
await this.connection.write(encode0(messageBuffer))
logger.trace('Stage 0 - Initiator finished sending first message.')
logLocalEphemeralKeys(this.session.hs.e)
this.log.trace('Stage 0 - Initiator finished sending first message.')
logLocalEphemeralKeys(this.session.hs.e, this.log)
} else {
logger.trace('Stage 0 - Responder waiting to receive first message...')
this.log.trace('Stage 0 - Responder waiting to receive first message...')
const receivedMessageBuffer = decode0((await this.connection.read()).subarray())
const { valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new InvalidCryptoExchangeError('xx handshake stage 0 validation fail')
}
logger.trace('Stage 0 - Responder received first message.')
logRemoteEphemeralKey(this.session.hs.re)
this.log.trace('Stage 0 - Responder received first message.')
logRemoteEphemeralKey(this.session.hs.re, this.log)
}
}

// stage 1
public async exchange (): Promise<void> {
if (this.isInitiator) {
logger.trace('Stage 1 - Initiator waiting to receive first message from responder...')
this.log.trace('Stage 1 - Initiator waiting to receive first message from responder...')
const receivedMessageBuffer = decode1((await this.connection.read()).subarray())
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new InvalidCryptoExchangeError('xx handshake stage 1 validation fail')
}
logger.trace('Stage 1 - Initiator received the message.')
logRemoteEphemeralKey(this.session.hs.re)
logRemoteStaticKey(this.session.hs.rs)
this.log.trace('Stage 1 - Initiator received the message.')
logRemoteEphemeralKey(this.session.hs.re, this.log)
logRemoteStaticKey(this.session.hs.rs, this.log)

logger.trace("Initiator going to check remote's signature...")
this.log.trace("Initiator going to check remote's signature...")
try {
const decodedPayload = decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
Expand All @@ -104,31 +107,31 @@ export class XXHandshake implements IHandshake {
const err = e as Error
throw new UnexpectedPeerError(`Error occurred while verifying signed payload: ${err.message}`)
}
logger.trace('All good with the signature!')
this.log.trace('All good with the signature!')
} else {
logger.trace('Stage 1 - Responder sending out first message with signed payload and static key.')
this.log.trace('Stage 1 - Responder sending out first message with signed payload and static key.')
const messageBuffer = this.xx.sendMessage(this.session, this.payload)
await this.connection.write(encode1(messageBuffer))
logger.trace('Stage 1 - Responder sent the second handshake message with signed payload.')
logLocalEphemeralKeys(this.session.hs.e)
this.log.trace('Stage 1 - Responder sent the second handshake message with signed payload.')
logLocalEphemeralKeys(this.session.hs.e, this.log)
}
}

// stage 2
public async finish (): Promise<void> {
if (this.isInitiator) {
logger.trace('Stage 2 - Initiator sending third handshake message.')
this.log.trace('Stage 2 - Initiator sending third handshake message.')
const messageBuffer = this.xx.sendMessage(this.session, this.payload)
await this.connection.write(encode2(messageBuffer))
logger.trace('Stage 2 - Initiator sent message with signed payload.')
this.log.trace('Stage 2 - Initiator sent message with signed payload.')
} else {
logger.trace('Stage 2 - Responder waiting for third handshake message...')
this.log.trace('Stage 2 - Responder waiting for third handshake message...')
const receivedMessageBuffer = decode2((await this.connection.read()).subarray())
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if (!valid) {
throw new InvalidCryptoExchangeError('xx handshake stage 2 validation fail')
}
logger.trace('Stage 2 - Responder received the message, finished handshake.')
this.log.trace('Stage 2 - Responder received the message, finished handshake.')

try {
const decodedPayload = decodePayload(plaintext)
Expand All @@ -140,7 +143,7 @@ export class XXHandshake implements IHandshake {
throw new UnexpectedPeerError(`Error occurred while verifying signed payload: ${err.message}`)
}
}
logCipherState(this.session)
logCipherState(this.session, this.log)
}

public encrypt (plaintext: Uint8Array | Uint8ArrayList, session: NoiseSession): Uint8Array | Uint8ArrayList {
Expand Down
9 changes: 6 additions & 3 deletions src/handshakes/abstract-handshake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import { Uint8ArrayList } from 'uint8arraylist'
import { fromString as uint8ArrayFromString } from 'uint8arrays'
import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc'
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
import { logger } from '../logger.js'
import { Nonce } from '../nonce.js'
import type { bytes, bytes32 } from '../@types/basic.js'
import type { CipherState, MessageBuffer, SymmetricState } from '../@types/handshake.js'
import type { ICryptoInterface } from '../crypto.js'
import type { NoiseComponents } from '../index.js'
import type { Logger } from '@libp2p/interface'

export interface DecryptedResult {
plaintext: Uint8ArrayList | Uint8Array
Expand All @@ -20,8 +21,10 @@ export interface SplitState {

export abstract class AbstractHandshake {
public crypto: ICryptoInterface
private readonly log: Logger

constructor (crypto: ICryptoInterface) {
constructor (components: NoiseComponents, crypto: ICryptoInterface) {
this.log = components.logger.forComponent('libp2p:noise:abstract-handshake')
this.crypto = crypto
}

Expand Down Expand Up @@ -113,7 +116,7 @@ export abstract class AbstractHandshake {
return derivedU8.subarray(0, 32)
} catch (e) {
const err = e as Error
logger.error(err)
this.log.error('error deriving shared key', err)
return uint8ArrayAlloc(32)
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Noise } from './noise.js'
import type { NoiseInit } from './noise.js'
import type { NoiseExtensions } from './proto/payload.js'
import type { ConnectionEncrypter } from '@libp2p/interface/connection-encrypter'
import type { ComponentLogger, ConnectionEncrypter, Metrics } from '@libp2p/interface'
export type { ICryptoInterface } from './crypto.js'
export { pureJsCrypto } from './crypto/js.js'

export function noise (init: NoiseInit = {}): () => ConnectionEncrypter<NoiseExtensions> {
return () => new Noise(init)
export interface NoiseComponents {
logger: ComponentLogger
metrics?: Metrics
}

export function noise (init: NoiseInit = {}): (components: NoiseComponents) => ConnectionEncrypter<NoiseExtensions> {
return (components: NoiseComponents) => new Noise(components, init)
}
37 changes: 11 additions & 26 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,21 @@
import { type Logger, logger } from '@libp2p/logger'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { DUMP_SESSION_KEYS } from './constants.js'
import type { NoiseSession } from './@types/handshake.js'
import type { KeyPair } from './@types/libp2p.js'
import type { Logger } from '@libp2p/interface'
import type { Uint8ArrayList } from 'uint8arraylist'

const log = logger('libp2p:noise')

export { log as logger }

let keyLogger: Logger
if (DUMP_SESSION_KEYS) {
keyLogger = log
} else {
keyLogger = Object.assign(() => { /* do nothing */ }, {
enabled: false,
trace: () => {},
error: () => {}
})
}

export function logLocalStaticKeys (s: KeyPair): void {
if (!keyLogger.enabled) {
export function logLocalStaticKeys (s: KeyPair, keyLogger: Logger): void {
if (!keyLogger.enabled || !DUMP_SESSION_KEYS) {
return
}

keyLogger(`LOCAL_STATIC_PUBLIC_KEY ${uint8ArrayToString(s.publicKey, 'hex')}`)
keyLogger(`LOCAL_STATIC_PRIVATE_KEY ${uint8ArrayToString(s.privateKey, 'hex')}`)
}

export function logLocalEphemeralKeys (e: KeyPair | undefined): void {
if (!keyLogger.enabled) {
export function logLocalEphemeralKeys (e: KeyPair | undefined, keyLogger: Logger): void {
if (!keyLogger.enabled || !DUMP_SESSION_KEYS) {
return
}

Expand All @@ -42,24 +27,24 @@ export function logLocalEphemeralKeys (e: KeyPair | undefined): void {
}
}

export function logRemoteStaticKey (rs: Uint8Array | Uint8ArrayList): void {
if (!keyLogger.enabled) {
export function logRemoteStaticKey (rs: Uint8Array | Uint8ArrayList, keyLogger: Logger): void {
if (!keyLogger.enabled || !DUMP_SESSION_KEYS) {
return
}

keyLogger(`REMOTE_STATIC_PUBLIC_KEY ${uint8ArrayToString(rs.subarray(), 'hex')}`)
}

export function logRemoteEphemeralKey (re: Uint8Array | Uint8ArrayList): void {
if (!keyLogger.enabled) {
export function logRemoteEphemeralKey (re: Uint8Array | Uint8ArrayList, keyLogger: Logger): void {
if (!keyLogger.enabled || !DUMP_SESSION_KEYS) {
return
}

keyLogger(`REMOTE_EPHEMERAL_PUBLIC_KEY ${uint8ArrayToString(re.subarray(), 'hex')}`)
}

export function logCipherState (session: NoiseSession): void {
if (!keyLogger.enabled) {
export function logCipherState (session: NoiseSession, keyLogger: Logger): void {
if (!keyLogger.enabled || !DUMP_SESSION_KEYS) {
return
}

Expand Down
2 changes: 1 addition & 1 deletion src/metrics.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Counter, Metrics } from '@libp2p/interface/metrics'
import type { Counter, Metrics } from '@libp2p/interface'

export type MetricsRegistry = Record<string, Counter>

Expand Down
15 changes: 8 additions & 7 deletions src/noise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ import type { bytes } from './@types/basic.js'
import type { IHandshake } from './@types/handshake-interface.js'
import type { INoiseConnection, KeyPair } from './@types/libp2p.js'
import type { ICryptoInterface } from './crypto.js'
import type { NoiseComponents } from './index.js'
import type { NoiseExtensions } from './proto/payload.js'
import type { MultiaddrConnection } from '@libp2p/interface/connection'
import type { SecuredConnection } from '@libp2p/interface/connection-encrypter'
import type { Metrics } from '@libp2p/interface/metrics'
import type { PeerId } from '@libp2p/interface/peer-id'
import type { MultiaddrConnection, SecuredConnection, PeerId } from '@libp2p/interface'
import type { Duplex } from 'it-stream-types'
import type { Uint8ArrayList } from 'uint8arraylist'

Expand All @@ -37,7 +35,6 @@ export interface NoiseInit {
extensions?: NoiseExtensions
crypto?: ICryptoInterface
prologueBytes?: Uint8Array
metrics?: Metrics
}

export class Noise implements INoiseConnection {
Expand All @@ -48,10 +45,13 @@ export class Noise implements INoiseConnection {
private readonly staticKeys: KeyPair
private readonly extensions?: NoiseExtensions
private readonly metrics?: MetricsRegistry
private readonly components: NoiseComponents

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

this.components = components
this.crypto = crypto ?? defaultCrypto
this.extensions = extensions
this.metrics = metrics ? registerMetrics(metrics) : undefined
Expand Down Expand Up @@ -154,6 +154,7 @@ export class Noise implements INoiseConnection {
): Promise<XXHandshake> {
const { isInitiator, remotePeer, connection } = params
const handshake = new XXHandshake(
this.components,
isInitiator,
payload,
this.prologue,
Expand Down
Loading

0 comments on commit bc7c158

Please sign in to comment.