diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f1bbc8d5eb..4dd416b62d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -38,7 +38,6 @@ "packages/protocol-ping": "3.0.1", "packages/record": "4.0.8", "packages/stream-multiplexer-mplex": "12.0.1", - "packages/stream-multiplexer-yamux": "8.0.1", "packages/transport-circuit-relay-v2": "4.0.1", "packages/transport-memory": "2.0.1", "packages/transport-tcp": "11.0.1", @@ -48,6 +47,5 @@ "packages/upnp-nat": "4.0.1", "packages/utils": "7.0.1", "packages/integration-tests": "1.0.1", - "packages/connection-encrypter-noise": "17.0.1", "interop": "1.0.1" } diff --git a/.release-please.json b/.release-please.json index ca65296fcd..eb8297f71b 100644 --- a/.release-please.json +++ b/.release-please.json @@ -11,7 +11,6 @@ "packages": { "interop": {}, "packages/config": {}, - "packages/connection-encrypter-noise": {}, "packages/connection-encrypter-plaintext": {}, "packages/connection-encrypter-tls": {}, "packages/crypto": {}, @@ -51,7 +50,6 @@ "packages/protocol-ping": {}, "packages/record": {}, "packages/stream-multiplexer-mplex": {}, - "packages/stream-multiplexer-yamux": {}, "packages/transport-circuit-relay-v2": {}, "packages/transport-memory": {}, "packages/transport-tcp": {}, diff --git a/README.md b/README.md index 3201f41554..cddc34a5a2 100644 --- a/README.md +++ b/README.md @@ -149,11 +149,11 @@ List of packages currently in existence for libp2p | [`@libp2p/websockets`](//github.com/libp2p/js-libp2p/tree/main/packages/transport-websockets) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fwebsockets.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/websockets) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fwebsockets?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fwebsockets) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-websockets/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-websockets) | | [`@libp2p/webtransport`](//github.com/libp2p/js-libp2p/tree/main/packages/transport-webtransport) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fwebtransport.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/webtransport) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fwebtransport?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fwebtransport) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-webtransport/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-webtransport) | | **secure channels** | -| [`@libp2p/noise`](//github.com/ChainSafe/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fnoise.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/noise) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fnoise?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fnoise) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-noise/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-noise/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-noise) | +| [`@chainsafe/libp2p-noise`](//github.com/ChainSafe/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/%40chainsafe%2Flibp2p-noise.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@chainsafe/libp2p-noise) | [![Deps](https://img.shields.io/librariesio/release/npm/%40chainsafe%2Flibp2p-noise?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40chainsafe%2Flibp2p-noise) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-noise/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-noise/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-noise) | | [`@libp2p/plaintext`](//github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fplaintext.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/plaintext) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fplaintext?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fplaintext) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext) | | [`@libp2p/tls`](//github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Ftls.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/tls) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Ftls?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Ftls) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls) | | **stream multiplexers** | -| [`@libp2p/yamux`](//github.com/ChainSafe/js-libp2p-yamux) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fyamux.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/yamux) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fyamux?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fyamux) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-yamux/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-yamux/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-yamux/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-yamux) | +| [`@chainsafe/libp2p-yamux`](//github.com/ChainSafe/js-libp2p-yamux) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fyamux.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@chainsafe/libp2p-yamux) | [![Deps](https://img.shields.io/librariesio/release/npm/%40chainsafe%2Flibp2p-yamux?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40chainsafe%2Flibp2p-yamux) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-yamux/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-yamux/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-yamux/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-yamux) | | [`@libp2p/mplex`](//github.com/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fmplex.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/mplex) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fmplex?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fmplex) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex) | | **peer discovery** | | [`@libp2p/bootstrap`](//github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fbootstrap.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/bootstrap) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fbootstrap?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fbootstrap) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap) | diff --git a/interop/package.json b/interop/package.json index 730041825f..1adf2d668e 100644 --- a/interop/package.json +++ b/interop/package.json @@ -11,8 +11,8 @@ "dep-check": "aegir dep-check" }, "devDependencies": { - "@libp2p/noise": "^17.0.1", - "@libp2p/yamux": "^8.0.1", + "@chainsafe/libp2p-noise": "^17.0.0", + "@chainsafe/libp2p-yamux": "^8.0.0", "@libp2p/circuit-relay-v2": "^4.0.1", "@libp2p/identify": "^4.0.1", "@libp2p/interface": "^3.0.0", diff --git a/interop/test/fixtures/get-libp2p.ts b/interop/test/fixtures/get-libp2p.ts index 56c92b5e6e..245842a7a9 100644 --- a/interop/test/fixtures/get-libp2p.ts +++ b/interop/test/fixtures/get-libp2p.ts @@ -1,17 +1,17 @@ /* eslint-disable complexity */ // import { quic } from '@chainsafe/libp2p-quic' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { mplex } from '@libp2p/mplex' -import { noise } from '@libp2p/noise' import { ping } from '@libp2p/ping' import { tcp } from '@libp2p/tcp' import { tls } from '@libp2p/tls' import { webRTC, webRTCDirect } from '@libp2p/webrtc' import { webSockets } from '@libp2p/websockets' import { webTransport } from '@libp2p/webtransport' -import { yamux } from '@libp2p/yamux' import { createLibp2p } from 'libp2p' import type { Identify } from '@libp2p/identify' import type { Libp2p } from '@libp2p/interface' diff --git a/interop/test/fixtures/relay.ts b/interop/test/fixtures/relay.ts index 25147dc5cd..bc8e47118d 100644 --- a/interop/test/fixtures/relay.ts +++ b/interop/test/fixtures/relay.ts @@ -1,8 +1,8 @@ +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayServer } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' -import { noise } from '@libp2p/noise' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import { createLibp2p } from 'libp2p' import type { Libp2p } from '@libp2p/interface' diff --git a/interop/tsconfig.json b/interop/tsconfig.json index 2755ff8244..ed657baf73 100644 --- a/interop/tsconfig.json +++ b/interop/tsconfig.json @@ -8,9 +8,6 @@ "test" ], "references": [ - { - "path": "../packages/connection-encrypter-noise" - }, { "path": "../packages/connection-encrypter-tls" }, @@ -29,9 +26,6 @@ { "path": "../packages/stream-multiplexer-mplex" }, - { - "path": "../packages/stream-multiplexer-yamux" - }, { "path": "../packages/transport-circuit-relay-v2" }, diff --git a/packages/connection-encrypter-noise/.aegir.js b/packages/connection-encrypter-noise/.aegir.js deleted file mode 100644 index 95c3276d23..0000000000 --- a/packages/connection-encrypter-noise/.aegir.js +++ /dev/null @@ -1,7 +0,0 @@ - -/** @type {import('aegir').PartialOptions} */ -export default { - docs: { - entryPoint: "src/index.ts" - } -} diff --git a/packages/connection-encrypter-noise/CHANGELOG.md b/packages/connection-encrypter-noise/CHANGELOG.md deleted file mode 100644 index 8dff19daf3..0000000000 --- a/packages/connection-encrypter-noise/CHANGELOG.md +++ /dev/null @@ -1,59 +0,0 @@ -# Changelog - -## [1.0.1](https://github.com/libp2p/js-libp2p/compare/noise-v1.0.0...noise-v1.0.1) (2025-09-24) - - -### Dependencies - -* update p-event, p-wait-for and noble deps ([#3302](https://github.com/libp2p/js-libp2p/issues/3302)) ([55bbd8c](https://github.com/libp2p/js-libp2p/commit/55bbd8cde12fe1c05e8d264e6e2406ca9fe2f044)) -* The following workspace dependencies were updated - * dependencies - * @libp2p/crypto bumped from ^5.1.9 to ^5.1.10 - * @libp2p/peer-id bumped from ^6.0.0 to ^6.0.1 - * @libp2p/utils bumped from ^7.0.0 to ^7.0.1 - * devDependencies - * @libp2p/daemon-client bumped from ^10.0.0 to ^10.0.1 - * @libp2p/daemon-server bumped from ^9.0.0 to ^9.0.1 - * @libp2p/interface-compliance-tests bumped from ^7.0.0 to ^7.0.1 - * @libp2p/interop bumped from ^14.0.0 to ^14.0.1 - * @libp2p/logger bumped from ^6.0.0 to ^6.0.1 - * @libp2p/tcp bumped from ^11.0.0 to ^11.0.1 - * @libp2p/yamux bumped from ^8.0.0 to ^8.0.1 - * libp2p bumped from ^3.0.0 to ^3.0.1 - -## 1.0.0 (2025-09-23) - - -### ⚠ BREAKING CHANGES - -* requires @mulitformats/multiaddr 13.x.x or later -* - Stream handlers accept `stream, connection`, not `{ stream, connection }` - -### Features - -* streams as EventTargets ([#3218](https://github.com/libp2p/js-libp2p/issues/3218)) ([0f68898](https://github.com/libp2p/js-libp2p/commit/0f68898e6503975aae6f2bb6ba36aff65dabdfe8)), closes [#3226](https://github.com/libp2p/js-libp2p/issues/3226) - - -### Bug Fixes - -* update project ([db9f40c](https://github.com/libp2p/js-libp2p/commit/db9f40c4fc4c230444d0f3ca79b65a0053bc35f7)) - - -### Dependencies - -* update @multiformats/multiaddr to 13.x.x ([#3268](https://github.com/libp2p/js-libp2p/issues/3268)) ([b8ecade](https://github.com/libp2p/js-libp2p/commit/b8ecade2a725d38d11dd8df888c5abb22e14f26b)) -* The following workspace dependencies were updated - * dependencies - * @libp2p/crypto bumped from ^5.1.8 to ^5.1.9 - * @libp2p/interface bumped from ^2.11.0 to ^3.0.0 - * @libp2p/peer-id bumped from ^5.1.9 to ^6.0.0 - * @libp2p/utils bumped from ^6.7.2 to ^7.0.0 - * devDependencies - * @libp2p/daemon-client bumped from ^9.0.8 to ^10.0.0 - * @libp2p/daemon-server bumped from ^8.0.6 to ^9.0.0 - * @libp2p/interface-compliance-tests bumped from ^6.5.0 to ^7.0.0 - * @libp2p/interop bumped from ^13.0.3 to ^14.0.0 - * @libp2p/logger bumped from ^5.2.0 to ^6.0.0 - * @libp2p/tcp bumped from ^10.1.19 to ^11.0.0 - * @libp2p/yamux bumped from ^7.0.1 to ^8.0.0 - * libp2p bumped from ^2.10.0 to ^3.0.0 diff --git a/packages/connection-encrypter-noise/CODE_OF_CONDUCT.md b/packages/connection-encrypter-noise/CODE_OF_CONDUCT.md deleted file mode 100644 index 6b0fa54c54..0000000000 --- a/packages/connection-encrypter-noise/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,3 +0,0 @@ -# Contributor Code of Conduct - -This project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md) diff --git a/packages/connection-encrypter-noise/LICENSE-APACHE b/packages/connection-encrypter-noise/LICENSE-APACHE deleted file mode 100644 index b09cd7856d..0000000000 --- a/packages/connection-encrypter-noise/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/connection-encrypter-noise/LICENSE-MIT b/packages/connection-encrypter-noise/LICENSE-MIT deleted file mode 100644 index 72dc60d84b..0000000000 --- a/packages/connection-encrypter-noise/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/connection-encrypter-noise/README.md b/packages/connection-encrypter-noise/README.md deleted file mode 100644 index b79a9d2133..0000000000 --- a/packages/connection-encrypter-noise/README.md +++ /dev/null @@ -1,95 +0,0 @@ -# @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) -[![](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](https://libp2p.io/) -![](https://img.shields.io/github/issues-raw/ChainSafe/js-libp2p-noise) -[![License Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -[![License MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -![](https://img.shields.io/badge/npm-%3E%3D7.0.0-orange.svg?style=flat-square) -![](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) - -> Noise libp2p handshake for js-libp2p - -# About - - - -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 "@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 - -# Install - -```console -$ npm i @chainsafe/libp2p-noise -``` - -## Browser ` -``` - -# API Docs - -- - -# License - -Licensed under either of - -- 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/packages/connection-encrypter-noise/package.json b/packages/connection-encrypter-noise/package.json deleted file mode 100644 index 348824538b..0000000000 --- a/packages/connection-encrypter-noise/package.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "name": "@libp2p/noise", - "version": "17.0.1", - "description": "Noise libp2p handshake for js-libp2p", - "author": "ChainSafe ", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-noise#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "publishConfig": { - "access": "public", - "provenance": true - }, - "keywords": [ - "crypto", - "libp2p", - "noise" - ], - "type": "module", - "types": "./dist/src/index.d.ts", - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/src/index.js" - } - }, - "scripts": { - "bench": "node benchmarks/benchmark.js", - "clean": "aegir clean", - "dep-check": "aegir dep-check", - "build": "aegir build", - "lint": "aegir lint", - "lint:fix": "aegir lint --fix", - "test": "aegir test", - "test:node": "aegir test -t node", - "test:browser": "aegir test -t browser -t webworker", - "test:electron-main": "aegir test -t electron-main", - "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" - }, - "dependencies": { - "@chainsafe/as-chacha20poly1305": "^0.1.0", - "@chainsafe/as-sha256": "^1.2.0", - "@libp2p/crypto": "^5.1.10", - "@libp2p/interface": "^3.0.0", - "@libp2p/peer-id": "^6.0.1", - "@libp2p/utils": "^7.0.1", - "@noble/ciphers": "^2.0.1", - "@noble/curves": "^2.0.1", - "@noble/hashes": "^2.0.1", - "protons-runtime": "^5.6.0", - "uint8arraylist": "^2.4.8", - "uint8arrays": "^5.1.0", - "wherearewe": "^2.0.1" - }, - "devDependencies": { - "@libp2p/daemon-client": "^10.0.1", - "@libp2p/daemon-server": "^9.0.1", - "@libp2p/interface-compliance-tests": "^7.0.1", - "@libp2p/interop": "^14.0.1", - "@libp2p/logger": "^6.0.1", - "@libp2p/tcp": "^11.0.1", - "@libp2p/yamux": "^8.0.1", - "@multiformats/multiaddr": "^13.0.1", - "@types/sinon": "^17.0.4", - "aegir": "^47.0.22", - "execa": "^9.6.0", - "go-libp2p": "^1.6.0", - "iso-random-stream": "^2.0.2", - "libp2p": "^3.0.1", - "mkdirp": "^3.0.1", - "multiformats": "^13.4.0", - "p-defer": "^4.0.1", - "protons": "^7.7.0", - "sinon": "^21.0.0", - "sinon-ts": "^2.0.0" - }, - "browser": { - "./dist/src/crypto/index.js": "./dist/src/crypto/index.browser.js" - } -} diff --git a/packages/connection-encrypter-noise/src/constants.ts b/packages/connection-encrypter-noise/src/constants.ts deleted file mode 100644 index 952bb74448..0000000000 --- a/packages/connection-encrypter-noise/src/constants.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const NOISE_MSG_MAX_LENGTH_BYTES = 65535 -export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16 - -export const DUMP_SESSION_KEYS = Boolean(globalThis.process?.env?.DUMP_SESSION_KEYS) -export const CHACHA_TAG_LENGTH = 16 diff --git a/packages/connection-encrypter-noise/src/crypto.ts b/packages/connection-encrypter-noise/src/crypto.ts deleted file mode 100644 index 75c0f57813..0000000000 --- a/packages/connection-encrypter-noise/src/crypto.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { ICrypto, KeyPair } from './types.js' -import type { Uint8ArrayList } from 'uint8arraylist' - -/** Underlying crypto implementation, meant to be overridable */ -export interface ICryptoInterface { - hashSHA256(data: Uint8Array | Uint8ArrayList): Uint8Array - - getHKDF(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array, Uint8Array] - - generateX25519KeyPair(): KeyPair - generateX25519KeyPairFromSeed(seed: Uint8Array): KeyPair - generateX25519SharedKey(privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array - - chaCha20Poly1305Encrypt(plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array): Uint8ArrayList | Uint8Array - chaCha20Poly1305Decrypt(ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array, dst?: Uint8Array): Uint8ArrayList | Uint8Array -} - -export function wrapCrypto (crypto: ICryptoInterface): ICrypto { - return { - generateKeypair: crypto.generateX25519KeyPair, - dh: (keypair, publicKey) => crypto.generateX25519SharedKey(keypair.privateKey, publicKey).subarray(0, 32), - encrypt: crypto.chaCha20Poly1305Encrypt, - decrypt: crypto.chaCha20Poly1305Decrypt, - hash: crypto.hashSHA256, - hkdf: crypto.getHKDF - } -} diff --git a/packages/connection-encrypter-noise/src/crypto/index.browser.ts b/packages/connection-encrypter-noise/src/crypto/index.browser.ts deleted file mode 100644 index 39c9ea7d01..0000000000 --- a/packages/connection-encrypter-noise/src/crypto/index.browser.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { pureJsCrypto } from './js.js' - -export const defaultCrypto = pureJsCrypto diff --git a/packages/connection-encrypter-noise/src/crypto/index.ts b/packages/connection-encrypter-noise/src/crypto/index.ts deleted file mode 100644 index 6d3639bb9e..0000000000 --- a/packages/connection-encrypter-noise/src/crypto/index.ts +++ /dev/null @@ -1,219 +0,0 @@ -import crypto from 'node:crypto' -import { newInstance, ChaCha20Poly1305 } from '@chainsafe/as-chacha20poly1305' -import { digest } from '@chainsafe/as-sha256' -import { Uint8ArrayList } from 'uint8arraylist' -import { isElectronMain } from 'wherearewe' -import { pureJsCrypto } from './js.js' -import type { ICryptoInterface } from '../crypto.js' -import type { KeyPair } from '../types.js' - -const ctx = newInstance() -const asImpl = new ChaCha20Poly1305(ctx) -const CHACHA_POLY1305 = 'chacha20-poly1305' -const PKCS8_PREFIX = Buffer.from([0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x04, 0x22, 0x04, 0x20]) -const X25519_PREFIX = Buffer.from([0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x6e, 0x03, 0x21, 0x00]) -const nodeCrypto: Pick = { - hashSHA256 (data) { - const hash = crypto.createHash('sha256') - - if (data instanceof Uint8Array) { - return hash.update(data).digest() - } - - for (const buf of data) { - hash.update(buf) - } - - return hash.digest() - }, - - chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) { - const cipher = crypto.createCipheriv(CHACHA_POLY1305, k, nonce, { - authTagLength: 16 - }) - cipher.setAAD(ad, { plaintextLength: plaintext.byteLength }) - - if (plaintext instanceof Uint8Array) { - const updated = cipher.update(plaintext) - const final = cipher.final() - const tag = cipher.getAuthTag() - - return Buffer.concat([updated, final, tag], updated.byteLength + final.byteLength + tag.byteLength) - } - - const output = new Uint8ArrayList() - - for (const buf of plaintext) { - output.append(cipher.update(buf)) - } - - const final = cipher.final() - - if (final.byteLength > 0) { - output.append(final) - } - - output.append(cipher.getAuthTag()) - - return output - }, - - chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, _dst) { - const authTag = ciphertext.subarray(ciphertext.length - 16) - const decipher = crypto.createDecipheriv(CHACHA_POLY1305, k, nonce, { - authTagLength: 16 - }) - - let text: Uint8Array | Uint8ArrayList - - if (ciphertext instanceof Uint8Array) { - text = ciphertext.subarray(0, ciphertext.length - 16) - } else { - text = ciphertext.sublist(0, ciphertext.length - 16) - } - - decipher.setAAD(ad, { - plaintextLength: text.byteLength - }) - decipher.setAuthTag(authTag) - - if (text instanceof Uint8Array) { - const output = decipher.update(text) - const final = decipher.final() - - if (final.byteLength > 0) { - return Buffer.concat([output, final], output.byteLength + final.byteLength) - } - - return output - } - - const output = new Uint8ArrayList() - - for (const buf of text) { - output.append(decipher.update(buf)) - } - - const final = decipher.final() - - if (final.byteLength > 0) { - output.append(final) - } - - return output - } -} - -const asCrypto: Pick = { - hashSHA256 (data) { - return digest(data.subarray()) - }, - chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) { - return asImpl.seal(k, nonce, plaintext.subarray(), ad) - }, - chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, dst) { - const plaintext = asImpl.open(k, nonce, ciphertext.subarray(), ad, dst) - if (!plaintext) { - throw new Error('Invalid chacha20poly1305 decryption') - } - return plaintext - } -} - -// benchmarks show that for chacha20poly1305 -// the as implementation is faster for smaller payloads(<1200) -// and the node implementation is faster for larger payloads -export const defaultCrypto: ICryptoInterface = { - ...pureJsCrypto, - hashSHA256 (data) { - return nodeCrypto.hashSHA256(data) - }, - chaCha20Poly1305Encrypt (plaintext, nonce, ad, k) { - if (plaintext.byteLength < 1200) { - return asCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k) - } - return nodeCrypto.chaCha20Poly1305Encrypt(plaintext, nonce, ad, k) - }, - chaCha20Poly1305Decrypt (ciphertext, nonce, ad, k, dst) { - if (ciphertext.byteLength < 1200) { - return asCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst) - } - return nodeCrypto.chaCha20Poly1305Decrypt(ciphertext, nonce, ad, k, dst) - }, - generateX25519KeyPair (): KeyPair { - const { publicKey, privateKey } = crypto.generateKeyPairSync('x25519', { - publicKeyEncoding: { - type: 'spki', - format: 'der' - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'der' - } - }) - - return { - publicKey: publicKey.subarray(X25519_PREFIX.length), - privateKey: privateKey.subarray(PKCS8_PREFIX.length) - } - }, - generateX25519KeyPairFromSeed (seed: Uint8Array): KeyPair { - const privateKey = crypto.createPrivateKey({ - key: Buffer.concat([ - PKCS8_PREFIX, - seed - ], PKCS8_PREFIX.byteLength + seed.byteLength), - type: 'pkcs8', - format: 'der' - }) - - const publicKey = crypto.createPublicKey(privateKey) - .export({ - type: 'spki', - format: 'der' - }).subarray(X25519_PREFIX.length) - - return { - publicKey, - privateKey: seed - } - }, - generateX25519SharedKey (privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array { - if (publicKey instanceof Uint8Array) { - publicKey = Buffer.concat([ - X25519_PREFIX, - publicKey - ], X25519_PREFIX.byteLength + publicKey.byteLength) - } else { - publicKey = new Uint8ArrayList(X25519_PREFIX, publicKey).subarray() - } - - if (privateKey instanceof Uint8Array) { - privateKey = Buffer.concat([ - PKCS8_PREFIX, - privateKey - ], PKCS8_PREFIX.byteLength + privateKey.byteLength) - } else { - privateKey = new Uint8ArrayList(PKCS8_PREFIX, privateKey).subarray() - } - - return crypto.diffieHellman({ - publicKey: crypto.createPublicKey({ - key: Buffer.from(publicKey.buffer, publicKey.byteOffset, publicKey.byteLength), - type: 'spki', - format: 'der' - }), - privateKey: crypto.createPrivateKey({ - key: Buffer.from(privateKey.buffer, privateKey.byteOffset, privateKey.byteLength), - type: 'pkcs8', - format: 'der' - }) - }) - } -} - -// no chacha20-poly1305 in electron https://github.com/electron/electron/issues/24024 -if (isElectronMain) { - defaultCrypto.chaCha20Poly1305Encrypt = asCrypto.chaCha20Poly1305Encrypt - defaultCrypto.chaCha20Poly1305Decrypt = asCrypto.chaCha20Poly1305Decrypt -} diff --git a/packages/connection-encrypter-noise/src/crypto/js.ts b/packages/connection-encrypter-noise/src/crypto/js.ts deleted file mode 100644 index 76a01ff807..0000000000 --- a/packages/connection-encrypter-noise/src/crypto/js.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { chacha20poly1305 } from '@noble/ciphers/chacha.js' -import { x25519 } from '@noble/curves/ed25519.js' -import { extract, expand } from '@noble/hashes/hkdf.js' -import { sha256 } from '@noble/hashes/sha2.js' -import type { ICryptoInterface } from '../crypto.js' -import type { KeyPair } from '../types.js' -import type { Uint8ArrayList } from 'uint8arraylist' - -export const pureJsCrypto: ICryptoInterface = { - hashSHA256 (data: Uint8Array | Uint8ArrayList): Uint8Array { - return sha256(data.subarray()) - }, - - getHKDF (ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array, Uint8Array] { - const prk = extract(sha256, ikm, ck) - const okmU8Array = expand(sha256, prk, undefined, 96) - const okm = okmU8Array - - const k1 = okm.subarray(0, 32) - const k2 = okm.subarray(32, 64) - const k3 = okm.subarray(64, 96) - - return [k1, k2, k3] - }, - - generateX25519KeyPair (): KeyPair { - const secretKey = x25519.utils.randomSecretKey() - const publicKey = x25519.getPublicKey(secretKey) - - return { - publicKey, - privateKey: secretKey - } - }, - - generateX25519KeyPairFromSeed (seed: Uint8Array): KeyPair { - const publicKey = x25519.getPublicKey(seed) - - return { - publicKey, - privateKey: seed - } - }, - - generateX25519SharedKey (privateKey: Uint8Array | Uint8ArrayList, publicKey: Uint8Array | Uint8ArrayList): Uint8Array { - return x25519.getSharedSecret(privateKey.subarray(), publicKey.subarray()) - }, - - chaCha20Poly1305Encrypt (plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array): Uint8Array { - return chacha20poly1305(k, nonce, ad).encrypt(plaintext.subarray()) - }, - - chaCha20Poly1305Decrypt (ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array, dst?: Uint8Array): Uint8Array { - return chacha20poly1305(k, nonce, ad).decrypt(ciphertext.subarray(), dst) - } -} diff --git a/packages/connection-encrypter-noise/src/encoder.ts b/packages/connection-encrypter-noise/src/encoder.ts deleted file mode 100644 index cbc2741ebf..0000000000 --- a/packages/connection-encrypter-noise/src/encoder.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { allocUnsafe as uint8ArrayAllocUnsafe } from 'uint8arrays/alloc' -import type { Uint8ArrayList } from 'uint8arraylist' - -export const uint16BEEncode = (value: number): Uint8Array => { - const target = uint8ArrayAllocUnsafe(2) - target[0] = value >> 8 - target[1] = value - return target -} -uint16BEEncode.bytes = 2 - -export const uint16BEDecode = (data: Uint8Array | Uint8ArrayList): number => { - if (data.length < 2) { throw RangeError('Could not decode int16BE') } - - if (data instanceof Uint8Array) { - let value = 0 - value += data[0] << 8 - value += data[1] - return value - } - - return data.getUint16(0) -} -uint16BEDecode.bytes = 2 diff --git a/packages/connection-encrypter-noise/src/errors.ts b/packages/connection-encrypter-noise/src/errors.ts deleted file mode 100644 index 6d526dae04..0000000000 --- a/packages/connection-encrypter-noise/src/errors.ts +++ /dev/null @@ -1,10 +0,0 @@ -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' -} diff --git a/packages/connection-encrypter-noise/src/index.ts b/packages/connection-encrypter-noise/src/index.ts deleted file mode 100644 index 00a67d5be8..0000000000 --- a/packages/connection-encrypter-noise/src/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @packageDocumentation - * - * This repository contains TypeScript implementation of noise protocol, an encryption protocol used in libp2p. - * - * ## Usage - * - * Install with `yarn add @libp2p/noise` or `npm i @libp2p/noise`. - * - * Example of using default noise configuration and passing it to the libp2p config: - * - * ```ts - * import {createLibp2p} from "libp2p" - * import {noise} from "@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, NoiseExtensions } from './noise.js' -import type { KeyPair } from './types.js' -import type { ComponentLogger, ConnectionEncrypter, Metrics, PeerId, PrivateKey, Upgrader } from '@libp2p/interface' - -export { pureJsCrypto } from './crypto/js.js' -export type { ICryptoInterface } from './crypto.js' -export type { NoiseInit, NoiseExtensions, KeyPair } - -export interface NoiseComponents { - peerId: PeerId - privateKey: PrivateKey - logger: ComponentLogger - upgrader: Upgrader - metrics?: Metrics -} - -export function noise (init: NoiseInit = {}): (components: NoiseComponents) => ConnectionEncrypter { - return (components: NoiseComponents) => new Noise(components, init) -} diff --git a/packages/connection-encrypter-noise/src/logger.ts b/packages/connection-encrypter-noise/src/logger.ts deleted file mode 100644 index 6a0a00672d..0000000000 --- a/packages/connection-encrypter-noise/src/logger.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { DUMP_SESSION_KEYS } from './constants.js' -import type { CipherState } from './protocol.js' -import type { KeyPair } from './types.js' -import type { Logger } from '@libp2p/interface' -import type { Uint8ArrayList } from 'uint8arraylist' - -export function logLocalStaticKeys (s: KeyPair | undefined, keyLogger: Logger): void { - if (!keyLogger.enabled || !DUMP_SESSION_KEYS) { - return - } - - if (s) { - keyLogger(`LOCAL_STATIC_PUBLIC_KEY ${uint8ArrayToString(s.publicKey, 'hex')}`) - keyLogger(`LOCAL_STATIC_PRIVATE_KEY ${uint8ArrayToString(s.privateKey, 'hex')}`) - } else { - keyLogger('Missing local static keys.') - } -} - -export function logLocalEphemeralKeys (e: KeyPair | undefined, keyLogger: Logger): void { - if (!keyLogger.enabled || !DUMP_SESSION_KEYS) { - return - } - - if (e) { - keyLogger(`LOCAL_PUBLIC_EPHEMERAL_KEY ${uint8ArrayToString(e.publicKey, 'hex')}`) - keyLogger(`LOCAL_PRIVATE_EPHEMERAL_KEY ${uint8ArrayToString(e.privateKey, 'hex')}`) - } else { - keyLogger('Missing local ephemeral keys.') - } -} - -export function logRemoteStaticKey (rs: Uint8Array | Uint8ArrayList | undefined, keyLogger: Logger): void { - if (!keyLogger.enabled || !DUMP_SESSION_KEYS) { - return - } - - if (rs) { - keyLogger(`REMOTE_STATIC_PUBLIC_KEY ${uint8ArrayToString(rs.subarray(), 'hex')}`) - } else { - keyLogger('Missing remote static public key.') - } -} - -export function logRemoteEphemeralKey (re: Uint8Array | Uint8ArrayList | undefined, keyLogger: Logger): void { - if (!keyLogger.enabled || !DUMP_SESSION_KEYS) { - return - } - - if (re) { - keyLogger(`REMOTE_EPHEMERAL_PUBLIC_KEY ${uint8ArrayToString(re.subarray(), 'hex')}`) - } else { - keyLogger('Missing remote ephemeral keys.') - } -} - -export function logCipherState (cs1: CipherState, cs2: CipherState, keyLogger: Logger): void { - if (!keyLogger.enabled || !DUMP_SESSION_KEYS) { - return - } - - keyLogger(`CIPHER_STATE_1 ${cs1.n.getUint64()} ${cs1.k && uint8ArrayToString(cs1.k, 'hex')}`) - keyLogger(`CIPHER_STATE_2 ${cs2.n.getUint64()} ${cs2.k && uint8ArrayToString(cs2.k, 'hex')}`) -} diff --git a/packages/connection-encrypter-noise/src/metrics.ts b/packages/connection-encrypter-noise/src/metrics.ts deleted file mode 100644 index 3733d41921..0000000000 --- a/packages/connection-encrypter-noise/src/metrics.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Counter, Metrics } from '@libp2p/interface' - -export type MetricsRegistry = Record - -export function registerMetrics (metrics: Metrics): MetricsRegistry { - return { - xxHandshakeSuccesses: metrics.registerCounter( - 'libp2p_noise_xxhandshake_successes_total', { - help: 'Total count of noise xxHandshakes successes_' - }), - - xxHandshakeErrors: metrics.registerCounter( - 'libp2p_noise_xxhandshake_error_total', { - help: 'Total count of noise xxHandshakes errors' - }), - - encryptedPackets: metrics.registerCounter( - 'libp2p_noise_encrypted_packets_total', { - help: 'Total count of noise encrypted packets successfully' - }), - - decryptedPackets: metrics.registerCounter( - 'libp2p_noise_decrypted_packets_total', { - help: 'Total count of noise decrypted packets' - }), - - decryptErrors: metrics.registerCounter( - 'libp2p_noise_decrypt_errors_total', { - help: 'Total count of noise decrypt errors' - }) - } -} diff --git a/packages/connection-encrypter-noise/src/noise.ts b/packages/connection-encrypter-noise/src/noise.ts deleted file mode 100644 index 0ae80f42ea..0000000000 --- a/packages/connection-encrypter-noise/src/noise.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { publicKeyFromProtobuf } from '@libp2p/crypto/keys' -import { InvalidCryptoExchangeError, serviceCapabilities } from '@libp2p/interface' -import { peerIdFromPublicKey } from '@libp2p/peer-id' -import { lpStream } from '@libp2p/utils' -import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' -import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants.js' -import { defaultCrypto } from './crypto/index.js' -import { wrapCrypto } from './crypto.js' -import { uint16BEDecode, uint16BEEncode } from './encoder.js' -import { registerMetrics } from './metrics.js' -import { performHandshakeInitiator, performHandshakeResponder } from './performHandshake.js' -import { toMessageStream } from './utils.ts' -import type { ICryptoInterface } from './crypto.js' -import type { NoiseComponents } from './index.js' -import type { MetricsRegistry } from './metrics.js' -import type { HandshakeResult, ICrypto, INoiseConnection, INoiseExtensions, KeyPair } from './types.js' -import type { SecuredConnection, PrivateKey, PublicKey, StreamMuxerFactory, SecureConnectionOptions, Logger, MessageStream } from '@libp2p/interface' -import type { LengthPrefixedStream } from '@libp2p/utils' - -export interface NoiseExtensions { - webtransportCerthashes: Uint8Array[] -} - -export interface NoiseInit { - /** - * x25519 private key, reuse for faster handshakes - */ - staticNoiseKey?: Uint8Array - extensions?: Partial - crypto?: ICryptoInterface - prologueBytes?: Uint8Array -} - -export class Noise implements INoiseConnection { - public protocol = '/noise' - public crypto: ICrypto - - private readonly prologue: Uint8Array - private readonly staticKey: KeyPair - private readonly extensions?: NoiseExtensions - private readonly metrics?: MetricsRegistry - private readonly components: NoiseComponents - private readonly log: Logger - - constructor (components: NoiseComponents, init: NoiseInit = {}) { - const { staticNoiseKey, extensions, crypto, prologueBytes } = init - const { metrics } = components - - this.components = components - this.log = components.logger.forComponent('libp2p:noise') - const _crypto = crypto ?? defaultCrypto - this.crypto = wrapCrypto(_crypto) - this.extensions = { - webtransportCerthashes: [], - ...extensions - } - this.metrics = metrics ? registerMetrics(metrics) : undefined - - if (staticNoiseKey) { - // accepts x25519 private key of length 32 - this.staticKey = _crypto.generateX25519KeyPairFromSeed(staticNoiseKey) - } else { - this.staticKey = _crypto.generateX25519KeyPair() - } - this.prologue = prologueBytes ?? uint8ArrayAlloc(0) - } - - readonly [Symbol.toStringTag] = '@libp2p/noise' - - readonly [serviceCapabilities]: string[] = [ - '@libp2p/connection-encryption', - '@libp2p/noise' - ] - - /** - * Encrypt outgoing data to the remote party (handshake as initiator) - * - * @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 - */ - async secureOutbound (connection: MessageStream, options?: SecureConnectionOptions): Promise> { - const log = connection.log?.newScope('noise') ?? this.log - const wrappedConnection = lpStream(connection, { - lengthEncoder: uint16BEEncode, - lengthDecoder: uint16BEDecode, - maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES - }) - - const handshake = await this.performHandshakeInitiator( - wrappedConnection, - this.components.privateKey, - log, - options?.remotePeer?.publicKey, - options - ) - const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey) - - return { - connection: toMessageStream(wrappedConnection.unwrap(), handshake, this.metrics), - remoteExtensions: handshake.payload.extensions, - remotePeer: peerIdFromPublicKey(publicKey), - streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers) - } - } - - private getStreamMuxer (protocols?: string[]): StreamMuxerFactory | undefined { - if (protocols == null || protocols.length === 0) { - return - } - - const streamMuxers = this.components.upgrader.getStreamMuxers() - - if (streamMuxers != null) { - for (const protocol of protocols) { - const streamMuxer = streamMuxers.get(protocol) - - if (streamMuxer != null) { - return streamMuxer - } - } - } - - if (protocols.length) { - throw new InvalidCryptoExchangeError('Early muxer negotiation was requested but the initiator and responder had no common muxers') - } - } - - /** - * Decrypt incoming data (handshake as responder). - * - * @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 - */ - async secureInbound (connection: MessageStream, options?: SecureConnectionOptions): Promise> { - const log = connection.log?.newScope('noise') ?? this.log - const wrappedConnection = lpStream(connection, { - lengthEncoder: uint16BEEncode, - lengthDecoder: uint16BEDecode, - maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES - }) - - const handshake = await this.performHandshakeResponder( - wrappedConnection, - this.components.privateKey, - log, - options?.remotePeer?.publicKey, - options - ) - const publicKey = publicKeyFromProtobuf(handshake.payload.identityKey) - - return { - connection: toMessageStream(wrappedConnection.unwrap(), handshake, this.metrics), - remoteExtensions: handshake.payload.extensions, - remotePeer: peerIdFromPublicKey(publicKey), - streamMuxer: options?.skipStreamMuxerNegotiation === true ? undefined : this.getStreamMuxer(handshake.payload.extensions?.streamMuxers) - } - } - - /** - * Perform XX handshake as initiator. - */ - private async performHandshakeInitiator ( - connection: LengthPrefixedStream, - // TODO: pass private key in noise constructor via Components - privateKey: PrivateKey, - log: Logger, - remoteIdentityKey?: PublicKey, - options?: SecureConnectionOptions - ): Promise { - let result: HandshakeResult - const streamMuxers = options?.skipStreamMuxerNegotiation === true ? [] : [...this.components.upgrader.getStreamMuxers().keys()] - - try { - result = await performHandshakeInitiator({ - connection, - privateKey, - remoteIdentityKey, - log: log.newScope('xxhandshake'), - crypto: this.crypto, - prologue: this.prologue, - s: this.staticKey, - extensions: { - streamMuxers, - webtransportCerthashes: [], - ...this.extensions - } - }, options) - this.metrics?.xxHandshakeSuccesses.increment() - } catch (e: unknown) { - this.metrics?.xxHandshakeErrors.increment() - throw e - } - - return result - } - - /** - * Perform XX handshake as responder. - */ - private async performHandshakeResponder ( - connection: LengthPrefixedStream, - privateKey: PrivateKey, - log: Logger, - remoteIdentityKey?: PublicKey, - options?: SecureConnectionOptions - ): Promise { - let result: HandshakeResult - const streamMuxers = options?.skipStreamMuxerNegotiation === true ? [] : [...this.components.upgrader.getStreamMuxers().keys()] - - try { - result = await performHandshakeResponder({ - connection, - privateKey, - remoteIdentityKey, - log: log.newScope('xxhandshake'), - crypto: this.crypto, - prologue: this.prologue, - s: this.staticKey, - extensions: { - streamMuxers, - webtransportCerthashes: [], - ...this.extensions - } - }, options) - this.metrics?.xxHandshakeSuccesses.increment() - } catch (e: unknown) { - this.metrics?.xxHandshakeErrors.increment() - throw e - } - - return result - } -} diff --git a/packages/connection-encrypter-noise/src/nonce.ts b/packages/connection-encrypter-noise/src/nonce.ts deleted file mode 100644 index 39c50b4d6c..0000000000 --- a/packages/connection-encrypter-noise/src/nonce.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' - -export const MIN_NONCE = 0 -// For performance reasons, the nonce is represented as a JS `number` -// Although JS `number` can safely represent integers up to 2 ** 53 - 1, we choose to only use -// 4 bytes to store the data for performance reason. -// This is a slight deviation from the noise spec, which describes the max nonce as 2 ** 64 - 2 -// The effect is that this implementation will need a new handshake to be performed after fewer messages are exchanged than other implementations with full uint64 nonces. -// this MAX_NONCE is still a large number of messages, so the practical effect of this is negligible. -export const MAX_NONCE = 0xffffffff - -const ERR_MAX_NONCE = 'Cipher state has reached maximum n, a new handshake must be performed' - -/** - * The nonce is an uint that's increased over time. - * Maintaining different representations help improve performance. - */ -export class Nonce { - private n: number - private readonly bytes: Uint8Array - private readonly view: DataView - - constructor (n = MIN_NONCE) { - this.n = n - this.bytes = uint8ArrayAlloc(12) - this.view = new DataView(this.bytes.buffer, this.bytes.byteOffset, this.bytes.byteLength) - this.view.setUint32(4, n, true) - } - - increment (): void { - this.n++ - // Even though we're treating the nonce as 8 bytes, RFC7539 specifies 12 bytes for a nonce. - this.view.setUint32(4, this.n, true) - } - - getBytes (): Uint8Array { - return this.bytes - } - - getUint64 (): number { - return this.n - } - - assertValue (): void { - if (this.n > MAX_NONCE) { - throw new Error(ERR_MAX_NONCE) - } - } -} diff --git a/packages/connection-encrypter-noise/src/performHandshake.ts b/packages/connection-encrypter-noise/src/performHandshake.ts deleted file mode 100644 index dc197ba978..0000000000 --- a/packages/connection-encrypter-noise/src/performHandshake.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { - logLocalStaticKeys, - logLocalEphemeralKeys, - logRemoteEphemeralKey, - logRemoteStaticKey, - logCipherState -} from './logger.js' -import { ZEROLEN, XXHandshakeState } from './protocol.js' -import { createHandshakePayload, decodeHandshakePayload } from './utils.js' -import type { HandshakeResult, HandshakeParams } from './types.js' -import type { AbortOptions } from '@libp2p/interface' - -export async function performHandshakeInitiator (init: HandshakeParams, options?: AbortOptions): Promise { - const { log, connection, crypto, privateKey, prologue, s, remoteIdentityKey, extensions } = init - - const payload = await createHandshakePayload(privateKey, s.publicKey, extensions) - const xx = new XXHandshakeState({ - crypto, - protocolName: 'Noise_XX_25519_ChaChaPoly_SHA256', - initiator: true, - prologue, - s - }) - - logLocalStaticKeys(xx.s, log) - log.trace('Stage 0 - Initiator starting to send first message.') - await connection.write(xx.writeMessageA(ZEROLEN), options) - log.trace('Stage 0 - Initiator finished sending first message.') - logLocalEphemeralKeys(xx.e, log) - - log.trace('Stage 1 - Initiator waiting to receive first message from responder...') - const plaintext = xx.readMessageB(await connection.read(options)) - log.trace('Stage 1 - Initiator received the message.') - logRemoteEphemeralKey(xx.re, log) - logRemoteStaticKey(xx.rs, log) - - log.trace("Initiator going to check remote's signature...") - const receivedPayload = await decodeHandshakePayload(plaintext, xx.rs, remoteIdentityKey) - log.trace('All good with the signature!') - - log.trace('Stage 2 - Initiator sending third handshake message.') - await connection.write(xx.writeMessageC(payload), options) - log.trace('Stage 2 - Initiator sent message with signed payload.') - - const [cs1, cs2] = xx.ss.split() - logCipherState(cs1, cs2, log) - - return { - payload: receivedPayload, - encrypt: (plaintext) => cs1.encryptWithAd(ZEROLEN, plaintext), - decrypt: (ciphertext, dst) => cs2.decryptWithAd(ZEROLEN, ciphertext, dst) - } -} - -export async function performHandshakeResponder (init: HandshakeParams, options?: AbortOptions): Promise { - const { log, connection, crypto, privateKey, prologue, s, remoteIdentityKey, extensions } = init - - const payload = await createHandshakePayload(privateKey, s.publicKey, extensions) - const xx = new XXHandshakeState({ - crypto, - protocolName: 'Noise_XX_25519_ChaChaPoly_SHA256', - initiator: false, - prologue, - s - }) - - logLocalStaticKeys(xx.s, log) - log.trace('Stage 0 - Responder waiting to receive first message.') - xx.readMessageA(await connection.read(options)) - log.trace('Stage 0 - Responder received first message.') - logRemoteEphemeralKey(xx.re, log) - - log.trace('Stage 1 - Responder sending out first message with signed payload and static key.') - await connection.write(xx.writeMessageB(payload), options) - log.trace('Stage 1 - Responder sent the second handshake message with signed payload.') - logLocalEphemeralKeys(xx.e, log) - - log.trace('Stage 2 - Responder waiting for third handshake message...') - const plaintext = xx.readMessageC(await connection.read(options)) - log.trace('Stage 2 - Responder received the message, finished handshake.') - const receivedPayload = await decodeHandshakePayload(plaintext, xx.rs, remoteIdentityKey) - - const [cs1, cs2] = xx.ss.split() - logCipherState(cs1, cs2, log) - - return { - payload: receivedPayload, - encrypt: (plaintext) => cs2.encryptWithAd(ZEROLEN, plaintext), - decrypt: (ciphertext, dst) => cs1.decryptWithAd(ZEROLEN, ciphertext, dst) - } -} diff --git a/packages/connection-encrypter-noise/src/proto/payload.proto b/packages/connection-encrypter-noise/src/proto/payload.proto deleted file mode 100644 index 5ee5490673..0000000000 --- a/packages/connection-encrypter-noise/src/proto/payload.proto +++ /dev/null @@ -1,12 +0,0 @@ -syntax = "proto3"; - -message NoiseExtensions { - repeated bytes webtransport_certhashes = 1; - repeated string stream_muxers = 2; -} - -message NoiseHandshakePayload { - bytes identity_key = 1; - bytes identity_sig = 2; - optional NoiseExtensions extensions = 4; -} diff --git a/packages/connection-encrypter-noise/src/proto/payload.ts b/packages/connection-encrypter-noise/src/proto/payload.ts deleted file mode 100644 index e5a79ff374..0000000000 --- a/packages/connection-encrypter-noise/src/proto/payload.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { decodeMessage, encodeMessage, MaxLengthError, message } from 'protons-runtime' -import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' -import type { Codec, DecodeOptions } from 'protons-runtime' -import type { Uint8ArrayList } from 'uint8arraylist' - -export interface NoiseExtensions { - webtransportCerthashes: Uint8Array[] - streamMuxers: string[] -} - -export namespace NoiseExtensions { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if (obj.webtransportCerthashes != null) { - for (const value of obj.webtransportCerthashes) { - w.uint32(10) - w.bytes(value) - } - } - - if (obj.streamMuxers != null) { - for (const value of obj.streamMuxers) { - w.uint32(18) - w.string(value) - } - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length, opts = {}) => { - const obj: any = { - webtransportCerthashes: [], - streamMuxers: [] - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: { - if (opts.limits?.webtransportCerthashes != null && obj.webtransportCerthashes.length === opts.limits.webtransportCerthashes) { - throw new MaxLengthError('Decode error - map field "webtransportCerthashes" had too many elements') - } - - obj.webtransportCerthashes.push(reader.bytes()) - break - } - case 2: { - if (opts.limits?.streamMuxers != null && obj.streamMuxers.length === opts.limits.streamMuxers) { - throw new MaxLengthError('Decode error - map field "streamMuxers" had too many elements') - } - - obj.streamMuxers.push(reader.string()) - break - } - default: { - reader.skipType(tag & 7) - break - } - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, NoiseExtensions.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): NoiseExtensions => { - return decodeMessage(buf, NoiseExtensions.codec(), opts) - } -} - -export interface NoiseHandshakePayload { - identityKey: Uint8Array - identitySig: Uint8Array - extensions?: NoiseExtensions -} - -export namespace NoiseHandshakePayload { - let _codec: Codec - - export const codec = (): Codec => { - if (_codec == null) { - _codec = message((obj, w, opts = {}) => { - if (opts.lengthDelimited !== false) { - w.fork() - } - - if ((obj.identityKey != null && obj.identityKey.byteLength > 0)) { - w.uint32(10) - w.bytes(obj.identityKey) - } - - if ((obj.identitySig != null && obj.identitySig.byteLength > 0)) { - w.uint32(18) - w.bytes(obj.identitySig) - } - - if (obj.extensions != null) { - w.uint32(34) - NoiseExtensions.codec().encode(obj.extensions, w) - } - - if (opts.lengthDelimited !== false) { - w.ldelim() - } - }, (reader, length, opts = {}) => { - const obj: any = { - identityKey: uint8ArrayAlloc(0), - identitySig: uint8ArrayAlloc(0) - } - - const end = length == null ? reader.len : reader.pos + length - - while (reader.pos < end) { - const tag = reader.uint32() - - switch (tag >>> 3) { - case 1: { - obj.identityKey = reader.bytes() - break - } - case 2: { - obj.identitySig = reader.bytes() - break - } - case 4: { - obj.extensions = NoiseExtensions.codec().decode(reader, reader.uint32(), { - limits: opts.limits?.extensions - }) - break - } - default: { - reader.skipType(tag & 7) - break - } - } - } - - return obj - }) - } - - return _codec - } - - export const encode = (obj: Partial): Uint8Array => { - return encodeMessage(obj, NoiseHandshakePayload.codec()) - } - - export const decode = (buf: Uint8Array | Uint8ArrayList, opts?: DecodeOptions): NoiseHandshakePayload => { - return decodeMessage(buf, NoiseHandshakePayload.codec(), opts) - } -} diff --git a/packages/connection-encrypter-noise/src/protocol.ts b/packages/connection-encrypter-noise/src/protocol.ts deleted file mode 100644 index a71ac3b177..0000000000 --- a/packages/connection-encrypter-noise/src/protocol.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { Uint8ArrayList } from 'uint8arraylist' -import { fromString as uint8ArrayFromString } from 'uint8arrays' -import { alloc as uint8ArrayAlloc } from 'uint8arrays/alloc' -import { InvalidCryptoExchangeError } from './errors.js' -import { Nonce } from './nonce.js' -import type { ICipherState, ISymmetricState, IHandshakeState, KeyPair, ICrypto } from './types.js' - -// Code in this file is a direct translation of a subset of the noise protocol https://noiseprotocol.org/noise.html, -// agnostic to libp2p's usage of noise - -export const ZEROLEN = uint8ArrayAlloc(0) - -interface ICipherStateWithKey extends ICipherState { - k: Uint8Array -} - -export class CipherState implements ICipherState { - public k?: Uint8Array - public n: Nonce - private readonly crypto: ICrypto - - constructor (crypto: ICrypto, k: Uint8Array | undefined = undefined, n = 0) { - this.crypto = crypto - this.k = k - this.n = new Nonce(n) - } - - public hasKey (): this is ICipherStateWithKey { - return Boolean(this.k) - } - - public encryptWithAd (ad: Uint8Array, plaintext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList { - if (!this.hasKey()) { - return plaintext - } - - this.n.assertValue() - const e = this.crypto.encrypt(plaintext, this.n.getBytes(), ad, this.k) - this.n.increment() - - return e - } - - public decryptWithAd (ad: Uint8Array, ciphertext: Uint8Array | Uint8ArrayList, dst?: Uint8Array): Uint8Array | Uint8ArrayList { - if (!this.hasKey()) { - return ciphertext - } - - this.n.assertValue() - const plaintext = this.crypto.decrypt(ciphertext, this.n.getBytes(), ad, this.k, dst) - this.n.increment() - - return plaintext - } -} - -export class SymmetricState implements ISymmetricState { - public cs: CipherState - public ck: Uint8Array - public h: Uint8Array - private readonly crypto: ICrypto - - constructor (crypto: ICrypto, protocolName: string) { - this.crypto = crypto - - const protocolNameBytes = uint8ArrayFromString(protocolName, 'utf-8') - this.h = hashProtocolName(crypto, protocolNameBytes) - - this.ck = this.h - this.cs = new CipherState(crypto) - } - - public mixKey (ikm: Uint8Array): void { - const [ck, tempK] = this.crypto.hkdf(this.ck, ikm) - this.ck = ck - this.cs = new CipherState(this.crypto, tempK) - } - - public mixHash (data: Uint8Array | Uint8ArrayList): void { - this.h = this.crypto.hash(new Uint8ArrayList(this.h, data)) - } - - public encryptAndHash (plaintext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList { - const ciphertext = this.cs.encryptWithAd(this.h, plaintext) - this.mixHash(ciphertext) - return ciphertext - } - - public decryptAndHash (ciphertext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList { - const plaintext = this.cs.decryptWithAd(this.h, ciphertext) - this.mixHash(ciphertext) - return plaintext - } - - public split (): [CipherState, CipherState] { - const [tempK1, tempK2] = this.crypto.hkdf(this.ck, ZEROLEN) - return [new CipherState(this.crypto, tempK1), new CipherState(this.crypto, tempK2)] - } -} - -// const MESSAGE_PATTERNS = ['e', 's', 'ee', 'es', 'se', 'ss'] as const -// type MessagePattern = Array - -export interface HandshakeStateInit { - crypto: ICrypto - protocolName: string - initiator: boolean - prologue: Uint8Array - s?: KeyPair - e?: KeyPair - rs?: Uint8Array | Uint8ArrayList - re?: Uint8Array | Uint8ArrayList -} - -export abstract class AbstractHandshakeState implements IHandshakeState { - public ss: SymmetricState - public s?: KeyPair - public e?: KeyPair - public rs?: Uint8Array | Uint8ArrayList - public re?: Uint8Array | Uint8ArrayList - public initiator: boolean - protected readonly crypto: ICrypto - - constructor (init: HandshakeStateInit) { - const { crypto, protocolName, prologue, initiator, s, e, rs, re } = init - this.crypto = crypto - this.ss = new SymmetricState(crypto, protocolName) - this.ss.mixHash(prologue) - this.initiator = initiator - this.s = s - this.e = e - this.rs = rs - this.re = re - } - - protected writeE (): Uint8Array { - if (this.e) { - throw new Error('ephemeral keypair is already set') - } - const e = this.crypto.generateKeypair() - this.ss.mixHash(e.publicKey) - this.e = e - return e.publicKey - } - - protected writeS (): Uint8Array | Uint8ArrayList { - if (!this.s) { - throw new Error('static keypair is not set') - } - return this.ss.encryptAndHash(this.s.publicKey) - } - - protected writeEE (): void { - if (!this.e) { - throw new Error('ephemeral keypair is not set') - } - if (!this.re) { - throw new Error('remote ephemeral public key is not set') - } - this.ss.mixKey(this.crypto.dh(this.e, this.re)) - } - - protected writeES (): void { - if (this.initiator) { - if (!this.e) { - throw new Error('ephemeral keypair is not set') - } - if (!this.rs) { - throw new Error('remote static public key is not set') - } - this.ss.mixKey(this.crypto.dh(this.e, this.rs)) - } else { - if (!this.s) { - throw new Error('static keypair is not set') - } - if (!this.re) { - throw new Error('remote ephemeral public key is not set') - } - this.ss.mixKey(this.crypto.dh(this.s, this.re)) - } - } - - protected writeSE (): void { - if (this.initiator) { - if (!this.s) { - throw new Error('static keypair is not set') - } - if (!this.re) { - throw new Error('remote ephemeral public key is not set') - } - this.ss.mixKey(this.crypto.dh(this.s, this.re)) - } else { - if (!this.e) { - throw new Error('ephemeral keypair is not set') - } - if (!this.rs) { - throw new Error('remote static public key is not set') - } - this.ss.mixKey(this.crypto.dh(this.e, this.rs)) - } - } - - protected readE (message: Uint8ArrayList, offset = 0): void { - if (this.re) { - throw new Error('remote ephemeral public key is already set') - } - if (message.byteLength < offset + 32) { - throw new Error('message is not long enough') - } - this.re = message.sublist(offset, offset + 32) - this.ss.mixHash(this.re) - } - - protected readS (message: Uint8ArrayList, offset = 0): number { - if (this.rs) { - throw new Error('remote static public key is already set') - } - const cipherLength = 32 + (this.ss.cs.hasKey() ? 16 : 0) - if (message.byteLength < offset + cipherLength) { - throw new Error('message is not long enough') - } - const temp = message.sublist(offset, offset + cipherLength) - this.rs = this.ss.decryptAndHash(temp) - return cipherLength - } - - protected readEE (): void { - this.writeEE() - } - - protected readES (): void { - this.writeES() - } - - protected readSE (): void { - this.writeSE() - } -} - -/** - * A IHandshakeState that's optimized for the XX pattern - */ -export class XXHandshakeState extends AbstractHandshakeState { - // e - writeMessageA (payload: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList { - return new Uint8ArrayList(this.writeE(), this.ss.encryptAndHash(payload)) - } - - // e, ee, s, es - writeMessageB (payload: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList { - const e = this.writeE() - this.writeEE() - const encS = this.writeS() - this.writeES() - - return new Uint8ArrayList(e, encS, this.ss.encryptAndHash(payload)) - } - - // s, se - writeMessageC (payload: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList { - const encS = this.writeS() - this.writeSE() - - return new Uint8ArrayList(encS, this.ss.encryptAndHash(payload)) - } - - // e - readMessageA (message: Uint8ArrayList): Uint8Array | Uint8ArrayList { - try { - this.readE(message) - - return this.ss.decryptAndHash(message.sublist(32)) - } catch (e) { - throw new InvalidCryptoExchangeError(`handshake stage 0 validation fail: ${(e as Error).message}`) - } - } - - // e, ee, s, es - readMessageB (message: Uint8ArrayList): Uint8Array | Uint8ArrayList { - try { - this.readE(message) - this.readEE() - const consumed = this.readS(message, 32) - this.readES() - - return this.ss.decryptAndHash(message.sublist(32 + consumed)) - } catch (e) { - throw new InvalidCryptoExchangeError(`handshake stage 1 validation fail: ${(e as Error).message}`) - } - } - - // s, se - readMessageC (message: Uint8ArrayList): Uint8Array | Uint8ArrayList { - try { - const consumed = this.readS(message) - this.readSE() - - return this.ss.decryptAndHash(message.sublist(consumed)) - } catch (e) { - throw new InvalidCryptoExchangeError(`handshake stage 2 validation fail: ${(e as Error).message}`) - } - } -} - -function hashProtocolName (crypto: ICrypto, protocolName: Uint8Array): Uint8Array { - if (protocolName.length <= 32) { - const h = uint8ArrayAlloc(32) - h.set(protocolName) - return h - } else { - return crypto.hash(protocolName) - } -} diff --git a/packages/connection-encrypter-noise/src/types.ts b/packages/connection-encrypter-noise/src/types.ts deleted file mode 100644 index 357780fdde..0000000000 --- a/packages/connection-encrypter-noise/src/types.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { Nonce } from './nonce.js' -import type { NoiseExtensions, NoiseHandshakePayload } from './proto/payload.js' -import type { ConnectionEncrypter, Logger, PrivateKey, PublicKey } from '@libp2p/interface' -import type { LengthPrefixedStream } from '@libp2p/utils' -import type { Uint8ArrayList } from 'uint8arraylist' - -/** - * Crypto functions defined by the noise protocol, abstracted from the - * underlying implementations - */ -export interface ICrypto { - generateKeypair(): KeyPair - dh(keypair: KeyPair, publicKey: Uint8Array | Uint8ArrayList): Uint8Array - encrypt(plaintext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array): Uint8ArrayList | Uint8Array - decrypt(ciphertext: Uint8Array | Uint8ArrayList, nonce: Uint8Array, ad: Uint8Array, k: Uint8Array, dst?: Uint8Array): Uint8ArrayList | Uint8Array - hash(data: Uint8Array | Uint8ArrayList): Uint8Array - hkdf(ck: Uint8Array, ikm: Uint8Array): [Uint8Array, Uint8Array, Uint8Array] -} - -export interface HandshakeParams { - log: Logger - connection: LengthPrefixedStream - crypto: ICrypto - privateKey: PrivateKey - prologue: Uint8Array - /** static keypair */ - s: KeyPair - remoteIdentityKey?: PublicKey - extensions?: NoiseExtensions -} - -export interface HandshakeResult { - payload: NoiseHandshakePayload - encrypt (plaintext: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList - decrypt (ciphertext: Uint8Array | Uint8ArrayList, dst?: Uint8Array): Uint8Array | Uint8ArrayList -} - -/** - * A CipherState object contains k and n variables, which it uses to encrypt and - * decrypt ciphertext. - * - * During the handshake phase each party has a single CipherState, but during - * the transport phase each party has two CipherState objects: one for sending, - * and one for receiving. - */ -export interface ICipherState { - /** - * A cipher key of 32 bytes (which may be empty). Empty is a special value - * which indicates k has not yet been initialized. - */ - k?: Uint8Array - /** - * An 8-byte (64-bit) unsigned integer nonce. - * - * For performance reasons, the nonce is represented as a Nonce object - * The nonce is treated as a uint64, even though the underlying `number` only - * has 52 safely-available bits. - */ - n: Nonce -} - -/** - * A SymmetricState object contains a CipherState plus ck and h variables. It is - * so-named because it encapsulates all the "symmetric crypto" used by Noise. - * - * During the handshake phase each party has a single SymmetricState, which can - * be deleted once the handshake is finished. - */ -export interface ISymmetricState { - cs: ICipherState - /** A chaining key of 32 bytes. */ - ck: Uint8Array - /** A hash output of 32 bytes. */ - h: Uint8Array -} - -/** - * A HandshakeState object contains a SymmetricState plus DH variables (s, e, - * rs, re) and a variable representing the handshake pattern. - * - * During the handshake phase each party has a single HandshakeState, which can - * be deleted once the handshake is finished. - */ -export interface IHandshakeState { - ss: ISymmetricState - /** The local static key pair */ - s?: KeyPair - /** The local ephemeral key pair */ - e?: KeyPair - /** The remote party's static public key */ - rs?: Uint8Array | Uint8ArrayList - /** The remote party's ephemeral public key */ - re?: Uint8Array | Uint8ArrayList -} - -export interface KeyPair { - publicKey: Uint8Array - privateKey: Uint8Array -} - -export interface INoiseExtensions { - webtransportCerthashes: Uint8Array[] -} - -export interface INoiseConnection extends ConnectionEncrypter { } diff --git a/packages/connection-encrypter-noise/src/utils.ts b/packages/connection-encrypter-noise/src/utils.ts deleted file mode 100644 index 73e8e1d1f2..0000000000 --- a/packages/connection-encrypter-noise/src/utils.ts +++ /dev/null @@ -1,221 +0,0 @@ -import { publicKeyFromProtobuf, publicKeyToProtobuf } from '@libp2p/crypto/keys' -import { StreamMessageEvent, UnexpectedPeerError } from '@libp2p/interface' -import { AbstractMessageStream, LengthPrefixedDecoder } from '@libp2p/utils' -import { Uint8ArrayList } from 'uint8arraylist' -import { concat as uint8ArrayConcat } from 'uint8arrays/concat' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { CHACHA_TAG_LENGTH, NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG } from './constants.ts' -import { uint16BEEncode, uint16BEDecode } from './encoder.ts' -import { NoiseHandshakePayload } from './proto/payload.js' -import type { MetricsRegistry } from './metrics.ts' -import type { NoiseExtensions } from './proto/payload.js' -import type { HandshakeResult } from './types.ts' -import type { AbortOptions, MessageStream, PrivateKey, PublicKey, StreamCloseEvent } from '@libp2p/interface' -import type { SendResult } from '@libp2p/utils' - -export async function createHandshakePayload ( - privateKey: PrivateKey, - staticPublicKey: Uint8Array | Uint8ArrayList, - extensions?: NoiseExtensions -): Promise { - const identitySig = await privateKey.sign(getSignaturePayload(staticPublicKey)) - - return NoiseHandshakePayload.encode({ - identityKey: publicKeyToProtobuf(privateKey.publicKey), - identitySig, - extensions - }) -} - -export async function decodeHandshakePayload ( - payloadBytes: Uint8Array | Uint8ArrayList, - remoteStaticKey?: Uint8Array | Uint8ArrayList, - remoteIdentityKey?: PublicKey -): Promise { - try { - const payload = NoiseHandshakePayload.decode(payloadBytes) - 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) { - throw new Error('Remote static does not exist') - } - - const signaturePayload = getSignaturePayload(remoteStaticKey) - - if (!(await publicKey.verify(signaturePayload, payload.identitySig))) { - throw new Error('Invalid payload signature') - } - - return payload - } catch (e) { - throw new UnexpectedPeerError((e as Error).message) - } -} - -export function getSignaturePayload (publicKey: Uint8Array | Uint8ArrayList): Uint8Array | Uint8ArrayList { - const prefix = uint8ArrayFromString('noise-libp2p-static-key:') - - if (publicKey instanceof Uint8Array) { - return uint8ArrayConcat([prefix, publicKey], prefix.length + publicKey.length) - } - - publicKey.prepend(prefix) - - return publicKey -} - -class EncryptedMessageStream extends AbstractMessageStream { - private stream: MessageStream - private handshake: HandshakeResult - private metrics?: MetricsRegistry - private decoder: LengthPrefixedDecoder - - constructor (stream: MessageStream, handshake: HandshakeResult, metrics?: MetricsRegistry) { - super({ - log: stream.log, - inactivityTimeout: stream.inactivityTimeout, - maxReadBufferLength: stream.maxReadBufferLength, - direction: stream.direction - }) - - this.stream = stream - this.handshake = handshake - this.metrics = metrics - this.decoder = new LengthPrefixedDecoder({ - lengthDecoder: uint16BEDecode, - maxBufferSize: 16 * 1024 * 1024, - encodingLength: () => 2 - }) - - const noiseOnMessageDecrypt = (evt: StreamMessageEvent): void => { - try { - for (const buf of this.decoder.decode(evt.data)) { - this.onData(this.decrypt(buf)) - } - } catch (err: any) { - this.abort(err) - } - } - this.stream.addEventListener('message', noiseOnMessageDecrypt) - - const noiseOnClose = (evt: StreamCloseEvent): void => { - if (evt.error != null) { - if (evt.local === true) { - this.abort(evt.error) - } else { - this.onRemoteReset() - } - } else { - this.onTransportClosed() - } - } - this.stream.addEventListener('close', noiseOnClose) - - const noiseOnDrain = (): void => { - this.safeDispatchEvent('drain') - } - this.stream.addEventListener('drain', noiseOnDrain) - - const noiseOnRemoteCloseWrite = (): void => { - this.onRemoteCloseWrite() - } - this.stream.addEventListener('remoteCloseWrite', noiseOnRemoteCloseWrite) - } - - encrypt (chunk: Uint8Array | Uint8ArrayList): Uint8ArrayList { - const output = new Uint8ArrayList() - - for (let i = 0; i < chunk.byteLength; i += NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG) { - let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG - if (end > chunk.byteLength) { - end = chunk.byteLength - } - - let data: Uint8Array | Uint8ArrayList - - if (chunk instanceof Uint8Array) { - data = this.handshake.encrypt(chunk.subarray(i, end)) - } else { - data = this.handshake.encrypt(chunk.sublist(i, end)) - } - - this.metrics?.encryptedPackets.increment() - - output.append(uint16BEEncode(data.byteLength)) - output.append(data) - } - - return output - } - - decrypt (chunk: Uint8Array | Uint8ArrayList): Uint8ArrayList { - const output = new Uint8ArrayList() - - for (let i = 0; i < chunk.byteLength; i += NOISE_MSG_MAX_LENGTH_BYTES) { - let end = i + NOISE_MSG_MAX_LENGTH_BYTES - if (end > chunk.byteLength) { - end = chunk.byteLength - } - - if (end - CHACHA_TAG_LENGTH < i) { - throw new Error('Invalid chunk') - } - - let encrypted: Uint8Array | Uint8ArrayList - - if (chunk instanceof Uint8Array) { - encrypted = chunk.subarray(i, end) - } else { - encrypted = chunk.sublist(i, end) - } - - // memory allocation is not cheap so reuse the encrypted Uint8Array - // see https://github.com/ChainSafe/js-libp2p-noise/pull/242#issue-1422126164 - // this is ok because chacha20 reads bytes one by one and don't reread after that - // it's also tested in https://github.com/ChainSafe/as-chacha20poly1305/pull/1/files#diff-25252846b58979dcaf4e41d47b3eadd7e4f335e7fb98da6c049b1f9cd011f381R48 - const dst = chunk.subarray(i, end - CHACHA_TAG_LENGTH) - try { - const plaintext = this.handshake.decrypt(encrypted, dst) - this.metrics?.decryptedPackets.increment() - - output.append(plaintext) - } catch (e) { - this.metrics?.decryptErrors.increment() - throw e - } - } - - return output - } - - close (options?: AbortOptions): Promise { - return this.stream.close(options) - } - - sendPause (): void { - this.stream.pause() - } - - sendResume (): void { - this.stream.resume() - } - - sendReset (err: Error): void { - this.stream.abort(err) - } - - sendData (data: Uint8ArrayList): SendResult { - return { - sentBytes: data.byteLength, - canSendMore: this.stream.send(this.encrypt(data)) - } - } -} - -export function toMessageStream (connection: MessageStream, handshake: HandshakeResult, metrics?: MetricsRegistry): MessageStream { - return new EncryptedMessageStream(connection, handshake, metrics) -} diff --git a/packages/connection-encrypter-noise/test/compliance.spec.ts b/packages/connection-encrypter-noise/test/compliance.spec.ts deleted file mode 100644 index fc458a5357..0000000000 --- a/packages/connection-encrypter-noise/test/compliance.spec.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { generateKeyPair } from '@libp2p/crypto/keys' -import tests from '@libp2p/interface-compliance-tests/connection-encryption' -import { defaultLogger } from '@libp2p/logger' -import { peerIdFromPrivateKey } from '@libp2p/peer-id' -import { stubInterface } from 'sinon-ts' -import { Noise } from '../src/noise.js' -import type { Upgrader } from '@libp2p/interface' - -describe('spec compliance tests', function () { - tests({ - async setup (opts) { - const privateKey = opts?.privateKey ?? await generateKeyPair('Ed25519') - const peerId = peerIdFromPrivateKey(privateKey) - - return new Noise({ - privateKey, - peerId, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }) - }, - async teardown () {} - }) -}) diff --git a/packages/connection-encrypter-noise/test/fixtures/peer.ts b/packages/connection-encrypter-noise/test/fixtures/peer.ts deleted file mode 100644 index a044c31c4f..0000000000 --- a/packages/connection-encrypter-noise/test/fixtures/peer.ts +++ /dev/null @@ -1,47 +0,0 @@ -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 = [{ - id: '12D3KooWH45PiqBjfnEfDfCD6TqJrpqTBJvQDwGHvjGpaWwms46D', - privKey: 'CAESYBtKXrMwawAARmLScynQUuSwi/gGSkwqDPxi15N3dqDHa4T4iWupkMe5oYGwGH3Hyfvd/QcgSTqg71oYZJadJ6prhPiJa6mQx7mhgbAYfcfJ+939ByBJOqDvWhhklp0nqg==', - pubKey: 'CAESIGuE+IlrqZDHuaGBsBh9x8n73f0HIEk6oO9aGGSWnSeq' -}, { - id: '12D3KooWP63uzL78BRMpkQ7augMdNi1h3VBrVWZucKjyhzGVaSi1', - privKey: 'CAESYPxO3SHyfc2578hDmfkGGBY255JjiLuVavJWy+9ivlpsxSyVKf36ipyRGL6szGzHuFs5ceEuuGVrPMg/rW2Ch1bFLJUp/fqKnJEYvqzMbMe4Wzlx4S64ZWs8yD+tbYKHVg==', - pubKey: 'CAESIMUslSn9+oqckRi+rMxsx7hbOXHhLrhlazzIP61tgodW' -}, { - id: '12D3KooWF85R7CM2Wikdtb2sjwnd24e1tgojf3MEWwizmVB8PA6U', - privKey: 'CAESYNXoQ5CnooE939AEqE2JJGPqvhoFJn0xP+j9KwjfOfDkTtPyfn2kJ1gn3uOYTcmoHFU1bbETNtRVuPMi1fmDmqFO0/J+faQnWCfe45hNyagcVTVtsRM21FW48yLV+YOaoQ==', - pubKey: 'CAESIE7T8n59pCdYJ97jmE3JqBxVNW2xEzbUVbjzItX5g5qh' -}, { - id: '12D3KooWPCofiCjhdtezP4eMnqBjjutFZNHjV39F5LWNrCvaLnzT', - privKey: 'CAESYLhUut01XPu+yIPbtZ3WnxOd26FYuTMRn/BbdFYsZE2KxueKRlo9yIAxmFReoNFUKztUU4G2aUiTbqDQaA6i0MDG54pGWj3IgDGYVF6g0VQrO1RTgbZpSJNuoNBoDqLQwA==', - pubKey: 'CAESIMbnikZaPciAMZhUXqDRVCs7VFOBtmlIk26g0GgOotDA' -}] - -export async function createPeerIdsFromFixtures (length: number): Promise> { - return Promise.all( - 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 privateKey = await generateKeyPair('Ed25519') - const id = peerIdFromPrivateKey(privateKey) - peerIds.push(id) - } - - return peerIds -} diff --git a/packages/connection-encrypter-noise/test/index.spec.ts b/packages/connection-encrypter-noise/test/index.spec.ts deleted file mode 100644 index 5d45cb7984..0000000000 --- a/packages/connection-encrypter-noise/test/index.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { generateKeyPair } from '@libp2p/crypto/keys' -import { defaultLogger } from '@libp2p/logger' -import { peerIdFromPrivateKey } from '@libp2p/peer-id' -import { lpStream, multiaddrConnectionPair } from '@libp2p/utils' -import { expect } from 'aegir/chai' -import sinon from 'sinon' -import { stubInterface } from 'sinon-ts' -import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string' -import { noise } from '../src/index.js' -import { Noise } from '../src/noise.js' -import type { Metrics, Upgrader } from '@libp2p/interface' - -function createCounterSpy (): ReturnType { - return sinon.spy({ - increment: () => {}, - reset: () => {} - }) -} - -describe('Index', () => { - it('should expose class with tag and required functions', async () => { - const privateKey = await generateKeyPair('Ed25519') - const peerId = peerIdFromPrivateKey(privateKey) - - const noiseInstance = noise()({ - privateKey, - peerId, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }) - expect(noiseInstance.protocol).to.equal('/noise') - expect(typeof (noiseInstance.secureInbound)).to.equal('function') - expect(typeof (noiseInstance.secureOutbound)).to.equal('function') - }) - - it('should collect metrics', async () => { - const metricsRegistry = new Map>() - const metrics = { - registerCounter: (name: string) => { - const counter = createCounterSpy() - metricsRegistry.set(name, counter) - return counter - } - } - - const privateKeyInit = await generateKeyPair('Ed25519') - const peerIdInit = peerIdFromPrivateKey(privateKeyInit) - const noiseInit = new Noise({ - privateKey: privateKeyInit, - peerId: peerIdInit, - logger: defaultLogger(), - metrics: metrics as any as Metrics, - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }) - - const privateKeyResp = await generateKeyPair('Ed25519') - const peerIdResp = peerIdFromPrivateKey(privateKeyResp) - const noiseResp = new Noise({ - privateKey: privateKeyResp, - peerId: peerIdResp, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: peerIdResp - }), - noiseResp.secureInbound(inboundConnection, { - remotePeer: peerIdInit - }) - ]) - const wrappedInbound = lpStream(inbound.connection) - const wrappedOutbound = lpStream(outbound.connection) - - await wrappedOutbound.write(uint8ArrayFromString('test')) - await wrappedInbound.read() - expect(metricsRegistry.get('libp2p_noise_xxhandshake_successes_total')?.increment.callCount).to.equal(1) - expect(metricsRegistry.get('libp2p_noise_xxhandshake_error_total')?.increment.callCount).to.equal(0) - expect(metricsRegistry.get('libp2p_noise_encrypted_packets_total')?.increment.callCount).to.equal(1) - expect(metricsRegistry.get('libp2p_noise_decrypt_errors_total')?.increment.callCount).to.equal(0) - }) -}) diff --git a/packages/connection-encrypter-noise/test/interop.ts b/packages/connection-encrypter-noise/test/interop.ts deleted file mode 100644 index 351ac18d54..0000000000 --- a/packages/connection-encrypter-noise/test/interop.ts +++ /dev/null @@ -1,113 +0,0 @@ -import fs from 'fs' -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 { tcp } from '@libp2p/tcp' -import { yamux } from '@libp2p/yamux' -import { multiaddr } from '@multiformats/multiaddr' -import { execa } from 'execa' -import { path as p2pd } from 'go-libp2p' -import { createLibp2p } from 'libp2p' -import pDefer from 'p-defer' -import { noise } from '../src/index.js' -import type { PrivateKey } from '@libp2p/interface' -import type { SpawnOptions, Daemon, DaemonFactory } from '@libp2p/interop' -import type { Libp2pOptions } from 'libp2p' - -async function createGoPeer (options: SpawnOptions): Promise { - const controlPort = Math.floor(Math.random() * (50000 - 10000 + 1)) + 10000 - const apiAddr = multiaddr(`/ip4/0.0.0.0/tcp/${controlPort}`) - - const log = logger(`go-libp2p:${controlPort}`) - - const opts = [ - `-listen=${apiAddr.toString()}`, - '-hostAddrs=/ip4/0.0.0.0/tcp/0' - ] - - if (options.encryption === 'noise') { - opts.push('-noise=true') - } - - if (options.key != null) { - opts.push(`-id=${options.key}`) - } - - const deferred = pDefer() - const proc = execa(p2pd(), opts) - - proc.stdout?.on('data', (buf: Buffer) => { - const str = buf.toString() - log(str) - - // daemon has started - if (str.includes('Control socket:')) { - deferred.resolve() - } - }) - - proc.stderr?.on('data', (buf) => { - log.error(buf.toString()) - }) - - await deferred.promise - - return { - client: createClient(apiAddr), - stop: async () => { - proc.kill() - } - } -} - -async function createJsPeer (options: SpawnOptions): Promise { - let privateKey: PrivateKey | undefined - - if (options.key != null) { - const keyFile = fs.readFileSync(options.key) - privateKey = privateKeyFromProtobuf(keyFile) - } - - const opts: Libp2pOptions = { - privateKey, - addresses: { - listen: ['/ip4/0.0.0.0/tcp/0'] - }, - transports: [tcp()], - streamMuxers: [yamux()], - connectionEncrypters: [noise()] - } - - const node = await createLibp2p(opts) - const server = createServer(multiaddr('/ip4/0.0.0.0/tcp/0'), node as any) - await server.start() - - return { - client: createClient(server.getMultiaddr()), - stop: async () => { - await server.stop() - await node.stop() - } - } -} - -async function main (): Promise { - const factory: DaemonFactory = { - async spawn (options: SpawnOptions) { - if (options.type === 'go') { - return createGoPeer(options) - } - - return createJsPeer(options) - } - } - - connectInteropTests(factory) -} - -main().catch(err => { - console.error(err) // eslint-disable-line no-console - process.exit(1) -}) diff --git a/packages/connection-encrypter-noise/test/muxers.spec.ts b/packages/connection-encrypter-noise/test/muxers.spec.ts deleted file mode 100644 index 3ccd6e6d4b..0000000000 --- a/packages/connection-encrypter-noise/test/muxers.spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { defaultLogger } from '@libp2p/logger' -import { multiaddrConnectionPair } from '@libp2p/utils' -import { expect } from 'aegir/chai' -import { stubInterface } from 'sinon-ts' -import { Noise } from '../src/noise.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' -import type { StreamMuxerFactory, Upgrader, SecureConnectionOptions, SecuredConnection, PeerId, PrivateKey } from '@libp2p/interface' -import type { StubbedInstance } from 'sinon-ts' - -describe('early muxer selection', () => { - let initUpgrader: StubbedInstance - let respUpgrader: StubbedInstance - let remotePeer: { peerId: PeerId, privateKey: PrivateKey } - let localPeer: { peerId: PeerId, privateKey: PrivateKey } - - beforeEach(async () => { - [localPeer, remotePeer] = await createPeerIdsFromFixtures(2) - - initUpgrader = stubInterface() - respUpgrader = stubInterface() - }) - - async function testMuxerNegotiation (outboundOpts?: SecureConnectionOptions, inboundOpts?: SecureConnectionOptions): Promise<[SecuredConnection, SecuredConnection]> { - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: initUpgrader - }) - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: respUpgrader - }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - - return Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId, - ...inboundOpts - }), - noiseResp.secureInbound(inboundConnection, { - remotePeer: localPeer.peerId, - ...outboundOpts - }) - ]) - } - - it('should negotiate early stream muxer', async () => { - const commonMuxer = '/common/muxer' - - initUpgrader.getStreamMuxers.returns(new Map([ - ['/other/muxer', stubInterface()], - [commonMuxer, stubInterface({ - protocol: commonMuxer - })] - ])) - respUpgrader.getStreamMuxers.returns(new Map([ - [commonMuxer, stubInterface({ - protocol: commonMuxer - })], - ['/another/muxer', stubInterface()] - ])) - - const [securedInbound, securedOutbound] = await testMuxerNegotiation() - - expect(securedInbound).to.have.nested.property('streamMuxer.protocol', commonMuxer) - expect(securedOutbound).to.have.nested.property('streamMuxer.protocol', commonMuxer) - }) - - it('should fail to negotiate early muxer when there are no common muxers', async () => { - initUpgrader.getStreamMuxers.returns(new Map([ - ['/other/muxer', stubInterface()], - ['/yet/other/muxer', stubInterface()] - ])) - respUpgrader.getStreamMuxers.returns(new Map([ - ['/another/muxer', stubInterface()], - ['/yet/another/muxer', stubInterface()] - ])) - - await expect(testMuxerNegotiation()).to.eventually.be.rejectedWith(/no common muxers/) - }) - - it('should not negotiate early muxer when no muxers are sent', async () => { - initUpgrader.getStreamMuxers.returns(new Map([])) - respUpgrader.getStreamMuxers.returns(new Map([])) - - const [securedInbound, securedOutbound] = await testMuxerNegotiation() - - expect(securedInbound).to.have.property('streamMuxer', undefined) - expect(securedOutbound).to.have.property('streamMuxer', undefined) - }) - - it('should skip selecting stream muxers', async () => { - const commonMuxer = '/common/muxer' - - initUpgrader.getStreamMuxers.returns(new Map([ - ['/other/muxer', stubInterface()], - [commonMuxer, stubInterface({ - protocol: commonMuxer - })] - ])) - respUpgrader.getStreamMuxers.returns(new Map([ - [commonMuxer, stubInterface({ - protocol: commonMuxer - })], - ['/another/muxer', stubInterface()] - ])) - - const [securedInbound, securedOutbound] = await testMuxerNegotiation({ - skipStreamMuxerNegotiation: true - }, { - skipStreamMuxerNegotiation: true - }) - - expect(securedInbound).to.have.property('streamMuxer', undefined) - expect(securedOutbound).to.have.property('streamMuxer', undefined) - }) - - it('should not select muxer if only initiator requires it', async () => { - const commonMuxer = '/common/muxer' - - initUpgrader.getStreamMuxers.returns(new Map([ - ['/other/muxer', stubInterface()], - [commonMuxer, stubInterface({ - protocol: commonMuxer - })] - ])) - respUpgrader.getStreamMuxers.returns(new Map([ - [commonMuxer, stubInterface({ - protocol: commonMuxer - })], - ['/another/muxer', stubInterface()] - ])) - - const [securedInbound, securedOutbound] = await testMuxerNegotiation({ - skipStreamMuxerNegotiation: true - }) - - expect(securedInbound).to.have.property('streamMuxer', undefined) - expect(securedOutbound).to.have.property('streamMuxer', undefined) - }) - - it('should not select muxer if only responder requires it', async () => { - const commonMuxer = '/common/muxer' - - initUpgrader.getStreamMuxers.returns(new Map([ - ['/other/muxer', stubInterface()], - [commonMuxer, stubInterface({ - protocol: commonMuxer - })] - ])) - respUpgrader.getStreamMuxers.returns(new Map([ - [commonMuxer, stubInterface({ - protocol: commonMuxer - })], - ['/another/muxer', stubInterface()] - ])) - - const [securedInbound, securedOutbound] = await testMuxerNegotiation({}, { - skipStreamMuxerNegotiation: true - }) - - expect(securedInbound).to.have.property('streamMuxer', undefined) - expect(securedOutbound).to.have.property('streamMuxer', undefined) - }) -}) diff --git a/packages/connection-encrypter-noise/test/noise.spec.ts b/packages/connection-encrypter-noise/test/noise.spec.ts deleted file mode 100644 index 27bdc08154..0000000000 --- a/packages/connection-encrypter-noise/test/noise.spec.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { Buffer } from 'buffer' -import { defaultLogger } from '@libp2p/logger' -import { lpStream, byteStream, multiaddrConnectionPair } from '@libp2p/utils' -import { assert, expect } from 'aegir/chai' -import { randomBytes } from 'iso-random-stream' -import sinon from 'sinon' -import { stubInterface } from 'sinon-ts' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -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 { StreamMuxerFactory, PeerId, PrivateKey, Upgrader } from '@libp2p/interface' - -describe('Noise', () => { - let remotePeer: { peerId: PeerId, privateKey: PrivateKey } - let localPeer: { peerId: PeerId, privateKey: PrivateKey } - const sandbox = sinon.createSandbox() - - before(async () => { - [localPeer, remotePeer] = await createPeerIdsFromFixtures(2) - }) - - afterEach(function () { - sandbox.restore() - }) - - it('should communicate through encrypted streams without noise pipes', async () => { - try { - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined, extensions: undefined }) - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined, extensions: undefined }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId - }), - noiseResp.secureInbound(inboundConnection, { - remotePeer: localPeer.peerId - }) - ]) - - expect(inbound).to.not.have.property('streamMuxer', 'inbound connection selected early muxer') - expect(outbound).to.not.have.property('streamMuxer', 'outbound connection selected early muxer') - - const wrappedInbound = lpStream(inbound.connection) - const wrappedOutbound = lpStream(outbound.connection) - - await wrappedOutbound.write(Buffer.from('test')) - const response = await wrappedInbound.read() - expect(uint8ArrayToString(response.slice())).equal('test') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should test large payloads', async function () { - this.timeout(10000) - try { - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined }) - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId - }), - noiseResp.secureInbound(inboundConnection, { - remotePeer: localPeer.peerId - }) - ]) - const wrappedInbound = byteStream(inbound.connection) - const wrappedOutbound = lpStream(outbound.connection) - - const largePlaintext = randomBytes(60000) - await wrappedOutbound.write(Buffer.from(largePlaintext)) - const response = await wrappedInbound.read({ - bytes: 60000 - }) - - expect(response.length).equals(largePlaintext.length) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should working without remote peer provided in incoming connection', async () => { - try { - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: staticKeysInitiator.privateKey }) - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: staticKeysResponder.privateKey }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId - }), - noiseResp.secureInbound(inboundConnection) - ]) - const wrappedInbound = lpStream(inbound.connection) - const wrappedOutbound = lpStream(outbound.connection) - - await wrappedOutbound.write(Buffer.from('test v2')) - const response = await wrappedInbound.read() - expect(uint8ArrayToString(response.slice())).equal('test v2') - - 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') - } - - 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) - } - }) - - it('should accept and return Noise extension from remote peer', async () => { - try { - const certhashInit = Buffer.from('certhash data from init') - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: staticKeysInitiator.privateKey, extensions: { webtransportCerthashes: [certhashInit] } }) - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - const certhashResp = Buffer.from('certhash data from response') - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: staticKeysResponder.privateKey, extensions: { webtransportCerthashes: [certhashResp] } }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId - }), - noiseResp.secureInbound(inboundConnection) - ]) - - assert(uint8ArrayEquals(inbound.remoteExtensions?.webtransportCerthashes[0] ?? new Uint8Array(), certhashInit)) - assert(uint8ArrayEquals(outbound.remoteExtensions?.webtransportCerthashes[0] ?? new Uint8Array(), certhashResp)) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should accept and return early muxer from remote peer', async () => { - try { - const streamMuxerProtocol = '/my-early-muxer' - const streamMuxer = stubInterface({ - protocol: streamMuxerProtocol - }) - const staticKeysInitiator = pureJsCrypto.generateX25519KeyPair() - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map([[streamMuxerProtocol, streamMuxer]]) - }) - }, { staticNoiseKey: staticKeysInitiator.privateKey }) - const staticKeysResponder = pureJsCrypto.generateX25519KeyPair() - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map([[streamMuxerProtocol, streamMuxer]]) - }) - }, { staticNoiseKey: staticKeysResponder.privateKey }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId - }), - noiseResp.secureInbound(inboundConnection) - ]) - - expect(inbound).to.have.nested.property('streamMuxer.protocol', streamMuxerProtocol, 'inbound connection did not select early muxer') - expect(outbound).to.have.nested.property('streamMuxer.protocol', streamMuxerProtocol, 'outbound connection did not select early muxer') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should accept a prologue', async () => { - try { - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined, crypto: pureJsCrypto, prologueBytes: Buffer.from('Some prologue') }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId - }), - noiseResp.secureInbound(inboundConnection, { - remotePeer: localPeer.peerId - }) - ]) - const wrappedInbound = lpStream(inbound.connection) - const wrappedOutbound = lpStream(outbound.connection) - - await wrappedOutbound.write(Buffer.from('test')) - const response = await wrappedInbound.read() - expect(uint8ArrayToString(response.slice())).equal('test') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it('should abort noise handshake', async () => { - const abortController = new AbortController() - abortController.abort() - - const noiseInit = new Noise({ - ...localPeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined, extensions: undefined }) - const noiseResp = new Noise({ - ...remotePeer, - logger: defaultLogger(), - upgrader: stubInterface({ - getStreamMuxers: () => new Map() - }) - }, { staticNoiseKey: undefined, extensions: undefined }) - - const [inboundConnection, outboundConnection] = multiaddrConnectionPair() - - await expect(Promise.all([ - noiseInit.secureOutbound(outboundConnection, { - remotePeer: remotePeer.peerId, - signal: abortController.signal - }), - noiseResp.secureInbound(inboundConnection, { - remotePeer: localPeer.peerId - }) - ])).to.eventually.be.rejected - .with.property('name', 'AbortError') - }) -}) diff --git a/packages/connection-encrypter-noise/test/performHandshake.spec.ts b/packages/connection-encrypter-noise/test/performHandshake.spec.ts deleted file mode 100644 index 96be395f3a..0000000000 --- a/packages/connection-encrypter-noise/test/performHandshake.spec.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { Buffer } from 'buffer' -import { defaultLogger } from '@libp2p/logger' -import { multiaddrConnectionPair, lpStream } from '@libp2p/utils' -import { assert, expect } from 'aegir/chai' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { defaultCrypto } from '../src/crypto/index.js' -import { wrapCrypto } from '../src/crypto.js' -import { performHandshakeInitiator, performHandshakeResponder } from '../src/performHandshake.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' -import type { PrivateKey, PeerId } from '@libp2p/interface' - -describe('performHandshake', () => { - 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') } - }) - - it('should propose, exchange and finish handshake', async () => { - const duplex = multiaddrConnectionPair() - const connectionInitiator = lpStream(duplex[0]) - const connectionResponder = lpStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = defaultCrypto.generateX25519KeyPair() - const staticKeysResponder = defaultCrypto.generateX25519KeyPair() - - const [initiator, responder] = await Promise.all([ - performHandshakeInitiator({ - log: defaultLogger().forComponent('test'), - connection: connectionInitiator, - crypto: wrapCrypto(defaultCrypto), - privateKey: peerA.privateKey, - prologue, - remoteIdentityKey: peerB.privateKey.publicKey, - s: staticKeysInitiator - }), - performHandshakeResponder({ - log: defaultLogger().forComponent('test'), - connection: connectionResponder, - crypto: wrapCrypto(defaultCrypto), - privateKey: peerB.privateKey, - prologue, - remoteIdentityKey: peerA.privateKey.publicKey, - s: staticKeysResponder - }) - ]) - - // Test encryption and decryption - const encrypted = initiator.encrypt(Buffer.from('encrypt this')) - const decrypted = responder.decrypt(encrypted) - assert(uint8ArrayEquals(decrypted.subarray(), Buffer.from('encrypt this'))) - }) - - it('Initiator should fail to exchange handshake if given wrong public key in payload', async () => { - try { - const duplex = multiaddrConnectionPair() - const connectionInitiator = lpStream(duplex[0]) - const connectionResponder = lpStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = defaultCrypto.generateX25519KeyPair() - const staticKeysResponder = defaultCrypto.generateX25519KeyPair() - - await Promise.all([ - performHandshakeInitiator({ - log: defaultLogger().forComponent('test'), - connection: connectionInitiator, - crypto: wrapCrypto(defaultCrypto), - privateKey: peerA.privateKey, - prologue, - remoteIdentityKey: fakePeer.privateKey.publicKey, // <----- look here - s: staticKeysInitiator - }), - performHandshakeResponder({ - log: defaultLogger().forComponent('test'), - connection: connectionResponder, - crypto: wrapCrypto(defaultCrypto), - privateKey: peerB.privateKey, - prologue, - remoteIdentityKey: peerA.privateKey.publicKey, - s: staticKeysResponder - }) - ]) - - assert(false, 'Should throw exception') - } catch (e) { - expect((e as Error).message).equals(`Payload identity key ${peerB.privateKey.publicKey} does not match expected remote identity key ${fakePeer.privateKey.publicKey}`) - } - }) - - it('Responder should fail to exchange handshake if given wrong public key in payload', async () => { - try { - const duplex = multiaddrConnectionPair() - const connectionInitiator = lpStream(duplex[0]) - const connectionResponder = lpStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = defaultCrypto.generateX25519KeyPair() - const staticKeysResponder = defaultCrypto.generateX25519KeyPair() - - await Promise.all([ - performHandshakeInitiator({ - log: defaultLogger().forComponent('test'), - connection: connectionInitiator, - crypto: wrapCrypto(defaultCrypto), - privateKey: peerA.privateKey, - prologue, - remoteIdentityKey: peerB.privateKey.publicKey, - s: staticKeysInitiator - }), - performHandshakeResponder({ - log: defaultLogger().forComponent('test'), - connection: connectionResponder, - crypto: wrapCrypto(defaultCrypto), - privateKey: peerB.privateKey, - prologue, - remoteIdentityKey: fakePeer.privateKey.publicKey, - s: staticKeysResponder - }) - ]) - - assert(false, 'Should throw exception') - } catch (e) { - 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/packages/connection-encrypter-noise/test/protocol.spec.ts b/packages/connection-encrypter-noise/test/protocol.spec.ts deleted file mode 100644 index 017b0cae12..0000000000 --- a/packages/connection-encrypter-noise/test/protocol.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { Buffer } from 'buffer' -import { expect, assert } from 'aegir/chai' -import { Uint8ArrayList } from 'uint8arraylist' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { toString as uint8ArrayToString } from 'uint8arrays/to-string' -import { pureJsCrypto } from '../src/crypto/js.js' -import { wrapCrypto } from '../src/crypto.js' -import { XXHandshakeState, ZEROLEN } from '../src/protocol.js' -import type { CipherState, SymmetricState } from '../src/protocol.js' - -describe('XXHandshakeState', () => { - const prologue = Buffer.alloc(0) - const protocolName = 'Noise_XX_25519_ChaChaPoly_SHA256' - - it('Test creating new XX session', async () => { - try { - // eslint-disable-next-line no-new - new XXHandshakeState({ crypto: wrapCrypto(pureJsCrypto), protocolName, initiator: true, prologue }) - } catch (e) { - assert(false, (e as Error).message) - } - }) - - it('Test get HKDF', () => { - const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex') - const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex') - const ck = Buffer.alloc(32) - ckBytes.copy(ck) - - const [k1, k2, k3] = pureJsCrypto.getHKDF(ck, ikm) - expect(uint8ArrayToString(k1, 'hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914') - expect(uint8ArrayToString(k2, 'hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa') - expect(uint8ArrayToString(k3, 'hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68') - }) - - interface ProtocolHandshakeResult { ss: SymmetricState, cs1: CipherState, cs2: CipherState } - async function doHandshake (): Promise<{ nsInit: ProtocolHandshakeResult, nsResp: ProtocolHandshakeResult }> { - const kpInit = pureJsCrypto.generateX25519KeyPair() - const kpResp = pureJsCrypto.generateX25519KeyPair() - - // initiator: new XX noise session - const nsInit = new XXHandshakeState({ crypto: wrapCrypto(pureJsCrypto), protocolName, prologue, initiator: true, s: kpInit }) - // responder: new XX noise session - const nsResp = new XXHandshakeState({ crypto: wrapCrypto(pureJsCrypto), protocolName, prologue, initiator: false, s: kpResp }) - - /* STAGE 0 */ - - // initiator sends message - // responder receives message - nsResp.readMessageA(new Uint8ArrayList(nsInit.writeMessageA(ZEROLEN))) - - /* STAGE 1 */ - - // responder sends message - // initiator receives message - nsInit.readMessageB(new Uint8ArrayList(nsResp.writeMessageB(ZEROLEN))) - - /* STAGE 2 */ - - // initiator sends message - // responder receives message - nsResp.readMessageC(new Uint8ArrayList(nsInit.writeMessageC(ZEROLEN))) - - const nsInitSplit = nsInit.ss.split() - const nsRespSplit = nsResp.ss.split() - - assert(uint8ArrayEquals(nsInitSplit[0].k!, nsRespSplit[0].k!)) - - assert(uint8ArrayEquals(nsInitSplit[1].k!, nsRespSplit[1].k!)) - - return { - nsInit: { ss: nsInit.ss, cs1: nsInitSplit[0], cs2: nsInitSplit[1] }, - nsResp: { ss: nsResp.ss, cs1: nsRespSplit[0], cs2: nsRespSplit[1] } - } - } - - it('Test symmetric encrypt and decrypt', async () => { - try { - const { nsInit, nsResp } = await doHandshake() - const ad = Buffer.from('authenticated') - const message = Buffer.from('HelloCrypto') - - const ciphertext = nsInit.cs1.encryptWithAd(ad, message) - assert(!uint8ArrayEquals(Buffer.from('HelloCrypto'), ciphertext.subarray()), 'Encrypted message should not be same as plaintext.') - const decrypted = nsResp.cs1.decryptWithAd(ad, ciphertext) - - assert(uint8ArrayEquals(Buffer.from('HelloCrypto'), decrypted.subarray()), 'Decrypted text not equal to original message.') - } catch (e) { - assert(false, (e as Error).message) - } - }) - - it('Test multiple messages encryption and decryption', async () => { - const { nsInit, nsResp } = await doHandshake() - const ad = Buffer.from('authenticated') - - for (let i = 0; i < 50; i++) { - const strMessage = 'ethereum' + String(i) - const message = Buffer.from(strMessage) - { - const encrypted = nsInit.cs1.encryptWithAd(ad, message) - const decrypted = nsResp.cs1.decryptWithAd(ad, encrypted) - assert.equal(strMessage, uint8ArrayToString(decrypted.subarray(), 'utf8'), 'Decrypted text not equal to original message.') - } - { - const encrypted = nsResp.cs2.encryptWithAd(ad, message) - const decrypted = nsInit.cs2.decryptWithAd(ad, encrypted) - assert.equal(strMessage, uint8ArrayToString(decrypted.subarray(), 'utf8'), 'Decrypted text not equal to original message.') - } - } - }) -}) diff --git a/packages/connection-encrypter-noise/test/utils.ts b/packages/connection-encrypter-noise/test/utils.ts deleted file mode 100644 index c342022a1d..0000000000 --- a/packages/connection-encrypter-noise/test/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { keys } from '@libp2p/crypto' -import type { PrivateKey } from '@libp2p/interface' - -export async function generateEd25519Keys (): Promise { - return keys.generateKeyPair('Ed25519', 32) -} diff --git a/packages/connection-encrypter-noise/tsconfig.json b/packages/connection-encrypter-noise/tsconfig.json deleted file mode 100644 index 255a02b857..0000000000 --- a/packages/connection-encrypter-noise/tsconfig.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../crypto" - }, - { - "path": "../interface" - }, - { - "path": "../interface-compliance-tests" - }, - { - "path": "../interop" - }, - { - "path": "../libp2p" - }, - { - "path": "../libp2p-daemon-client" - }, - { - "path": "../libp2p-daemon-server" - }, - { - "path": "../logger" - }, - { - "path": "../peer-id" - }, - { - "path": "../stream-multiplexer-yamux" - }, - { - "path": "../transport-tcp" - }, - { - "path": "../utils" - } - ] -} diff --git a/packages/connection-encrypter-noise/typedoc.json b/packages/connection-encrypter-noise/typedoc.json deleted file mode 100644 index db0b0747ef..0000000000 --- a/packages/connection-encrypter-noise/typedoc.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "readme": "none", - "entryPoints": [ - "./src/index.ts" - ] -} diff --git a/packages/integration-tests/.aegir.js b/packages/integration-tests/.aegir.js index 4e33586886..bbd789eb5c 100644 --- a/packages/integration-tests/.aegir.js +++ b/packages/integration-tests/.aegir.js @@ -9,8 +9,8 @@ export default { test: { before: async () => { // use dynamic import because we only want to reference these files during the test run, e.g. after building - const { noise } = await import('@libp2p/noise') - const { yamux } = await import('@libp2p/yamux') + const { noise } = await import('@chainsafe/libp2p-noise') + const { yamux } = await import('@chainsafe/libp2p-yamux') const { WebSockets, WebRTCDirect } = await import('@multiformats/multiaddr-matcher') const { webSockets } = await import('@libp2p/websockets') const { mplex } = await import('@libp2p/mplex') diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f2804f90cb..89632d4bde 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -38,7 +38,7 @@ "@libp2p/mdns": "^12.0.1", "@libp2p/memory": "^2.0.1", "@libp2p/mplex": "^12.0.1", - "@libp2p/noise": "^17.0.1", + "@chainsafe/libp2p-noise": "^17.0.0", "@libp2p/peer-collections": "^7.0.1", "@libp2p/peer-id": "^6.0.1", "@libp2p/ping": "^3.0.1", @@ -49,7 +49,7 @@ "@libp2p/webrtc": "^6.0.1", "@libp2p/websockets": "^10.0.1", "@libp2p/webtransport": "^6.0.1", - "@libp2p/yamux": "^8.0.1", + "@chainsafe/libp2p-yamux": "^8.0.0", "@multiformats/dns": "^1.0.6", "@multiformats/multiaddr": "^13.0.1", "@multiformats/multiaddr-matcher": "^3.0.1", diff --git a/packages/integration-tests/test/circuit-relay-discovery.node.ts b/packages/integration-tests/test/circuit-relay-discovery.node.ts index e9b962a239..ce08da913c 100644 --- a/packages/integration-tests/test/circuit-relay-discovery.node.ts +++ b/packages/integration-tests/test/circuit-relay-discovery.node.ts @@ -1,5 +1,6 @@ /* eslint-env mocha */ +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { stop } from '@libp2p/interface' @@ -7,7 +8,6 @@ import { kadDHT, passthroughMapper } from '@libp2p/kad-dht' import { ping } from '@libp2p/ping' import { plaintext } from '@libp2p/plaintext' import { tcp } from '@libp2p/tcp' -import { yamux } from '@libp2p/yamux' import { expect } from 'aegir/chai' import { createLibp2p } from 'libp2p' import pDefer from 'p-defer' diff --git a/packages/integration-tests/test/circuit-relay-discovery.spec.ts b/packages/integration-tests/test/circuit-relay-discovery.spec.ts index ecad988847..a261fbb39c 100644 --- a/packages/integration-tests/test/circuit-relay-discovery.spec.ts +++ b/packages/integration-tests/test/circuit-relay-discovery.spec.ts @@ -1,15 +1,15 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ['error', 6] */ +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { stop } from '@libp2p/interface' import { mplex } from '@libp2p/mplex' -import { noise } from '@libp2p/noise' import { plaintext } from '@libp2p/plaintext' import { webSockets } from '@libp2p/websockets' import { webTransport } from '@libp2p/webtransport' -import { yamux } from '@libp2p/yamux' import { multiaddr } from '@multiformats/multiaddr' import { WebSockets, WebTransport } from '@multiformats/multiaddr-matcher' import { createLibp2p } from 'libp2p' diff --git a/packages/integration-tests/test/circuit-relay.node.ts b/packages/integration-tests/test/circuit-relay.node.ts index 3622ae1651..d97d1844bc 100644 --- a/packages/integration-tests/test/circuit-relay.node.ts +++ b/packages/integration-tests/test/circuit-relay.node.ts @@ -1,6 +1,7 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ['error', 6] */ +import { yamux } from '@chainsafe/libp2p-yamux' import { RELAY_V2_HOP_CODEC, circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { mplex } from '@libp2p/mplex' @@ -8,7 +9,6 @@ import { PeerSet } from '@libp2p/peer-collections' import { plaintext } from '@libp2p/plaintext' import { tcp } from '@libp2p/tcp' import { echo } from '@libp2p/utils' -import { yamux } from '@libp2p/yamux' import { CODE_P2P, multiaddr } from '@multiformats/multiaddr' import { Circuit } from '@multiformats/multiaddr-matcher' import { expect } from 'aegir/chai' diff --git a/packages/integration-tests/test/circuit-relay.spec.ts b/packages/integration-tests/test/circuit-relay.spec.ts index 20c36d8cd9..eace613484 100644 --- a/packages/integration-tests/test/circuit-relay.spec.ts +++ b/packages/integration-tests/test/circuit-relay.spec.ts @@ -1,6 +1,7 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ['error', 6] */ +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { stop } from '@libp2p/interface' @@ -8,7 +9,6 @@ import { prefixLogger } from '@libp2p/logger' import { mplex } from '@libp2p/mplex' import { plaintext } from '@libp2p/plaintext' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import { Circuit } from '@multiformats/multiaddr-matcher' import { expect } from 'aegir/chai' import { createLibp2p } from 'libp2p' diff --git a/packages/integration-tests/test/compliance/transport/circuit-relay.spec.ts b/packages/integration-tests/test/compliance/transport/circuit-relay.spec.ts index 485a90b96a..70f87d6590 100644 --- a/packages/integration-tests/test/compliance/transport/circuit-relay.spec.ts +++ b/packages/integration-tests/test/compliance/transport/circuit-relay.spec.ts @@ -1,12 +1,12 @@ /* eslint-env mocha */ +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import tests from '@libp2p/interface-compliance-tests/transport' import { prefixLogger } from '@libp2p/logger' -import { noise } from '@libp2p/noise' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import { CODE_P2P_CIRCUIT } from '@multiformats/multiaddr' import { Circuit, P2P } from '@multiformats/multiaddr-matcher' import { and, fmt, code } from '@multiformats/multiaddr-matcher/utils' diff --git a/packages/integration-tests/test/compliance/transport/memory.spec.ts b/packages/integration-tests/test/compliance/transport/memory.spec.ts index 0b42112cd1..b31a615443 100644 --- a/packages/integration-tests/test/compliance/transport/memory.spec.ts +++ b/packages/integration-tests/test/compliance/transport/memory.spec.ts @@ -1,7 +1,7 @@ +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import tests from '@libp2p/interface-compliance-tests/transport' import { memory } from '@libp2p/memory' -import { noise } from '@libp2p/noise' -import { yamux } from '@libp2p/yamux' import { Memory } from '@multiformats/multiaddr-matcher' describe('memory transport interface compliance tests', () => { diff --git a/packages/integration-tests/test/compliance/transport/tcp.spec.ts b/packages/integration-tests/test/compliance/transport/tcp.spec.ts index 447a682319..031b4e61ae 100644 --- a/packages/integration-tests/test/compliance/transport/tcp.spec.ts +++ b/packages/integration-tests/test/compliance/transport/tcp.spec.ts @@ -1,7 +1,7 @@ +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import tests from '@libp2p/interface-compliance-tests/transport' -import { noise } from '@libp2p/noise' import { tcp } from '@libp2p/tcp' -import { yamux } from '@libp2p/yamux' import { TCP } from '@multiformats/multiaddr-matcher' import { isBrowser, isWebWorker } from 'wherearewe' diff --git a/packages/integration-tests/test/compliance/transport/webrtc.spec.ts b/packages/integration-tests/test/compliance/transport/webrtc.spec.ts index a9cec94fa6..0a917d9cbc 100644 --- a/packages/integration-tests/test/compliance/transport/webrtc.spec.ts +++ b/packages/integration-tests/test/compliance/transport/webrtc.spec.ts @@ -1,13 +1,13 @@ /* eslint-env mocha */ +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import tests from '@libp2p/interface-compliance-tests/transport' -import { noise } from '@libp2p/noise' import { ping } from '@libp2p/ping' import { webRTC } from '@libp2p/webrtc' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import { WebRTC } from '@multiformats/multiaddr-matcher' import { isWebWorker } from 'wherearewe' diff --git a/packages/integration-tests/test/compliance/transport/websockets.spec.ts b/packages/integration-tests/test/compliance/transport/websockets.spec.ts index 41523364cd..a8fe2423ef 100644 --- a/packages/integration-tests/test/compliance/transport/websockets.spec.ts +++ b/packages/integration-tests/test/compliance/transport/websockets.spec.ts @@ -1,9 +1,9 @@ /* eslint-env mocha */ +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import tests from '@libp2p/interface-compliance-tests/transport' -import { noise } from '@libp2p/noise' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import { multiaddr } from '@multiformats/multiaddr' import { WebSockets } from '@multiformats/multiaddr-matcher' import { isElectronMain, isNode } from 'wherearewe' diff --git a/packages/integration-tests/test/dht.node.ts b/packages/integration-tests/test/dht.node.ts index fc38af58d5..cf52542d4a 100644 --- a/packages/integration-tests/test/dht.node.ts +++ b/packages/integration-tests/test/dht.node.ts @@ -1,5 +1,6 @@ /* eslint-env mocha */ +import { yamux } from '@chainsafe/libp2p-yamux' import { identify } from '@libp2p/identify' import { start, stop } from '@libp2p/interface' import { kadDHT, passthroughMapper } from '@libp2p/kad-dht' @@ -7,7 +8,6 @@ import { mplex } from '@libp2p/mplex' import { ping } from '@libp2p/ping' import { plaintext } from '@libp2p/plaintext' import { tcp } from '@libp2p/tcp' -import { yamux } from '@libp2p/yamux' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' import { createLibp2p } from 'libp2p' diff --git a/packages/integration-tests/test/fixtures/base-options.browser.ts b/packages/integration-tests/test/fixtures/base-options.browser.ts index e00bfe0b94..d83c5af3dd 100644 --- a/packages/integration-tests/test/fixtures/base-options.browser.ts +++ b/packages/integration-tests/test/fixtures/base-options.browser.ts @@ -1,10 +1,10 @@ +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { mplex } from '@libp2p/mplex' import { plaintext } from '@libp2p/plaintext' import { webRTC } from '@libp2p/webrtc' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import { isWebWorker } from 'wherearewe' import type { ServiceMap } from '@libp2p/interface' import type { Libp2pOptions } from 'libp2p' diff --git a/packages/integration-tests/test/fixtures/base-options.ts b/packages/integration-tests/test/fixtures/base-options.ts index 0b0d2d7e52..27613b8ba2 100644 --- a/packages/integration-tests/test/fixtures/base-options.ts +++ b/packages/integration-tests/test/fixtures/base-options.ts @@ -1,3 +1,4 @@ +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { mplex } from '@libp2p/mplex' @@ -5,7 +6,6 @@ import { plaintext } from '@libp2p/plaintext' import { tcp } from '@libp2p/tcp' import { webRTC } from '@libp2p/webrtc' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import type { ServiceMap } from '@libp2p/interface' import type { Libp2pOptions } from 'libp2p' diff --git a/packages/integration-tests/test/fixtures/create-peers.ts b/packages/integration-tests/test/fixtures/create-peers.ts index 5eda5ca6ab..8f2e647920 100644 --- a/packages/integration-tests/test/fixtures/create-peers.ts +++ b/packages/integration-tests/test/fixtures/create-peers.ts @@ -1,9 +1,9 @@ /* eslint-env mocha */ +import { yamux } from '@chainsafe/libp2p-yamux' import { echo } from '@libp2p/echo' import { memory } from '@libp2p/memory' import { plaintext } from '@libp2p/plaintext' -import { yamux } from '@libp2p/yamux' import { createLibp2p } from 'libp2p' import type { Echo } from '@libp2p/echo' import type { Libp2p, ServiceMap } from '@libp2p/interface' diff --git a/packages/integration-tests/test/interop.ts b/packages/integration-tests/test/interop.ts index 37c9311aec..38873d2ac4 100644 --- a/packages/integration-tests/test/interop.ts +++ b/packages/integration-tests/test/interop.ts @@ -1,4 +1,6 @@ import fs from 'fs' +import { noise } from '@chainsafe/libp2p-noise' +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { privateKeyFromProtobuf } from '@libp2p/crypto/keys' import { createClient } from '@libp2p/daemon-client' @@ -10,13 +12,11 @@ import { UnsupportedError, interopTests } from '@libp2p/interop' import { kadDHT, passthroughMapper } from '@libp2p/kad-dht' import { logger } from '@libp2p/logger' import { mplex } from '@libp2p/mplex' -import { noise } from '@libp2p/noise' import { ping } from '@libp2p/ping' import { plaintext } from '@libp2p/plaintext' import { tcp } from '@libp2p/tcp' import { tls } from '@libp2p/tls' import { webRTCDirect } from '@libp2p/webrtc' -import { yamux } from '@libp2p/yamux' import { multiaddr } from '@multiformats/multiaddr' import { execa } from 'execa' import { path as p2pd } from 'go-libp2p' diff --git a/packages/integration-tests/test/webrtc-private-to-private.spec.ts b/packages/integration-tests/test/webrtc-private-to-private.spec.ts index 1da59d3b70..ea3b35e592 100644 --- a/packages/integration-tests/test/webrtc-private-to-private.spec.ts +++ b/packages/integration-tests/test/webrtc-private-to-private.spec.ts @@ -1,13 +1,13 @@ /* eslint-env mocha */ /* eslint max-nested-callbacks: ['error', 6] */ +import { yamux } from '@chainsafe/libp2p-yamux' import { circuitRelayTransport } from '@libp2p/circuit-relay-v2' import { identify } from '@libp2p/identify' import { stop } from '@libp2p/interface' import { plaintext } from '@libp2p/plaintext' import { webRTC, webRTCDirect } from '@libp2p/webrtc' import { webSockets } from '@libp2p/websockets' -import { yamux } from '@libp2p/yamux' import { Circuit, WebRTC } from '@multiformats/multiaddr-matcher' import { expect } from 'aegir/chai' import { createLibp2p } from 'libp2p' diff --git a/packages/integration-tests/tsconfig.json b/packages/integration-tests/tsconfig.json index 80c70a3a05..a7eebbe048 100644 --- a/packages/integration-tests/tsconfig.json +++ b/packages/integration-tests/tsconfig.json @@ -8,9 +8,6 @@ "test" ], "references": [ - { - "path": "../connection-encrypter-noise" - }, { "path": "../connection-encrypter-plaintext" }, @@ -80,9 +77,6 @@ { "path": "../stream-multiplexer-mplex" }, - { - "path": "../stream-multiplexer-yamux" - }, { "path": "../transport-circuit-relay-v2" }, diff --git a/packages/libp2p/README.md b/packages/libp2p/README.md index 79771279f1..b8686e1507 100644 --- a/packages/libp2p/README.md +++ b/packages/libp2p/README.md @@ -97,11 +97,11 @@ List of packages currently in existence for libp2p | [`@libp2p/websockets`](//github.com/libp2p/js-libp2p/tree/main/packages/transport-websockets) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fwebsockets.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/websockets) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fwebsockets?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fwebsockets) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-websockets/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-websockets) | | [`@libp2p/webtransport`](//github.com/libp2p/js-libp2p/tree/main/packages/transport-webtransport) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fwebtransport.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/webtransport) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fwebtransport?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fwebtransport) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-webtransport/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/transport-webtransport) | | **secure channels** | -| [`@libp2p/noise`](//github.com/ChainSafe/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fnoise.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/noise) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fnoise?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fnoise) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-noise/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-noise/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-noise) | +| [`@chainsafe/libp2p-noise`](//github.com/ChainSafe/js-libp2p-noise) | [![npm](https://img.shields.io/npm/v/%40chainsafe%2Flibp2p-noise.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@chainsafe/libp2p-noise) | [![Deps](https://img.shields.io/librariesio/release/npm/%40chainsafe%2Flibp2p-noise?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40chainsafe%2Flibp2p-noise) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-noise/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-noise/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-noise/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-noise) | | [`@libp2p/plaintext`](//github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fplaintext.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/plaintext) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fplaintext?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fplaintext) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext) | | [`@libp2p/tls`](//github.com/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Ftls.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/tls) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Ftls?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Ftls) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls) | | **stream multiplexers** | -| [`@libp2p/yamux`](//github.com/ChainSafe/js-libp2p-yamux) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fyamux.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/yamux) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fyamux?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fyamux) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-yamux/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-yamux/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-yamux/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-yamux) | +| [`@chainsafe/libp2p-yamux`](//github.com/ChainSafe/js-libp2p-yamux) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fyamux.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@chainsafe/libp2p-yamux) | [![Deps](https://img.shields.io/librariesio/release/npm/%40chainsafe%2Flibp2p-yamux?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40chainsafe%2Flibp2p-yamux) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-yamux/js-test-and-release.yml?branch=master&label=ci&style=flat-square)](//github.com/ChainSafe/js-libp2p-yamux/actions?query=branch%3Amaster+workflow%3Aci+) | [![codecov](https://codecov.io/gh/ChainSafe/js-libp2p-yamux/branch/master/graph/badge.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-yamux) | | [`@libp2p/mplex`](//github.com/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fmplex.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/mplex) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fmplex?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fmplex) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex) | | **peer discovery** | | [`@libp2p/bootstrap`](//github.com/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap) | [![npm](https://img.shields.io/npm/v/%40libp2p%2Fbootstrap.svg?maxAge=86400&style=flat-square)](//npmjs.com/package/@libp2p/bootstrap) | [![Deps](https://img.shields.io/librariesio/release/npm/%40libp2p%2Fbootstrap?logo=Libraries.io&logoColor=white&style=flat-square)](//libraries.io/npm/%40libp2p%2Fbootstrap) | [![GitHub CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p/main.yml?branch=main&label=ci&style=flat-square)](//github.com/libp2p/js-libp2p/actions?query=branch%3Amain+workflow%3Aci+) | [![codecov](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap/branch/main/graph/badge.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p/tree/main/packages/peer-discovery-bootstrap) | diff --git a/packages/libp2p/package-list.json b/packages/libp2p/package-list.json index 9c9331d3fc..35dcee6242 100644 --- a/packages/libp2p/package-list.json +++ b/packages/libp2p/package-list.json @@ -19,12 +19,12 @@ ["libp2p/js-libp2p/tree/main/packages/transport-webtransport", "@libp2p/webtransport", "main", "main.yml"], "secure channels", - ["ChainSafe/js-libp2p-noise", "@libp2p/noise", "master", "js-test-and-release.yml"], + ["ChainSafe/js-libp2p-noise", "@chainsafe/libp2p-noise", "master", "js-test-and-release.yml"], ["libp2p/js-libp2p/tree/main/packages/connection-encrypter-plaintext", "@libp2p/plaintext", "main", "main.yml"], ["libp2p/js-libp2p/tree/main/packages/connection-encrypter-tls", "@libp2p/tls", "main", "main.yml"], "stream multiplexers", - ["ChainSafe/js-libp2p-yamux", "@libp2p/yamux", "master", "js-test-and-release.yml"], + ["ChainSafe/js-libp2p-yamux", "@chainsafe/libp2p-yamux", "master", "js-test-and-release.yml"], ["libp2p/js-libp2p/tree/main/packages/stream-multiplexer-mplex", "@libp2p/mplex", "main", "main.yml"], "peer discovery", diff --git a/packages/libp2p/src/index.ts b/packages/libp2p/src/index.ts index 7c92163d13..777050ce3d 100644 --- a/packages/libp2p/src/index.ts +++ b/packages/libp2p/src/index.ts @@ -184,8 +184,8 @@ export type Libp2pOptions = Libp2pInit & { * import { createLibp2p } from 'libp2p' * import { tcp } from '@libp2p/tcp' * import { mplex } from '@libp2p/mplex' - * import { noise } from '@libp2p/noise' - * import { yamux } from '@libp2p/yamux' + * import { noise } from '@chainsafe/libp2p-noise' + * import { yamux } from '@chainsafe/libp2p-yamux' * * // specify options * const options = { diff --git a/packages/protocol-echo/src/index.ts b/packages/protocol-echo/src/index.ts index 221a1b0131..5e338ccff5 100644 --- a/packages/protocol-echo/src/index.ts +++ b/packages/protocol-echo/src/index.ts @@ -8,8 +8,8 @@ * @example * * ```TypeScript - * import { noise } from '@libp2p/noise' - * import { yamux } from '@libp2p/yamux' + * import { noise } from '@chainsafe/libp2p-noise' + * import { yamux } from '@chainsafe/libp2p-yamux' * import { echo } from '@libp2p/echo' * import { peerIdFromString } from '@libp2p/peer-id' * import { createLibp2p } from 'libp2p' diff --git a/packages/protocol-perf/src/index.ts b/packages/protocol-perf/src/index.ts index f10236e668..4965614f5e 100644 --- a/packages/protocol-perf/src/index.ts +++ b/packages/protocol-perf/src/index.ts @@ -6,8 +6,8 @@ * @example * * ```typescript - * import { noise } from '@libp2p/noise' - * import { yamux } from '@libp2p/yamux' + * import { noise } from '@chainsafe/libp2p-noise' + * import { yamux } from '@chainsafe/libp2p-yamux' * import { tcp } from '@libp2p/tcp' * import { createLibp2p, type Libp2p } from 'libp2p' * import { plaintext } from '@libp2p/plaintext' diff --git a/packages/stream-multiplexer-mplex/src/index.ts b/packages/stream-multiplexer-mplex/src/index.ts index 70b2f75b21..e02e4890c1 100644 --- a/packages/stream-multiplexer-mplex/src/index.ts +++ b/packages/stream-multiplexer-mplex/src/index.ts @@ -3,7 +3,7 @@ * * This is a [simple stream multiplexer(https://docs.libp2p.io/concepts/multiplex/mplex/) that has been deprecated. * - * Please use [@libp2p/yamux](https://www.npmjs.com/package/@libp2p/yamux) instead. + * Please use [@chainsafe/libp2p-yamux](https://www.npmjs.com/package/@chainsafe/libp2p-yamux) instead. * * @example * diff --git a/packages/stream-multiplexer-yamux/.aegir.js b/packages/stream-multiplexer-yamux/.aegir.js deleted file mode 100644 index 7ecc20b05a..0000000000 --- a/packages/stream-multiplexer-yamux/.aegir.js +++ /dev/null @@ -1,7 +0,0 @@ - -/** @type {import('aegir/types').PartialOptions} */ -export default { - build: { - bundlesizeMax: '9.5kB' - } -} diff --git a/packages/stream-multiplexer-yamux/CHANGELOG.md b/packages/stream-multiplexer-yamux/CHANGELOG.md deleted file mode 100644 index a6f07df96d..0000000000 --- a/packages/stream-multiplexer-yamux/CHANGELOG.md +++ /dev/null @@ -1,41 +0,0 @@ -# Changelog - -## [8.0.1](https://github.com/libp2p/js-libp2p/compare/yamux-v8.0.0...yamux-v8.0.1) (2025-09-24) - - -### Dependencies - -* update p-event, p-wait-for and noble deps ([#3302](https://github.com/libp2p/js-libp2p/issues/3302)) ([55bbd8c](https://github.com/libp2p/js-libp2p/commit/55bbd8cde12fe1c05e8d264e6e2406ca9fe2f044)) -* The following workspace dependencies were updated - * dependencies - * @libp2p/utils bumped from ^7.0.0 to ^7.0.1 - * devDependencies - * @libp2p/interface-compliance-tests bumped from ^7.0.0 to ^7.0.1 - * @libp2p/mplex bumped from ^12.0.0 to ^12.0.1 - -## [8.0.0](https://github.com/libp2p/js-libp2p/compare/yamux-v7.0.4...yamux-v8.0.0) (2025-09-23) - - -### ⚠ BREAKING CHANGES - -* - Stream handlers accept `stream, connection`, not `{ stream, connection }` - -### Features - -* streams as EventTargets ([#3218](https://github.com/libp2p/js-libp2p/issues/3218)) ([0f68898](https://github.com/libp2p/js-libp2p/commit/0f68898e6503975aae6f2bb6ba36aff65dabdfe8)), closes [#3226](https://github.com/libp2p/js-libp2p/issues/3226) - - -### Bug Fixes - -* update project ([db9f40c](https://github.com/libp2p/js-libp2p/commit/db9f40c4fc4c230444d0f3ca79b65a0053bc35f7)) - - -### Dependencies - -* The following workspace dependencies were updated - * dependencies - * @libp2p/interface bumped from ^2.11.0 to ^3.0.0 - * @libp2p/utils bumped from ^6.7.2 to ^7.0.0 - * devDependencies - * @libp2p/interface-compliance-tests bumped from ^6.5.0 to ^7.0.0 - * @libp2p/mplex bumped from ^11.0.47 to ^12.0.0 diff --git a/packages/stream-multiplexer-yamux/CODE_OF_CONDUCT.md b/packages/stream-multiplexer-yamux/CODE_OF_CONDUCT.md deleted file mode 100644 index 6b0fa54c54..0000000000 --- a/packages/stream-multiplexer-yamux/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,3 +0,0 @@ -# Contributor Code of Conduct - -This project follows the [`IPFS Community Code of Conduct`](https://github.com/ipfs/community/blob/master/code-of-conduct.md) diff --git a/packages/stream-multiplexer-yamux/LICENSE-APACHE b/packages/stream-multiplexer-yamux/LICENSE-APACHE deleted file mode 100644 index b09cd7856d..0000000000 --- a/packages/stream-multiplexer-yamux/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ -Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/stream-multiplexer-yamux/LICENSE-MIT b/packages/stream-multiplexer-yamux/LICENSE-MIT deleted file mode 100644 index 72dc60d84b..0000000000 --- a/packages/stream-multiplexer-yamux/LICENSE-MIT +++ /dev/null @@ -1,19 +0,0 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/packages/stream-multiplexer-yamux/README.md b/packages/stream-multiplexer-yamux/README.md deleted file mode 100644 index f3b3ba4c8a..0000000000 --- a/packages/stream-multiplexer-yamux/README.md +++ /dev/null @@ -1,127 +0,0 @@ -# @chainsafe/libp2p-yamux - -[![codecov](https://img.shields.io/codecov/c/github/ChainSafe/js-libp2p-yamux.svg?style=flat-square)](https://codecov.io/gh/ChainSafe/js-libp2p-yamux) -[![CI](https://img.shields.io/github/actions/workflow/status/ChainSafe/js-libp2p-yamux/js-test-and-release.yml?branch=master\&style=flat-square)](https://github.com/ChainSafe/js-libp2p-yamux/actions/workflows/js-test-and-release.yml?query=branch%3Amaster) - -> Yamux stream multiplexer for libp2p - -# About - - - -This module is a JavaScript implementation of [Yamux from Hashicorp](https://github.com/hashicorp/yamux/blob/master/spec.md) designed to be used with [js-libp2p](https://github.com/libp2p/js-libp2p). - -## Example - Configure libp2p with Yamux - -```typescript -import { createLibp2p } from 'libp2p' -import { yamux } from '@chainsafe/libp2p-yamux' - -const node = await createLibp2p({ - // ... other options - streamMuxers: [ - yamux() - ] -}) -``` - -## Example - Using the low-level API - -```js -import { yamux } from '@chainsafe/libp2p-yamux' -import { pipe } from 'it-pipe' -import { duplexPair } from 'it-pair/duplex' -import all from 'it-all' - -// Connect two yamux muxers to demo basic stream multiplexing functionality - -const clientMuxer = yamux({ - client: true, - onIncomingStream: stream => { - // echo data on incoming streams - pipe(stream, stream) - }, - onStreamEnd: stream => { - // do nothing - } -})() - -const serverMuxer = yamux({ - client: false, - onIncomingStream: stream => { - // echo data on incoming streams - pipe(stream, stream) - }, - onStreamEnd: stream => { - // do nothing - } -})() - -// `p` is our "connections", what we use to connect the two sides -// In a real application, a connection is usually to a remote computer -const p = duplexPair() - -// connect the muxers together -pipe(p[0], clientMuxer, p[0]) -pipe(p[1], serverMuxer, p[1]) - -// now either side can open streams -const stream0 = clientMuxer.newStream() -const stream1 = serverMuxer.newStream() - -// Send some data to the other side -const encoder = new TextEncoder() -const data = [encoder.encode('hello'), encoder.encode('world')] -pipe(data, stream0) - -// Receive data back -const result = await pipe(stream0, all) - -// close a stream -stream1.close() - -// close the muxer -clientMuxer.close() -``` - -# Install - -```console -$ npm i @chainsafe/libp2p-yamux -``` - -## Browser ` -``` - -# API Docs - -- - -# License - -Licensed under either of - -- Apache 2.0, ([LICENSE-APACHE](https://github.com/ChainSafe/js-libp2p-yamux/LICENSE-APACHE) / ) -- MIT ([LICENSE-MIT](https://github.com/ChainSafe/js-libp2p-yamux/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/packages/stream-multiplexer-yamux/package.json b/packages/stream-multiplexer-yamux/package.json deleted file mode 100644 index 06d4eb12c2..0000000000 --- a/packages/stream-multiplexer-yamux/package.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "name": "@libp2p/yamux", - "version": "8.0.1", - "description": "Yamux stream multiplexer for libp2p", - "license": "Apache-2.0 OR MIT", - "homepage": "https://github.com/libp2p/js-libp2p/tree/main/packages/stream-multiplexer-yamux#readme", - "repository": { - "type": "git", - "url": "git+https://github.com/libp2p/js-libp2p.git" - }, - "bugs": { - "url": "https://github.com/libp2p/js-libp2p/issues" - }, - "publishConfig": { - "access": "public", - "provenance": true - }, - "keywords": [ - "IPFS", - "libp2p", - "multiplexer", - "muxer", - "stream" - ], - "type": "module", - "types": "./dist/src/index.d.ts", - "typesVersions": { - "*": { - "*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ], - "src/*": [ - "*", - "dist/*", - "dist/src/*", - "dist/src/*/index" - ] - } - }, - "files": [ - "src", - "dist", - "!dist/test", - "!**/*.tsbuildinfo" - ], - "exports": { - ".": { - "types": "./dist/src/index.d.ts", - "import": "./dist/src/index.js" - }, - "./config": { - "types": "./dist/src/config.d.ts", - "import": "./dist/src/config.js" - }, - "./stream": { - "types": "./dist/src/stream.d.ts", - "import": "./dist/src/stream.js" - } - }, - "scripts": { - "clean": "aegir clean", - "lint": "aegir lint", - "dep-check": "aegir dep-check", - "doc-check": "aegir doc-check", - "benchmark": "benchmark dist/test/bench/*.bench.js --timeout 400000", - "build": "aegir build", - "test": "aegir test", - "test:chrome": "aegir test -t browser", - "test:chrome-webworker": "aegir test -t webworker", - "test:firefox": "aegir test -t browser -- --browser firefox", - "test:firefox-webworker": "aegir test -t webworker -- --browser firefox", - "test:node": "aegir test -t node --cov", - "test:electron-main": "aegir test -t electron-main", - "docs": "aegir docs" - }, - "dependencies": { - "@libp2p/interface": "^3.0.0", - "@libp2p/utils": "^7.0.1", - "race-signal": "^2.0.0", - "uint8arraylist": "^2.4.8" - }, - "devDependencies": { - "@dapplion/benchmark": "^1.0.0", - "@libp2p/interface-compliance-tests": "^7.0.1", - "@libp2p/mplex": "^12.0.1", - "aegir": "^47.0.22", - "it-all": "^3.0.9", - "it-drain": "^3.0.10", - "it-pushable": "^3.2.3", - "p-event": "^7.0.0" - } -} diff --git a/packages/stream-multiplexer-yamux/src/config.ts b/packages/stream-multiplexer-yamux/src/config.ts deleted file mode 100644 index 59510376c3..0000000000 --- a/packages/stream-multiplexer-yamux/src/config.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { InvalidParametersError } from '@libp2p/interface' -import { INITIAL_STREAM_WINDOW, MAX_STREAM_WINDOW } from './constants.js' -import type { StreamMuxerOptions, StreamOptions } from '@libp2p/interface' - -export interface YamuxStreamOptions extends StreamOptions { - /** - * Used to control the initial window size that we allow for a stream. - * - * measured in bytes - */ - initialStreamWindowSize?: number - - /** - * Used to control the maximum window size that we allow for a stream. - */ - maxStreamWindowSize?: number -} - -// TODO use config items or delete them -export interface Config extends StreamMuxerOptions { - /** - * Used to do periodic keep alive messages using a ping - */ - enableKeepAlive?: boolean - - /** - * How often to perform the keep alive - * - * measured in milliseconds - */ - keepAliveInterval?: number -} - -export const defaultConfig: Required & { streamOptions: Required } = { - enableKeepAlive: true, - keepAliveInterval: 30_000, - maxInboundStreams: 1_000, - maxOutboundStreams: 1_000, - maxMessageSize: 64 * 1024, - maxEarlyStreams: 10, - streamOptions: { - initialStreamWindowSize: INITIAL_STREAM_WINDOW, - maxStreamWindowSize: MAX_STREAM_WINDOW, - inactivityTimeout: 120_000, - maxReadBufferLength: 4_194_304, - maxWriteBufferLength: Infinity - } -} - -export function verifyConfig (config: Config): void { - if (config.keepAliveInterval != null && config.keepAliveInterval <= 0) { - throw new InvalidParametersError('keep-alive interval must be positive') - } - if (config.maxInboundStreams != null && config.maxInboundStreams < 0) { - throw new InvalidParametersError('max inbound streams must be larger or equal 0') - } - if (config.maxOutboundStreams != null && config.maxOutboundStreams < 0) { - throw new InvalidParametersError('max outbound streams must be larger or equal 0') - } - if (config.maxMessageSize != null && config.maxMessageSize < 1024) { - throw new InvalidParametersError('MaxMessageSize must be greater than a kilobyte') - } - if (config.streamOptions?.initialStreamWindowSize != null && config.streamOptions?.initialStreamWindowSize < INITIAL_STREAM_WINDOW) { - throw new InvalidParametersError('InitialStreamWindowSize must be larger or equal 256 kB') - } - if (config.streamOptions?.maxStreamWindowSize != null && config.streamOptions?.initialStreamWindowSize != null && config.streamOptions?.maxStreamWindowSize < config.streamOptions?.initialStreamWindowSize) { - throw new InvalidParametersError('MaxStreamWindowSize must be larger than the InitialStreamWindowSize') - } - if (config.streamOptions?.maxStreamWindowSize != null && config.streamOptions?.maxStreamWindowSize > 2 ** 32 - 1) { - throw new InvalidParametersError('MaxStreamWindowSize must be less than equal MAX_UINT32') - } -} diff --git a/packages/stream-multiplexer-yamux/src/constants.ts b/packages/stream-multiplexer-yamux/src/constants.ts deleted file mode 100644 index 0bba202c16..0000000000 --- a/packages/stream-multiplexer-yamux/src/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -// Protocol violation errors - -import { BothClientsError, DecodeInvalidVersionError, InvalidFrameError, NotMatchingPingError, ReceiveWindowExceededError, StreamAlreadyExistsError, UnRequestedPingError } from './errors.js' - -export const PROTOCOL_ERRORS = new Set([ - InvalidFrameError.name, - UnRequestedPingError.name, - NotMatchingPingError.name, - StreamAlreadyExistsError.name, - DecodeInvalidVersionError.name, - BothClientsError.name, - ReceiveWindowExceededError.name -]) - -/** - * INITIAL_STREAM_WINDOW is the initial stream window size. - * - * Not an implementation choice, this is defined in the specification - */ -export const INITIAL_STREAM_WINDOW = 256 * 1024 - -/** - * Default max stream window - */ -export const MAX_STREAM_WINDOW = 16 * 1024 * 1024 diff --git a/packages/stream-multiplexer-yamux/src/decode.ts b/packages/stream-multiplexer-yamux/src/decode.ts deleted file mode 100644 index 103e4def29..0000000000 --- a/packages/stream-multiplexer-yamux/src/decode.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { Uint8ArrayList } from 'uint8arraylist' -import { InvalidFrameError } from './errors.js' -import { FrameType, HEADER_LENGTH, YAMUX_VERSION } from './frame.js' -import type { FrameHeader } from './frame.js' - -export interface Frame { - header: FrameHeader - data?: Uint8ArrayList -} - -export interface DataFrame { - header: FrameHeader - data: Uint8ArrayList -} - -export function isDataFrame (frame: Frame): frame is DataFrame { - return frame.header.type === FrameType.Data && frame.data !== null -} - -// used to bit shift in decoding -// native bit shift can overflow into a negative number, so we bit shift by -// multiplying by a power of 2 -const twoPow24 = 2 ** 24 - -/** - * Decode a header from the front of a buffer - * - * @param data - Assumed to have enough bytes for a header - */ -export function decodeHeader (data: Uint8Array): FrameHeader { - if (data[0] !== YAMUX_VERSION) { - throw new InvalidFrameError('Invalid frame version') - } - - return { - type: data[1], - flag: (data[2] << 8) + data[3], - streamID: (data[4] * twoPow24) + (data[5] << 16) + (data[6] << 8) + data[7], - length: (data[8] * twoPow24) + (data[9] << 16) + (data[10] << 8) + data[11] - } -} - -/** - * Decodes yamux frames from a source - */ -export class Decoder { - /** Buffer for in-progress frames */ - private readonly buffer: Uint8ArrayList - - constructor () { - this.buffer = new Uint8ArrayList() - } - - /** - * Emits frames from the decoder source. - * - * Note: If `readData` is emitted, it _must_ be called before the next iteration - * Otherwise an error is thrown - */ - * emitFrames (buf: Uint8Array | Uint8ArrayList): Generator { - this.buffer.append(buf) - - // Loop to consume as many bytes from the buffer as possible - // Eg: when a single chunk contains several frames - while (true) { - const frame = this.readFrame() - - if (frame === undefined) { - break - } - - yield frame - } - } - - private readFrame (): Frame | undefined { - let frameSize = HEADER_LENGTH - - if (this.buffer.byteLength < HEADER_LENGTH) { - // not enough data yet - return - } - - // TODO: use sublist? - const header = decodeHeader(this.buffer.subarray(0, HEADER_LENGTH)) - - if (header.type === FrameType.Data) { - frameSize += header.length - - if (this.buffer.byteLength < frameSize) { - // not enough data yet - return - } - - const data = this.buffer.sublist(HEADER_LENGTH, frameSize) - this.buffer.consume(frameSize) - - return { header, data } - } - - this.buffer.consume(frameSize) - - return { header } - } -} diff --git a/packages/stream-multiplexer-yamux/src/encode.ts b/packages/stream-multiplexer-yamux/src/encode.ts deleted file mode 100644 index 6353c00916..0000000000 --- a/packages/stream-multiplexer-yamux/src/encode.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { HEADER_LENGTH } from './frame.js' -import type { FrameHeader } from './frame.js' - -export function encodeHeader (header: FrameHeader): Uint8Array { - const frame = new Uint8Array(HEADER_LENGTH) - - // always assume version 0 - // frameView.setUint8(0, header.version) - - frame[1] = header.type - - frame[2] = header.flag >>> 8 - frame[3] = header.flag - - frame[4] = header.streamID >>> 24 - frame[5] = header.streamID >>> 16 - frame[6] = header.streamID >>> 8 - frame[7] = header.streamID - - frame[8] = header.length >>> 24 - frame[9] = header.length >>> 16 - frame[10] = header.length >>> 8 - frame[11] = header.length - - return frame -} diff --git a/packages/stream-multiplexer-yamux/src/errors.ts b/packages/stream-multiplexer-yamux/src/errors.ts deleted file mode 100644 index e449d3211b..0000000000 --- a/packages/stream-multiplexer-yamux/src/errors.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { GoAwayCode } from './frame.ts' - -export class ProtocolError extends Error { - static name = 'ProtocolError' - - public reason: GoAwayCode - - constructor (message: string, reason: GoAwayCode) { - super(message) - this.name = 'ProtocolError' - this.reason = reason - } -} - -export function isProtocolError (err?: any): err is ProtocolError { - return err?.reason !== null -} - -export class InvalidFrameError extends ProtocolError { - static name = 'InvalidFrameError' - - constructor (message = 'The frame was invalid') { - super(message, GoAwayCode.ProtocolError) - this.name = 'InvalidFrameError' - } -} - -export class UnRequestedPingError extends ProtocolError { - static name = 'UnRequestedPingError' - - constructor (message = 'Un-requested ping error') { - super(message, GoAwayCode.ProtocolError) - this.name = 'UnRequestedPingError' - } -} - -export class NotMatchingPingError extends ProtocolError { - static name = 'NotMatchingPingError' - - constructor (message = 'Not matching ping error') { - super(message, GoAwayCode.ProtocolError) - this.name = 'NotMatchingPingError' - } -} - -export class InvalidStateError extends Error { - static name = 'InvalidStateError' - - constructor (message = 'Invalid state') { - super(message) - this.name = 'InvalidStateError' - } -} - -export class StreamAlreadyExistsError extends ProtocolError { - static name = 'StreamAlreadyExistsError' - - constructor (message = 'Stream already exists') { - super(message, GoAwayCode.ProtocolError) - this.name = 'StreamAlreadyExistsError' - } -} - -export class DecodeInvalidVersionError extends ProtocolError { - static name = 'DecodeInvalidVersionError' - - constructor (message = 'Decode invalid version') { - super(message, GoAwayCode.ProtocolError) - this.name = 'DecodeInvalidVersionError' - } -} - -export class BothClientsError extends ProtocolError { - static name = 'BothClientsError' - - constructor (message = 'Both clients') { - super(message, GoAwayCode.ProtocolError) - this.name = 'BothClientsError' - } -} - -export class ReceiveWindowExceededError extends ProtocolError { - static name = 'ReceiveWindowExceededError' - - constructor (message = 'Receive window exceeded') { - super(message, GoAwayCode.ProtocolError) - this.name = 'ReceiveWindowExceededError' - } -} diff --git a/packages/stream-multiplexer-yamux/src/frame.ts b/packages/stream-multiplexer-yamux/src/frame.ts deleted file mode 100644 index d2d814fe35..0000000000 --- a/packages/stream-multiplexer-yamux/src/frame.ts +++ /dev/null @@ -1,64 +0,0 @@ -export enum FrameType { - /** Used to transmit data. May transmit zero length payloads depending on the flags. */ - Data = 0x0, - /** Used to updated the senders receive window size. This is used to implement per-session flow control. */ - WindowUpdate = 0x1, - /** Used to measure RTT. It can also be used to heart-beat and do keep-alive over TCP. */ - Ping = 0x2, - /** Used to close a session. */ - GoAway = 0x3 -} - -export enum Flag { - /** Signals the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate outbound. */ - SYN = 0x1, - /** Acknowledges the start of a new stream. May be sent with a data or window update message. Also sent with a ping to indicate response. */ - ACK = 0x2, - /** Performs a half-close of a stream. May be sent with a data message or window update. */ - FIN = 0x4, - /** Reset a stream immediately. May be sent with a data or window update message. */ - RST = 0x8 -} - -const flagCodes = Object.values(Flag).filter((x) => typeof x !== 'string') as Flag[] - -export const YAMUX_VERSION = 0 - -export enum GoAwayCode { - NormalTermination = 0x0, - ProtocolError = 0x1, - InternalError = 0x2 -} - -export const HEADER_LENGTH = 12 - -export interface FrameHeader { - /** - * The version field is used for future backward compatibility. - * At the current time, the field is always set to 0, to indicate the initial version. - */ - version?: number - /** The type field is used to switch the frame message type. */ - type: FrameType - /** The flags field is used to provide additional information related to the message type. */ - flag: number - /** - * The StreamID field is used to identify the logical stream the frame is addressing. - * The client side should use odd ID's, and the server even. - * This prevents any collisions. Additionally, the 0 ID is reserved to represent the session. - */ - streamID: number - /** - * The meaning of the length field depends on the message type: - * Data - provides the length of bytes following the header - * Window update - provides a delta update to the window size - * Ping - Contains an opaque value, echoed back - * Go Away - Contains an error code - */ - length: number -} - -export function stringifyHeader (header: FrameHeader): string { - const flags = flagCodes.filter(f => (header.flag & f) === f).map(f => Flag[f]).join('|') - return `streamID=${header.streamID} type=${FrameType[header.type]} flag=${flags} length=${header.length}` -} diff --git a/packages/stream-multiplexer-yamux/src/index.ts b/packages/stream-multiplexer-yamux/src/index.ts deleted file mode 100644 index c54e05872f..0000000000 --- a/packages/stream-multiplexer-yamux/src/index.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * @packageDocumentation - * - * This module is a JavaScript implementation of [Yamux from Hashicorp](https://github.com/hashicorp/yamux/blob/master/spec.md) designed to be used with [js-libp2p](https://github.com/libp2p/js-libp2p). - * - * @example Configure libp2p with Yamux - * - * ```typescript - * import { createLibp2p } from 'libp2p' - * import { yamux } from '@libp2p/yamux' - * - * const node = await createLibp2p({ - * // ... other options - * streamMuxers: [ - * yamux() - * ] - * }) - * ``` - * - * @example Using the low-level API - * - * ```js - * import { yamux } from '@libp2p/yamux' - * import { pipe } from 'it-pipe' - * import { duplexPair } from 'it-pair/duplex' - * import all from 'it-all' - * - * // Connect two yamux muxers to demo basic stream multiplexing functionality - * - * const clientMuxer = yamux({ - * client: true, - * onIncomingStream: stream => { - * // echo data on incoming streams - * pipe(stream, stream) - * }, - * onStreamEnd: stream => { - * // do nothing - * } - * })() - * - * const serverMuxer = yamux({ - * client: false, - * onIncomingStream: stream => { - * // echo data on incoming streams - * pipe(stream, stream) - * }, - * onStreamEnd: stream => { - * // do nothing - * } - * })() - * - * // `p` is our "connections", what we use to connect the two sides - * // In a real application, a connection is usually to a remote computer - * const p = duplexPair() - * - * // connect the muxers together - * pipe(p[0], clientMuxer, p[0]) - * pipe(p[1], serverMuxer, p[1]) - * - * // now either side can open streams - * const stream0 = clientMuxer.newStream() - * const stream1 = serverMuxer.newStream() - * - * // Send some data to the other side - * const encoder = new TextEncoder() - * const data = [encoder.encode('hello'), encoder.encode('world')] - * pipe(data, stream0) - * - * // Receive data back - * const result = await pipe(stream0, all) - * - * // close a stream - * stream1.close() - * - * // close the muxer - * clientMuxer.close() - * ``` - */ - -import { Yamux } from './muxer.js' -import type { YamuxMuxer, YamuxMuxerInit } from './muxer.js' -import type { StreamMuxerFactory } from '@libp2p/interface' - -export { GoAwayCode } from './frame.js' -export type { FrameHeader, FrameType } from './frame.js' -export type { YamuxMuxerInit } - -export function yamux (init: YamuxMuxerInit = {}): () => StreamMuxerFactory { - return () => new Yamux(init) -} diff --git a/packages/stream-multiplexer-yamux/src/muxer.ts b/packages/stream-multiplexer-yamux/src/muxer.ts deleted file mode 100644 index 9279e9783d..0000000000 --- a/packages/stream-multiplexer-yamux/src/muxer.ts +++ /dev/null @@ -1,507 +0,0 @@ -import { InvalidParametersError, MuxerClosedError, TooManyOutboundProtocolStreamsError, serviceCapabilities } from '@libp2p/interface' -import { AbstractStreamMuxer, repeatingTask } from '@libp2p/utils' -import { raceSignal } from 'race-signal' -import { Uint8ArrayList } from 'uint8arraylist' -import { defaultConfig, verifyConfig } from './config.js' -import { Decoder } from './decode.js' -import { encodeHeader } from './encode.js' -import { InvalidFrameError, isProtocolError, NotMatchingPingError, UnRequestedPingError } from './errors.js' -import { Flag, FrameType, GoAwayCode } from './frame.js' -import { StreamState, YamuxStream } from './stream.js' -import type { Config } from './config.js' -import type { Frame } from './decode.js' -import type { FrameHeader } from './frame.js' -import type { AbortOptions, MessageStream, StreamMuxerFactory } from '@libp2p/interface' -import type { RepeatingTask } from '@libp2p/utils' - -function debugFrame (header: FrameHeader): any { - return { - type: FrameType[header.type], - flags: [ - (header.flag & Flag.SYN) === Flag.SYN ? 'SYN' : undefined, - (header.flag & Flag.ACK) === Flag.ACK ? 'ACK' : undefined, - (header.flag & Flag.FIN) === Flag.FIN ? 'FIN' : undefined, - (header.flag & Flag.RST) === Flag.RST ? 'RST' : undefined - ].filter(Boolean), - streamID: header.streamID, - length: header.length - } -} - -const YAMUX_PROTOCOL_ID = '/yamux/1.0.0' - -export interface YamuxMuxerInit extends Partial { -} - -export class Yamux implements StreamMuxerFactory { - protocol = YAMUX_PROTOCOL_ID - private readonly _init: Partial - - constructor (init: Partial = {}) { - this._init = init - } - - readonly [Symbol.toStringTag] = '@libp2p/yamux' - - readonly [serviceCapabilities]: string[] = [ - '@libp2p/stream-multiplexing' - ] - - createStreamMuxer (maConn: MessageStream): YamuxMuxer { - return new YamuxMuxer(maConn, { - ...this._init - }) - } -} - -export interface CloseOptions extends AbortOptions { - reason?: GoAwayCode -} - -export interface ActivePing extends PromiseWithResolvers { - id: number - start: number -} - -export class YamuxMuxer extends AbstractStreamMuxer { - /** The next stream id to be used when initiating a new stream */ - private nextStreamID: number - - /** The next ping id to be used when pinging */ - private nextPingID: number - /** Tracking info for the currently active ping */ - private activePing?: ActivePing - /** Round trip time */ - private rtt: number - - /** True if client, false if server */ - private client: boolean - - private localGoAway?: GoAwayCode - private remoteGoAway?: GoAwayCode - - /** Number of tracked inbound streams */ - private numInboundStreams: number - /** Number of tracked outbound streams */ - private numOutboundStreams: number - - private decoder: Decoder - private keepAlive?: RepeatingTask - - private enableKeepAlive: boolean - private keepAliveInterval: number - private maxInboundStreams: number - private maxOutboundStreams: number - - constructor (maConn: MessageStream, init: YamuxMuxerInit = {}) { - super(maConn, { - ...init, - protocol: YAMUX_PROTOCOL_ID, - name: 'yamux' - }) - - this.client = maConn.direction === 'outbound' - verifyConfig(init) - - this.enableKeepAlive = init.enableKeepAlive ?? defaultConfig.enableKeepAlive - this.keepAliveInterval = init.keepAliveInterval ?? defaultConfig.keepAliveInterval - this.maxInboundStreams = init.maxInboundStreams ?? defaultConfig.maxInboundStreams - this.maxOutboundStreams = init.maxOutboundStreams ?? defaultConfig.maxOutboundStreams - - this.decoder = new Decoder() - - this.numInboundStreams = 0 - this.numOutboundStreams = 0 - - // client uses odd streamIDs, server uses even streamIDs - this.nextStreamID = this.client ? 1 : 2 - - this.nextPingID = 0 - this.rtt = -1 - - this.log.trace('muxer created') - - if (this.enableKeepAlive) { - this.log.trace('muxer keepalive enabled interval=%s', this.keepAliveInterval) - this.keepAlive = repeatingTask(async (options) => { - try { - await this.ping(options) - } catch (err: any) { - // TODO: should abort here? - this.log.error('ping error: %s', err) - } - }, this.keepAliveInterval, { - // send an initial ping to establish RTT - runImmediately: true - }) - this.keepAlive.start() - } - } - - onData (buf: Uint8Array | Uint8ArrayList): void { - for (const frame of this.decoder.emitFrames(buf)) { - this.handleFrame(frame) - } - } - - onCreateStream (): YamuxStream { - if (this.remoteGoAway !== undefined) { - throw new MuxerClosedError('Muxer closed remotely') - } - - if (this.localGoAway !== undefined) { - throw new MuxerClosedError('Muxer closed locally') - } - - const id = this.nextStreamID - this.nextStreamID += 2 - - // check against our configured maximum number of outbound streams - if (this.numOutboundStreams >= this.maxOutboundStreams) { - throw new TooManyOutboundProtocolStreamsError('max outbound streams exceeded') - } - - this.log.trace('new outgoing stream id=%s', id) - - const stream = this._newStream(id, StreamState.Init, 'outbound') - - this.numOutboundStreams++ - - // send a window update to open the stream on the receiver end. do this in a - // microtask so the stream gets added to the streams array by the superclass - // before we send the SYN flag, otherwise we create a race condition whereby - // we can receive the ACK before the stream is added to the streams list - queueMicrotask(() => { - stream.sendWindowUpdate() - }) - - return stream - } - - /** - * Initiate a ping and wait for a response - * - * Note: only a single ping will be initiated at a time. - * If a ping is already in progress, a new ping will not be initiated. - * - * @returns the round-trip-time in milliseconds - */ - async ping (options?: AbortOptions): Promise { - if (this.remoteGoAway !== undefined) { - throw new MuxerClosedError('Muxer closed remotely') - } - if (this.localGoAway !== undefined) { - throw new MuxerClosedError('Muxer closed locally') - } - - if (this.activePing != null) { - // an active ping is already in progress, piggyback off that - return raceSignal(this.activePing.promise, options?.signal) - } - - // An active ping does not yet exist, handle the process here - // create active ping - this.activePing = Object.assign(Promise.withResolvers(), { - id: this.nextPingID++, - start: Date.now() - }) - // send ping - this.sendPing(this.activePing.id) - // await pong - try { - this.rtt = await raceSignal(this.activePing.promise, options?.signal) - } finally { - // clean-up active ping - this.activePing = undefined - } - - return this.rtt - } - - /** - * Get the ping round trip time - * - * Note: Will return 0 if no successful ping has yet been completed - * - * @returns the round-trip-time in milliseconds - */ - getRTT (): number { - return this.rtt - } - - /** - * Close the muxer - */ - async close (options: CloseOptions = {}): Promise { - if (this.status !== 'open') { - // already closed - return - } - - try { - const reason = options?.reason ?? GoAwayCode.NormalTermination - - this.log.trace('muxer close reason=%s', GoAwayCode[reason]) - - await super.close(options) - - // send reason to the other side, allow the other side to close gracefully - this.sendGoAway(reason) - } finally { - this.keepAlive?.stop() - } - } - - abort (err: Error): void { - if (this.status !== 'open') { - // already closed - return - } - - try { - super.abort(err) - - let reason = GoAwayCode.InternalError - - if (isProtocolError(err)) { - reason = err.reason - } - - // If reason was provided, use that, otherwise use the presence of `err` to determine the reason - this.log.error('muxer abort reason=%s error=%s', reason, err) - - // send reason to the other side, allow the other side to close gracefully - this.sendGoAway(reason) - } finally { - this.keepAlive?.stop() - } - } - - onTransportClosed (): void { - try { - super.onTransportClosed() - } finally { - this.keepAlive?.stop() - } - } - - /** Create a new stream */ - private _newStream (streamId: number, state: StreamState, direction: 'inbound' | 'outbound'): YamuxStream { - if (this.streams.find(s => s.streamId === streamId) != null) { - throw new InvalidParametersError('Stream already exists with that id') - } - - const stream = new YamuxStream({ - ...this.streamOptions, - id: `${streamId}`, - streamId, - state, - direction, - sendFrame: this.sendFrame.bind(this), - log: this.log.newScope(`${direction}:${streamId}`), - getRTT: this.getRTT.bind(this) - }) - - stream.addEventListener('close', () => { - this.closeStream(streamId) - }, { - once: true - }) - - return stream - } - - /** - * closeStream is used to close a stream once both sides have - * issued a close. - */ - private closeStream (id: number): void { - if (this.client === (id % 2 === 0)) { - this.numInboundStreams-- - } else { - this.numOutboundStreams-- - } - } - - private handleFrame (frame: Frame): void { - const { - streamID, - type, - length - } = frame.header - - this.log.trace('received frame %o', debugFrame(frame.header)) - - if (streamID === 0) { - switch (type) { - case FrameType.Ping: - { this.handlePing(frame.header); return } - case FrameType.GoAway: - { this.handleGoAway(length); return } - default: - // Invalid state - throw new InvalidFrameError('Invalid frame type') - } - } else { - switch (frame.header.type) { - case FrameType.Data: - case FrameType.WindowUpdate: - { this.handleStreamMessage(frame); return } - default: - // Invalid state - throw new InvalidFrameError('Invalid frame type') - } - } - } - - private handlePing (header: FrameHeader): void { - // If the ping is initiated by the sender, send a response - if (header.flag === Flag.SYN) { - this.log.trace('received ping request pingId=%s', header.length) - this.sendPing(header.length, Flag.ACK) - } else if (header.flag === Flag.ACK) { - this.log.trace('received ping response pingId=%s', header.length) - this.handlePingResponse(header.length) - } else { - // Invalid state - throw new InvalidFrameError('Invalid frame flag') - } - } - - private handlePingResponse (pingId: number): void { - if (this.activePing === undefined) { - // this ping was not requested - throw new UnRequestedPingError('ping not requested') - } - if (this.activePing.id !== pingId) { - // this ping doesn't match our active ping request - throw new NotMatchingPingError('ping doesn\'t match our id') - } - - // valid ping response - this.activePing.resolve(Date.now() - this.activePing.start) - } - - private handleGoAway (reason: GoAwayCode): void { - this.log.trace('received GoAway reason=%s', GoAwayCode[reason] ?? 'unknown') - this.remoteGoAway = reason - - if (reason === GoAwayCode.NormalTermination) { - this.onTransportClosed() - } else { - // reset any streams that are still open and close the muxer - this.abort(new Error('Remote sent GoAway')) - } - } - - private handleStreamMessage (frame: Frame): void { - const { streamID, flag, type } = frame.header - - if ((flag & Flag.SYN) === Flag.SYN) { - this.incomingStream(streamID) - } - - const stream = this.streams.find(s => s.streamId === streamID) - if (stream === undefined) { - this.log.trace('frame for missing stream id=%s', streamID) - - return - } - - switch (type) { - case FrameType.WindowUpdate: { - stream.handleWindowUpdate(frame); return - } - case FrameType.Data: { - stream.handleData(frame); return - } - default: - throw new Error('unreachable') - } - } - - private incomingStream (id: number): void { - if (this.client !== (id % 2 === 0)) { - throw new InvalidParametersError('Both endpoints are clients') - } - if (this.streams.find(s => s.streamId === id)) { - return - } - - this.log.trace('new incoming stream id=%s', id) - - if (this.localGoAway !== undefined) { - // reject (reset) immediately if we are doing a go away - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: Flag.RST, - streamID: id, - length: 0 - }) - return - } - - // check against our configured maximum number of inbound streams - if (this.numInboundStreams >= this.maxInboundStreams) { - this.log('maxIncomingStreams exceeded, forcing stream reset') - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: Flag.RST, - streamID: id, - length: 0 - }) - return - } - - // allocate a new stream - const stream = this._newStream(id, StreamState.SYNReceived, 'inbound') - - this.numInboundStreams++ - - // the stream should now be tracked - this.onRemoteStream(stream) - } - - private sendFrame (header: FrameHeader, data?: Uint8ArrayList): boolean { - let encoded: Uint8Array | Uint8ArrayList - - if (header.type === FrameType.Data) { - if (data == null) { - throw new InvalidFrameError('Invalid frame') - } - - encoded = new Uint8ArrayList(encodeHeader(header), data) - } else { - encoded = encodeHeader(header) - } - - this.log.trace('sending frame %o', debugFrame(header)) - - return this.send(encoded) - } - - private sendPing (pingId: number, flag: Flag = Flag.SYN): void { - if (flag === Flag.SYN) { - this.log.trace('sending ping request pingId=%s', pingId) - } else { - this.log.trace('sending ping response pingId=%s', pingId) - } - this.sendFrame({ - type: FrameType.Ping, - flag, - streamID: 0, - length: pingId - }) - } - - private sendGoAway (reason: GoAwayCode = GoAwayCode.NormalTermination): void { - this.log('sending GoAway reason=%s', GoAwayCode[reason]) - this.localGoAway = reason - this.sendFrame({ - type: FrameType.GoAway, - flag: 0, - streamID: 0, - length: reason - }) - } -} diff --git a/packages/stream-multiplexer-yamux/src/stream.ts b/packages/stream-multiplexer-yamux/src/stream.ts deleted file mode 100644 index 8c46f2ef01..0000000000 --- a/packages/stream-multiplexer-yamux/src/stream.ts +++ /dev/null @@ -1,313 +0,0 @@ -import { AbstractStream } from '@libp2p/utils' -import { Uint8ArrayList } from 'uint8arraylist' -import { INITIAL_STREAM_WINDOW, MAX_STREAM_WINDOW } from './constants.js' -import { isDataFrame } from './decode.ts' -import { InvalidFrameError, ReceiveWindowExceededError } from './errors.js' -import { Flag, FrameType, HEADER_LENGTH } from './frame.js' -import type { Frame } from './decode.ts' -import type { FrameHeader } from './frame.js' -import type { AbortOptions } from '@libp2p/interface' -import type { AbstractStreamInit, SendResult } from '@libp2p/utils' - -export enum StreamState { - Init, - SYNSent, - SYNReceived, - Established, - Finished, - Paused -} - -export interface YamuxStreamInit extends AbstractStreamInit { - streamId: number - sendFrame(header: FrameHeader, body?: Uint8ArrayList): boolean - getRTT(): number - initialStreamWindowSize?: number - maxMessageSize?: number - maxStreamWindowSize?: number - state: StreamState -} - -/** YamuxStream is used to represent a logical stream within a session */ -export class YamuxStream extends AbstractStream { - streamId: number - state: StreamState - - /** The number of available bytes to send */ - private sendWindowCapacity: number - /** The number of bytes available to receive in a full window */ - private recvWindow: number - /** The number of available bytes to receive */ - private recvWindowCapacity: number - private maxStreamWindowSize: number - - /** - * An 'epoch' is the time it takes to process and read data - * - * Used in conjunction with RTT to determine whether to increase the recvWindow - */ - private epochStart: number - private readonly getRTT: () => number - - private readonly sendFrame: (header: FrameHeader, body?: Uint8ArrayList) => boolean - - constructor (init: YamuxStreamInit) { - const initialWindowSize = init.initialStreamWindowSize ?? INITIAL_STREAM_WINDOW - - super({ - ...init, - maxMessageSize: initialWindowSize - HEADER_LENGTH - }) - - this.streamId = init.streamId - this.state = init.state - this.sendWindowCapacity = initialWindowSize - this.recvWindow = initialWindowSize - this.recvWindowCapacity = this.recvWindow - this.maxStreamWindowSize = init.maxStreamWindowSize ?? MAX_STREAM_WINDOW - this.epochStart = Date.now() - this.getRTT = init.getRTT - this.sendFrame = init.sendFrame - - const setStateToFinishedOnCloseListener = (): void => { - this.state = StreamState.Finished - } - this.addEventListener('close', setStateToFinishedOnCloseListener) - } - - /** - * Send a data message to the remote muxer - */ - sendData (buf: Uint8ArrayList): SendResult { - const totalBytes = buf.byteLength - let sentBytes = 0 - let canSendMore = true - - this.log?.trace('send window capacity is %d bytes', this.sendWindowCapacity) - - // send in chunks, waiting for window updates - while (buf.byteLength > 0) { - // we exhausted the send window, sending will resume later - if (this.sendWindowCapacity === 0) { - canSendMore = false - this.log?.trace('sent %d/%d bytes, exhausted send window, waiting for window update', sentBytes, totalBytes) - break - } - - // send as much as we can - const toSend = Math.min(this.sendWindowCapacity, buf.byteLength) - const flags = this.getSendFlags() - - const data = buf.sublist(0, toSend) - buf.consume(toSend) - - const muxerSendMore = this.sendFrame({ - type: FrameType.Data, - flag: flags, - streamID: this.streamId, - length: toSend - }, data) - - this.sendWindowCapacity -= toSend - sentBytes += toSend - - if (!muxerSendMore) { - canSendMore = muxerSendMore - this.log.trace('sent %d/%d bytes, wait for muxer to have more send capacity', sentBytes, totalBytes) - break - } - } - - return { - sentBytes, - canSendMore - } - } - - /** - * Send a reset message to the remote muxer - */ - async sendReset (): Promise { - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: Flag.RST, - streamID: this.streamId, - length: 0 - }) - } - - /** - * Send a message to the remote muxer, informing them no more data messages - * will be sent by this end of the stream - */ - async sendCloseWrite (): Promise { - const flags = this.getSendFlags() | Flag.FIN - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: flags, - streamID: this.streamId, - length: 0 - }) - } - - /** - * Send a message to the remote muxer, informing them no more data messages - * will be read by this end of the stream - this is a no-op on Yamux streams - */ - async sendCloseRead (options?: AbortOptions): Promise { - options?.signal?.throwIfAborted() - } - - /** - * Stop sending window updates temporarily - in the interim the the remote - * send window will exhaust and the remote will stop sending data - */ - sendPause (): void { - this.state = StreamState.Paused - } - - /** - * Start sending window updates as normal - */ - sendResume (): void { - this.state = StreamState.Established - this.sendWindowUpdate() - } - - /** - * handleWindowUpdate is called when the stream receives a window update frame - */ - handleWindowUpdate (frame: Frame): void { - this.processFlags(frame.header.flag) - - // increase send window - this.sendWindowCapacity += frame.header.length - - // change the chunk size the superclass uses - this.maxMessageSize = this.sendWindowCapacity - HEADER_LENGTH - - if (this.maxMessageSize < 0) { - this.maxMessageSize = 0 - } - - if (this.maxMessageSize === 0) { - return - } - - // if writing is paused and the update increases our send window, notify - // writers that writing can resume - if (this.writeBuffer.byteLength > 0) { - this.log?.trace('window update of %d bytes allows more data to be sent, have %d bytes queued, sending data %s', frame.header.length, this.writeBuffer.byteLength, this.sendingData) - this.safeDispatchEvent('drain') - } - } - - /** - * handleData is called when the stream receives a data frame - */ - handleData (frame: Frame): void { - if (!isDataFrame(frame)) { - throw new InvalidFrameError('Frame was not data frame') - } - - this.processFlags(frame.header.flag) - - // check that our recv window is not exceeded - if (this.recvWindowCapacity < frame.header.length) { - throw new ReceiveWindowExceededError('Receive window exceeded') - } - - this.recvWindowCapacity -= frame.header.length - - this.onData(frame.data) - - this.sendWindowUpdate() - } - - /** - * processFlags is used to update the state of the stream based on set flags, if any. - */ - private processFlags (flags: number): void { - if ((flags & Flag.ACK) === Flag.ACK) { - if (this.state === StreamState.SYNSent) { - this.state = StreamState.Established - } - } - - if ((flags & Flag.FIN) === Flag.FIN) { - this.onRemoteCloseWrite() - } - - if ((flags & Flag.RST) === Flag.RST) { - this.onRemoteReset() - } - } - - /** - * getSendFlags determines any flags that are appropriate - * based on the current stream state. - * - * The state is updated as a side-effect. - */ - private getSendFlags (): number { - switch (this.state) { - case StreamState.Init: - this.state = StreamState.SYNSent - return Flag.SYN - case StreamState.SYNReceived: - this.state = StreamState.Established - return Flag.ACK - default: - return 0 - } - } - - /** - * Potentially sends a window update enabling further remote writes to take - * place. - */ - sendWindowUpdate (): void { - if (this.state === StreamState.Paused) { - // we don't want any more data from the remote right now - update the - // epoch start as otherwise when we unpause we'd be looking at the epoch - // start from before we were paused - this.epochStart = Date.now() - - return - } - - // determine the flags if any - const flags = this.getSendFlags() - - // If the stream has already been established - // and we've processed data within the time it takes for 4 round trips - // then we (up to) double the recvWindow - const now = Date.now() - const rtt = this.getRTT() - - if (flags === 0 && rtt > -1 && (now - this.epochStart) <= (rtt * 4)) { - // we've already validated that maxStreamWindowSize can't be more than MAX_UINT32 - this.recvWindow = Math.min(this.recvWindow * 2, this.maxStreamWindowSize) - } - - if (this.recvWindowCapacity >= this.recvWindow && flags === 0) { - // a window update isn't needed - return - } - - // update the receive window - const delta = this.recvWindow - this.recvWindowCapacity - this.recvWindowCapacity = this.recvWindow - - // update the epoch start - this.epochStart = now - - // send window update - this.sendFrame({ - type: FrameType.WindowUpdate, - flag: flags, - streamID: this.streamId, - length: delta - }) - } -} diff --git a/packages/stream-multiplexer-yamux/test/bench/codec.bench.ts b/packages/stream-multiplexer-yamux/test/bench/codec.bench.ts deleted file mode 100644 index fcbf9efd1c..0000000000 --- a/packages/stream-multiplexer-yamux/test/bench/codec.bench.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { itBench } from '@dapplion/benchmark' -import { decodeHeader } from '../../src/decode.js' -import { encodeHeader } from '../../src/encode.js' -import { Flag, FrameType } from '../../src/frame.js' -import { decodeHeaderNaive, encodeHeaderNaive } from '../codec.util.js' -import type { FrameHeader } from '../../src/frame.js' - -describe('codec benchmark', () => { - for (const { encode, name } of [ - { encode: encodeHeader, name: 'encodeFrameHeader' }, - { encode: encodeHeaderNaive, name: 'encodeFrameHeaderNaive' } - ]) { - itBench({ - id: `frame header - ${name}`, - timeoutBench: 100000000, - beforeEach: () => { - return { - type: FrameType.WindowUpdate, - flag: Flag.ACK, - streamID: 0xffffffff, - length: 0xffffffff - } - }, - fn: (header) => { - encode(header) - } - }) - } - - for (const { decode, name } of [ - { decode: decodeHeader, name: 'decodeHeader' }, - { decode: decodeHeaderNaive, name: 'decodeHeaderNaive' } - ]) { - itBench({ - id: `frame header ${name}`, - beforeEach: () => { - const header = new Uint8Array(12) - for (let i = 1; i < 12; i++) { - header[i] = 255 - } - return header - }, - fn: (header) => { - decode(header) - } - }) - } -}) diff --git a/packages/stream-multiplexer-yamux/test/bench/comparison.bench.ts b/packages/stream-multiplexer-yamux/test/bench/comparison.bench.ts deleted file mode 100644 index d1a4534d5f..0000000000 --- a/packages/stream-multiplexer-yamux/test/bench/comparison.bench.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { itBench } from '@dapplion/benchmark' -import { mplex } from '@libp2p/mplex' -import { multiaddrConnectionPair } from '@libp2p/utils' -import { pEvent } from 'p-event' -import { yamux } from '../../src/index.ts' -import type { StreamMuxer } from '@libp2p/interface' - -interface Fixture { - client: StreamMuxer - server: StreamMuxer -} - -describe('comparison benchmark', () => { - for (const { impl, name } of [ - { impl: yamux()(), name: 'yamux' }, - { impl: mplex()(), name: 'mplex' } - ]) { - for (const { numMessages, msgSize } of [ - { numMessages: 1, msgSize: 2 ** 6 }, - { numMessages: 1, msgSize: 2 ** 10 }, - { numMessages: 1, msgSize: 2 ** 16 }, - { numMessages: 1, msgSize: 2 ** 20 }, - { numMessages: 1000, msgSize: 2 ** 6 }, - { numMessages: 1000, msgSize: 2 ** 10 }, - { numMessages: 1000, msgSize: 2 ** 16 }, - { numMessages: 1000, msgSize: 2 ** 20 } - ]) { - itBench({ - id: `${name} send and receive ${numMessages} ${msgSize / 1024}KB chunks`, - beforeEach: () => { - const [outboundConnection, inboundConnection] = multiaddrConnectionPair() - - return { - client: impl.createStreamMuxer(outboundConnection), - server: impl.createStreamMuxer(inboundConnection) - } - }, - fn: async ({ client, server }) => { - const stream = await client.createStream() - - for (let i = 0; i < numMessages; i++) { - const sendMore = stream.send(new Uint8Array(msgSize)) - - if (!sendMore) { - await pEvent(stream, 'drain') - } - } - - await stream.close() - } - }) - } - } -}) diff --git a/packages/stream-multiplexer-yamux/test/codec.spec.ts b/packages/stream-multiplexer-yamux/test/codec.spec.ts deleted file mode 100644 index 1bb0bf58d2..0000000000 --- a/packages/stream-multiplexer-yamux/test/codec.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { expect } from 'aegir/chai' -import { decodeHeader } from '../src/decode.js' -import { encodeHeader } from '../src/encode.js' -import { Flag, FrameType, GoAwayCode, stringifyHeader } from '../src/frame.js' -import { decodeHeaderNaive, encodeHeaderNaive } from './codec.util.js' -import type { FrameHeader } from '../src/frame.js' - -const frames: Array<{ header: FrameHeader, data?: Uint8Array }> = [ - { header: { type: FrameType.Ping, flag: Flag.SYN, streamID: 0, length: 1 } }, - { header: { type: FrameType.WindowUpdate, flag: Flag.SYN, streamID: 1, length: 1 } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.NormalTermination } }, - { header: { type: FrameType.Ping, flag: Flag.ACK, streamID: 0, length: 100 } }, - { header: { type: FrameType.WindowUpdate, flag: 0, streamID: 99, length: 1000 } }, - { header: { type: FrameType.WindowUpdate, flag: 0, streamID: 0xffffffff, length: 0xffffffff } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.ProtocolError } } -] - -describe('codec', () => { - for (const { header } of frames) { - it(`should round trip encode/decode header ${stringifyHeader(header)}`, () => { - expect(decodeHeader(encodeHeader(header))).to.deep.equal(header) - }) - } - - for (const { header } of frames) { - it(`should match naive implementations of encode/decode for header ${stringifyHeader(header)}`, () => { - expect(encodeHeader(header)).to.deep.equal(encodeHeaderNaive(header)) - expect(decodeHeader(encodeHeader(header))).to.deep.equal(decodeHeaderNaive(encodeHeaderNaive(header))) - }) - } -}) diff --git a/packages/stream-multiplexer-yamux/test/codec.util.ts b/packages/stream-multiplexer-yamux/test/codec.util.ts deleted file mode 100644 index 1478e70e0d..0000000000 --- a/packages/stream-multiplexer-yamux/test/codec.util.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { InvalidFrameError } from '../src/errors.js' -import { HEADER_LENGTH, YAMUX_VERSION } from '../src/frame.js' -import type { FrameHeader } from '../src/frame.js' - -// Slower encode / decode functions that use dataview - -export function decodeHeaderNaive (data: Uint8Array): FrameHeader { - const view = new DataView(data.buffer, data.byteOffset, data.byteLength) - - if (view.getUint8(0) !== YAMUX_VERSION) { - throw new InvalidFrameError('Invalid frame version') - } - return { - type: view.getUint8(1), - flag: view.getUint16(2, false), - streamID: view.getUint32(4, false), - length: view.getUint32(8, false) - } -} - -export function encodeHeaderNaive (header: FrameHeader): Uint8Array { - const frame = new Uint8Array(HEADER_LENGTH) - - const frameView = new DataView(frame.buffer, frame.byteOffset, frame.byteLength) - - // always assume version 0 - // frameView.setUint8(0, header.version) - - frameView.setUint8(1, header.type) - frameView.setUint16(2, header.flag, false) - frameView.setUint32(4, header.streamID, false) - frameView.setUint32(8, header.length, false) - - return frame -} diff --git a/packages/stream-multiplexer-yamux/test/compliance.spec.ts b/packages/stream-multiplexer-yamux/test/compliance.spec.ts deleted file mode 100644 index 60fbfd7142..0000000000 --- a/packages/stream-multiplexer-yamux/test/compliance.spec.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* eslint-env mocha */ - -import tests from '@libp2p/interface-compliance-tests/stream-muxer' -import { yamux } from '../src/index.ts' - -describe('compliance', () => { - tests({ - async setup () { - return yamux()() - }, - async teardown () {} - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/decode.spec.ts b/packages/stream-multiplexer-yamux/test/decode.spec.ts deleted file mode 100644 index d6da7987a9..0000000000 --- a/packages/stream-multiplexer-yamux/test/decode.spec.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { expect } from 'aegir/chai' -import all from 'it-all' -import { Uint8ArrayList } from 'uint8arraylist' -import { Decoder } from '../src/decode.js' -import { encodeHeader } from '../src/encode.js' -import { Flag, FrameType, GoAwayCode } from '../src/frame.js' -import type { FrameHeader } from '../src/frame.js' - -const frames: Array<{ header: FrameHeader, data?: Uint8Array }> = [ - { header: { type: FrameType.Ping, flag: Flag.SYN, streamID: 0, length: 1 } }, - { header: { type: FrameType.WindowUpdate, flag: Flag.SYN, streamID: 1, length: 1 } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.NormalTermination } }, - { header: { type: FrameType.Ping, flag: Flag.ACK, streamID: 0, length: 100 } }, - { header: { type: FrameType.WindowUpdate, flag: 0, streamID: 99, length: 1000 } }, - { header: { type: FrameType.GoAway, flag: 0, streamID: 0, length: GoAwayCode.ProtocolError } } -] - -const data = (length: number): Uint8Array => Uint8Array.from(Array.from({ length }), (_, i) => i) - -const expectEqualBytes = (actual: Uint8Array | Uint8ArrayList, expected: Uint8Array | Uint8ArrayList, reason?: string): void => { - expect(actual instanceof Uint8Array ? actual : actual.subarray(), reason).to.deep.equal(expected instanceof Uint8Array ? expected : expected.subarray()) -} - -const expectEqualDataFrame = (actual: { header: FrameHeader, data?: Uint8Array | Uint8ArrayList }, expected: { header: FrameHeader, data?: Uint8Array | Uint8ArrayList }, reason = ''): void => { - expect(actual.header, reason + ' header').to.deep.equal(expected.header) - if (actual.data == null && expected.data != null) { - expect.fail('actual has no data but expected does') - } - if (actual.data != null && expected.data == null) { - expect.fail('actual has data but expected does not') - } - if (actual.data != null && expected.data != null) { - expectEqualBytes(actual.data, expected.data, reason + ' data?: string') - } -} - -const expectEqualDataFrames = (actual: Array<{ header: FrameHeader, data?: Uint8Array | Uint8ArrayList }>, expected: Array<{ header: FrameHeader, data?: Uint8Array | Uint8ArrayList }>): void => { - if (actual.length !== expected.length) { - expect.fail('actual') - } - for (let i = 0; i < actual.length; i++) { - expectEqualDataFrame(actual[i], expected[i], String(i)) - } -} - -const dataFrame = (length: number): { header: FrameHeader, data: Uint8Array } => ({ - header: { type: FrameType.Data, flag: 0, streamID: 1, length }, - data: data(length) -}) - -export const randomRanges = (length: number): number[][] => { - const indices = [] - let i = 0 - let j = 0 - while (i < length) { - j = i - i += Math.floor(Math.random() * length) - indices.push([j, i]) - } - return indices -} - -describe('Decoder internals', () => { - describe('readHeader', () => { - const frame = frames[0] - const d = new Decoder() - - afterEach(() => { - d['buffer'].consume(d['buffer'].length) - }) - - it('should handle an empty buffer', async () => { - expect(d['buffer'].length, 'a freshly created decoder should have an empty buffer').to.equal(0) - expect(all(d.emitFrames(new Uint8Array()))).to.be.empty('an empty buffer should read no header') - }) - - it('should handle buffer length == header length', async () => { - expect(all(d.emitFrames(encodeHeader(frame.header)))).to.deep.equal([frame]) - expect(d['buffer'].length, 'the buffer should be fully drained').to.equal(0) - }) - - it('should handle buffer length < header length', async () => { - const upTo = 2 - - const buf = encodeHeader(frame.header) - - expect(all(d.emitFrames(buf.slice(0, upTo)))).to.be.empty('an buffer that has insufficient bytes should read no header') - expect(d['buffer'].length, 'a buffer that has insufficient bytes should not be consumed').to.equal(upTo) - - expect(all(d.emitFrames(buf.slice(upTo)))).to.deep.equal([frame], 'the decoded header should match the input') - expect(d['buffer'].length, 'the buffer should be fully drained').to.equal(0) - }) - - it('should handle buffer length > header length', async () => { - const more = 10 - - const buf = new Uint8ArrayList( - encodeHeader(frame.header), - new Uint8Array(more) - ) - - expect(all(d.emitFrames(buf.subarray()))).to.deep.equal([frame], 'the decoded header should match the input') - expect(d['buffer'].length, 'the buffer should be partially drained').to.equal(more) - }) - }) -}) - -describe('Decoder', () => { - describe('emitFrames', () => { - let d: Decoder - - beforeEach(() => { - d = new Decoder() - }) - - it('should emit frames from source chunked by frame', async () => { - const input = new Uint8ArrayList() - const expected = [] - for (const [i, frame] of frames.entries()) { - input.append(encodeHeader(frame.header)) - expected.push(frame) - - // sprinkle in more data frames - if (i % 2 === 1) { - const df = dataFrame(i * 100) - input.append(encodeHeader(df.header)) - input.append(df.data) - expected.push(df) - } - } - - const actual = all(d.emitFrames(input.subarray())) - - expectEqualDataFrames(actual, expected) - }) - - it('should emit frames from source chunked by partial frame', async () => { - const chunkSize = 5 - const input = new Uint8ArrayList() - const expected = [] - for (const [i, frame] of frames.entries()) { - const encoded = encodeHeader(frame.header) - for (let i = 0; i < encoded.length; i += chunkSize) { - input.append(encoded.slice(i, i + chunkSize)) - } - expected.push(frame) - - // sprinkle in more data frames - if (i % 2 === 1) { - const df = dataFrame(i * 100) - const encoded = Uint8Array.from([...encodeHeader(df.header), ...df.data]) - for (let i = 0; i < encoded.length; i += chunkSize) { - input.append(encoded.slice(i, i + chunkSize)) - } - expected.push(df) - } - } - - const actual = all(d.emitFrames(input.subarray())) - - expectEqualDataFrames(actual, expected) - }) - - it('should emit frames from source chunked by multiple frames', async () => { - const input = new Uint8ArrayList() - const expected = [] - for (let i = 0; i < frames.length; i++) { - const encoded1 = encodeHeader(frames[i].header) - expected.push(frames[i]) - - i++ - const encoded2 = encodeHeader(frames[i].header) - expected.push(frames[i]) - - // sprinkle in more data frames - const df = dataFrame(i * 100) - const encoded3 = Uint8Array.from([...encodeHeader(df.header), ...df.data]) - expected.push(df) - - const encodedChunk = new Uint8Array(encoded1.length + encoded2.length + encoded3.length) - encodedChunk.set(encoded1, 0) - encodedChunk.set(encoded2, encoded1.length) - encodedChunk.set(encoded3, encoded1.length + encoded2.length) - - input.append(encodedChunk) - } - - const actual = all(d.emitFrames(input.subarray())) - - expectEqualDataFrames(actual, expected) - }) - - it('should emit frames from source chunked chaotically', async () => { - const input = new Uint8ArrayList() - const expected = [] - const encodedFrames = [] - for (const [i, frame] of frames.entries()) { - encodedFrames.push(encodeHeader(frame.header)) - expected.push(frame) - - // sprinkle in more data frames - if (i % 2 === 1) { - const df = dataFrame(i * 100) - encodedFrames.push(encodeHeader(df.header)) - encodedFrames.push(df.data) - expected.push(df) - } - } - - // create a single byte array of all frames to send - // so that we can chunk them chaotically - const encoded = new Uint8Array(encodedFrames.reduce((a, b) => a + b.length, 0)) - let i = 0 - for (const e of encodedFrames) { - encoded.set(e, i) - i += e.length - } - - for (const [i, j] of randomRanges(encoded.length)) { - input.append(encoded.slice(i, j)) - } - - const actual = all(d.emitFrames(input.subarray())) - - expectEqualDataFrames(actual, expected) - }) - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/muxer.spec.ts b/packages/stream-multiplexer-yamux/test/muxer.spec.ts deleted file mode 100644 index bb59e8b50e..0000000000 --- a/packages/stream-multiplexer-yamux/test/muxer.spec.ts +++ /dev/null @@ -1,136 +0,0 @@ -/* eslint-env mocha */ - -import { multiaddrConnectionPair } from '@libp2p/utils' -import { expect } from 'aegir/chai' -import { YamuxMuxer } from '../src/muxer.ts' -import { sleep } from './util.js' -import type { MultiaddrConnection } from '@libp2p/interface' - -describe('muxer', () => { - let client: YamuxMuxer - let server: YamuxMuxer - let outboundConnection: MultiaddrConnection - let inboundConnection: MultiaddrConnection - - beforeEach(() => { - ([outboundConnection, inboundConnection] = multiaddrConnectionPair()) - client = new YamuxMuxer(outboundConnection) - server = new YamuxMuxer(inboundConnection) - }) - - afterEach(async () => { - if (client != null) { - await client.close() - } - - if (server != null) { - await server.close() - } - }) - - it('test repeated close', async () => { - // inspect logs to ensure its only closed once - await client.close() - await client.close() - await client.close() - }) - - it('test client<->client', async () => { - server['client'] = true - - await client.createStream().catch(() => {}) - await server.createStream().catch(() => {}) - - await sleep(20) - - expect(client).to.have.property('status', 'closed') - expect(server).to.have.property('status', 'closed') - }) - - it('test server<->server', async () => { - client['client'] = false - - await client.createStream().catch(() => {}) - await server.createStream().catch(() => {}) - - await sleep(20) - - expect(client).to.have.property('status', 'closed') - expect(server).to.have.property('status', 'closed') - }) - - it('test ping', async () => { - inboundConnection.pause() - const clientRTT = client.ping() - await sleep(10) - inboundConnection.resume() - await expect(clientRTT).to.eventually.not.equal(0) - - outboundConnection.pause() - const serverRTT = server.ping() - await sleep(10) - outboundConnection.resume() - expect(await serverRTT).to.not.equal(0) - }) - - it('test multiple simultaneous pings', async () => { - inboundConnection.pause() - const promise = [ - client.ping(), - client.ping(), - client.ping() - ] - await sleep(10) - inboundConnection.resume() - - const clientRTTs = await Promise.all(promise) - expect(clientRTTs[0]).to.not.equal(0) - expect(clientRTTs[0]).to.equal(clientRTTs[1]) - expect(clientRTTs[1]).to.equal(clientRTTs[2]) - - expect(client['nextPingID']).to.equal(1) - - await client.close() - }) - - it('test go away', async () => { - await client.close() - - await expect(client.createStream()).to.eventually.be.rejected() - .with.property('name', 'MuxerClosedError', 'should not be able to open a stream after close') - }) - - it('test keep alive', async () => { - client['keepAlive']?.setInterval(10) - - await sleep(1000) - - expect(client['nextPingID']).to.be.gt(2) - }) - - it('test max inbound streams', async () => { - server['maxInboundStreams'] = 1 - - await client.createStream() - await client.createStream() - await sleep(10) - - expect(server.streams.length).to.equal(1) - expect(client.streams.length).to.equal(1) - }) - - it('test max outbound streams', async () => { - client['maxOutboundStreams'] = 1 - - await client.createStream() - await sleep(10) - - try { - await client.createStream() - expect.fail('stream creation should fail if exceeding maxOutboundStreams') - } catch (e) { - expect(server.streams.length).to.equal(1) - expect(client.streams.length).to.equal(1) - } - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/stream.spec.ts b/packages/stream-multiplexer-yamux/test/stream.spec.ts deleted file mode 100644 index a13eb810ae..0000000000 --- a/packages/stream-multiplexer-yamux/test/stream.spec.ts +++ /dev/null @@ -1,277 +0,0 @@ -/* eslint-env mocha */ - -import { multiaddrConnectionPair, pipe } from '@libp2p/utils' -import { expect } from 'aegir/chai' -import drain from 'it-drain' -import { pushable } from 'it-pushable' -import { pEvent } from 'p-event' -import { defaultConfig } from '../src/config.js' -import { GoAwayCode } from '../src/frame.js' -import { YamuxMuxer } from '../src/muxer.ts' -import { StreamState, YamuxStream } from '../src/stream.js' -import { sleep } from './util.js' -import type { MultiaddrConnection } from '@libp2p/interface' -import type { Pushable } from 'it-pushable' - -describe('stream', () => { - let inboundConnection: MultiaddrConnection - let outboundConnection: MultiaddrConnection - let client: YamuxMuxer - let server: YamuxMuxer - - beforeEach(() => { - ([inboundConnection, outboundConnection] = multiaddrConnectionPair()) - client = new YamuxMuxer(inboundConnection, { - maxEarlyStreams: 2000 - }) - server = new YamuxMuxer(outboundConnection, { - maxEarlyStreams: 2000 - }) - }) - - afterEach(async () => { - await client?.close().catch(err => { - client.abort(err) - }) - await server?.close().catch(err => { - server.abort(err) - }) - }) - - it('test send data - small', async () => { - const [ - s1, c1 - ] = await Promise.all([ - pEvent<'stream', CustomEvent>(server, 'stream').then(evt => evt.detail), - client.createStream() - ]) - - await Promise.all([ - Promise.resolve().then(async () => { - for (let i = 0; i < 10; i++) { - const sendMore = c1.send(new Uint8Array(256)) - - if (!sendMore) { - await pEvent(c1, 'drain') - } - } - - await c1.close() - }), - drain(s1) - ]) - - // the window capacities should have refilled via window updates as received data was consumed - expect(c1['sendWindowCapacity']).to.be.gte(defaultConfig.streamOptions.initialStreamWindowSize) - expect(s1['recvWindowCapacity']).to.be.gte(defaultConfig.streamOptions.initialStreamWindowSize) - }) - - it('test send data - large', async () => { - const [ - s1, c1 - ] = await Promise.all([ - pEvent<'stream', CustomEvent>(server, 'stream').then(evt => evt.detail), - client.createStream() - ]) - - await Promise.all([ - Promise.resolve().then(async () => { - // amount of data is greater than initial window size - // and each payload is also greater than the max message size - // this will payload chunking and also waiting for window updates before - // continuing to send - for (let i = 0; i < 10; i++) { - const sendMore = c1.send(new Uint8Array(defaultConfig.streamOptions.initialStreamWindowSize)) - - if (!sendMore) { - await pEvent(c1, 'drain') - } - } - - await c1.close() - }), - drain(s1) - ]) - - // the window capacities should have refilled via window updates as received data was consumed - expect(c1['sendWindowCapacity']).to.be.gte(defaultConfig.streamOptions.initialStreamWindowSize) - expect(s1['recvWindowCapacity']).to.be.gte(defaultConfig.streamOptions.initialStreamWindowSize) - }) - - it('test send data - large with increasing recv window size', async () => { - const [ - s1, c1 - ] = await Promise.all([ - pEvent<'stream', CustomEvent>(server, 'stream').then(evt => evt.detail), - client.createStream(), - server.ping() - ]) - - await Promise.all([ - Promise.resolve().then(async () => { - // amount of data is greater than initial window size - // and each payload is also greater than the max message size - // this will payload chunking and also waiting for window updates before - // continuing to send - for (let i = 0; i < 10; i++) { - const sendMore = c1.send(new Uint8Array(defaultConfig.streamOptions.initialStreamWindowSize)) - - if (!sendMore) { - await pEvent(c1, 'drain') - } - } - await c1.close() - }), - drain(s1) - ]) - - // the window capacities should have refilled via window updates as received data was consumed - expect(c1['sendWindowCapacity']).to.be.gte(defaultConfig.streamOptions.initialStreamWindowSize) - expect(s1['recvWindowCapacity']).to.be.gte(defaultConfig.streamOptions.initialStreamWindowSize) - }) - - it('test many streams', async () => { - for (let i = 0; i < 1000; i++) { - await client.createStream() - } - await sleep(100) - - expect(client.streams.length).to.equal(1000) - expect(server.streams.length).to.equal(1000) - }) - - it('test many streams - ping pong', async () => { - server.addEventListener('stream', (evt) => { - // echo on incoming streams - pipe(evt.detail, evt.detail) - }) - - const numStreams = 10 - - const p: Array> = [] - for (let i = 0; i < numStreams; i++) { - client.createStream() - p.push(pushable()) - } - await sleep(100) - - for (let i = 0; i < numStreams; i++) { - const s = client.streams[i] - void pipe(p[i], s) - p[i].push(new Uint8Array(16)) - } - await sleep(100) - - expect(client.streams.length).to.equal(numStreams) - expect(server.streams.length).to.equal(numStreams) - - await client.close() - }) - - it('test stream close', async () => { - server.addEventListener('stream', (evt) => { - // close incoming streams - evt.detail.close() - }) - - const c1 = await client.createStream() - await c1.close() - await sleep(100) - - expect(c1.state).to.equal(StreamState.Finished) - - expect(client.streams).to.be.empty() - expect(server.streams).to.be.empty() - }) - - it('test stream close write', async () => { - const c1 = await client.createStream() - await c1.close() - await sleep(100) - - expect(c1.state).to.equal(StreamState.SYNSent) - expect(c1.writeStatus).to.equal('closed') - - const s1 = server.streams[0] - expect(s1).to.not.be.undefined() - expect(s1.state).to.equal(StreamState.SYNReceived) - }) - - it('test stream close read', async () => { - const c1 = await client.createStream() - await c1.closeRead() - await sleep(5) - - const s1 = server.streams[0] - expect(s1).to.not.be.undefined() - expect(s1.readStatus).to.equal('readable') - expect(s1.writeStatus).to.equal('writable') - }) - - it('test stream close write', async () => { - const c1 = await client.createStream() - await c1.close() - await sleep(5) - - expect(c1.readStatus).to.equal('readable') - expect(c1.writeStatus).to.equal('closed') - - const s1 = server.streams[0] - expect(s1).to.not.be.undefined() - expect(s1.readStatus).to.equal('readable') - expect(s1.writeStatus).to.equal('writable') - }) - - it('test window overflow', async () => { - const [ - s1, c1 - ] = await Promise.all([ - pEvent<'stream', CustomEvent>(server, 'stream').then(evt => evt.detail), - client.createStream() - ]) - - await expect( - Promise.all([ - (async () => { - const data = new Array(10).fill(new Uint8Array(s1['recvWindowCapacity'] * 2)) - - for (const buf of data) { - c1['maxMessageSize'] = s1['recvWindowCapacity'] * 2 - c1['sendWindowCapacity'] = s1['recvWindowCapacity'] * 2 - const sendMore = c1.send(buf) - - if (!sendMore) { - await pEvent(c1, 'drain') - } - } - - await c1.close() - })(), - drain(s1) - ]) - ).to.eventually.be.rejected() - .with.property('name', 'ReceiveWindowExceededError') - - expect(client).to.have.property('remoteGoAway', GoAwayCode.ProtocolError) - expect(server).to.have.property('localGoAway', GoAwayCode.ProtocolError) - }) - - it('test stream sink error', async () => { - // make the 'drain' event slow to fire - // @ts-expect-error private fields - inboundConnection.local.delay = 1000 - - const c1 = await client.createStream() - - // send more data than the window size, will trigger a wait - while (c1.send(new Uint8Array(defaultConfig.streamOptions.initialStreamWindowSize))) { - - } - - // the client should fail to close gracefully because there is unsent data - // that will never be sent - await expect(client.close({ - signal: AbortSignal.timeout(10) - })).to.eventually.be.rejected() - }) -}) diff --git a/packages/stream-multiplexer-yamux/test/util.ts b/packages/stream-multiplexer-yamux/test/util.ts deleted file mode 100644 index 3eb3182e48..0000000000 --- a/packages/stream-multiplexer-yamux/test/util.ts +++ /dev/null @@ -1,3 +0,0 @@ -export async function sleep (ms: number): Promise { - return new Promise(resolve => setTimeout(() => { resolve(ms) }, ms)) -} diff --git a/packages/stream-multiplexer-yamux/tsconfig.json b/packages/stream-multiplexer-yamux/tsconfig.json deleted file mode 100644 index 01e2110b8f..0000000000 --- a/packages/stream-multiplexer-yamux/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "extends": "aegir/src/config/tsconfig.aegir.json", - "compilerOptions": { - "outDir": "dist" - }, - "include": [ - "src", - "test" - ], - "references": [ - { - "path": "../interface" - }, - { - "path": "../interface-compliance-tests" - }, - { - "path": "../stream-multiplexer-mplex" - }, - { - "path": "../utils" - } - ] -} diff --git a/packages/stream-multiplexer-yamux/typedoc.json b/packages/stream-multiplexer-yamux/typedoc.json deleted file mode 100644 index 1c46781ee6..0000000000 --- a/packages/stream-multiplexer-yamux/typedoc.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "readme": "none", - "entryPoints": [ - "./src/index.ts", - "./src/config.ts", - "./src/stream.ts" - ] -} diff --git a/packages/transport-webrtc/package.json b/packages/transport-webrtc/package.json index eefc810406..2e8832672a 100644 --- a/packages/transport-webrtc/package.json +++ b/packages/transport-webrtc/package.json @@ -49,7 +49,7 @@ "@libp2p/interface": "^3.0.0", "@libp2p/interface-internal": "^3.0.1", "@libp2p/keychain": "^6.0.1", - "@libp2p/noise": "^17.0.1", + "@chainsafe/libp2p-noise": "^17.0.0", "@libp2p/peer-id": "^6.0.1", "@libp2p/utils": "^7.0.1", "@multiformats/multiaddr": "^13.0.1", diff --git a/packages/transport-webrtc/src/index.ts b/packages/transport-webrtc/src/index.ts index ffdbd6faf4..7f7fc2d361 100644 --- a/packages/transport-webrtc/src/index.ts +++ b/packages/transport-webrtc/src/index.ts @@ -26,8 +26,8 @@ * WebRTC requires use of a relay to connect two nodes. The listener first discovers a relay server and makes a reservation, then the dialer can connect via the relayed address. * * ```TypeScript - * import { noise } from '@libp2p/noise' - * import { yamux } from '@libp2p/yamux' + * import { noise } from '@chainsafe/libp2p-noise' + * import { yamux } from '@chainsafe/libp2p-yamux' * import { echo } from '@libp2p/echo' * import { circuitRelayTransport, circuitRelayServer } from '@libp2p/circuit-relay-v2' * import { identify } from '@libp2p/identify' diff --git a/packages/transport-webrtc/src/private-to-public/utils/connect.ts b/packages/transport-webrtc/src/private-to-public/utils/connect.ts index 14e31aa7f6..137e1dc37f 100644 --- a/packages/transport-webrtc/src/private-to-public/utils/connect.ts +++ b/packages/transport-webrtc/src/private-to-public/utils/connect.ts @@ -1,4 +1,4 @@ -import { noise } from '@libp2p/noise' +import { noise } from '@chainsafe/libp2p-noise' import { pEvent } from 'p-event' import { WebRTCTransportError } from '../../error.js' import { DataChannelMuxerFactory } from '../../muxer.js' diff --git a/packages/transport-webrtc/tsconfig.json b/packages/transport-webrtc/tsconfig.json index 687a4c5074..86f9a305bc 100644 --- a/packages/transport-webrtc/tsconfig.json +++ b/packages/transport-webrtc/tsconfig.json @@ -9,9 +9,6 @@ "proto_ts" ], "references": [ - { - "path": "../connection-encrypter-noise" - }, { "path": "../crypto" }, diff --git a/packages/transport-webtransport/package.json b/packages/transport-webtransport/package.json index b5490dbb38..7d6de5891d 100644 --- a/packages/transport-webtransport/package.json +++ b/packages/transport-webtransport/package.json @@ -45,7 +45,7 @@ }, "dependencies": { "@libp2p/interface": "^3.0.0", - "@libp2p/noise": "^17.0.1", + "@chainsafe/libp2p-noise": "^17.0.0", "@libp2p/peer-id": "^6.0.1", "@libp2p/utils": "^7.0.1", "@multiformats/multiaddr": "^13.0.1", diff --git a/packages/transport-webtransport/src/index.ts b/packages/transport-webtransport/src/index.ts index 4ea0f7dd11..c67317a473 100644 --- a/packages/transport-webtransport/src/index.ts +++ b/packages/transport-webtransport/src/index.ts @@ -14,7 +14,7 @@ * ```TypeScript * import { createLibp2p } from 'libp2p' * import { webTransport } from '@libp2p/webtransport' - * import { noise } from '@libp2p/noise' + * import { noise } from '@chainsafe/libp2p-noise' * * const node = await createLibp2p({ * transports: [ @@ -27,8 +27,8 @@ * ``` */ +import { noise } from '@chainsafe/libp2p-noise' import { InvalidCryptoExchangeError, InvalidParametersError, serviceCapabilities, transportSymbol } from '@libp2p/interface' -import { noise } from '@libp2p/noise' import { WebTransport as WebTransportMatcher } from '@multiformats/multiaddr-matcher' import { CustomProgressEvent } from 'progress-events' import createListener from './listener.js' diff --git a/packages/transport-webtransport/test/browser.ts b/packages/transport-webtransport/test/browser.ts index 1886b63a82..1d947cf584 100644 --- a/packages/transport-webtransport/test/browser.ts +++ b/packages/transport-webtransport/test/browser.ts @@ -1,7 +1,7 @@ /* eslint-env mocha */ +import { noise } from '@chainsafe/libp2p-noise' import { stop } from '@libp2p/interface' -import { noise } from '@libp2p/noise' import { ping } from '@libp2p/ping' import { multiaddr } from '@multiformats/multiaddr' import { expect } from 'aegir/chai' diff --git a/packages/transport-webtransport/tsconfig.json b/packages/transport-webtransport/tsconfig.json index 21c1dfed22..1b96cbef49 100644 --- a/packages/transport-webtransport/tsconfig.json +++ b/packages/transport-webtransport/tsconfig.json @@ -8,9 +8,6 @@ "test" ], "references": [ - { - "path": "../connection-encrypter-noise" - }, { "path": "../crypto" },