From 8a5321bf280bdd7d650112ba82503ea599782d59 Mon Sep 17 00:00:00 2001 From: Alex Potsides Date: Wed, 11 Sep 2024 14:45:07 +0100 Subject: [PATCH] feat!: update to libp2p@2.x.x (#444) - Incorporates API changes from the upcoming `libp2p@2.x.x` release. - Removal of libp2p 1.0 interfaces workarounds - Alignment of CI w other libp2p packages - More documentation BREAKING CHANGE: Can only be used with `libp2p@2.x.x` or later --- .github/dependabot.yml | 6 +- .github/workflows/js-test-and-release.yml | 187 +++----------------- .github/workflows/semantic-pr.yaml | 24 --- .github/workflows/semantic-pull-request.yml | 12 ++ .github/workflows/stale.yml | 13 ++ .gitignore | 21 +-- README.md | 64 +++++-- benchmarks/benchmark.js | 40 +++-- package.json | 126 +++++++++++-- src/errors.ts | 11 -- src/index.ts | 41 ++++- src/noise.ts | 99 +++-------- src/types.ts | 4 +- src/utils.ts | 20 +-- test/compliance.spec.ts | 12 +- test/fixtures/peer.ts | 20 ++- test/index.spec.ts | 31 +++- test/interop.ts | 14 +- test/noise.spec.ts | 77 ++++---- test/performHandshake.spec.ts | 37 ++-- test/utils.ts | 14 +- typedoc.json | 5 + 22 files changed, 433 insertions(+), 445 deletions(-) delete mode 100644 .github/workflows/semantic-pr.yaml create mode 100644 .github/workflows/semantic-pull-request.yml create mode 100644 .github/workflows/stale.yml create mode 100644 typedoc.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 006b5de..d401a77 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ updates: schedule: interval: daily time: "10:00" - open-pull-requests-limit: 10 + open-pull-requests-limit: 20 commit-message: - prefix: "fix: " - prefix-development: "chore: " + prefix: "deps" + prefix-development: "deps(dev)" diff --git a/.github/workflows/js-test-and-release.yml b/.github/workflows/js-test-and-release.yml index b34e510..d31e058 100644 --- a/.github/workflows/js-test-and-release.yml +++ b/.github/workflows/js-test-and-release.yml @@ -1,175 +1,28 @@ name: test & maybe release + on: push: branches: - - master # with #262 - ${{{ github.default_branch }}} + - master pull_request: - branches: - - master # with #262 - ${{{ github.default_branch }}} - -jobs: - - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run --if-present lint - - run: npm run --if-present dep-check - - test-node: - needs: check - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [windows-latest, ubuntu-latest, macos-latest] - node: [18,20] - fail-fast: true - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run --if-present test:node - - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 - with: - directory: ./.nyc_output - flags: node - - test-chrome: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run --if-present test:chrome - - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 - with: - directory: ./.nyc_output - flags: chrome - - test-chrome-webworker: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run --if-present test:chrome-webworker - - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 - with: - directory: ./.nyc_output - flags: chrome-webworker + workflow_dispatch: - test-firefox: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run --if-present test:firefox - - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 - with: - directory: ./.nyc_output - flags: firefox - - test-firefox-webworker: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run --if-present test:firefox-webworker - - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 - with: - directory: ./.nyc_output - flags: firefox-webworker - - test-electron-main: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npx xvfb-maybe npm run --if-present test:electron-main - - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 - with: - directory: ./.nyc_output - flags: electron-main - - test-electron-renderer: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npx xvfb-maybe npm run --if-present test:electron-renderer - - uses: codecov/codecov-action@f32b3a3741e1053eb607407145bc9619351dc93b # v2.1.0 - with: - directory: ./.nyc_output - flags: electron-renderer - - test-interop: - needs: check - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - uses: ipfs/aegir/actions/cache-node-modules@master - - run: npm run test:interop -- --bail - - release: - needs: [test-node, test-chrome, test-chrome-webworker, test-firefox, test-firefox-webworker, test-electron-main, test-electron-renderer, test-interop] - runs-on: ubuntu-latest - if: github.event_name == 'push' && github.ref == 'refs/heads/master' # with #262 - 'refs/heads/${{{ github.default_branch }}}' - steps: - - uses: google-github-actions/release-please-action@v3 - id: release - with: - release-type: node - package-name: release-please-action - changelog-types: '[{"type":"feat","section":"Features","hidden":false},{"type":"fix","section":"Bug Fixes","hidden":false},{"type":"chore","section":"Miscellaneous","hidden":true}]' - - - uses: actions/checkout@v4 - if: ${{ steps.release.outputs.release_created }} - with: - fetch-depth: 0 - - - uses: actions/setup-node@v4 - if: ${{ steps.release.outputs.release_created }} - with: - node-version: 18 - registry-url: 'https://registry.npmjs.org' - - - uses: ipfs/aegir/actions/cache-node-modules@master - if: ${{ steps.release.outputs.release_created }} - - - run: npm publish - if: ${{ steps.release.outputs.release_created }} - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +permissions: + contents: write + id-token: write + packages: write + pull-requests: write +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event_name == 'push' && github.sha || github.ref }} + cancel-in-progress: true +jobs: + js-test-and-release: + uses: ipdxco/unified-github-workflows/.github/workflows/js-test-and-release.yml@v1.0 + secrets: + DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + UCI_GITHUB_TOKEN: ${{ secrets.UCI_GITHUB_TOKEN }} + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/semantic-pr.yaml b/.github/workflows/semantic-pr.yaml deleted file mode 100644 index c97f3e2..0000000 --- a/.github/workflows/semantic-pr.yaml +++ /dev/null @@ -1,24 +0,0 @@ -name: "Semantic PR" - -on: - pull_request_target: - types: - - opened - - edited - - synchronize - -jobs: - main: - name: Validate PR title - runs-on: ubuntu-latest - steps: - - uses: amannn/action-semantic-pull-request@v5 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - requireScope: false - types: | - fix - feat - chore - docs diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml new file mode 100644 index 0000000..bd00f09 --- /dev/null +++ b/.github/workflows/semantic-pull-request.yml @@ -0,0 +1,12 @@ +name: Semantic PR + +on: + pull_request_target: + types: + - opened + - edited + - synchronize + +jobs: + main: + uses: pl-strflt/.github/.github/workflows/reusable-semantic-pull-request.yml@v0.3 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..16d65d7 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,13 @@ +name: Close and mark stale issue + +on: + schedule: + - cron: '0 0 * * *' + +permissions: + issues: write + pull-requests: write + +jobs: + stale: + uses: pl-strflt/.github/.github/workflows/reusable-stale-issue.yml@v0.3 diff --git a/.gitignore b/.gitignore index 0697132..7ad9e67 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,9 @@ -bundle -node_modules/ -.idea -.env -.nyc_output -lib +node_modules +build dist -docs - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* -coverage/* +.docs +.coverage +node_modules package-lock.json yarn.lock .vscode diff --git a/README.md b/README.md index 1acc4e3..177b73b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# js-libp2p-noise +# @chainsafe/libp2p-noise ![npm](https://img.shields.io/npm/v/@chainsafe/libp2p-noise) [![](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-noise/js-test-and-release.yml?branch=master)](https://github.com/ChainSafe/js-libp2p-noise/actions) @@ -10,14 +10,28 @@ ![](https://img.shields.io/badge/Node.js-%3E%3D16.0.0-orange.svg?style=flat-square) ![](https://img.shields.io/badge/browsers-last%202%20versions%2C%20not%20ie%20%3C%3D11-orange) [![Twitter](https://img.shields.io/twitter/follow/ChainSafeth.svg?label=Twitter)](https://twitter.com/ChainSafeth) -[![Discord](https://img.shields.io/discord/593655374469660673.svg?label=Discord&logo=discord)](https://discord.gg/Q6A3YA2) +[![Discord](https://img.shields.io/discord/593655374469660673.svg?label=Discord\&logo=discord)](https://discord.gg/Q6A3YA2) > Noise libp2p handshake for js-libp2p -This repository contains TypeScript implementation of noise protocol, an encryption protocol used in libp2p. +# About + + + +This repository contains TypeScript implementation of noise protocol, an encryption protocol used in libp2p. ## Usage @@ -25,7 +39,7 @@ Install with `yarn add @chainsafe/libp2p-noise` or `npm i @chainsafe/libp2p-nois Example of using default noise configuration and passing it to the libp2p config: -```js +```ts import {createLibp2p} from "libp2p" import {noise} from "@chainsafe/libp2p-noise" @@ -34,32 +48,48 @@ import {noise} from "@chainsafe/libp2p-noise" const n = noise({ staticNoiseKey }); const libp2p = await createLibp2p({ - connectionEncryption: [noise()], + connectionEncrypters: [noise()], //... other options }) ``` -See the [NoiseInit](https://github.com/ChainSafe/js-libp2p-noise/blob/master/src/noise.ts#L29-L38) interface for noise configuration options. +See the [NoiseInit](https://github.com/ChainSafe/js-libp2p-noise/blob/master/src/noise.ts#L22-L30) interface for noise configuration options. ## API -This module exposes an implementation of the [ConnectionEncrypter](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.connection_encrypter.ConnectionEncrypter.html) interface. +This module exposes an implementation of the [ConnectionEncrypter](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.ConnectionEncrypter.html) interface. ## Bring your own crypto -You can provide a custom crypto implementation (instead of the default, based on [@noble](https://paulmillr.com/noble/)) by adding a `crypto` field to the init argument passed to the `Noise` factory. +You can provide a custom crypto implementation (instead of the default, based on [@noble](https://paulmillr.com/noble/)) by adding a `crypto` field to the init argument passed to the `Noise` factory. -The implementation must conform to the `ICryptoInterface`, defined in https://github.com/ChainSafe/js-libp2p-noise/blob/master/src/crypto.ts +The implementation must conform to the `ICryptoInterface`, defined in -## Contribute +# Install -Feel free to join in. All welcome. Open an issue! +```console +$ npm i @chainsafe/libp2p-noise +``` + +## Browser ` +``` + +# API Docs -[![](https://cdn.rawgit.com/jbenet/contribute-ipfs-gif/master/img/contribute.gif)](https://github.com/ipfs/community/blob/master/contributing.md) +- -## License +# License Licensed under either of - * Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / http://www.apache.org/licenses/LICENSE-2.0) - * MIT ([LICENSE-MIT](LICENSE-MIT) / http://opensource.org/licenses/MIT) +- Apache 2.0, ([LICENSE-APACHE](https://github.com/ChainSafe/js-libp2p-noise/LICENSE-APACHE) / ) +- MIT ([LICENSE-MIT](https://github.com/ChainSafe/js-libp2p-noise/LICENSE-MIT) / ) + +# Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/benchmarks/benchmark.js b/benchmarks/benchmark.js index a38d79e..e7c5354 100644 --- a/benchmarks/benchmark.js +++ b/benchmarks/benchmark.js @@ -3,30 +3,44 @@ import { noise } from '../dist/src/index.js' import benchmark from 'benchmark' import { duplexPair } from 'it-pair/duplex' -import { createFromJSON } from '@libp2p/peer-id-factory' +import { base64pad } from 'multiformats/bases/base64' +import { defaultLogger } from '@libp2p/logger' +import { privateKeyFromProtobuf } from '@libp2p/crypto/keys' +import { peerIdFromPublicKey } from '@libp2p/peer-id' const bench = async function () { console.log('Initializing handshake benchmark') - const initiator = noise()() - const initiatorPeer = await createFromJSON({ - id: '12D3KooWH45PiqBjfnEfDfCD6TqJrpqTBJvQDwGHvjGpaWwms46D', - privKey: 'CAESYBtKXrMwawAARmLScynQUuSwi/gGSkwqDPxi15N3dqDHa4T4iWupkMe5oYGwGH3Hyfvd/QcgSTqg71oYZJadJ6prhPiJa6mQx7mhgbAYfcfJ+939ByBJOqDvWhhklp0nqg==', - pubKey: 'CAESIGuE+IlrqZDHuaGBsBh9x8n73f0HIEk6oO9aGGSWnSeq' + + const initiatorPeer = 'CAESYBtKXrMwawAARmLScynQUuSwi/gGSkwqDPxi15N3dqDHa4T4iWupkMe5oYGwGH3Hyfvd/QcgSTqg71oYZJadJ6prhPiJa6mQx7mhgbAYfcfJ+939ByBJOqDvWhhklp0nqg==' + const initiatorPrivateKey = privateKeyFromProtobuf(base64pad.decode(`M${initiatorPeer}`)) + const initiatorPeerId = peerIdFromPublicKey(initiatorPrivateKey.publicKey) + const initiator = noise()({ + privateKey: initiatorPrivateKey, + peerId: initiatorPeerId, + logger: defaultLogger() }) - const responder = noise()() - const responderPeer = await createFromJSON({ - id: '12D3KooWP63uzL78BRMpkQ7augMdNi1h3VBrVWZucKjyhzGVaSi1', - privKey: 'CAESYPxO3SHyfc2578hDmfkGGBY255JjiLuVavJWy+9ivlpsxSyVKf36ipyRGL6szGzHuFs5ceEuuGVrPMg/rW2Ch1bFLJUp/fqKnJEYvqzMbMe4Wzlx4S64ZWs8yD+tbYKHVg==', - pubKey: 'CAESIMUslSn9+oqckRi+rMxsx7hbOXHhLrhlazzIP61tgodW' + + const responderPeer = 'CAESYPxO3SHyfc2578hDmfkGGBY255JjiLuVavJWy+9ivlpsxSyVKf36ipyRGL6szGzHuFs5ceEuuGVrPMg/rW2Ch1bFLJUp/fqKnJEYvqzMbMe4Wzlx4S64ZWs8yD+tbYKHVg==' + const responderPrivateKey = privateKeyFromProtobuf(base64pad.decode(`M${responderPeer}`)) + const responderPeerId = peerIdFromPublicKey(responderPrivateKey.publicKey) + const responder = noise()({ + privateKey: responderPrivateKey, + peerId: responderPeerId, + logger: defaultLogger() }) + console.log('Init complete, running benchmark') const bench = new benchmark('handshake', { defer: true, fn: async function (deferred) { const [inboundConnection, outboundConnection] = duplexPair() await Promise.all([ - initiator.secureOutbound(initiatorPeer, outboundConnection, responderPeer), - responder.secureInbound(responderPeer, inboundConnection, initiatorPeer) + initiator.secureOutbound(outboundConnection, { + remotePeer: responderPeerId + }), + responder.secureInbound(inboundConnection, { + remotePeer: initiatorPeerId + }) ]) deferred.resolve() } diff --git a/package.json b/package.json index 838ce81..03abe90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "@chainsafe/libp2p-noise", "version": "15.1.2", + "description": "Noise libp2p handshake for js-libp2p", "author": "ChainSafe ", "license": "Apache-2.0 OR MIT", "homepage": "https://github.com/ChainSafe/js-libp2p-noise#readme", @@ -11,15 +12,15 @@ "bugs": { "url": "https://github.com/ChainSafe/js-libp2p-noise/issues" }, + "publishConfig": { + "access": "public", + "provenance": true + }, "keywords": [ "crypto", "libp2p", "noise" ], - "engines": { - "node": ">=16.0.0", - "npm": ">=7.0.0" - }, "type": "module", "types": "./dist/src/index.d.ts", "files": [ @@ -37,6 +38,7 @@ "eslintConfig": { "extends": "ipfs", "parserOptions": { + "project": true, "sourceType": "module" }, "rules": { @@ -50,6 +52,91 @@ "test/fixtures/node-globals.js" ] }, + "release": { + "branches": [ + "master" + ], + "plugins": [ + [ + "@semantic-release/commit-analyzer", + { + "preset": "conventionalcommits", + "releaseRules": [ + { + "breaking": true, + "release": "major" + }, + { + "revert": true, + "release": "patch" + }, + { + "type": "feat", + "release": "minor" + }, + { + "type": "fix", + "release": "patch" + }, + { + "type": "docs", + "release": "patch" + }, + { + "type": "test", + "release": "patch" + }, + { + "type": "deps", + "release": "patch" + }, + { + "scope": "no-release", + "release": false + } + ] + } + ], + [ + "@semantic-release/release-notes-generator", + { + "preset": "conventionalcommits", + "presetConfig": { + "types": [ + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Bug Fixes" + }, + { + "type": "chore", + "section": "Trivial Changes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "deps", + "section": "Dependencies" + }, + { + "type": "test", + "section": "Tests" + } + ] + } + } + ], + "@semantic-release/changelog", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/git" + ] + }, "scripts": { "bench": "node benchmarks/benchmark.js", "clean": "aegir clean", @@ -64,14 +151,15 @@ "test:interop": "aegir test -t node -f dist/test/interop.js", "docs": "aegir docs", "proto:gen": "protons ./src/proto/payload.proto", - "prepublish": "npm run build" + "prepublish": "npm run build", + "release": "aegir release" }, "dependencies": { "@chainsafe/as-chacha20poly1305": "^0.1.0", "@chainsafe/as-sha256": "^0.4.1", - "@libp2p/crypto": "^4.0.0", - "@libp2p/interface": "^1.5.0", - "@libp2p/peer-id": "^4.0.0", + "@libp2p/crypto": "^5.0.0", + "@libp2p/interface": "^2.0.0", + "@libp2p/peer-id": "^5.0.0", "@noble/ciphers": "^0.6.0", "@noble/curves": "^1.1.0", "@noble/hashes": "^1.3.1", @@ -80,20 +168,19 @@ "it-pair": "^2.0.6", "it-pipe": "^3.0.1", "it-stream-types": "^2.0.1", - "protons-runtime": "^5.0.0", + "protons-runtime": "^5.5.0", "uint8arraylist": "^2.4.3", "uint8arrays": "^5.0.0", "wherearewe": "^2.0.1" }, "devDependencies": { - "@chainsafe/libp2p-yamux": "^6.0.1", - "@libp2p/daemon-client": "^8.0.1", - "@libp2p/daemon-server": "^7.0.1", - "@libp2p/interface-compliance-tests": "^5.0.5", - "@libp2p/interop": "^12.1.0", - "@libp2p/logger": "^4.0.0", - "@libp2p/peer-id-factory": "^4.0.4", - "@libp2p/tcp": "^9.0.0", + "@chainsafe/libp2p-yamux": "^7.0.0", + "@libp2p/daemon-client": "^9.0.0", + "@libp2p/daemon-server": "^8.0.0", + "@libp2p/interface-compliance-tests": "^6.0.0", + "@libp2p/interop": "^13.0.0", + "@libp2p/logger": "^5.0.0", + "@libp2p/tcp": "^10.0.0", "@multiformats/multiaddr": "^12.1.0", "@types/sinon": "^17.0.1", "aegir": "^44.1.1", @@ -102,10 +189,11 @@ "go-libp2p": "^1.0.3", "iso-random-stream": "^2.0.2", "it-byte-stream": "^1.0.0", - "libp2p": "^1.0.8", + "libp2p": "^2.0.0", "mkdirp": "^3.0.0", + "multiformats": "^13.2.2", "p-defer": "^4.0.0", - "protons": "^7.0.0", + "protons": "^7.6.0", "sinon": "^18.0.0" }, "browser": { diff --git a/src/errors.ts b/src/errors.ts index 2e1aa43..6d526da 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -1,14 +1,3 @@ -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 diff --git a/src/index.ts b/src/index.ts index 60725b4..35a6ff0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,51 @@ +/** + * @packageDocumentation + * + * This repository contains TypeScript implementation of noise protocol, an encryption protocol used in libp2p. + * + * ## Usage + * + * Install with `yarn add @chainsafe/libp2p-noise` or `npm i @chainsafe/libp2p-noise`. + * + * Example of using default noise configuration and passing it to the libp2p config: + * + * ```ts + * import {createLibp2p} from "libp2p" + * import {noise} from "@chainsafe/libp2p-noise" + * + * //custom noise configuration, pass it instead of `noise()` + * //x25519 private key + * const n = noise({ staticNoiseKey }); + * + * const libp2p = await createLibp2p({ + * connectionEncrypters: [noise()], + * //... other options + * }) + * ``` + * + * See the [NoiseInit](https://github.com/ChainSafe/js-libp2p-noise/blob/master/src/noise.ts#L22-L30) interface for noise configuration options. + * + * ## API + * + * This module exposes an implementation of the [ConnectionEncrypter](https://libp2p.github.io/js-libp2p/interfaces/_libp2p_interface.ConnectionEncrypter.html) interface. + * + * ## Bring your own crypto + * + * You can provide a custom crypto implementation (instead of the default, based on [@noble](https://paulmillr.com/noble/)) by adding a `crypto` field to the init argument passed to the `Noise` factory. + * + * The implementation must conform to the `ICryptoInterface`, defined in + */ + import { Noise } from './noise.js' import type { NoiseInit } from './noise.js' import type { NoiseExtensions } from './proto/payload.js' -import type { ComponentLogger, ConnectionEncrypter, Metrics, PeerId } from '@libp2p/interface' +import type { ComponentLogger, ConnectionEncrypter, Metrics, PeerId, PrivateKey } from '@libp2p/interface' export type { ICryptoInterface } from './crypto.js' export { pureJsCrypto } from './crypto/js.js' export interface NoiseComponents { peerId: PeerId + privateKey: PrivateKey logger: ComponentLogger metrics?: Metrics } diff --git a/src/noise.ts b/src/noise.ts index 640b4a2..5fddd9b 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -1,6 +1,6 @@ -import { unmarshalPrivateKey } from '@libp2p/crypto/keys' -import { type MultiaddrConnection, type SecuredConnection, type PeerId, CodeError, type PrivateKey, serviceCapabilities, isPeerId, type AbortOptions } from '@libp2p/interface' -import { peerIdFromKeys } from '@libp2p/peer-id' +import { publicKeyFromProtobuf } from '@libp2p/crypto/keys' +import { serviceCapabilities } from '@libp2p/interface' +import { peerIdFromPublicKey } from '@libp2p/peer-id' import { decode } from 'it-length-prefixed' import { lpStream, type LengthPrefixedStream } from 'it-length-prefixed-stream' import { duplexPair } from 'it-pair/duplex' @@ -16,6 +16,7 @@ import { decryptStream, encryptStream } from './streaming.js' import type { NoiseComponents } from './index.js' import type { NoiseExtensions } from './proto/payload.js' import type { HandshakeResult, ICrypto, INoiseConnection, KeyPair } from './types.js' +import type { MultiaddrConnection, SecuredConnection, PeerId, PrivateKey, PublicKey, AbortOptions } from '@libp2p/interface' import type { Duplex } from 'it-stream-types' import type { Uint8ArrayList } from 'uint8arraylist' @@ -68,15 +69,12 @@ export class Noise implements INoiseConnection { /** * Encrypt outgoing data to the remote party (handshake as initiator) * - * @param localPeer - PeerId of the receiving peer * @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. + * @param options + * @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer + * @param options.signal - Used to abort the operation */ - public async secureOutbound > = MultiaddrConnection> (connection: Stream, options?: { remotePeer?: PeerId, signal?: AbortSignal }): Promise> - public async secureOutbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> - public async secureOutbound > = MultiaddrConnection> (...args: any[]): Promise> { - const { localPeer, connection, remotePeer, signal } = this.parseArgs(args) - + public async secureOutbound > = MultiaddrConnection> (connection: Stream, options?: { remotePeer?: PeerId, signal?: AbortSignal }): Promise> { const wrappedConnection = lpStream( connection, { @@ -86,44 +84,35 @@ export class Noise implements INoiseConnection { } ) - if (!localPeer.privateKey) { - throw new CodeError('local peerId does not contain private key', 'ERR_NO_PRIVATE_KEY') - } - const privateKey = await unmarshalPrivateKey(localPeer.privateKey) - - const remoteIdentityKey = remotePeer?.publicKey - const handshake = await this.performHandshakeInitiator( wrappedConnection, - privateKey, - remoteIdentityKey, { - signal - } + this.components.privateKey, + options?.remotePeer?.publicKey, + options ) const conn = await this.createSecureConnection(wrappedConnection, handshake) connection.source = conn.source connection.sink = conn.sink + const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey) + return { conn: connection, remoteExtensions: handshake.payload.extensions, - remotePeer: await peerIdFromKeys(handshake.payload.identityKey) + remotePeer: peerIdFromPublicKey(publicKey) } } /** * Decrypt incoming data (handshake as responder). * - * @param localPeer - PeerId of the receiving peer. - * @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. + * @param connection - streaming iterable duplex that will be encrypted + * @param options + * @param options.remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer + * @param options.signal - Used to abort the operation */ - public async secureInbound > = MultiaddrConnection> (connection: Stream, options?: { remotePeer?: PeerId, signal?: AbortSignal }): Promise> - public async secureInbound > = MultiaddrConnection> (localPeer: PeerId, connection: Stream, remotePeer?: PeerId): Promise> - public async secureInbound > = MultiaddrConnection> (...args: any[]): Promise> { - const { localPeer, connection, remotePeer, signal } = this.parseArgs(args) - + public async secureInbound > = MultiaddrConnection> (connection: Stream, options?: { remotePeer?: PeerId, signal?: AbortSignal }): Promise> { const wrappedConnection = lpStream( connection, { @@ -133,29 +122,23 @@ export class Noise implements INoiseConnection { } ) - if (!localPeer.privateKey) { - throw new CodeError('local peerId does not contain private key', 'ERR_NO_PRIVATE_KEY') - } - const privateKey = await unmarshalPrivateKey(localPeer.privateKey) - - const remoteIdentityKey = remotePeer?.publicKey - const handshake = await this.performHandshakeResponder( wrappedConnection, - privateKey, - remoteIdentityKey, { - signal - } + this.components.privateKey, + options?.remotePeer?.publicKey, + options ) const conn = await this.createSecureConnection(wrappedConnection, handshake) connection.source = conn.source connection.sink = conn.sink + const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey) + return { conn: connection, remoteExtensions: handshake.payload.extensions, - remotePeer: await peerIdFromKeys(handshake.payload.identityKey) + remotePeer: peerIdFromPublicKey(publicKey) } } @@ -166,7 +149,7 @@ export class Noise implements INoiseConnection { connection: LengthPrefixedStream, // TODO: pass private key in noise constructor via Components privateKey: PrivateKey, - remoteIdentityKey?: Uint8Array | Uint8ArrayList, + remoteIdentityKey?: PublicKey, options?: AbortOptions ): Promise { let result: HandshakeResult @@ -195,9 +178,8 @@ export class Noise implements INoiseConnection { */ private async performHandshakeResponder ( connection: LengthPrefixedStream, - // TODO: pass private key in noise constructor via Components privateKey: PrivateKey, - remoteIdentityKey?: Uint8Array | Uint8ArrayList, + remoteIdentityKey?: PublicKey, options?: AbortOptions ): Promise { let result: HandshakeResult @@ -240,31 +222,4 @@ 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, signal?: AbortSignal } { - // 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]?.remotePeer, - signal: args[1]?.signal - } - } - } } diff --git a/src/types.ts b/src/types.ts index 37feb73..cf2d650 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,6 @@ import type { Nonce } from './nonce' import type { NoiseExtensions, NoiseHandshakePayload } from './proto/payload' -import type { ConnectionEncrypter, Logger, PrivateKey } from '@libp2p/interface' +import type { ConnectionEncrypter, Logger, PrivateKey, PublicKey } from '@libp2p/interface' import type { LengthPrefixedStream } from 'it-length-prefixed-stream' import type { Uint8ArrayList } from 'uint8arraylist' @@ -22,7 +22,7 @@ export interface HandshakeParams { prologue: Uint8Array /** static keypair */ s: KeyPair - remoteIdentityKey?: Uint8Array | Uint8ArrayList + remoteIdentityKey?: PublicKey extensions?: NoiseExtensions } diff --git a/src/utils.ts b/src/utils.ts index 4c75fb4..528daf4 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,11 +1,9 @@ -import { unmarshalPublicKey } from '@libp2p/crypto/keys' +import { publicKeyFromProtobuf, publicKeyToProtobuf } from '@libp2p/crypto/keys' +import { UnexpectedPeerError, type PrivateKey, type PublicKey } from '@libp2p/interface' import { type Uint8ArrayList } from 'uint8arraylist' -import { equals, toString } from 'uint8arrays' import { concat as uint8ArrayConcat } from 'uint8arrays/concat' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { UnexpectedPeerError } from './errors.js' import { type NoiseExtensions, NoiseHandshakePayload } from './proto/payload.js' -import type { PrivateKey } from '@libp2p/interface' export async function createHandshakePayload ( privateKey: PrivateKey, @@ -15,7 +13,7 @@ export async function createHandshakePayload ( const identitySig = await privateKey.sign(getSignaturePayload(staticPublicKey)) return NoiseHandshakePayload.encode({ - identityKey: privateKey.public.bytes, + identityKey: publicKeyToProtobuf(privateKey.publicKey), identitySig, extensions }) @@ -24,15 +22,14 @@ export async function createHandshakePayload ( export async function decodeHandshakePayload ( payloadBytes: Uint8Array | Uint8ArrayList, remoteStaticKey?: Uint8Array | Uint8ArrayList, - remoteIdentityKey?: Uint8Array | Uint8ArrayList + remoteIdentityKey?: PublicKey ): Promise { try { const payload = NoiseHandshakePayload.decode(payloadBytes) - if (remoteIdentityKey) { - const remoteIdentityKeyBytes = remoteIdentityKey.subarray() - if (!equals(remoteIdentityKeyBytes, payload.identityKey)) { - throw new Error(`Payload identity key ${toString(payload.identityKey, 'hex')} does not match expected remote identity key ${toString(remoteIdentityKeyBytes, 'hex')}`) - } + const publicKey = publicKeyFromProtobuf(payload.identityKey) + + if (remoteIdentityKey?.equals(publicKey) === false) { + throw new Error(`Payload identity key ${publicKey} does not match expected remote identity key ${remoteIdentityKey}`) } if (!remoteStaticKey) { @@ -40,7 +37,6 @@ export async function decodeHandshakePayload ( } const signaturePayload = getSignaturePayload(remoteStaticKey) - const publicKey = unmarshalPublicKey(payload.identityKey) if (!(await publicKey.verify(signaturePayload, payload.identitySig))) { throw new Error('Invalid payload signature') diff --git a/test/compliance.spec.ts b/test/compliance.spec.ts index a7aa4eb..27361aa 100644 --- a/test/compliance.spec.ts +++ b/test/compliance.spec.ts @@ -1,14 +1,18 @@ +import { generateKeyPair } from '@libp2p/crypto/keys' import tests from '@libp2p/interface-compliance-tests/connection-encryption' import { defaultLogger } from '@libp2p/logger' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { Noise } from '../src/noise.js' -import type { PeerId } from '@libp2p/interface' describe('spec compliance tests', function () { tests({ - async setup (opts: { peerId?: PeerId }) { + async setup (opts) { + const privateKey = opts?.privateKey ?? await generateKeyPair('Ed25519') + const peerId = peerIdFromPrivateKey(privateKey) + return new Noise({ - peerId: opts?.peerId ?? await createEd25519PeerId(), + privateKey, + peerId, logger: defaultLogger() }) }, diff --git a/test/fixtures/peer.ts b/test/fixtures/peer.ts index 701c92f..a044c31 100644 --- a/test/fixtures/peer.ts +++ b/test/fixtures/peer.ts @@ -1,5 +1,7 @@ -import { createEd25519PeerId, createFromJSON } from '@libp2p/peer-id-factory' -import type { PeerId } from '@libp2p/interface' +import { generateKeyPair, privateKeyFromProtobuf } from '@libp2p/crypto/keys' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' +import { base64pad } from 'multiformats/bases/base64' +import type { PeerId, PrivateKey } from '@libp2p/interface' // ed25519 keys const peers = [{ @@ -20,16 +22,24 @@ const peers = [{ pubKey: 'CAESIMbnikZaPciAMZhUXqDRVCs7VFOBtmlIk26g0GgOotDA' }] -export async function createPeerIdsFromFixtures (length: number): Promise { +export async function createPeerIdsFromFixtures (length: number): Promise> { return Promise.all( - Array.from({ length }).map(async (_, i) => createFromJSON(peers[i])) + Array.from({ length }).map(async (_, i) => { + const privateKey = privateKeyFromProtobuf(base64pad.decode(`M${peers[i].privKey}`)) + + return { + privateKey, + peerId: peerIdFromPrivateKey(privateKey) + } + }) ) } export async function createPeerIds (length: number): Promise { const peerIds: PeerId[] = [] for (let i = 0; i < length; i++) { - const id = await createEd25519PeerId() + const privateKey = await generateKeyPair('Ed25519') + const id = peerIdFromPrivateKey(privateKey) peerIds.push(id) } diff --git a/test/index.spec.ts b/test/index.spec.ts index 777eccc..d160e1d 100644 --- a/test/index.spec.ts +++ b/test/index.spec.ts @@ -1,5 +1,6 @@ +import { generateKeyPair } from '@libp2p/crypto/keys' import { defaultLogger } from '@libp2p/logger' -import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { peerIdFromPrivateKey } from '@libp2p/peer-id' import { expect } from 'aegir/chai' import { lpStream } from 'it-length-prefixed-stream' import { duplexPair } from 'it-pair/duplex' @@ -7,7 +8,6 @@ import sinon from 'sinon' import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' import { noise } from '../src/index.js' import { Noise } from '../src/noise.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' import type { Metrics } from '@libp2p/interface' import type { Uint8ArrayList } from 'uint8arraylist' @@ -20,8 +20,12 @@ function createCounterSpy (): ReturnType { describe('Index', () => { it('should expose class with tag and required functions', async () => { + const privateKey = await generateKeyPair('Ed25519') + const peerId = peerIdFromPrivateKey(privateKey) + const noiseInstance = noise()({ - peerId: await createEd25519PeerId(), + privateKey, + peerId, logger: defaultLogger() }) expect(noiseInstance.protocol).to.equal('/noise') @@ -30,7 +34,6 @@ describe('Index', () => { }) it('should collect metrics', async () => { - const [localPeer, remotePeer] = await createPeerIdsFromFixtures(2) const metricsRegistry = new Map>() const metrics = { registerCounter: (name: string) => { @@ -39,20 +42,32 @@ describe('Index', () => { return counter } } + + const privateKeyInit = await generateKeyPair('Ed25519') + const peerIdInit = peerIdFromPrivateKey(privateKeyInit) const noiseInit = new Noise({ - peerId: await createEd25519PeerId(), + privateKey: privateKeyInit, + peerId: peerIdInit, logger: defaultLogger(), metrics: metrics as any as Metrics }) + + const privateKeyResp = await generateKeyPair('Ed25519') + const peerIdResp = peerIdFromPrivateKey(privateKeyResp) const noiseResp = new Noise({ - peerId: await createEd25519PeerId(), + privateKey: privateKeyResp, + peerId: peerIdResp, logger: defaultLogger() }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) + noiseInit.secureOutbound(outboundConnection, { + remotePeer: peerIdResp + }), + noiseResp.secureInbound(inboundConnection, { + remotePeer: peerIdInit + }) ]) const wrappedInbound = lpStream(inbound.conn) const wrappedOutbound = lpStream(outbound.conn) diff --git a/test/interop.ts b/test/interop.ts index f39c085..80f3519 100644 --- a/test/interop.ts +++ b/test/interop.ts @@ -1,11 +1,10 @@ import fs from 'fs' import { yamux } from '@chainsafe/libp2p-yamux' -import { unmarshalPrivateKey } from '@libp2p/crypto/keys' +import { privateKeyFromProtobuf } from '@libp2p/crypto/keys' import { createClient } from '@libp2p/daemon-client' import { createServer } from '@libp2p/daemon-server' import { connectInteropTests } from '@libp2p/interop' import { logger } from '@libp2p/logger' -import { peerIdFromKeys } from '@libp2p/peer-id' import { tcp } from '@libp2p/tcp' import { multiaddr } from '@multiformats/multiaddr' import { execa } from 'execa' @@ -13,7 +12,7 @@ import { path as p2pd } from 'go-libp2p' import { createLibp2p, type Libp2pOptions } from 'libp2p' import pDefer from 'p-defer' import { noise } from '../src/index.js' -import type { PeerId } from '@libp2p/interface' +import type { PrivateKey } from '@libp2p/interface' import type { SpawnOptions, Daemon, DaemonFactory } from '@libp2p/interop' async function createGoPeer (options: SpawnOptions): Promise { @@ -63,22 +62,21 @@ async function createGoPeer (options: SpawnOptions): Promise { } async function createJsPeer (options: SpawnOptions): Promise { - let peerId: PeerId | undefined + let privateKey: PrivateKey | undefined if (options.key != null) { const keyFile = fs.readFileSync(options.key) - const privateKey = await unmarshalPrivateKey(keyFile) - peerId = await peerIdFromKeys(privateKey.public.bytes, privateKey.bytes) + privateKey = privateKeyFromProtobuf(keyFile) } const opts: Libp2pOptions = { - peerId, + privateKey, addresses: { listen: ['/ip4/0.0.0.0/tcp/0'] }, transports: [tcp()], streamMuxers: [yamux()], - connectionEncryption: [noise()] + connectionEncrypters: [noise()] } const node = await createLibp2p(opts) diff --git a/test/noise.spec.ts b/test/noise.spec.ts index dbe5539..be3375c 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -11,11 +11,12 @@ import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { pureJsCrypto } from '../src/crypto/js.js' import { Noise } from '../src/noise.js' import { createPeerIdsFromFixtures } from './fixtures/peer.js' -import type { PeerId } from '@libp2p/interface' +import type { PeerId, PrivateKey } from '@libp2p/interface' import type { Uint8ArrayList } from 'uint8arraylist' describe('Noise', () => { - let remotePeer: PeerId, localPeer: PeerId + let remotePeer: { peerId: PeerId, privateKey: PrivateKey } + let localPeer: { peerId: PeerId, privateKey: PrivateKey } const sandbox = sinon.createSandbox() before(async () => { @@ -29,18 +30,22 @@ describe('Noise', () => { it('should communicate through encrypted streams without noise pipes', async () => { try { const noiseInit = new Noise({ - peerId: localPeer, + ...localPeer, logger: defaultLogger() }, { staticNoiseKey: undefined, extensions: undefined }) const noiseResp = new Noise({ - peerId: remotePeer, + ...remotePeer, logger: defaultLogger() }, { staticNoiseKey: undefined, extensions: undefined }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) + noiseInit.secureOutbound(outboundConnection, { + remotePeer: remotePeer.peerId + }), + noiseResp.secureInbound(inboundConnection, { + remotePeer: localPeer.peerId + }) ]) const wrappedInbound = lpStream(inbound.conn) const wrappedOutbound = lpStream(outbound.conn) @@ -58,18 +63,22 @@ describe('Noise', () => { this.timeout(10000) try { const noiseInit = new Noise({ - peerId: localPeer, + ...localPeer, logger: defaultLogger() }, { staticNoiseKey: undefined }) const noiseResp = new Noise({ - peerId: remotePeer, + ...remotePeer, logger: defaultLogger() }, { staticNoiseKey: undefined }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) + noiseInit.secureOutbound(outboundConnection, { + remotePeer: remotePeer.peerId + }), + noiseResp.secureInbound(inboundConnection, { + remotePeer: localPeer.peerId + }) ]) const wrappedInbound = byteStream(inbound.conn) const wrappedOutbound = lpStream(outbound.conn) @@ -89,19 +98,21 @@ describe('Noise', () => { try { const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() const noiseInit = new Noise({ - peerId: localPeer, + ...localPeer, logger: defaultLogger() }, { staticNoiseKey: staticKeysInitiator.privateKey }) const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() const noiseResp = new Noise({ - peerId: remotePeer, + ...remotePeer, logger: defaultLogger() }, { staticNoiseKey: staticKeysResponder.privateKey }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection) + noiseInit.secureOutbound(outboundConnection, { + remotePeer: remotePeer.peerId + }), + noiseResp.secureInbound(inboundConnection) ]) const wrappedInbound = lpStream(inbound.conn) const wrappedOutbound = lpStream(outbound.conn) @@ -110,13 +121,13 @@ describe('Noise', () => { const response = await wrappedInbound.read() expect(uint8ArrayToString(response.slice())).equal('test v2') - if (inbound.remotePeer.publicKey == null || localPeer.publicKey == null || - outbound.remotePeer.publicKey == null || remotePeer.publicKey == null) { + if (inbound.remotePeer.publicKey == null || localPeer.peerId.publicKey == null || + outbound.remotePeer.publicKey == null || remotePeer.peerId.publicKey == null) { throw new Error('Public key missing from PeerId') } - assert(uint8ArrayEquals(inbound.remotePeer.publicKey, localPeer.publicKey)) - assert(uint8ArrayEquals(outbound.remotePeer.publicKey, remotePeer.publicKey)) + expect(inbound.remotePeer.publicKey?.raw).to.equalBytes(localPeer.peerId.publicKey.raw) + expect(outbound.remotePeer.publicKey?.raw).to.equalBytes(remotePeer.peerId.publicKey.raw) } catch (e) { const err = e as Error assert(false, err.message) @@ -128,20 +139,22 @@ describe('Noise', () => { const certhashInit = Buffer.from('certhash data from init') const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() const noiseInit = new Noise({ - peerId: localPeer, + ...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({ - peerId: remotePeer, + ...remotePeer, logger: defaultLogger() }, { staticNoiseKey: staticKeysResponder.privateKey, extensions: { webtransportCerthashes: [certhashResp] } }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection) + noiseInit.secureOutbound(outboundConnection, { + remotePeer: remotePeer.peerId + }), + noiseResp.secureInbound(inboundConnection) ]) assert(uint8ArrayEquals(inbound.remoteExtensions?.webtransportCerthashes[0] ?? new Uint8Array(), certhashInit)) @@ -155,18 +168,22 @@ describe('Noise', () => { it('should accept a prologue', async () => { try { const noiseInit = new Noise({ - peerId: localPeer, + ...localPeer, logger: defaultLogger() }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) const noiseResp = new Noise({ - peerId: remotePeer, + ...remotePeer, logger: defaultLogger() }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) const [inboundConnection, outboundConnection] = duplexPair() const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) + noiseInit.secureOutbound(outboundConnection, { + remotePeer: remotePeer.peerId + }), + noiseResp.secureInbound(inboundConnection, { + remotePeer: localPeer.peerId + }) ]) const wrappedInbound = lpStream(inbound.conn) const wrappedOutbound = lpStream(outbound.conn) @@ -185,11 +202,11 @@ describe('Noise', () => { abortController.abort() const noiseInit = new Noise({ - peerId: localPeer, + ...localPeer, logger: defaultLogger() }, { staticNoiseKey: undefined, extensions: undefined }) const noiseResp = new Noise({ - peerId: remotePeer, + ...remotePeer, logger: defaultLogger() }, { staticNoiseKey: undefined, extensions: undefined }) @@ -197,11 +214,11 @@ describe('Noise', () => { await expect(Promise.all([ noiseInit.secureOutbound(outboundConnection, { - remotePeer, + remotePeer: remotePeer.peerId, signal: abortController.signal }), noiseResp.secureInbound(inboundConnection, { - remotePeer: localPeer + remotePeer: localPeer.peerId }) ])).to.eventually.be.rejected .with.property('name', 'AbortError') diff --git a/test/performHandshake.spec.ts b/test/performHandshake.spec.ts index 20bc1e4..63a661f 100644 --- a/test/performHandshake.spec.ts +++ b/test/performHandshake.spec.ts @@ -1,10 +1,8 @@ import { Buffer } from 'buffer' -import { unmarshalPrivateKey } from '@libp2p/crypto/keys' import { defaultLogger } from '@libp2p/logger' import { assert, expect } from 'aegir/chai' import { lpStream } from 'it-length-prefixed-stream' import { duplexPair } from 'it-pair/duplex' -import { toString as uint8ArrayToString } from 'uint8arrays' import { equals as uint8ArrayEquals } from 'uint8arrays/equals' import { defaultCrypto } from '../src/crypto/index.js' import { wrapCrypto } from '../src/crypto.js' @@ -13,14 +11,13 @@ import { createPeerIdsFromFixtures } from './fixtures/peer.js' import type { PrivateKey, PeerId } from '@libp2p/interface' describe('performHandshake', () => { - let peerA: PeerId, peerB: PeerId, fakePeer: PeerId - let privateKeyA: PrivateKey, privateKeyB: PrivateKey + let peerA: { peerId: PeerId, privateKey: PrivateKey } + let peerB: { peerId: PeerId, privateKey: PrivateKey } + let fakePeer: { peerId: PeerId, privateKey: PrivateKey } before(async () => { [peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3) if (!peerA.privateKey || !peerB.privateKey || !fakePeer.privateKey) throw new Error('unreachable') - privateKeyA = await unmarshalPrivateKey(peerA.privateKey) - privateKeyB = await unmarshalPrivateKey(peerB.privateKey) }) it('should propose, exchange and finish handshake', async () => { @@ -37,18 +34,18 @@ describe('performHandshake', () => { log: defaultLogger().forComponent('test'), connection: connectionInitiator, crypto: wrapCrypto(defaultCrypto), - privateKey: privateKeyA, + privateKey: peerA.privateKey, prologue, - remoteIdentityKey: peerB.publicKey, + remoteIdentityKey: peerB.privateKey.publicKey, s: staticKeysInitiator }), performHandshakeResponder({ log: defaultLogger().forComponent('test'), connection: connectionResponder, crypto: wrapCrypto(defaultCrypto), - privateKey: privateKeyB, + privateKey: peerB.privateKey, prologue, - remoteIdentityKey: peerA.publicKey, + remoteIdentityKey: peerA.privateKey.publicKey, s: staticKeysResponder }) ]) @@ -74,18 +71,18 @@ describe('performHandshake', () => { log: defaultLogger().forComponent('test'), connection: connectionInitiator, crypto: wrapCrypto(defaultCrypto), - privateKey: privateKeyA, + privateKey: peerA.privateKey, prologue, - remoteIdentityKey: fakePeer.publicKey, // <----- look here + remoteIdentityKey: fakePeer.privateKey.publicKey, // <----- look here s: staticKeysInitiator }), performHandshakeResponder({ log: defaultLogger().forComponent('test'), connection: connectionResponder, crypto: wrapCrypto(defaultCrypto), - privateKey: privateKeyB, + privateKey: peerB.privateKey, prologue, - remoteIdentityKey: peerA.publicKey, + remoteIdentityKey: peerA.privateKey.publicKey, s: staticKeysResponder }) ]) @@ -93,7 +90,7 @@ describe('performHandshake', () => { assert(false, 'Should throw exception') } catch (e) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect((e as Error).message).equals(`Payload identity key ${uint8ArrayToString(peerB.publicKey!, 'hex')} does not match expected remote identity key ${uint8ArrayToString(fakePeer.publicKey!, 'hex')}`) + expect((e as Error).message).equals(`Payload identity key ${peerB.privateKey.publicKey} does not match expected remote identity key ${fakePeer.privateKey.publicKey}`) } }) @@ -112,18 +109,18 @@ describe('performHandshake', () => { log: defaultLogger().forComponent('test'), connection: connectionInitiator, crypto: wrapCrypto(defaultCrypto), - privateKey: privateKeyA, + privateKey: peerA.privateKey, prologue, - remoteIdentityKey: peerB.publicKey, + remoteIdentityKey: peerB.privateKey.publicKey, s: staticKeysInitiator }), performHandshakeResponder({ log: defaultLogger().forComponent('test'), connection: connectionResponder, crypto: wrapCrypto(defaultCrypto), - privateKey: privateKeyB, + privateKey: peerB.privateKey, prologue, - remoteIdentityKey: fakePeer.publicKey, + remoteIdentityKey: fakePeer.privateKey.publicKey, s: staticKeysResponder }) ]) @@ -131,7 +128,7 @@ describe('performHandshake', () => { assert(false, 'Should throw exception') } catch (e) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - expect((e as Error).message).equals(`Payload identity key ${uint8ArrayToString(peerA.publicKey!, 'hex')} does not match expected remote identity key ${uint8ArrayToString(fakePeer.publicKey!, 'hex')}`) + expect((e as Error).message).equals(`Payload identity key ${peerA.privateKey.publicKey} does not match expected remote identity key ${fakePeer.privateKey.publicKey}`) } }) }) diff --git a/test/utils.ts b/test/utils.ts index 3863f5a..c342022 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,18 +1,6 @@ import { keys } from '@libp2p/crypto' -import type { KeyPair } from '../src/types.js' -import type { PrivateKey, PeerId } from '@libp2p/interface' +import type { PrivateKey } from '@libp2p/interface' export async function generateEd25519Keys (): Promise { return keys.generateKeyPair('Ed25519', 32) } - -export function getKeyPairFromPeerId (peerId: PeerId): KeyPair { - if (peerId.privateKey == null || peerId.publicKey == null) { - throw new Error('PrivateKey or PublicKey missing from PeerId') - } - - return { - privateKey: peerId.privateKey.subarray(0, 32), - publicKey: peerId.publicKey - } -} diff --git a/typedoc.json b/typedoc.json new file mode 100644 index 0000000..f599dc7 --- /dev/null +++ b/typedoc.json @@ -0,0 +1,5 @@ +{ + "entryPoints": [ + "./src/index.ts" + ] +}