diff --git a/.github/workflows/coverage-protocols.yaml b/.github/workflows/coverage-protocols.yaml new file mode 100644 index 0000000000..9e49d26721 --- /dev/null +++ b/.github/workflows/coverage-protocols.yaml @@ -0,0 +1,146 @@ +name: Protocol test Coverage + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + protocols-coverage: + + name: tarpaulin Test + runs-on: ubuntu-latest + container: + image: xd009642/tarpaulin:0.27.1-nightly + options: --security-opt seccomp=unconfined + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate code coverage + run: | + ./scripts/coverage-protocols.sh + + - name: Upload protocols coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports + file: ./protocols/target/tarpaulin-reports/cobertura.xml + flags: protocols + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload binary_codec_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/codec-coverage + file: ./protocols/target/tarpaulin-reports/codec-coverage/cobertura.xml + flags: binary_codec_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload binary_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/binary-sv2-coverage + file: ./protocols/target/tarpaulin-reports/binary-sv2-coverage/cobertura.xml + flags: binary_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload binary_serde_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/serde-sv2-coverage + file: ./protocols/target/tarpaulin-reports/serde-sv2-coverage/cobertura.xml + flags: binary_serde_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload codec_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/codec-sv2-coverage + file: ./protocols/target/tarpaulin-reports/codec-sv2-coverage/cobertura.xml + flags: codec_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload common_messages_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/common-messages-coverage + file: ./protocols/target/tarpaulin-reports/common-messages-coverage/cobertura.xml + flags: common_messages_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload const_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/const-sv2-coverage + file: ./protocols/target/tarpaulin-reports/const-sv2-coverage/cobertura.xml + flags: const_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload framing_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/framing-sv2-coverage + file: ./protocols/target/tarpaulin-reports/framing-sv2-coverage/cobertura.xml + flags: framing_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload job_declaration_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/job-declaration-coverage + file: ./protocols/target/tarpaulin-reports/job-declaration-coverage/cobertura.xml + flags: job_declaration_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + + - name: Upload noise_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/noise-sv2-coverage + file: ./protocols/target/tarpaulin-reports/noise-sv2-coverage/cobertura.xml + flags: noise_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload roles_logic_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/roles-logic-sv2-coverage + file: ./protocols/target/tarpaulin-reports/roles-logic-sv2-coverage/cobertura.xml + flags: roles_logic_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload v1-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/v1-coverage + file: ./protocols/target/tarpaulin-reports/v1-coverage/cobertura.xml + flags: v1-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload sv2_ffi-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/sv2-ffi-coverage + file: ./protocols/target/tarpaulin-reports/sv2-ffi-coverage/cobertura.xml + flags: sv2_ffi-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload template_distribution_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/template-distribution-coverage + file: ./protocols/target/tarpaulin-reports/template-distribution-coverage/cobertura.xml + flags: template_distribution_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload mining-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./protocols/target/tarpaulin-reports/mining-coverage + file: ./protocols/target/tarpaulin-reports/mining-coverage/cobertura.xml + flags: mining-coverage + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/coverage-roles.yaml b/.github/workflows/coverage-roles.yaml new file mode 100644 index 0000000000..434417ce8b --- /dev/null +++ b/.github/workflows/coverage-roles.yaml @@ -0,0 +1,89 @@ +name: Roles test Coverage + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + roles-coverage: + + name: tarpaulin Test + runs-on: ubuntu-latest + container: + image: xd009642/tarpaulin:0.27.1-nightly + options: --security-opt seccomp=unconfined + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate code coverage + run: | + ./scripts/coverage-roles.sh + + - name: Upload roles coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports + file: ./roles/target/tarpaulin-reports/cobertura.xml + flags: roles + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload jd_client-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports/jd-client-coverage + file: ./roles/target/tarpaulin-reports/jd-client-coverage/cobertura.xml + flags: jd_client-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload jd_server-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports/jd-server-coverage + file: ./roles/target/tarpaulin-reports/jd-server-coverage/cobertura.xml + flags: jd_server-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload mining_device-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports/mining-device-coverage + file: ./rroles/target/tarpaulin-reports/mining-device-coverage/cobertura.xml + flags: mining_device-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload mining_proxy_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports/mining-proxy-coverage + file: ./roles/target/tarpaulin-reports/mining-proxy-coverage/cobertura.xml + flags: mining_proxy_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload pool_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports/pool-coverage + file: ./roles/target/tarpaulin-reports/pool-coverage/cobertura.xml + flags: pool_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload sv1-mining-device-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports/sv1-mining-device-coverage + file: ./roles/target/tarpaulin-reports/sv1-mining-device-coverage/cobertura.xml + flags: sv1-mining-device-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload translator_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./roles/target/tarpaulin-reports/translator-coverage + file: ./roles/target/tarpaulin-reports/translator-coverage/cobertura.xml + flags: translator_sv2-coverage + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/coverage-utils.yaml b/.github/workflows/coverage-utils.yaml new file mode 100644 index 0000000000..5fd6ebc1fe --- /dev/null +++ b/.github/workflows/coverage-utils.yaml @@ -0,0 +1,66 @@ +name: Util Test Coverage + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + utils-coverage: + + name: tarpaulin Test + runs-on: ubuntu-latest + container: + image: xd009642/tarpaulin:0.27.1-nightly + options: --security-opt seccomp=unconfined + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate code coverage + run: | + ./scripts/coverage-utils.sh + + - name: Upload utils coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./utils/target/tarpaulin-reports + file: ./utils/target/tarpaulin-reports/cobertura.xml + flags: utils + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload bip32_derivation-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./utils/target/tarpaulin-reports/bip32-key-derivation-coverage + file: ./utils/target/tarpaulin-reports/bip32-key-derivation-coverage/cobertura.xml + flags: bip32_derivation-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload buffer_sv2-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./utils/target/tarpaulin-reports/buffer-coverage + file: ./utils/target/tarpaulin-reports/buffer-coverage/cobertura.xml + flags: buffer_sv2-coverage + + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload error_handling-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./utils/target/tarpaulin-reports/error-handling-coverage + file: ./utils/target/tarpaulin-reports/error-handling-coverage/cobertura.xml + flags: error_handling-coverage + token: ${{ secrets.CODECOV_TOKEN }} + + - name: Upload key-utils-coverage to codecov.io + uses: codecov/codecov-action@v4 + with: + directory: ./utils/target/tarpaulin-reports/key-utils-coverage + file: ./utils/target/tarpaulin-reports/key-utils-coverage/cobertura.xml + flags: key-utils-coverage + token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml deleted file mode 100644 index c8e18d346d..0000000000 --- a/.github/workflows/coverage.yaml +++ /dev/null @@ -1,50 +0,0 @@ -# Performs test coverage of project's libraries using cargo-tarpaulin and generates results using codecov.io. -# The following flags are set inside `tarpaulin.toml`: -# `features = "..."`: Includes the code with the listed features. The following features result in a -# tarpaulin error and are NOT included: derive, alloc, arbitrary-derive, attributes, and -# with_serde -# `run-types = [ "Lib" ]`: Only tests the package's library unit tests. Includes protocols, and utils (without the -# exclude-files flag, it includes this example because it contains a lib.rs file) -# `exclude-files = [ "examples/*" ]`: Excludes all projects in examples directory (specifically added to -# ignore examples that that contain a lib.rs file like interop-cpp) -# `timeout = "120s"`: If unresponsive for 120 seconds, action will fail -# `fail-under = 20`: If code coverage is less than 20%, action will fail -# `out = ["Xml"]`: Required for codecov.io to generate coverage result -# All message-generator test flags are in tests in test/message-generator/test -# This test loops through every test in test/message-generator/test, and runs each one, collecting -# code coverage data for anything in the roles/ directory that is relevant to SV2(pool, mining-proxy) - -name: Test Coverage - -on: - push: - branches: - - main - pull_request: - branches: - - main - -jobs: - tarpaulin-test: - - name: Tarpaulin Test - runs-on: ubuntu-latest - container: - image: xd009642/tarpaulin:0.27.1-nightly - options: --security-opt seccomp=unconfined - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Generate code coverage - run: | - ./scripts/tarpaulin.sh - - - name: Archive Tarpaulin code coverage results - uses: actions/upload-artifact@v4 - with: - name: tarpaulin-report - path: | - protocols/cobertura.xml - roles/cobertura.xml - utils/cobertura.xml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 6665a902f0..970bf0e51b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -38,6 +38,10 @@ jobs: cargo build --manifest-path=roles/Cargo.toml cargo build --manifest-path=utils/Cargo.toml + - name: Roles Integration Tests + run: | + cargo test --manifest-path=roles/Cargo.toml --verbose --test '*' -- --nocapture + - name: Run sv1-client-and-server example run: | cargo run --manifest-path=examples/sv1-client-and-server/Cargo.toml --bin client_and_server -- 60 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/RELEASE.md b/RELEASE.md index 1d27085b25..21f1dec0f7 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,13 +1,11 @@ ## Changelog -The changelog is a file that contains a curated, chronologically ordered list of -notable changes for each version of a project. It is a way to keep track of the -project's progress and to communicate the changes to the users and the -community. The changelog should be updated for each release and is part of the -release process. - -Things to take into account while writing a changelog: +The changelog is a list of notable changes for each version of a project. +It is a way to keep track of the project's progress and to communicate +the changes to the users and the community. +The changelog is automatically generated on each release page, and extra contextual +information is added as needed, such as: - General release information. - Breaking changes. - Notable changes. @@ -21,18 +19,13 @@ version number, from the `main` branch. The release branch is used as a breaking point and future reference for the release and should include the changelog entries for the release and any other release-specific tasks. Any bug fixes or changes for the release should be done on the release branch. When the -release branch is ready, create a Pull Request from release branch to the `main` -branch. Once the Pull Request is approved and merged, create a new tag for the -release branch and initiate any publishing tasks. +release branch is ready, create a new tag and initiate any publishing tasks. Usually the release process is as follows: 1. Create a new release branch from the `main` branch. -2. Amend the changelog with the new release information. -3. Create a Pull Request with `main` branch as base. -4. Merge the Pull Request. -5. Create a new tag for the release branch. -6. Publish the release. +2. Create a new tag for the release branch. +3. Publish the release. ## Versioning diff --git a/common/Cargo.toml b/common/Cargo.toml index 3b9d7cc2cd..b2a01f0ec1 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -9,3 +9,6 @@ repository = "https://github.com/stratum-mining/stratum" [dependencies] bitcoin = {version="0.29.1",optional=true} secp256k1 = { version = "0.28.2", default-features = false, features =["alloc","rand","rand-std"] } + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/examples/interop-cpp/Cargo.toml b/examples/interop-cpp/Cargo.toml index 6bd1d959cb..f6f192d79e 100644 --- a/examples/interop-cpp/Cargo.toml +++ b/examples/interop-cpp/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "interop-cpp" version = "0.1.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" publish = false diff --git a/examples/ping-pong-with-noise/Cargo.toml b/examples/ping-pong-with-noise/Cargo.toml index b1220bce7d..760f89eced 100644 --- a/examples/ping-pong-with-noise/Cargo.toml +++ b/examples/ping-pong-with-noise/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ping_pong_with_noise" version = "0.1.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" publish = false diff --git a/examples/ping-pong-without-noise/Cargo.toml b/examples/ping-pong-without-noise/Cargo.toml index 5619bfa4bb..9f5c90c584 100644 --- a/examples/ping-pong-without-noise/Cargo.toml +++ b/examples/ping-pong-without-noise/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ping_pong_without_noise" version = "0.1.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" publish = false diff --git a/git-branching.png b/git-branching.png deleted file mode 100644 index 1337f45ea1..0000000000 Binary files a/git-branching.png and /dev/null differ diff --git a/protocols/Cargo.toml b/protocols/Cargo.toml index d1f21ca36d..2af4c9c982 100644 --- a/protocols/Cargo.toml +++ b/protocols/Cargo.toml @@ -1,16 +1,3 @@ -name = "stratum_v2_protocols" -version = "1.0.0" -authors = ["The Stratum v2 Developers"] -edition = "2021" -description = "The Stratum protocol defines how miners, proxies, and pools communicate to contribute hashrate to the Bitcoin network. Stratum v2 is a robust set of primitives which anyone can use to expand the protocol or implement a role." -documentation = "https://github.com/stratum-mining/stratum" -readme = "README.md" -homepage = "https://stratumprotocol.org" -repository = "https://github.com/stratum-mining/stratum" -license = "MIT + Apache-2.0" -license-file = "LICENSE.md" -keywords = ["stratum", "mining", "bitcoin", "protocol"] - [workspace] resolver="2" diff --git a/protocols/fuzz-tests/Cargo.toml b/protocols/fuzz-tests/Cargo.toml index d29d588ce9..5f0ee7ef61 100644 --- a/protocols/fuzz-tests/Cargo.toml +++ b/protocols/fuzz-tests/Cargo.toml @@ -4,6 +4,11 @@ version = "1.0.0" edition = "2021" authors = ["Automatically generated"] publish = false +documentation = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [package.metadata] cargo-fuzz = true diff --git a/protocols/tarpaulin.toml b/protocols/tarpaulin.toml index d611e2d34d..1a7b3b4609 100644 --- a/protocols/tarpaulin.toml +++ b/protocols/tarpaulin.toml @@ -2,7 +2,7 @@ features = "disable_nopanic prop_test noise_sv2 with_buffer_pool derive_codec_sv2 binary_codec_sv2 default core" run-types = [ "Lib" ] timeout = "120s" -fail-under = 20 +fail-under = 0 [report] out = ["Xml"] diff --git a/protocols/v1/Cargo.toml b/protocols/v1/Cargo.toml index 0e7bd18267..a983e6748a 100644 --- a/protocols/v1/Cargo.toml +++ b/protocols/v1/Cargo.toml @@ -1,19 +1,21 @@ [package] name = "sv1_api" version = "1.0.1" -authors = ["user"] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "API for bridging SV1 miners to SV2 pools" +documentation = "https://docs.rs/sv1_api" license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] bitcoin_hashes = "0.3.2" byteorder = "1.2.7" -pretty_env_logger = "0.4.0" -log = "0.4.0" hex = "0.4.3" serde = { version = "1.0.89", default-features = false, features = ["derive", "alloc"] } serde_json = { version = "1.0.64", default-features = false, features = ["alloc"] } @@ -23,3 +25,6 @@ binary_sv2 = { version = "^1.0.0", path = "../v2/binary-sv2/binary-sv2"} [dev-dependencies] quickcheck = "1" quickcheck_macros = "1" + +[package.metadata.docs.rs] +all-features = true diff --git a/protocols/v2/binary-sv2/binary-sv2/Cargo.toml b/protocols/v2/binary-sv2/binary-sv2/Cargo.toml index 922d491a21..3df8d5e3b0 100644 --- a/protocols/v2/binary-sv2/binary-sv2/Cargo.toml +++ b/protocols/v2/binary-sv2/binary-sv2/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "binary_sv2" version = "1.2.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Sv2 data format" -license = "MIT" +documentation = "https://docs.rs/binary_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,3 +26,6 @@ core = ["binary_codec_sv2", "derive_codec_sv2"] with_serde = ["serde_sv2", "serde"] prop_test = ["binary_codec_sv2/prop_test", "derive_codec_sv2"] with_buffer_pool = ["binary_codec_sv2/with_buffer_pool", "derive_codec_sv2"] + +[package.metadata.docs.rs] +all-features = true diff --git a/protocols/v2/binary-sv2/no-serde-sv2/codec/Cargo.toml b/protocols/v2/binary-sv2/no-serde-sv2/codec/Cargo.toml index 5394f336cc..459ccd2a14 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/codec/Cargo.toml +++ b/protocols/v2/binary-sv2/no-serde-sv2/codec/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "binary_codec_sv2" version = "1.2.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Sv2 data format" -license = "MIT" +documentation = "https://docs.rs/binary_codec_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -18,3 +22,6 @@ no_std = [] default = ["no_std"] prop_test = ["quickcheck"] with_buffer_pool = ["buffer_sv2"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml index 93f202d8bd..c7884c73af 100644 --- a/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml +++ b/protocols/v2/binary-sv2/no-serde-sv2/derive_codec/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "derive_codec_sv2" version = "1.1.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Derive macro for Sv2 binary format serializer and deserializer" -license = "MIT" +documentation = "https://docs.rs/derive_codec_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -14,3 +18,6 @@ binary_codec_sv2 = {version = "^1.0.0", path="../codec"} [lib] proc-macro = true + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/binary-sv2/serde-sv2/Cargo.toml b/protocols/v2/binary-sv2/serde-sv2/Cargo.toml index 2dcc744c8e..0b30e21990 100644 --- a/protocols/v2/binary-sv2/serde-sv2/Cargo.toml +++ b/protocols/v2/binary-sv2/serde-sv2/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "serde_sv2" version = "1.0.1" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Serlializer and Deserializer for Stratum V2 data format" -license = "MIT" +documentation = "https://docs.rs/serde_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,3 +20,6 @@ buffer_sv2 = {version = "^1.0.0", path = "../../../../utils/buffer"} [features] no_std = [] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/codec-sv2/Cargo.toml b/protocols/v2/codec-sv2/Cargo.toml index 20b0288afa..ddfbd66f22 100644 --- a/protocols/v2/codec-sv2/Cargo.toml +++ b/protocols/v2/codec-sv2/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "codec_sv2" -version = "1.2.1" -authors = ["fi3 "] +version = "1.3.0" +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Sv2 data format" -license = "MIT" +documentation = "https://docs.rs/codec_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] serde = { version = "1.0.89", default-features = false, optional = true } @@ -16,9 +20,13 @@ const_sv2 = { version = "2.0.0", path = "../../../protocols/v2/const-sv2"} buffer_sv2 = { version = "1.0.0", path = "../../../utils/buffer"} tracing = { version = "0.1"} - +[dev-dependencies] +key-utils = { version = "^1.0.0", path = "../../../utils/key-utils" } [features] with_serde = ["binary_sv2/with_serde", "serde", "framing_sv2/with_serde", "buffer_sv2/with_serde"] with_buffer_pool = ["framing_sv2/with_buffer_pool"] -no_std = [] \ No newline at end of file +no_std = [] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/codec-sv2/README.md b/protocols/v2/codec-sv2/README.md new file mode 100644 index 0000000000..a57a4a2fd0 --- /dev/null +++ b/protocols/v2/codec-sv2/README.md @@ -0,0 +1,49 @@ +# `codec_sv2` + +[![crates.io](https://img.shields.io/crates/v/codec_sv2.svg)](https://crates.io/crates/codec_sv2) +[![docs.rs](https://docs.rs/codec_sv2/badge.svg)](https://docs.rs/codec_sv2) +[![rustc+](https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) +[![license](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/stratum-mining/stratum/blob/main/LICENSE.md) +[![codecov](https://codecov.io/gh/stratum-mining/stratum/branch/main/graph/badge.svg?flag=codec_sv2-coverage)](https://codecov.io/gh/stratum-mining/stratum) + +`codec_sv2` provides the message encoding and decoding functionality for the Stratum V2 (Sv2) +protocol, handling secure communication between Sv2 roles. This crate abstracts the complexity of +message encoding/decoding with optional Noise protocol support, ensuring both regular and encrypted +messages can be serialized, transmitted, and decoded consistently and reliably. + +## Main Components + +- **Encoder**: Encodes Sv2 messages with or without Noise protocol support. +- **Decoder**: Decodes Sv2 messages with or without Noise protocol support. +- **Handshake State**: Manages the current Noise protocol handshake state of the codec. + + +## Usage + +To include this crate in your project, run: + +```bash +cargo add codec_sv2 +``` + +This crate can be built with the following feature flags: + +- `noise_sv2`: Enables support for Noise protocol encryption and decryption. +- `with_buffer_pool`: Enables buffer pooling for more efficient memory management. +- `with_serde`: builds [`binary_sv2`](https://crates.io/crates/binary_sv2) and + [`buffer_sv2`](https://crates.io/crates/buffer_sv2) crates with `serde`-based encoding and + decoding. Note that this feature flag is only used for the Message Generator, and deprecated + for any other kind of usage. It will likely be fully deprecated in the future. + +### Examples + +This crate provides two examples demonstrating how to encode and decode Sv2 frames: + +1. **[Unencrypted Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/codec-sv2/examples/unencrypted.rs)**: + Encode and decode standard Sv2 frames, detailing the message serialization and transmission + process for unencrypted communications. + +2. **[Encrypted Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/codec-sv2/examples/encrypted.rs)**: + Encode and decode Sv2 frames with Noise protocol encryption, detailing the entire encryption + handshake and transport phase and serialization and transmission process for encrypted + communications. diff --git a/protocols/v2/codec-sv2/examples/encrypted.rs b/protocols/v2/codec-sv2/examples/encrypted.rs new file mode 100644 index 0000000000..6713c46798 --- /dev/null +++ b/protocols/v2/codec-sv2/examples/encrypted.rs @@ -0,0 +1,208 @@ +// # Using Sv2 Codec with Noise Encryption +// +// This example demonstrates how to use the `codec-sv2` crate to encode and decode Sv2 frames +// with Noise protocol encryption over a TCP connection. It showcases how to: +// +// * Perform a Noise handshake between the sender and receiver. +// * Create an arbitrary custom message type (`CustomMessage`) for encryption/encoding and +// decryption/decoding. +// * Encode the message into an encrypted Sv2 frame using Noise. +// * Send the encrypted frame over a TCP connection. +// * Decode the encrypted Sv2 frame on the receiving side after completing the Noise handshake. +// +// ## Run +// +// ``` +// cargo run --example encrypted --features noise_sv2 +// ``` + +use binary_sv2::{binary_codec_sv2, Deserialize, Serialize}; +#[cfg(feature = "noise_sv2")] +use codec_sv2::{ + Error, HandshakeRole, Initiator, NoiseEncoder, Responder, StandardEitherFrame, + StandardNoiseDecoder, StandardSv2Frame, State, Sv2Frame, +}; +#[cfg(feature = "noise_sv2")] +use const_sv2::{ + INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE, RESPONDER_EXPECTED_HANDSHAKE_MESSAGE_SIZE, +}; +#[cfg(feature = "noise_sv2")] +use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}; +use std::convert::TryInto; +#[cfg(feature = "noise_sv2")] +use std::{ + io::{Read, Write}, + net::{TcpListener, TcpStream}, +}; + +// Arbitrary message type. +// Supported Sv2 message types are listed in the [Sv2 Spec Message +// Types](https://github.com/stratum-mining/sv2-spec/blob/main/08-Message-Types.md). +#[cfg(feature = "noise_sv2")] +const CUSTOM_MSG_TYPE: u8 = 0xff; + +// Emulate a TCP connection +#[cfg(feature = "noise_sv2")] +const TCP_ADDR: &str = "127.0.0.1:3333"; + +#[cfg(feature = "noise_sv2")] +const AUTHORITY_PUBLIC_K: &str = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72"; +#[cfg(feature = "noise_sv2")] +const AUTHORITY_PRIVATE_K: &str = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n"; +#[cfg(feature = "noise_sv2")] +const CERT_VALIDITY: std::time::Duration = std::time::Duration::from_secs(3600); + +// Example message type. +// In practice, all Sv2 messages are defined in the following crates: +// * `common_messages_sv2` +// * `mining_sv2` +// * `job_declaration_sv2` +// * `template_distribution_sv2` +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CustomMessage { + nonce: u16, +} + +#[cfg(feature = "noise_sv2")] +fn main() { + // Start a receiving listener + let listener_receiver = TcpListener::bind(TCP_ADDR).expect("Failed to bind TCP listener"); + + // Start a sender stream + let mut stream_sender: TcpStream = + TcpStream::connect(TCP_ADDR).expect("Failed to connect to TCP stream"); + + // Start a receiver stream + let mut stream_receiver: TcpStream = listener_receiver + .incoming() + .next() + .expect("Failed to accept incoming TCP stream") + .expect("Failed to connect to incoming TCP stream"); + + // Handshake + let authority_public_k: Secp256k1PublicKey = AUTHORITY_PUBLIC_K + .to_string() + .try_into() + .expect("Failed to convert receiver public key to Secp256k1PublicKey"); + + let authority_private_k: Secp256k1SecretKey = AUTHORITY_PRIVATE_K + .to_string() + .try_into() + .expect("Failed to convert receiver private key to Secp256k1PublicKey"); + + let initiator = Initiator::from_raw_k(authority_public_k.into_bytes()) + .expect("Failed to create initiator role from raw pub key"); + + let responder = Responder::from_authority_kp( + &authority_public_k.into_bytes(), + &authority_private_k.into_bytes(), + CERT_VALIDITY, + ) + .expect("Failed to initialize responder from pub/key pair and/or cert"); + + let mut sender_state = State::initialized(HandshakeRole::Initiator(initiator)); + let mut receiver_state = State::initialized(HandshakeRole::Responder(responder)); + + let first_message = sender_state + .step_0() + .expect("Initiator failed first step of handshake"); + let first_message: [u8; RESPONDER_EXPECTED_HANDSHAKE_MESSAGE_SIZE] = first_message + .get_payload_when_handshaking() + .try_into() + .expect("Handshake remote invlaid message"); + + let (second_message, receiver_state) = receiver_state + .step_1(first_message) + .expect("Responder failed second step of handshake"); + let second_message: [u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE] = second_message + .get_payload_when_handshaking() + .try_into() + .expect("Handshake remote invlaid message"); + + let sender_state = sender_state + .step_2(second_message) + .expect("Initiator failed third step of handshake"); + + let mut sender_state = match sender_state { + State::Transport(c) => State::with_transport_mode(c), + _ => panic!("todo"), + }; + + let mut receiver_state = match receiver_state { + State::Transport(c) => State::with_transport_mode(c), + _ => panic!("todo"), + }; + + // Create a message + let nonce = 1337; + let msg = CustomMessage { nonce }; + let msg_type = CUSTOM_MSG_TYPE; + // Unique identifier of the extension describing the protocol message, as defined by Sv2 Framing + let extension_type = 0; + // This message is intended for the receiver, so set to false + let channel_msg = false; + + let frame = StandardEitherFrame::::Sv2( + Sv2Frame::from_message(msg, msg_type, extension_type, channel_msg) + .expect("Failed to create the frame"), + ); + + let mut encoder = NoiseEncoder::::new(); + let encoded_frame = encoder + .encode(frame, &mut sender_state) + .expect("Failed to encode the frame"); + + // Send the encoded frame + stream_sender + .write_all(encoded_frame.as_slice()) + .expect("Failed to send the encoded frame"); + + // Initialize the decoder + let mut decoder = StandardNoiseDecoder::::new(); + + let mut decoded_frame; + + // Continuously read the frame from the TCP stream into the decoder buffer until the full + // message is received. + // + // Note: The length of the payload is defined in a header field. Every call to `next_frame` + // will return a `MissingBytes` error, until the full payload is received. + loop { + let decoder_buf = decoder.writable(); + + // Read the frame header into the decoder buffer + stream_receiver + .read_exact(decoder_buf) + .expect("Failed to read the encoded frame header"); + + let result = decoder.next_frame(&mut receiver_state); + match result { + Ok(frame) => { + let frame: StandardSv2Frame = frame + .try_into() + .expect("Failed to decode frame into Sv2Frame"); + decoded_frame = frame; + break; + } + Err(Error::MissingBytes(_)) => {} + Err(_) => panic!("Failed to decode the frame"), + } + } + + // Parse the decoded frame header and payload + let decoded_frame_header = decoded_frame + .get_header() + .expect("Failed to get the frame header"); + + let decoded_msg: CustomMessage = binary_sv2::from_bytes(decoded_frame.payload()) + .expect("Failed to extract the message from the payload"); + + // Assert that the decoded message is as expected + assert_eq!(decoded_frame_header.msg_type(), CUSTOM_MSG_TYPE); + assert_eq!(decoded_msg.nonce, nonce); +} + +#[cfg(not(feature = "noise_sv2"))] +fn main() { + eprintln!("Noise feature not enabled. Skipping example."); +} diff --git a/protocols/v2/codec-sv2/examples/unencrypted.rs b/protocols/v2/codec-sv2/examples/unencrypted.rs new file mode 100644 index 0000000000..4b5d53c566 --- /dev/null +++ b/protocols/v2/codec-sv2/examples/unencrypted.rs @@ -0,0 +1,135 @@ +// # Using Sv2 Codec Without Encryption +// +// This example demonstrates how to use the `codec-sv2` crate to encode and decode Sv2 frames +// without encryption over a TCP connection. It showcases how to: +// +// * Create an arbitrary custom message type (`CustomMessage`) for encoding and decoding. +// * Encode the message into a Sv2 frame. +// * Send the encoded frame over a TCP connection. +// * Decode the Sv2 frame on the receiving side and extract the original message. +// +// ## Run +// +// ``` +// cargo run --example unencrypted +// ``` + +use binary_sv2::{binary_codec_sv2, Deserialize, Serialize}; +use codec_sv2::{Encoder, Error, StandardDecoder, StandardSv2Frame, Sv2Frame}; +use std::{ + convert::TryInto, + io::{Read, Write}, + net::{TcpListener, TcpStream}, +}; + +// Arbitrary message type. +// Supported Sv2 message types are listed in the [Sv2 Spec Message +// Types](https://github.com/stratum-mining/sv2-spec/blob/main/08-Message-Types.md). +const CUSTOM_MSG_TYPE: u8 = 0xff; + +// Emulate a TCP connection +const TCP_ADDR: &str = "127.0.0.1:3333"; + +// Example message type. +// In practice, all Sv2 messages are defined in the following crates: +// * `common_messages_sv2` +// * `mining_sv2` +// * `job_declaration_sv2` +// * `template_distribution_sv2` +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct CustomMessage { + nonce: u16, +} + +fn main() { + // Start a receiving listener + let listener_receiver = TcpListener::bind(TCP_ADDR).expect("Failed to bind TCP listener"); + + // Start a sender stream + let stream_sender: TcpStream = + TcpStream::connect(TCP_ADDR).expect("Failed to connect to TCP stream"); + + // Start a receiver stream + let stream_receiver: TcpStream = listener_receiver + .incoming() + .next() + .expect("Failed to accept incoming TCP stream") + .expect("Failed to connect to incoming TCP stream"); + + // Server Side + + // Create a message + let nonce = 1337; + let msg = CustomMessage { nonce }; + let msg_type = CUSTOM_MSG_TYPE; + // Unique identifier of the extension describing the protocol message, as defined by Sv2 Framing + let extension_type = 0; + // This message is intended for the receiver, so set to false + let channel_msg = false; + sender_side(stream_sender, msg, msg_type, extension_type, channel_msg); + + // Receiver Side + let mut decoded_frame = receiver_side(stream_receiver); + + // Parse the decoded frame header and payload + let decoded_frame_header = decoded_frame + .get_header() + .expect("Failed to get the frame header"); + let decoded_msg: CustomMessage = binary_sv2::from_bytes(decoded_frame.payload()) + .expect("Failed to extract the message from the payload"); + + // Assert that the decoded message is as expected + assert_eq!(decoded_frame_header.msg_type(), CUSTOM_MSG_TYPE); + assert_eq!(decoded_msg.nonce, nonce); +} + +fn sender_side( + mut stream_sender: TcpStream, + msg: CustomMessage, + msg_type: u8, + extension_type: u16, + channel_msg: bool, +) { + // Create the frame + let frame = + StandardSv2Frame::::from_message(msg, msg_type, extension_type, channel_msg) + .expect("Failed to create the frame"); + + // Encode the frame + let mut encoder = Encoder::::new(); + let encoded_frame = encoder + .encode(frame.clone()) + .expect("Failed to encode the frame"); + + // Send the encoded frame + stream_sender + .write_all(encoded_frame) + .expect("Failed to send the encoded frame"); +} + +fn receiver_side(mut stream_receiver: TcpStream) -> Sv2Frame> { + // Initialize the decoder + let mut decoder = StandardDecoder::::new(); + + // Continuously read the frame from the TCP stream into the decoder buffer until the full + // message is received. + // + // Note: The length of the payload is defined in a header field. Every call to `next_frame` + // will return a `MissingBytes` error, until the full payload is received. + loop { + let decoder_buf = decoder.writable(); + + // Read the frame header into the decoder buffer + stream_receiver + .read_exact(decoder_buf) + .expect("Failed to read the encoded frame header"); + + match decoder.next_frame() { + Ok(decoded_frame) => { + return decoded_frame; + } + Err(Error::MissingBytes(_)) => {} + Err(_) => panic!("Failed to decode the frame"), + } + } +} diff --git a/protocols/v2/codec-sv2/src/decoder.rs b/protocols/v2/codec-sv2/src/decoder.rs index 4d1440018e..751ae17d53 100644 --- a/protocols/v2/codec-sv2/src/decoder.rs +++ b/protocols/v2/codec-sv2/src/decoder.rs @@ -1,3 +1,28 @@ +// # Decoder +// +// Provides utilities for decoding messages held by Sv2 frames, with or without Noise protocol +// support. +// +// It includes primitives to both decode encoded standard Sv2 frames and to decrypt and decode +// Noise-encrypted encoded Sv2 frames, ensuring secure communication when required. +// +// ## Usage +// All messages passed between Sv2 roles are encoded as Sv2 frames. These frames are decoded using +// primitives in this module. There are two types of decoders for reading these frames: one for +// regular Sv2 frames [`StandardDecoder`], and another for Noise-encrypted frames +// [`StandardNoiseDecoder`]. Both decoders manage the deserialization of incoming data and, when +// applicable, the decryption of the data upon receiving the transmitted message. +// +// ### Buffer Management +// +// The decoders rely on buffers to hold intermediate data during the decoding process. +// +// - When the `with_buffer_pool` feature is enabled, the internal `Buffer` type is backed by a +// pool-allocated buffer [`binary_sv2::BufferPool`], providing more efficient memory usage, +// particularly in high-throughput scenarios. +// - If this feature is not enabled, a system memory buffer [`binary_sv2::BufferFromSystemMemory`] +// is used for simpler applications where memory efficiency is less critical. + #[cfg(feature = "noise_sv2")] use binary_sv2::Deserialize; #[cfg(feature = "noise_sv2")] @@ -18,38 +43,101 @@ use framing_sv2::{ #[cfg(feature = "noise_sv2")] use noise_sv2::NoiseCodec; +#[cfg(feature = "noise_sv2")] +use crate::error::Error; +use crate::error::Result; + +use crate::Error::MissingBytes; +#[cfg(feature = "noise_sv2")] +use crate::State; + #[cfg(not(feature = "with_buffer_pool"))] use buffer_sv2::{Buffer as IsBuffer, BufferFromSystemMemory as Buffer}; #[cfg(feature = "with_buffer_pool")] use buffer_sv2::{Buffer as IsBuffer, BufferFromSystemMemory, BufferPool}; + +// The buffer type for holding intermediate data during decoding. +// +// When the `with_buffer_pool` feature is enabled, `Buffer` is a pool-allocated buffer type +// [`BufferPool`], which allows for more efficient memory management. Otherwise, it defaults to +// [`BufferFromSystemMemory`]. +// +// `Buffer` is used for storing both serialized Sv2 frames and encrypted Noise data. #[cfg(feature = "with_buffer_pool")] type Buffer = BufferPool; -#[cfg(feature = "noise_sv2")] -use crate::error::Error; -use crate::error::Result; +/// An encoded or decoded Sv2 frame containing either a regular or Noise-protected message. +/// +/// A wrapper around the [`Frame`] enum that represents either a regular or Noise-protected Sv2 +/// frame containing the generic message type (`T`). +pub type StandardEitherFrame = Frame::Slice>; -use crate::Error::MissingBytes; -#[cfg(feature = "noise_sv2")] -use crate::State; +/// An encoded or decoded Sv2 frame. +/// +/// A wrapper around the [`Sv2Frame`] that represents a regular Sv2 frame containing the generic +/// message type (`T`). +pub type StandardSv2Frame = Sv2Frame::Slice>; +/// Standard Sv2 decoder with Noise protocol support. +/// +/// Used for decoding and decrypting generic message types (`T`) encoded in Sv2 frames and +/// encrypted via the Noise protocol. #[cfg(feature = "noise_sv2")] pub type StandardNoiseDecoder = WithNoise; -pub type StandardEitherFrame = Frame::Slice>; -pub type StandardSv2Frame = Sv2Frame::Slice>; + +/// Standard Sv2 decoder without Noise protocol support. +/// +/// Used for decoding generic message types (`T`) encoded in Sv2 frames. pub type StandardDecoder = WithoutNoise; +/// Decoder for Sv2 frames with Noise protocol support. +/// +/// Accumulates the encrypted data into a dedicated buffer until the entire encrypted frame is +/// received. The Noise protocol is then used to decrypt the accumulated data into another +/// dedicated buffer, converting it back into its original serialized form. This decrypted data is +/// then deserialized into the original Sv2 frame and message format. #[cfg(feature = "noise_sv2")] +#[derive(Debug)] pub struct WithNoise { + // Marker for the type of frame being decoded. + // + // Used to maintain the generic type (`T`) information of the message payload held by the + // frame. `T` refers to a type that implements the necessary traits for serialization + // [`binary_sv2::Serialize`] and size calculation [`binary_sv2::GetSize`]. frame: PhantomData, + + // Tracks the number of bytes remaining until the full frame is received. + // + // Ensures that the full encrypted Noise frame has been received by keeping track of the + // remaining bytes. Once the complete frame is received, decoding can proceed. missing_noise_b: usize, + + // Buffer for holding incoming encrypted Noise data to be decrypted. + // + // Stores the incoming encrypted data, allowing the decoder to accumulate the necessary bytes + // for full decryption. Once the entire encrypted frame is received, the decoder processes the + // buffer to extract the underlying frame. noise_buffer: B, + + // Buffer for holding decrypted data to be decoded. + // + // Stores the decrypted data until it is ready to be processed and converted into a Sv2 frame. sv2_buffer: B, } #[cfg(feature = "noise_sv2")] impl<'a, T: Serialize + GetSize + Deserialize<'a>, B: IsBuffer + AeadBuffer> WithNoise { + /// Attempts to decode the next Noise encrypted frame. + /// + /// On success, the decoded and decrypted frame is returned. Otherwise, an error indicating the + /// number of missing bytes required to complete the encoded frame, an error on a badly + /// formatted message header, or an error on decryption failure is returned. + /// + /// In this case of the `Error::MissingBytes`, the user should resize the decoder buffer using + /// `writable`, read another chunk from the incoming message stream, and then call `next_frame` + /// again. This process should be repeated until `next_frame` returns `Ok`, indicating that the + /// full message has been received, and the decoding and decryption of the frame can proceed. #[inline] pub fn next_frame(&mut self, state: &mut State) -> Result> { match state { @@ -96,6 +184,57 @@ impl<'a, T: Serialize + GetSize + Deserialize<'a>, B: IsBuffer + AeadBuffer> Wit } } + /// Provides a writable buffer for receiving incoming Noise-encrypted Sv2 data. + /// + /// This buffer is used to store incoming data, and its size is adjusted based on the number + /// of missing bytes. As new data is read, it is written into this buffer until enough data has + /// been received to fully decode a frame. The buffer must have the correct number of bytes + /// available to progress to the decoding process. + #[inline] + pub fn writable(&mut self) -> &mut [u8] { + self.noise_buffer.get_writable(self.missing_noise_b) + } + + /// Determines whether the decoder's internal buffers can be safely dropped. + /// + /// For more information, refer to the [`buffer_sv2` + /// crate](https://docs.rs/buffer_sv2/latest/buffer_sv2/). + pub fn droppable(&self) -> bool { + self.noise_buffer.is_droppable() && self.sv2_buffer.is_droppable() + } + + // Processes and decodes a Sv2 frame during the Noise protocol handshake phase. + // + // Handles the decoding of a handshake frame from the `noise_buffer`. It converts the received + // data into a `HandShakeFrame` and encapsulates it into a `Frame` for further processing by + // the codec. + // + // This is used exclusively during the initial handshake phase of the Noise protocol, before + // transitioning to regular frame encryption and decryption. + fn while_handshaking(&mut self) -> Frame { + let src = self.noise_buffer.get_data_owned().as_mut().to_vec(); + + // Since the frame length is already validated during the handshake process, this + // operation is infallible + let frame = HandShakeFrame::from_bytes_unchecked(src.into()); + + frame.into() + } + + // Decodes a Noise-encrypted Sv2 frame, handling both the message header and payload + // decryption. + // + // Processes Noise-encrypted Sv2 frames by first decrypting the header, followed by the + // payload. If the frame's data is received in chunks, it ensures that decryption occurs + // incrementally as more encrypted data becomes available. The decrypted data is then stored in + // the `sv2_buffer`, from which the resulting Sv2 frame is extracted and returned. + // + // On success, the decoded frame is returned. Otherwise, an error indicating the number of + // missing bytes required to complete the encoded frame, an error on a badly formatted message + // header, or a decryption failure error is returned. If there are still bytes missing to + // complete the frame, the function will return an `Error::MissingBytes` with the number of + // additional bytes required to fully decrypt the frame. Once all bytes are available, the + // decryption process completes and the frame can be successfully decoded. #[inline] fn decode_noise_frame(&mut self, noise_codec: &mut NoiseCodec) -> Result> { match ( @@ -144,27 +283,14 @@ impl<'a, T: Serialize + GetSize + Deserialize<'a>, B: IsBuffer + AeadBuffer> Wit } } } - - fn while_handshaking(&mut self) -> Frame { - let src = self.noise_buffer.get_data_owned().as_mut().to_vec(); - - // below is inffalible as noise frame length has been already checked - let frame = HandShakeFrame::from_bytes_unchecked(src.into()); - - frame.into() - } - - #[inline] - pub fn writable(&mut self) -> &mut [u8] { - self.noise_buffer.get_writable(self.missing_noise_b) - } - pub fn droppable(&self) -> bool { - self.noise_buffer.is_droppable() && self.sv2_buffer.is_droppable() - } } #[cfg(feature = "noise_sv2")] impl WithNoise { + /// Crates a new [`WithNoise`] decoder with default buffer sizes. + /// + /// Initializes the decoder with default buffer sizes and sets the number of missing bytes to + /// 0. pub fn new() -> Self { Self { frame: PhantomData, @@ -182,14 +308,46 @@ impl Default for WithNoise { } } +/// Decoder for standard Sv2 frames. +/// +/// Accumulates the data into a dedicated buffer until the entire Sv2 frame is received. This data +/// is then deserialized into the original Sv2 frame and message format. #[derive(Debug)] pub struct WithoutNoise { + // Marker for the type of frame being decoded. + // + // Used to maintain the generic type (`T`) information of the message payload held by the + // frame. `T` refers to a type that implements the necessary traits for serialization + // [`binary_sv2::Serialize`] and size calculation [`binary_sv2::GetSize`]. frame: PhantomData, + + // Tracks the number of bytes remaining until the full frame is received. + // + // Ensures that the full Sv2 frame has been received by keeping track of the remaining bytes. + // Once the complete frame is received, decoding can proceed. missing_b: usize, + + // Buffer for holding incoming data to be decoded into a Sv2 frame. + // + // This buffer stores incoming data as it is received, allowing the decoder to accumulate the + // necessary bytes until a full frame is available. Once the full encoded frame has been + // received, the buffer's contents are processed and decoded into an Sv2 frame. buffer: B, } impl WithoutNoise { + /// Attempts to decode the next frame, returning either a frame or an error indicating how many + /// bytes are missing. + /// + /// Attempts to decode the next Sv2 frame. + /// + /// On success, the decoded frame is returned. Otherwise, an error indicating the number of + /// missing bytes required to complete the frame is returned. + /// + /// In the case of `Error::MissingBytes`, the user should resize the decoder buffer using + /// `writable`, read another chunk from the incoming message stream, and then call `next_frame` + /// again. This process should be repeated until `next_frame` returns `Ok`, indicating that the + /// full message has been received, and the frame can be fully decoded. #[inline] pub fn next_frame(&mut self) -> Result> { let len = self.buffer.len(); @@ -210,12 +368,22 @@ impl WithoutNoise { } } + /// Provides a writable buffer for receiving incoming Sv2 data. + /// + /// This buffer is used to store incoming data, and its size is adjusted based on the number of + /// missing bytes. As new data is read, it is written into this buffer until enough data has + /// been received to fully decode a frame. The buffer must have the correct number of bytes + /// available to progress to the decoding process. pub fn writable(&mut self) -> &mut [u8] { self.buffer.get_writable(self.missing_b) } } impl WithoutNoise { + /// Creates a new [`WithoutNoise`] with a buffer of default size. + /// + /// Initializes the decoder with a default buffer size and sets the number of missing bytes to + /// the size of the header. pub fn new() -> Self { Self { frame: PhantomData, @@ -230,3 +398,20 @@ impl Default for WithoutNoise { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + use binary_sv2::{binary_codec_sv2, Serialize}; + + #[derive(Serialize)] + pub struct TestMessage {} + + #[test] + fn unencrypted_writable_with_missing_b_initialized_as_header_size() { + let mut decoder = StandardDecoder::::new(); + let actual = decoder.writable(); + let expect = [0u8; Header::SIZE]; + assert_eq!(actual, expect); + } +} diff --git a/protocols/v2/codec-sv2/src/encoder.rs b/protocols/v2/codec-sv2/src/encoder.rs index 21618fda51..62d41cae9e 100644 --- a/protocols/v2/codec-sv2/src/encoder.rs +++ b/protocols/v2/codec-sv2/src/encoder.rs @@ -1,3 +1,26 @@ +// # Encoder +// +// Provides utilities for encoding messages into Sv2 frames, with or without Noise protocol +// support. +// +// ## Usage +// +// All messages passed between Sv2 roles are encoded as Sv2 frames using primitives in this module. +// There are two types of encoders for creating these frames: one for regular Sv2 frames +// [`Encoder`], and another for Noise-encrypted frames [`NoiseEncoder`]. Both encoders manage the +// serialization of outgoing data and, when applicable, the encryption of the data before +// transmission. +// +// ### Buffer Management +// +// The encoders rely on buffers to hold intermediate data during the encoding process. +// +// - When the `with_buffer_pool` feature is enabled, the internal `Buffer` type is backed by a +// pool-allocated buffer [`binary_sv2::BufferPool`], providing more efficient memory usage, +// particularly in high-throughput scenarios. +// - If the feature is not enabled, a system memory buffer [`binary_sv2::BufferFromSystemMemory`] +// is used for simpler applications where memory efficiency is less critical. + use alloc::vec::Vec; use binary_sv2::{GetSize, Serialize}; #[allow(unused_imports)] @@ -25,28 +48,93 @@ use buffer_sv2::{Buffer as IsBuffer, BufferFromSystemMemory as Buffer}; #[cfg(feature = "with_buffer_pool")] use buffer_sv2::{Buffer as IsBuffer, BufferFromSystemMemory, BufferPool}; +// The buffer type for holding intermediate data during encoding. +// +// When the `with_buffer_pool` feature is enabled, `Buffer` uses a pool-allocated buffer +// [`BufferPool`], providing more efficient memory management, particularly in high-throughput +// environments. If the feature is not enabled, it defaults to [`BufferFromSystemMemory`], a +// simpler system memory buffer. +// +// `Buffer` is utilized for storing both serialized Sv2 frames and encrypted Noise data during the +// encoding process, ensuring that all frames are correctly handled before transmission. #[cfg(feature = "noise_sv2")] #[cfg(feature = "with_buffer_pool")] type Buffer = BufferPool; +// A simple buffer slice for holding serialized Sv2 frame data before transmission. +// +// When the `with_buffer_pool` feature is disabled, [`Slice`] defaults to a `Vec`, which serves +// as a dynamically allocated array to hold the serialized bytes of Sv2 frames. This provides +// flexibility in managing the encoded data during transmission or further processing, though it +// may not offer the same memory efficiency as the pool-allocated version [`BufferPool`]. #[cfg(not(feature = "with_buffer_pool"))] type Slice = Vec; +// A buffer slice used for holding serialized Sv2 frame data before transmission. +// +// When the `with_buffer_pool` feature is enabled, [`Slice`] defaults to a `buffer_sv2::Slice`, +// which serves as a slice of the `Buffer` that stores the encoded data. It holds the frame's +// serialized bytes temporarily, ensuring the data is ready for transmission or encryption, +// depending on whether Noise protocol support is enabled. #[cfg(feature = "with_buffer_pool")] type Slice = buffer_sv2::Slice; +/// Encoder for Sv2 frames with Noise protocol encryption. +/// +/// Serializes the Sv2 frame into a dedicated buffer. Encrypts this serialized data using the Noise +/// protocol, storing it into another dedicated buffer. Encodes the serialized and encrypted data, +/// such that it is ready for transmission. #[cfg(feature = "noise_sv2")] pub struct NoiseEncoder { + // Buffer for holding encrypted Noise data to be transmitted. + // + // Stores the encrypted data after the Sv2 frame has been processed by the Noise protocol + // and is ready for transmission. This buffer holds the outgoing encrypted data, ensuring + // that the full frame is correctly prepared before being sent. noise_buffer: Buffer, + + // Buffer for holding serialized Sv2 data before encryption. + // + // Stores the data after it has been serialized into an Sv2 frame but before it is encrypted + // by the Noise protocol. The buffer accumulates the frame's serialized bytes before they are + // encrypted and then encoded for transmission. sv2_buffer: Buffer, + + // Marker for the type of frame being encoded. + // + // Used to maintain the generic type information for `T`, which represents the message payload + // contained within the Sv2 frame. `T` refers to a type that implements the necessary traits + // for serialization [`binary_sv2::Serialize`] and size calculation [`binary_sv2::GetSize`], + // ensuring that the encoder can handle different message types correctly during the encoding + // process. frame: PhantomData, } +// A Sv2 frame that will be encoded and optionally encrypted using the Noise protocol. +// +// Represent a Sv2 frame during the encoding process. It encapsulates the frame's generic payload +// message type (`T`) and is stored in a [`Slice`] buffer. The `Item` is passed to the encoder, +// which either processes it for normal transmission or applies Noise encryption, depending on the +// codec's state. #[cfg(feature = "noise_sv2")] type Item = Frame; #[cfg(feature = "noise_sv2")] impl NoiseEncoder { + /// Encodes an Sv2 frame and encrypts it using the Noise protocol. + /// + /// Takes an `item`, which is an Sv2 frame containing a payload of type `T`, and encodes it for + /// transmission. The frame is encrypted after being serialized. The `state` parameter + /// determines whether the encoder is in the handshake or transport phase, guiding the + /// appropriate encoding and encryption action. + /// + /// - In the handshake phase, the initial handshake messages are processed to establish secure + /// communication. + /// - In the transport phase, the full frame is serialized, encrypted, and stored in a buffer + /// for transmission. + /// + /// On success, the method returns an encrypted (`Slice`) (buffer) ready for transmission. + /// Otherwise, errors on an encryption or serialization failure. #[inline] pub fn encode(&mut self, item: Item, state: &mut State) -> Result { match state { @@ -99,6 +187,13 @@ impl NoiseEncoder { Ok(self.noise_buffer.get_data_owned()) } + // Encodes Sv2 frames during the handshake phase of the Noise protocol. + // + // Used when the encoder is in the handshake phase, before secure communication is fully + // established. It encodes the provided `item` into a handshake frame, storing the resulting + // data in the `noise_buffer`. The handshake phase is necessary to exchange initial messages + // and set up the Noise encryption state before transitioning to the transport phase, where + // full frames are encrypted and transmitted. #[inline(never)] fn while_handshaking(&mut self, item: Item) -> Result<()> { // ENCODE THE SV2 FRAME @@ -114,6 +209,7 @@ impl NoiseEncoder { Ok(()) } + /// Determines whether the encoder's internal buffers can be safely dropped. pub fn droppable(&self) -> bool { self.noise_buffer.is_droppable() && self.sv2_buffer.is_droppable() } @@ -121,6 +217,7 @@ impl NoiseEncoder { #[cfg(feature = "noise_sv2")] impl NoiseEncoder { + /// Creates a new `NoiseEncoder` with default buffer sizes. pub fn new() -> Self { #[cfg(not(feature = "with_buffer_pool"))] let size = 512; @@ -141,13 +238,36 @@ impl Default for NoiseEncoder { } } +/// Encoder for standard Sv2 frames. +/// +/// Serializes the Sv2 frame into a dedicated buffer then encodes it, such that it is ready for +/// transmission. #[derive(Debug)] pub struct Encoder { + // Buffer for holding serialized Sv2 data. + // + // Stores the serialized bytes of the Sv2 frame after it has been encoded. Once the frame is + // serialized, the resulting bytes are stored in this buffer to be transmitted. The buffer is + // dynamically resized to accommodate the size of the encoded frame. buffer: Vec, + + // Marker for the type of frame being encoded. + // + // Used to maintain the generic type information for `T`, which represents the message payload + // contained within the Sv2 frame. `T` refers to a type that implements the necessary traits + // for serialization [`binary_sv2::Serialize`] and size calculation [`binary_sv2::GetSize`], + // ensuring that the encoder can handle different message types correctly during the encoding + // process. frame: PhantomData, } impl Encoder { + /// Encodes a standard Sv2 frame for transmission. + /// + /// Takes a standard Sv2 frame containing a payload of type `T` and serializes it into a byte + /// stream. The resulting serialized bytes are stored in the internal `buffer`, preparing the + /// frame for transmission. On success, the method returns a reference to the serialized bytes + /// stored in the internal buffer. Otherwise, errors on a serialization failure. pub fn encode( &mut self, item: Sv2Frame, @@ -161,6 +281,7 @@ impl Encoder { Ok(&self.buffer[..]) } + /// Creates a new `Encoder` with a buffer of default size. pub fn new() -> Self { Self { buffer: Vec::with_capacity(512), diff --git a/protocols/v2/codec-sv2/src/error.rs b/protocols/v2/codec-sv2/src/error.rs index 3201a6f2a4..b5810cb850 100644 --- a/protocols/v2/codec-sv2/src/error.rs +++ b/protocols/v2/codec-sv2/src/error.rs @@ -1,3 +1,9 @@ +//! # Error Handling and Result Types +//! +//! This module defines error types and utilities for handling errors in the `codec_sv2` module. +//! It includes the [`Error`] enum for representing various errors, a C-compatible [`CError`] enum +//! for FFI, and a `Result` type alias for convenience. + #[cfg(test)] use core::cmp; use core::fmt; @@ -5,65 +11,95 @@ use framing_sv2::Error as FramingError; #[cfg(feature = "noise_sv2")] use noise_sv2::{AeadError, Error as NoiseError}; +/// A type alias for results returned by the `codec_sv2` modules. +/// +/// `Result` is a convenient wrapper around the [`core::result::Result`] type, using the [`Error`] +/// enum defined in this crate as the error type. pub type Result = core::result::Result; +/// Enumeration of possible errors in the `codec_sv2` module. +/// +/// This enum represents various errors that can occur within the `codec_sv2` module, including +/// errors from related crates like [`binary_sv2`], [`framing_sv2`], and [`noise_sv2`]. #[derive(Debug, PartialEq, Eq)] pub enum Error { - /// Errors from the `binary_sv2` crate + /// AEAD (`snow`) error in the Noise protocol. + #[cfg(feature = "noise_sv2")] + AeadError(AeadError), + + /// Binary Sv2 data format error. BinarySv2Error(binary_sv2::Error), + + /// Framing Sv2 error. + FramingError(FramingError), + + /// Framing Sv2 error. FramingSv2Error(framing_sv2::Error), - /// Errors if there are missing bytes in the Noise protocol - MissingBytes(usize), - /// Errors from the `noise_sv2` crate - #[cfg(feature = "noise_sv2")] - NoiseSv2Error(NoiseError), + + /// Invalid step for initiator in the Noise protocol. #[cfg(feature = "noise_sv2")] - AeadError(AeadError), - /// Error if Noise protocol state is not as expected - UnexpectedNoiseState, + InvalidStepForInitiator, + + /// Invalid step for responder in the Noise protocol. #[cfg(feature = "noise_sv2")] InvalidStepForResponder, + + /// Incomplete frame with the number of missing bytes remaining to completion. + MissingBytes(usize), + + /// Sv2 Noise protocol error. #[cfg(feature = "noise_sv2")] - InvalidStepForInitiator, + NoiseSv2Error(NoiseError), + + /// Noise protocol is not in the expected handshake state. #[cfg(feature = "noise_sv2")] NotInHandShakeState, - FramingError(FramingError), + + /// Unexpected state in the Noise protocol. + UnexpectedNoiseState, } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use Error::*; match self { + #[cfg(feature = "noise_sv2")] + AeadError(e) => write!(f, "Aead Error: `{:?}`", e), BinarySv2Error(e) => write!(f, "Binary Sv2 Error: `{:?}`", e), + FramingError(e) => write!(f, "Framing error in codec: `{:?}`", e), FramingSv2Error(e) => write!(f, "Framing Sv2 Error: `{:?}`", e), - MissingBytes(u) => write!(f, "Missing `{}` Noise bytes", u), - #[cfg(feature = "noise_sv2")] - NoiseSv2Error(e) => write!(f, "Noise SV2 Error: `{:?}`", e), #[cfg(feature = "noise_sv2")] - AeadError(e) => write!(f, "Aead Error: `{:?}`", e), - UnexpectedNoiseState => { - write!(f, "Noise state is incorrect") - } + InvalidStepForInitiator => write!( + f, + "This noise handshake step can not be executed by an initiato" + ), #[cfg(feature = "noise_sv2")] InvalidStepForResponder => write!( f, "This noise handshake step can not be executed by a responder" ), + MissingBytes(u) => write!(f, "Missing `{}` Noise bytes", u), #[cfg(feature = "noise_sv2")] - InvalidStepForInitiator => write!( - f, - "This noise handshake step can not be executed by an initiato" - ), + NoiseSv2Error(e) => write!(f, "Noise SV2 Error: `{:?}`", e), #[cfg(feature = "noise_sv2")] NotInHandShakeState => write!( f, "This operation can be executed only during the noise handshake" ), - FramingError(e) => write!(f, "Framing error in codec: `{:?}`", e), + UnexpectedNoiseState => { + write!(f, "Noise state is incorrect") + } } } } +#[cfg(feature = "noise_sv2")] +impl From for Error { + fn from(e: AeadError) -> Self { + Error::AeadError(e) + } +} + impl From for Error { fn from(e: binary_sv2::Error) -> Self { Error::BinarySv2Error(e) @@ -76,13 +112,6 @@ impl From for Error { } } -#[cfg(feature = "noise_sv2")] -impl From for Error { - fn from(e: AeadError) -> Self { - Error::AeadError(e) - } -} - #[cfg(feature = "noise_sv2")] impl From for Error { fn from(e: NoiseError) -> Self { @@ -90,28 +119,49 @@ impl From for Error { } } +/// C-compatible enumeration of possible errors in the `codec_sv2` module. +/// +/// This enum mirrors the [`Error`] enum but is designed to be used in C code through FFI. It +/// represents the same set of errors as [`Error`], making them accessible to C programs. #[repr(C)] #[derive(Debug)] pub enum CError { - /// Errors from the `binary_sv2` crate + /// AEAD (`snow`) error in the Noise protocol. + AeadError, + + /// Binary Sv2 data format error. BinarySv2Error, - /// Errors from the `framing_sv2` crate + + /// Framing Sv2 error. + FramingError, + + /// Framing Sv2 error. FramingSv2Error, - /// Errors if there are missing bytes in the Noise protocol + + /// Invalid step for initiator in the Noise protocol. + InvalidStepForInitiator, + + /// Invalid step for responder in the Noise protocol. + InvalidStepForResponder, + + /// Missing bytes in the Noise protocol. MissingBytes(usize), - /// Errors from the `noise_sv2` crate + + /// Sv2 Noise protocol error. NoiseSv2Error, - /// `snow` errors - AeadError, - /// Error if Noise protocol state is not as expected - UnexpectedNoiseState, - InvalidStepForResponder, - InvalidStepForInitiator, + + /// Noise protocol is not in the expected handshake state. NotInHandShakeState, - FramingError, + + /// Unexpected state in the Noise protocol. + UnexpectedNoiseState, } -/// Here only to force cbindgen to create header for CError +/// Force `cbindgen` to create a header for [`CError`]. +/// +/// It ensures that [`CError`] is included in the generated C header file. This function is not meant +/// to be called and will panic if called. Its only purpose is to make [`CError`] visible to +/// `cbindgen`. #[no_mangle] pub extern "C" fn export_cerror() -> CError { unimplemented!() @@ -120,21 +170,21 @@ pub extern "C" fn export_cerror() -> CError { impl From for CError { fn from(e: Error) -> CError { match e { + #[cfg(feature = "noise_sv2")] + Error::AeadError(_) => CError::AeadError, Error::BinarySv2Error(_) => CError::BinarySv2Error, Error::FramingSv2Error(_) => CError::FramingSv2Error, - Error::MissingBytes(u) => CError::MissingBytes(u), - #[cfg(feature = "noise_sv2")] - Error::NoiseSv2Error(_) => CError::NoiseSv2Error, + Error::FramingError(_) => CError::FramingError, #[cfg(feature = "noise_sv2")] - Error::AeadError(_) => CError::AeadError, - Error::UnexpectedNoiseState => CError::UnexpectedNoiseState, + Error::InvalidStepForInitiator => CError::InvalidStepForInitiator, #[cfg(feature = "noise_sv2")] Error::InvalidStepForResponder => CError::InvalidStepForResponder, + Error::MissingBytes(u) => CError::MissingBytes(u), #[cfg(feature = "noise_sv2")] - Error::InvalidStepForInitiator => CError::InvalidStepForInitiator, + Error::NoiseSv2Error(_) => CError::NoiseSv2Error, #[cfg(feature = "noise_sv2")] Error::NotInHandShakeState => CError::NotInHandShakeState, - Error::FramingError(_) => CError::FramingError, + Error::UnexpectedNoiseState => CError::UnexpectedNoiseState, } } } @@ -142,16 +192,16 @@ impl From for CError { impl Drop for CError { fn drop(&mut self) { match self { + CError::AeadError => (), CError::BinarySv2Error => (), + CError::FramingError => (), CError::FramingSv2Error => (), + CError::InvalidStepForInitiator => (), + CError::InvalidStepForResponder => (), CError::MissingBytes(_) => (), CError::NoiseSv2Error => (), - CError::AeadError => (), - CError::UnexpectedNoiseState => (), - CError::InvalidStepForResponder => (), - CError::InvalidStepForInitiator => (), CError::NotInHandShakeState => (), - CError::FramingError => (), + CError::UnexpectedNoiseState => (), }; } } diff --git a/protocols/v2/codec-sv2/src/lib.rs b/protocols/v2/codec-sv2/src/lib.rs index a4eec1efd1..8b2c05ce9e 100644 --- a/protocols/v2/codec-sv2/src/lib.rs +++ b/protocols/v2/codec-sv2/src/lib.rs @@ -1,3 +1,37 @@ +//! # Stratum V2 Codec Library +//! +//! `codec_sv2` provides the message encoding and decoding functionality for the Stratum V2 (Sv2) +//! protocol, handling secure communication between Sv2 roles. +//! +//! This crate abstracts the complexity of message encoding/decoding with optional Noise protocol +//! support, ensuring both regular and encrypted messages can be serialized, transmitted, and +//! decoded consistently and reliably. +//! +//! +//! ## Usage +//! `codec-sv2` supports both standard Sv2 frames (unencrypted) and Noise-encrypted Sv2 frames to +//! ensure secure communication. To encode messages for transmission, choose between the +//! [`Encoder`] for standard Sv2 frames or the [`NoiseEncoder`] for encrypted frames. To decode +//! received messages, choose between the [`StandardDecoder`] for standard Sv2 frames or +//! [`StandardNoiseDecoder`] to decrypt Noise frames. +//! +//! ## Build Options +//! +//! This crate can be built with the following features: +//! +//! - `noise_sv2`: Enables support for Noise protocol encryption and decryption. +//! - `with_buffer_pool`: Enables buffer pooling for more efficient memory management. +//! - `with_serde`: builds [`binary_sv2`] and [`buffer_sv2`] crates with `serde`-based encoding and +//! decoding. Note that this feature flag is only used for the Message Generator, and deprecated +//! for any other kind of usage. It will likely be fully deprecated in the future. +//! +//! ## Examples +//! +//! See the examples for more information: +//! +//! - [Unencrypted Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/codec-sv2/examples/unencrypted.rs) +//! - [Encrypted Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/codec-sv2/examples/encrypted.rs) + #![cfg_attr(feature = "no_std", no_std)] pub use framing_sv2::framing::Frame; @@ -34,20 +68,75 @@ pub use buffer_sv2; pub use framing_sv2::{self, framing::handshake_message_to_frame as h2f}; +/// Represents the role in the Noise handshake process, either as an initiator or a responder. +/// +/// The Noise protocol requires two roles during the handshake process: +/// - **Initiator**: The party that starts the handshake by sending the initial message. +/// - **Responder**: The party that waits for the initiator's message and responds to it. +/// +/// This enum distinguishes between these two roles, allowing the codec to handle the handshake +/// process accordingly. +#[allow(clippy::large_enum_variant)] +#[cfg(feature = "noise_sv2")] +#[derive(Debug)] +pub enum HandshakeRole { + /// The initiator role in the Noise handshake process. + /// + /// The initiator starts the handshake by sending the initial message. This variant stores an + /// `Initiator` object, which contains the necessary state and cryptographic materials for the + /// initiator's part in the Noise handshake. + Initiator(Box), + + /// The responder role in the Noise handshake process. + /// + /// The responder waits for the initiator's handshake message and then responds. This variant + /// stores a `Responder` object, which contains the necessary state and cryptographic materials + /// for the responder's part in the Noise handshake. + Responder(Box), +} + +/// Represents the state of the Noise protocol codec during different phases: initialization, +/// handshake, or transport mode, where encryption and decryption are fully operational. +/// +/// The state transitions from initialization [`State::NotInitialized`] to handshake +/// [`State::HandShake`] and finally to transport mode [`State::Transport`] as the encryption +/// handshake is completed. #[cfg(feature = "noise_sv2")] #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum State { - /// Not yet initialized + /// The codec has not been initialized yet. + /// + /// This state is used when the codec is first created or reset, before the handshake process + /// begins. The variant carries the expected size of the handshake message, which can vary + /// depending on whether the codec is acting as an initiator or a responder. NotInitialized(usize), - /// Handshake mode where codec is negotiating keys + + /// The codec is in the handshake phase, where cryptographic keys are being negotiated. + /// + /// In this state, the codec is in the process of establishing secure communication by + /// exchanging handshake messages. Once the handshake is complete, the codec transitions to + /// [`State::Transport`] mode. HandShake(HandshakeRole), - /// Transport mode where AEAD is fully operational. The `TransportMode` object in this variant - /// as able to perform encryption and decryption resp. + + /// The codec is in transport mode, where AEAD encryption and decryption are fully operational. + /// + /// In this state, the codec is performing full encryption and decryption using the Noise + /// protocol in transport mode. The [`NoiseCodec`] object is responsible for handling the + /// encryption and decryption of data. Transport(NoiseCodec), } + #[cfg(feature = "noise_sv2")] impl State { + /// Initiates the first step of the handshake process for the initiator. + /// + /// Creates and sends the initial handshake message for the initiator. It is the first step in + /// establishing a secure communication channel. Responders cannot perform this step. + /// + /// nb: This method returns a [`HandShakeFrame`] but does not change the current state + /// (`self`). The state remains `State::HandShake(HandshakeRole::Initiator)` until `step_1` is + /// called to advance the handshake process. pub fn step_0(&mut self) -> core::result::Result { match self { Self::HandShake(h) => match h { @@ -58,6 +147,15 @@ impl State { } } + /// Processes the second step of the handshake process for the responder. + /// + /// The responder receives the public key from the initiator, generates a response message + /// containing the handshake frame, and prepares the [`NoiseCodec`] for transitioning the + /// initiator state to transport mode in `step_2`. + /// + /// nb: Returns a new state [`State::Transport`] but does not update the current state + /// (`self`). The caller is responsible for updating the state, allowing for more flexible + /// control over the handshake process as the caller decides what to do with this state. pub fn step_1( &mut self, re_pub: [u8; const_sv2::RESPONDER_EXPECTED_HANDSHAKE_MESSAGE_SIZE], @@ -74,6 +172,14 @@ impl State { } } + /// Processes the final step of the handshake process for the initiator. + /// + /// Receives the response message from the responder containing the handshake frame, and + /// transitions the state to transport mode. This finalizes the secure communication setup and + /// enables full encryption and decryption in [`State::Transport`] mode. + /// + /// nb: Directly updates the current state (`self`) to [`State::Transport`], completing the + /// handshake process. pub fn step_2( &mut self, message: [u8; const_sv2::INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE], @@ -89,16 +195,16 @@ impl State { } } } -#[allow(clippy::large_enum_variant)] -#[cfg(feature = "noise_sv2")] -#[derive(Debug)] -pub enum HandshakeRole { - Initiator(Box), - Responder(Box), -} #[cfg(feature = "noise_sv2")] impl State { + /// Creates a new uninitialized handshake [`State`]. + /// + /// Sets the codec to the initial state, [`State::NotInitialized`], based on the provided + /// handshake role. This state is used before the handshake process begins, and the handshake + /// message size guides the codec on how much data to expect before advancing to the next step. + /// The expected size of the handshake message is determined by whether the codec is acting as + /// an initiator or responder. pub fn not_initialized(role: &HandshakeRole) -> Self { match role { HandshakeRole::Initiator(_) => { @@ -110,10 +216,28 @@ impl State { } } + /// Initializes the codec state to [`State::HandShake`] mode with the given handshake role. + /// + /// Transitions the codec into the handshake phase by accepting a [`HandshakeRole`], which + /// determines whether the codec is the initiator or responder in the handshake process. Once + /// in [`State::HandShake`] mode, the codec begins negotiating cryptographic keys with the + /// peer, eventually transitioning to the secure [`State::Transport`] phase. + /// + /// The role passed to this method defines how the handshake proceeds: + /// - [`HandshakeRole::Initiator`]: The codec will start the handshake process. + /// - [`HandshakeRole::Responder`]: The codec will wait for the initiator's handshake message. pub fn initialized(inner: HandshakeRole) -> Self { Self::HandShake(inner) } + /// Transitions the codec state to [`State::Transport`] mode with the given [`NoiseCodec`]. + /// + /// Finalizes the handshake process and transitions the codec into [`State::Transport`] mode, + /// where full encryption and decryption are active. The codec uses the provided [`NoiseCodec`] + /// to perform encryption and decryption for all communication in this mode, ensuring secure + /// data transmission. + /// + /// Once in [`State::Transport`] mode, the codec is fully operational for secure communication. pub fn with_transport_mode(tm: NoiseCodec) -> Self { Self::Transport(tm) } diff --git a/protocols/v2/const-sv2/Cargo.toml b/protocols/v2/const-sv2/Cargo.toml index 1a74cfa0b3..da7294550c 100644 --- a/protocols/v2/const-sv2/Cargo.toml +++ b/protocols/v2/const-sv2/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "const_sv2" -version = "2.0.0" -authors = ["fi3 "] +version = "2.1.0" +authors = ["The Stratum V2 Developers"] edition = "2018" -description = "Sv2 constatnts" -license = "MIT" +readme = "README.md" +description = "This crate holds all of the shared constants used across the different Stratum V2 protocols and roles crates" +documentation = "https://docs.rs/const_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -17,3 +21,6 @@ secp256k1 = { version = "0.28.2", default-features = false, features =["hashes", [features] no_std = [] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/const-sv2/README.md b/protocols/v2/const-sv2/README.md index 1c919b895c..b491997450 100644 --- a/protocols/v2/const-sv2/README.md +++ b/protocols/v2/const-sv2/README.md @@ -2,20 +2,21 @@ [![crates.io](https://img.shields.io/crates/v/const_sv2.svg)](https://crates.io/crates/const_sv2) [![docs.rs](https://docs.rs/const_sv2/badge.svg)](https://docs.rs/const_sv2) +[![rustc+](https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) +[![license](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/stratum-mining/stratum/blob/main/LICENSE.md) -`const_sv2` is a Rust crate that provides essential constants for the SV2 (Stratum V2) protocol. These constants are crucial for message framing, encryption, and protocol-specific identifiers across various SV2 components, including Mining, Job Declaration, and Template Distribution protocols. +`const_sv2` is a Rust crate that provides essential constants for the Sv2 (Stratum V2) protocol. These constants are crucial for message framing, encryption, and protocol-specific identifiers across various Sv2 components, including Mining, Job Declaration, and Template Distribution protocols. -## Main Components +## Key Capabilities -- **Protocol Constants**: Define key protocol discriminants, message types, and sizes for the SV2 binary protocol. -- **Encryption Support**: Includes constants for encryption using ChaChaPoly and ElligatorSwift encoding. +- **Protocol Constants**: Define key protocol discriminants, message types, and sizes for the Sv2 binary protocol. +- **Encryption Support**: Includes constants for encryption using `ChaChaPoly` and `ElligatorSwift` encoding. - **Channel Bits**: Defines whether specific messages are associated with a channel, simplifying protocol handling. - **Modular**: Supports a `no_std` environment, enabling use in embedded systems or environments without a standard library. ## Usage -To use this crate, add it to your `Cargo.toml`: +To include this crate in your project, run: -```toml -[dependencies] -const_sv2 = "2.0.0" +```sh +cargo add const_sv2 diff --git a/protocols/v2/framing-sv2/Cargo.toml b/protocols/v2/framing-sv2/Cargo.toml index 4450a46531..6797d79468 100644 --- a/protocols/v2/framing-sv2/Cargo.toml +++ b/protocols/v2/framing-sv2/Cargo.toml @@ -1,15 +1,18 @@ [package] name = "framing_sv2" version = "2.0.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Sv2 frames" -license = "MIT" +documentation = "https://docs.rs/framing_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] serde = { version = "1.0.89", default-features = false, optional = true } const_sv2 = { version = "^2.0.0", path = "../../../protocols/v2/const-sv2"} @@ -20,3 +23,6 @@ buffer_sv2 = { version = "^1.0.0", path = "../../../utils/buffer", optional=true no_std = [] with_serde = ["binary_sv2/with_serde", "serde", "buffer_sv2/with_serde"] with_buffer_pool = ["binary_sv2/with_buffer_pool", "buffer_sv2"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/noise-sv2/Cargo.toml b/protocols/v2/noise-sv2/Cargo.toml index c627bc1af2..2bcb8ba6d0 100644 --- a/protocols/v2/noise-sv2/Cargo.toml +++ b/protocols/v2/noise-sv2/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "noise_sv2" -version = "1.1.0" -authors = ["fi3 "] +version = "1.2.0" +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Sv2 noise" -license = "MIT" +documentation = "https://docs.rs/noise_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] secp256k1 = { version = "0.28.2", default-features = false, features =["hashes", "alloc","rand","rand-std"] } @@ -18,3 +22,6 @@ const_sv2 = { version = "^2.0.0", path = "../../../protocols/v2/const-sv2"} [dev-dependencies] quickcheck = "1.0.3" quickcheck_macros = "1" + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/noise-sv2/README.md b/protocols/v2/noise-sv2/README.md new file mode 100644 index 0000000000..481ee8a2fb --- /dev/null +++ b/protocols/v2/noise-sv2/README.md @@ -0,0 +1,31 @@ + +# noise_sv2 + +[![crates.io](https://img.shields.io/crates/v/const_sv2.svg)](https://crates.io/crates/const_sv2) +[![docs.rs](https://docs.rs/const_sv2/badge.svg)](https://docs.rs/const_sv2) +[![rustc+](https://img.shields.io/badge/rustc-1.75.0%2B-lightgrey.svg)](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html) +[![license](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](https://github.com/stratum-mining/stratum/blob/main/LICENSE.md) +[![codecov](https://codecov.io/gh/stratum-mining/stratum/branch/main/graph/badge.svg?flag=noise_sv2-coverage)](https://codecov.io/gh/stratum-mining/stratum) + +`noise_sv2` is primarily intended to secure communication in the Stratum V2 (Sv2) protocol. It handles the necessary Noise handshakes, encrypts outgoing messages, and decrypts incoming responses, ensuring privacy and integrity across the communication link between Sv2 roles. See the [Protocol Security specification](https://github.com/stratum-mining/sv2-spec/blob/main/04-Protocol-Security.md) for more details. + +## Key Capabilities +* **Secure Communication**: Provides encryption and authentication for messages exchanged between different Sv2 roles. +* **Cipher Support**: Includes support for both `AES-GCM` and `ChaCha20-Poly1305`. +* **Handshake Roles**: Implements the `Initiator` and `Responder` roles required by the Noise handshake, allowing both sides of a connection to establish secure communication. +* **Cryptographic Helpers**: Facilitates the management of cryptographic state and encryption operations. + +## Usage +To include this crate in your project, run: + +```bash +cargo add noise_sv2 +``` + +### Examples + +This crate provides example on establishing a secure line: + +1. **[Noise Handshake Example](https://github.com/stratum-mining/stratum/blob/main/protocols/v2/noise-sv2/examples/handshake.rs)**: + Establish a secure line of communication between an Initiator and Responder via the Noise + protocol, allowing for the encryption and decryption of a secret message. \ No newline at end of file diff --git a/protocols/v2/noise-sv2/examples/handshake.rs b/protocols/v2/noise-sv2/examples/handshake.rs new file mode 100644 index 0000000000..d3d6a22645 --- /dev/null +++ b/protocols/v2/noise-sv2/examples/handshake.rs @@ -0,0 +1,68 @@ +// # Noise Protocol Handshake +// +// This example demonstrates how to use the `noise-sv2` crate to establish a Noise handshake +// between and initiator and responder, and encrypt and decrypt a secret message. It showcases how +// to: +// +// - Generate a cryptographic keypair using the `secp256k1` library. +// - Perform a Noise handshake between an initiator and responder. +// - Transition from handshake to secure communication mode. +// - Encrypt a message as the initiator role. +// - Decrypt the message as the responder role. +// +// ## Run +// +// ```sh +// cargo run --example handshake +// ``` + +use noise_sv2::{Initiator, Responder}; +use secp256k1::{Keypair, Parity, Secp256k1}; + +// Even parity used in the Schnorr signature process +const PARITY: Parity = Parity::Even; +// Validity duration of the responder's certificate, seconds +const RESPONDER_CERT_VALIDITY: u32 = 3600; + +// Generates a secp256k1 public/private key pair for the responder. +fn generate_key() -> Keypair { + let secp = Secp256k1::new(); + let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng()); + let kp = Keypair::from_secret_key(&secp, &secret_key); + if kp.x_only_public_key().1 == PARITY { + kp + } else { + generate_key() + } +} + +fn main() { + let mut secret_message = "Ciao, Mondo!".as_bytes().to_vec(); + + let responder_key_pair = generate_key(); + + let mut initiator = Initiator::new(Some(responder_key_pair.public_key().into())); + let mut responder = Responder::new(responder_key_pair, RESPONDER_CERT_VALIDITY); + + let first_message = initiator + .step_0() + .expect("Initiator failed first step of handshake"); + + let (second_message, mut responder_state) = responder + .step_1(first_message) + .expect("Responder failed second step of handshake"); + + let mut initiator_state = initiator + .step_2(second_message) + .expect("Initiator failed third step of handshake"); + + initiator_state + .encrypt(&mut secret_message) + .expect("Initiator failed to encrypt the secret message"); + assert!(secret_message != "Ciao, Mondo!".as_bytes().to_vec()); + + responder_state + .decrypt(&mut secret_message) + .expect("Responder failed to decrypt the secret message"); + assert!(secret_message == "Ciao, Mondo!".as_bytes().to_vec()); +} diff --git a/protocols/v2/noise-sv2/src/aed_cipher.rs b/protocols/v2/noise-sv2/src/aed_cipher.rs index 857524f37f..4ce0dc70f9 100644 --- a/protocols/v2/noise-sv2/src/aed_cipher.rs +++ b/protocols/v2/noise-sv2/src/aed_cipher.rs @@ -1,9 +1,52 @@ +// # AEAD Cipher +// +// Abstracts the encryption and decryption operations for authenticated encryption with associated +// data (AEAD) ciphers used in the Noise protocol. +// +// The [`AeadCipher`] trait provides a unified interface for AEAD ciphers, including +// [`ChaCha20Poly1305`] and [`Aes256Gcm`], allowing flexible cryptographic operations in different +// contexts. +// +// The trait supports core AEAD operations, including: +// +// - Key initialization via the `from_key` method to derive a cipher instance from a 32-byte key. +// - Authenticated encryption via the `encrypt` method to securely encrypt data with a nonce and +// additional associated data (AAD). +// - Authenticated decryption via the `decrypt` method to securely decrypt data using the provided +// nonce and AAD. +// +// ## Usage +// +// The `AeadCipher` trait can be implemented for any AEAD cipher, enabling encryption and decryption +// of Noise protocol messages. Two default implementations are provided for the +// [`ChaCha20Poly1305`] and [`Aes256Gcm`] ciphers. + use aes_gcm::Aes256Gcm; use chacha20poly1305::{aead::Buffer, AeadInPlace, ChaCha20Poly1305, ChaChaPoly1305, KeyInit}; +// Defines the interface for AEAD ciphers. +// +// The [`AeadCipher`] trait provides a standard interface for initializing AEAD ciphers, and for +// performing encryption and decryption operations with additional Authenticated Associated Data (AAD). This trait is implemented +// by either the [`ChaCha20Poly1305`] or [`Aes256Gcm`] specific cipher types, allowing them to be +// used interchangeably in cryptographic protocols. It is utilized by the +// [`crate::handshake::HandshakeOp`] trait to secure the handshake process. +// +// The `T: Buffer` represents the data buffer to be encrypted or decrypted. The buffer must +// implement the [`Buffer`] trait, which provides necessary operations for in-place encryption and +// decryption. pub trait AeadCipher { + // Creates a new instance of the cipher from a 32-byte key. + // + // Initializes the AEAD cipher with the provided key (`k`), preparing it for + // encryption and decryption operations. fn from_key(k: [u8; 32]) -> Self; + // Encrypts the data in place using the provided 12-byte `nonce` and AAD (`ad`). + // + // Performs authenticated encryption on the provided mutable data buffer (`data`), modifying + // it in place to contain the ciphertext. The encryption is performed using the provided nonce + // and AAD, which ensures that the data has not been tampered with during transit. fn encrypt( &mut self, nonce: &[u8; 12], @@ -11,6 +54,11 @@ pub trait AeadCipher { data: &mut T, ) -> Result<(), aes_gcm::Error>; + // Decrypts the data in place using the provided 12-byte nonce (`n`) and AAD (`ad`). + // + // Performs authenticated decryption on the provided mutable data buffer, modifying it in + // place to contain the plaintext. The decryption is performed using the provided nonce and + // AAD, ensuring that the data has not been tampered with during transit. fn decrypt( &mut self, nonce: &[u8; 12], @@ -47,6 +95,7 @@ impl AeadCipher for Aes256Gcm { fn from_key(k: [u8; 32]) -> Self { Aes256Gcm::new(&k.into()) } + fn encrypt( &mut self, nonce: &[u8; 12], @@ -55,6 +104,7 @@ impl AeadCipher for Aes256Gcm { ) -> Result<(), aes_gcm::Error> { self.encrypt_in_place(nonce.into(), ad, data) } + fn decrypt( &mut self, nonce: &[u8; 12], diff --git a/protocols/v2/noise-sv2/src/cipher_state.rs b/protocols/v2/noise-sv2/src/cipher_state.rs index b76f247c17..dc324328ff 100644 --- a/protocols/v2/noise-sv2/src/cipher_state.rs +++ b/protocols/v2/noise-sv2/src/cipher_state.rs @@ -1,19 +1,91 @@ +// # Cipher State Management +// +// Defines the [`CipherState`] trait and the [`GenericCipher`] enum, which manage the state of +// AEAD ciphers used in the Noise protocol. This includes managing the encryption key, nonce, and +// the cipher instance itself, facilitating secure encryption and decryption during communication. +// +// The [`CipherState`] trait abstracts the management of core elements for AEAD ciphers: +// - Manages the encryption key lifecycle used by the AEAD cipher. +// - Generates and tracks unique nonces for each encryption operation, preventing replay attacks. +// - Initializes the appropriate cipher (e.g., [`ChaCha20Poly1305`] or [`Aes256Gcm`]) for secure +// communication. +// +// The trait provides methods for encrypting and decrypting data using additional associated data +// (AAD) and securely erasing sensitive cryptographic material when no longer needed. +// +// The [`GenericCipher`] enum enables flexible use of either [`ChaCha20Poly1305`] or [`Aes256Gcm`] +// ciphers. It abstracts away the specific cipher being used while ensuring consistent handling of +// cryptographic operations (e.g., encryption, decryption, key erasure) across both ciphers. +// +// ## Usage +// +// The [`CipherState`] trait is used by the [`crate::handshake::HandshakeOp`] trait to manage +// stateful encryption and decryption tasks during the Noise protocol handshake. By implementing +// [`CipherState`], the handshake process securely manages cryptographic material and transforms +// messages exchanged between the initiator and responder. +// +// Once the Noise handshake is complete, the [`crate::Initiator`] and [`crate::Responder`] use +// [`GenericCipher`] instances (`c1` and `c2`) to perform symmetric encryption and decryption. +// These ciphers, initialized and managed through the [`CipherState`] trait, ensure ongoing +// communication remains confidential and authenticated. +// +// The [`CipherState`] trait and [`GenericCipher`] enum are essential for managing AEAD ciphers +// within the Noise protocol, ensuring secure data handling, key management, and nonce tracking +// throughout the communication session. + use std::ptr; use crate::aed_cipher::AeadCipher; use aes_gcm::Aes256Gcm; use chacha20poly1305::{aead::Buffer, ChaCha20Poly1305}; +// The `CipherState` trait manages AEAD ciphers for secure communication, handling the encryption key, nonce, +// and cipher instance. It supports encryption and decryption with ciphers like [`ChaCha20Poly1305`] and +// [`Aes256Gcm`], ensuring proper key and nonce management. +// +// Key responsibilities: +// - **Key management**: Set and retrieve the 32-byte encryption key. +// - **Nonce management**: Track unique nonces for encryption operations. +// - **Cipher handling**: Initialize and manage AEAD ciphers for secure data encryption. +// +// Used in protocols like Noise, `CipherState` ensures secure communication by managing cryptographic material +// during and after handshakes. pub trait CipherState where Self: Sized, { + // Retrieves a mutable reference to the 32-byte encryption key (`k`). fn get_k(&mut self) -> &mut Option<[u8; 32]>; + + // Sets the 32-byte encryption key to the optionally provided value (`k`). + // + // Allows the encryption key to be explicitly set, typically after it has been derived or + // initialized during the handshake process. If `None`, the encryption key is unset. fn set_k(&mut self, k: Option<[u8; 32]>); + + // Retrieves the current nonce (`n`) used for encryption. + // + // The nonce is a counter that is incremented with each encryption/decryption operations to + // ensure that each encryption operation with the same key produces a unique ciphertext. fn get_n(&self) -> u64; + + // Sets the nonce (`n`) to the provided value. + // + // Allows the nonce to be explicitly set, typically after it has been initialized, incremented + // during the encryption process, or reset. fn set_n(&mut self, n: u64); + + // Retrieves a mutable reference to the optional cipher instance. + // + // Provides access to the underlying AEAD cipher instance used for encryption and decryption + // operations. fn get_cipher(&mut self) -> &mut Option; + // Converts the current 64-bit nonce value (`n`) to a 12-byte array. + // + // Converts the 64-bit nonce value to a 12-byte array suitable for use with AEAD ciphers, + // which typically expect a 96-bit (12-byte) nonce. The result is a correctly formatted nonce + // for use in encryption and decryption operations. fn nonce_to_bytes(&self) -> [u8; 12] { let mut res = [0u8; 12]; let n = self.get_n(); @@ -37,6 +109,11 @@ where Some(Cipher::from_cipher(c)) } + // Encrypts the provided `data` in place using the cipher and AAD (`ad`). + // + // Performs authenticated encryption on the provided `data` buffer, modifying it in place to + // contain the ciphertext. The encryption is performed using the current nonce and the AAD. + // The nonce is incremented after each successful encryption. fn encrypt_with_ad( &mut self, ad: &[u8], @@ -58,6 +135,11 @@ where } } + // Decrypts the data in place using the cipher and AAD (`ad`). + // + // Performs authenticated decryption on the provided `data` buffer, modifying it in place to + // contain the plaintext. The decryption is performed using the current nonce and the provided + // AAD. The nonce is incremented after each successful decryption. fn decrypt_with_ad( &mut self, ad: &[u8], @@ -80,6 +162,15 @@ where } } +// The `GenericCipher` enum abstracts the use of two AEAD ciphers: [`ChaCha20Poly1305`] and [`Aes256Gcm`]. +// It provides a unified interface for secure encryption and decryption, allowing flexibility in choosing +// the cipher while ensuring consistent cryptographic operations. +// +// Variants: +// - **ChaCha20Poly1305**: Uses the `ChaCha20Poly1305` cipher for encryption. +// - **Aes256Gcm**: Uses the `Aes256Gcm` cipher for encryption. +// +// `GenericCipher` enables easy switching between ciphers while maintaining secure key and nonce management. #[allow(clippy::large_enum_variant)] pub enum GenericCipher { ChaCha20Poly1305(Cipher), @@ -88,24 +179,45 @@ pub enum GenericCipher { } impl Drop for GenericCipher { + // Securely erases the encryption key when the [`GenericCipher`] is dropped. + // + // Ensures that the encryption key is securely erased from memory when the [`GenericCipher`] + // instance is dropped, preventing any potential leakage of sensitive cryptographic material. fn drop(&mut self) { self.erase_k(); } } impl GenericCipher { + // Encrypts the data (`msg`) in place using the underlying cipher. + // + // Performs authenticated encryption on the provided data buffer, modifying it in place to + // contain the ciphertext. The encryption is performed using the current nonce and an empty + // additional associated data (AAD) buffer. pub fn encrypt(&mut self, msg: &mut T) -> Result<(), aes_gcm::Error> { match self { GenericCipher::ChaCha20Poly1305(c) => c.encrypt_with_ad(&[], msg), GenericCipher::Aes256Gcm(c) => c.encrypt_with_ad(&[], msg), } } + + // Decrypts the data (`msg`) in place using the underlying cipher. + // + // Performs authenticated decryption on the provided data buffer, modifying it in place to + // contain the plaintext. The decryption is performed using the current nonce and an empty + // additional associated data (AAD) buffer. pub fn decrypt(&mut self, msg: &mut T) -> Result<(), aes_gcm::Error> { match self { GenericCipher::ChaCha20Poly1305(c) => c.decrypt_with_ad(&[], msg), GenericCipher::Aes256Gcm(c) => c.decrypt_with_ad(&[], msg), } } + + // Securely erases the encryption key (`k`) from memory. + // + // Overwrites the encryption key stored within the [`GenericCipher`] with zeros and sets it to + // `None`, ensuring that the key cannot be recovered after the [`GenericCipher`] is dropped or + // no longer needed. pub fn erase_k(&mut self) { match self { GenericCipher::ChaCha20Poly1305(c) => { @@ -180,20 +292,35 @@ impl CipherState for GenericCipher { } } +// Represents the state of an AEAD cipher, including the optional 32-byte encryption key (`k`), +// nonce (`n`), and optional cipher instance (`cipher`). +// +// Manages the cryptographic state required to perform AEAD encryption and decryption operations. +// It stores the optional encryption key, the nonce, and the optional cipher instance itself. The +// [`CipherState`] trait is implemented to provide a consistent interface for managing cipher +// state across different AEAD ciphers. pub struct Cipher { + // Optional 32-byte encryption key. k: Option<[u8; 32]>, + // Nonce value. n: u64, + // Optional cipher instance. cipher: Option, } -// Make sure that Cipher is not sync so we do not need to worry about what other memory accessor see -// after that we zeroize k is send cause if we send it the original thread can not access -// anymore it -//impl !Sync for Cipher {} -//impl !Copy for Cipher {} - +// Ensures that the `Cipher` type is not `Sync`, which prevents multiple threads from +// simultaneously accessing the same instance of `Cipher`. This eliminates the need to handle +// potential issues related to visibility of changes across threads. +// +// After sending the `k` value, we immediately clear it to prevent the original thread from +// accessing the value again, thereby enhancing security by ensuring the sensitive data is no +// longer available in memory. +// +// The `Cipher` struct is neither `Sync` nor `Copy` due to its `cipher` field, which implements +// the `AeadCipher` trait. This trait requires mutable access, making the entire struct non-`Sync` +// and non-`Copy`, even though the key and nonce are simple types. impl Cipher { - /// Internal use only, we need k for handshake + // Internal use only, we need k for handshake pub fn from_key_and_cipher(k: [u8; 32], c: C) -> Self { Self { k: Some(k), @@ -202,7 +329,7 @@ impl Cipher { } } - /// At the end of the handshake we return a cipher with hidden key + // At the end of the handshake we return a cipher with hidden key #[allow(dead_code)] pub fn from_cipher(c: C) -> Self { Self { diff --git a/protocols/v2/noise-sv2/src/error.rs b/protocols/v2/noise-sv2/src/error.rs index f8f10a6ea4..5e5be3fff0 100644 --- a/protocols/v2/noise-sv2/src/error.rs +++ b/protocols/v2/noise-sv2/src/error.rs @@ -1,18 +1,46 @@ +// # Error Handling +// +// Defines error types and utilities for handling errors in the `noise_sv2` module. + use aes_gcm::Error as AesGcm; +/// Noise protocol error handling. #[derive(Debug, PartialEq, Eq)] pub enum Error { + /// The handshake has not been completed when a finalization step is executed. HandshakeNotFinalized, + + /// Error on an empty cipher list is provided where one is required. CipherListMustBeNonEmpty, + + /// Error on unsupported ciphers. UnsupportedCiphers(Vec), + + /// Provided cipher list is invalid or malformed. InvalidCipherList(Vec), + + /// Chosen cipher is invalid or unsupported. InvalidCipherChosed(Vec), + + /// Wraps AES-GCM errors during encryption/decryption. AesGcm(AesGcm), + + /// Cipher is in an invalid state during encryption/decryption operations. InvalidCipherState, + + /// Provided certificate is invalid or cannot be verified. InvalidCertificate([u8; 74]), + + /// A raw public key is invalid or cannot be parsed. InvalidRawPublicKey, + + /// A raw private key is invalid or cannot be parsed. InvalidRawPrivateKey, + + /// An incoming handshake message is expected but not received. ExpectedIncomingHandshakeMessage, + + /// A message has an incorrect or unexpected length. InvalidMessageLength, } diff --git a/protocols/v2/noise-sv2/src/handshake.rs b/protocols/v2/noise-sv2/src/handshake.rs index 3ab0213740..0d71fde445 100644 --- a/protocols/v2/noise-sv2/src/handshake.rs +++ b/protocols/v2/noise-sv2/src/handshake.rs @@ -1,3 +1,35 @@ +// # Noise Handshake Operations +// +// The [`HandshakeOp`] trait defines the cryptographic operations and utilities required to perform +// the Noise protocol handshake between Sv2 roles. +// +// This trait abstracts key management, encryption, and hashing for the Noise protocol handshake, +// outlining core operations implemented by the [`crate::Initiator`] and [`crate::Responder`] +// roles. The trait governs the following processes: +// +// - Elliptic curve Diffie-Hellman (ECDH) key exchange using the [`secp256k1`] curve to establish a +// shared secret. +// - HMAC and HKDF for deriving encryption keys from the shared secret. +// - AEAD encryption and decryption using either [`ChaCha20Poly1305`] or `AES-GCM` ciphers to +// ensure message confidentiality and integrity. +// - Chaining key and handshake hash updates to maintain the security of the session. +// +// The handshake begins with the exchange of ephemeral key pairs, followed by the derivation of +// shared secrets, which are then used to securely encrypt all subsequent communication. +// +// ## Usage +// The handshake secures communication between two Sv2 roles, with one acting as the +// [`crate::Initiator`] (e.g., a local mining proxy) and the other as the [`crate::Responder`] +// (e.g., a remote pool). Both roles implement the [`HandshakeOp`] trait to manage cryptographic +// state, updating the handshake hash (`h`), chaining key (`ck`), and encryption key (`k`) to +// ensure confidentiality and integrity throughout the handshake. +// +// Securing communication via the Noise protocol guarantees the confidentiality and authenticity of +// sensitive data, such as share submissions. While the use of a secure channel is optional for Sv2 +// roles within a local network (e.g., between a local mining device and mining proxy), it is +// mandatory for communication across external networks (e.g., between a local mining proxy and a +// remote pool). + use crate::{aed_cipher::AeadCipher, cipher_state::CipherState, NOISE_HASHED_PROTOCOL_NAME_CHACHA}; use chacha20poly1305::ChaCha20Poly1305; use secp256k1::{ @@ -6,16 +38,54 @@ use secp256k1::{ rand, Keypair, Secp256k1, SecretKey, XOnlyPublicKey, }; +// Represents the operations needed during a Noise protocol handshake. +// +// The [`HandshakeOp`] trait defines the necessary functions for managing the state and +// cryptographic operations required during the Noise protocol handshake. It provides methods for +// key generation, hash mixing, encryption, decryption, and key derivation, ensuring that the +// handshake process is secure and consistent. pub trait HandshakeOp: CipherState { + // Returns the name of the entity implementing the handshake operation. + // + // Provides a string that identifies the entity (e.g., "Initiator" or "Responder") that is + // performing the handshake. It is primarily used for debugging or logging purposes. + #[allow(dead_code)] fn name(&self) -> String; + + // Retrieves a mutable reference to the handshake hash (`h`). + // + // The handshake hash accumulates the state of the handshake, incorporating all exchanged + // messages to ensure integrity and prevent tampering. This method provides access to the + // current state of the handshake hash, allowing it to be updated as the handshake progresses. fn get_h(&mut self) -> &mut [u8; 32]; + // Retrieves a mutable reference to the chaining key (`ck`). + // + // The chaining key is used during the key derivation process to generate new keys throughout + // the handshake. This method provides access to the current chaining key, which is updated + // as the handshake progresses and new keys are derived. fn get_ck(&mut self) -> &mut [u8; 32]; + // Sets the handshake hash (`h`) to the provided value. + // + // This method allows the handshake hash to be explicitly set, typically after it has been + // initialized or updated during the handshake process. The handshake hash ensures the + // integrity of the handshake by incorporating all exchanged messages. fn set_h(&mut self, data: [u8; 32]); + // Sets the chaining key (`ck`) to the provided value. + // + // This method allows the chaining key to be explicitly set, typically after it has been + // initialized or updated during the handshake process. The chaining key is crucial for + // deriving new keys as the handshake progresses. fn set_ck(&mut self, data: [u8; 32]); + // Mixes the data into the handshake hash (`h`). + // + // Updates the current handshake hash by combining it with the provided `data`. The result is + // a new SHA-256 hash digest that reflects all previous handshake messages, ensuring the + // integrity of the handshake process. This method is typically called whenever a new piece of + // data (e.g., a public key or ciphertext) needs to be incorporated into the handshake state. fn mix_hash(&mut self, data: &[u8]) { let h = self.get_h(); let mut to_hash = Vec::with_capacity(32 + data.len()); @@ -24,6 +94,11 @@ pub trait HandshakeOp: CipherState { *h = Sha256Hash::hash(&to_hash).to_byte_array(); } + // Generates a new cryptographic key pair using the [`Secp256k1`] curve. + // + // Generates a fresh key pair, consisting of a secret key and a corresponding public key, + // using the [`Secp256k1`] elliptic curve. If the generated public key does not match the + // expected parity, a new key pair is generated to ensure consistency. fn generate_key() -> Keypair { let secp = Secp256k1::new(); let (secret_key, _) = secp.generate_keypair(&mut rand::thread_rng()); @@ -35,6 +110,17 @@ pub trait HandshakeOp: CipherState { } } + // Computes an HMAC-SHA256 (Hash-based Message Authentication Code) hash of the provided data + // using the given key. + // + // This method implements the HMAC-SHA256 hashing algorithm, which combines a key and data to + // produce a 32-byte hash. It is used during the handshake to securely derive new keys from + // existing material, ensuring that the resulting keys are cryptographically strong. + // + // This method uses a two-step process: + // 1. The key is XORed with an inner padding (`ipad`) and hashed with the data. + // 2. The result is XORed with the outer padding (`opad`) and hashed again to produce the + // final HMAC. fn hmac_hash(key: &[u8; 32], data: &[u8]) -> [u8; 32] { #[allow(clippy::identity_op)] let mut ipad = [(0 ^ 0x36); 64]; @@ -59,6 +145,21 @@ pub trait HandshakeOp: CipherState { Sha256Hash::hash(&to_hash).to_byte_array() } + // Derives two new keys using the HKDF (HMAC-based Key Derivation Function) process. + // + // Performs the HKDF key derivation process, which uses an initial chaining key and input key + // material to produce two new 32-byte keys. This process is used throughout the handshake to + // generate fresh keys for encryption and authentication, ensuring that each step of the + // handshake is securely linked. + // + // This method performs the following steps: + // 1. Performs a HMAC hash on the chaining key and input key material to derive a temporary + // key. + // 2. Performs a HMAC hash on the temporary key and specific byte sequence (`0x01`) to derive + // the first output. + // 3. Performs a HMAC hash on the temporary key and the concatenation of the first output and + // a specific byte sequence (`0x02`). + // 4. Returns both outputs. fn hkdf_2(chaining_key: &[u8; 32], input_key_material: &[u8]) -> ([u8; 32], [u8; 32]) { let temp_key = Self::hmac_hash(chaining_key, input_key_material); let out_1 = Self::hmac_hash(&temp_key, &[0x1]); @@ -77,6 +178,13 @@ pub trait HandshakeOp: CipherState { (out_1, out_2, out_3) } + // Mixes the input key material into the current chaining key (`ck`) and initializes the + // handshake cipher with an updated encryption key (`k`). + // + // Updates the chaining key by incorporating the provided input key material (e.g., the result + // of a Diffie-Hellman exchange) and uses the updated chaining key to derive a new encryption + // key. The encryption key is then used to initialize the handshake cipher, preparing it for + // use in the next step of the handshake. fn mix_key(&mut self, input_key_material: &[u8]) { let ck = self.get_ck(); let (ck, temp_k) = Self::hkdf_2(ck, input_key_material); @@ -84,6 +192,14 @@ pub trait HandshakeOp: CipherState { self.initialize_key(temp_k); } + // Encrypts the provided plaintext and updates the hash `h` value. + // + // The `encrypt_and_hash` method encrypts the given plaintext using the + // current encryption key and then updates `h` with the resulting ciphertext. + // If an encryption key is present `k`, the method encrypts the data using + // using AEAD, where the associated data is the current hash value. After + // encryption, the ciphertext is mixed into the hash to ensure integrity + // and authenticity of the messages exchanged during the handshake. fn encrypt_and_hash(&mut self, plaintext: &mut Vec) -> Result<(), aes_gcm::Error> { if self.get_k().is_some() { #[allow(clippy::clone_on_copy)] @@ -95,6 +211,14 @@ pub trait HandshakeOp: CipherState { Ok(()) } + // Decrypts the provided ciphertext and updates the handshake hash (`h`). + // + // Decrypts the given ciphertext using the handshake cipher and then mixes the ciphertext + // (before decryption) into the handshake hash. If the encryption key (`k`) is present, the + // data is decrypted using AEAD, where the associated data is the current handshake hash. This + // ensures that each decryption step is securely linked to the previous handshake state, + // maintaining the integrity of the + // handshake. fn decrypt_and_hash(&mut self, ciphertext: &mut Vec) -> Result<(), aes_gcm::Error> { let encrypted = ciphertext.clone(); if self.get_k().is_some() { @@ -113,8 +237,12 @@ pub trait HandshakeOp: CipherState { res.secret_bytes() } - /// Prior to starting first round of NX-handshake, both initiator and responder initializes - /// handshake variables h (hash output), ck (chaining key) and k (encryption key): + // Initializes the handshake state by setting the initial chaining key (`ck`) and handshake + // hash (`h`). + // + // Prepares the handshake state for use by setting the initial chaining key and handshake + // hash. The chaining key is typically derived from a protocol name or other agreed-upon + // value, and the handshake hash is initialized to reflect this starting state. fn initialize_self(&mut self) { let ck = NOISE_HASHED_PROTOCOL_NAME_CHACHA; let h = Sha256Hash::hash(&ck[..]); @@ -123,6 +251,11 @@ pub trait HandshakeOp: CipherState { self.set_k(None); } + // Initializes the handshake cipher with the provided encryption key (`k`). + // + // Resets the nonce (`n`) to 0 and initializes the handshake cipher using the given 32-byte + // encryption key. It also updates the internal key storage (`k`) with the new key, preparing + // the cipher for encrypting or decrypting subsequent messages in the handshake. fn initialize_key(&mut self, key: [u8; 32]) { self.set_n(0); let cipher = ChaCha20Poly1305::from_key(key); @@ -143,6 +276,7 @@ mod test { use super::*; use quickcheck::{Arbitrary, TestResult}; use quickcheck_macros; + use secp256k1::{ecdh::SharedSecret, SecretKey, XOnlyPublicKey}; use std::convert::TryInto; struct TestHandShake { diff --git a/protocols/v2/noise-sv2/src/initiator.rs b/protocols/v2/noise-sv2/src/initiator.rs index d789aef201..24a3423f35 100644 --- a/protocols/v2/noise-sv2/src/initiator.rs +++ b/protocols/v2/noise-sv2/src/initiator.rs @@ -1,3 +1,39 @@ +// # Initiator Role +// +// Manages the [`Initiator`] role in the Noise protocol handshake for communication between Sv2 +// roles. The initiator is responsible for starting the handshake process by sending the first +// cryptographic message to the [`crate::Responder`] role (e.g., a mining pool). +// +// The [`Initiator`] role is equipped with utilities for generating and managing the initiator's +// key pairs, performing elliptic curve Diffie-Hellman (ECDH) key exchanges, and encrypting +// messages during the handshake phase. The initiator's responsibilities include: +// +// - Generating an ephemeral key pair for the handshake. +// - Using the [`secp256k1`] elliptic curve for ECDH to derive a shared secret. +// - Encrypting the initial handshake message to securely exchange cryptographic material. +// - Managing the state transitions between handshake steps, including updating the handshake hash, +// chaining key, and encryption key as the session progresses. +// +// ## Usage +// The initiator role is typically used by a downstream Sv2 role (e.g., a local mining proxy) to +// establish a secure connection with an upstream responder (e.g., a remote mining pool). The +// initiator begins the handshake by generating a public key and sending it to the responder. After +// receiving a response, the initiator computes a shared secret and encrypts further messages using +// this key. +// +// The [`Initiator`] struct implements the [`HandshakeOp`] trait, which defines the core +// cryptographic operations during the handshake. It ensures secure communication by supporting +// both the [`ChaCha20Poly1305`] or `AES-GCM` cipher, providing both confidentiality and message +// authentication for all subsequent communication. +// +// ### Secure Data Erasure +// +// The [`Initiator`] includes functionality for securely erasing sensitive cryptographic material, +// ensuring that private keys and other sensitive data are wiped from memory when no longer needed. +// +// The [`Drop`] trait is implemented to automatically trigger secure erasure when the [`Initiator`] +// instance goes out of scope, preventing potential misuse or leakage of cryptographic material. + use std::{convert::TryInto, ptr}; use crate::{ @@ -19,20 +55,41 @@ use secp256k1::{ Keypair, PublicKey, XOnlyPublicKey, }; +/// Manages the initiator's role in the Noise NX handshake, handling key exchange, encryption, and +/// handshake state. It securely generates and manages cryptographic keys, performs Diffie-Hellman +/// exchanges, and maintains the handshake hash, chaining key, and nonce for message encryption. +/// After the handshake, it facilitates secure communication using either [`ChaCha20Poly1305`] or +/// `AES-GCM` ciphers. Sensitive data is securely erased when no longer needed. pub struct Initiator { + // Cipher used for encrypting and decrypting messages during the handshake. + // + // It is initialized once enough information is available from the handshake process. handshake_cipher: Option, + // Optional static key used in the handshake. This key may be derived from the pre-shared key + // (PSK) or generated during the handshake. k: Option<[u8; 32]>, + // Current nonce used in the encryption process. + // + // Ensures that the same plaintext encrypted twice will produce different ciphertexts. n: u64, - // Chaining key + // Chaining key used in the key derivation process to generate new keys throughout the + // handshake. ck: [u8; 32], - // Handshake hash + // Handshake hash which accumulates all handshake messages to ensure integrity and prevent + // tampering. h: [u8; 32], - // ephemeral keypair + // Ephemeral key pair generated by the initiator for this session, used for generating the + // shared secret with the responder. e: Keypair, - // upstream pub key + // Optional public key of the responder, used to authenticate the responder during the + // handshake. #[allow(unused)] responder_authority_pk: Option, + // First [`CipherState`] used for encrypting messages from the initiator to the responder + // after the handshake is complete. c1: Option, + // Second [`CipherState`] used for encrypting messages from the responder to the initiator + // after the handshake is complete. c2: Option, } @@ -42,22 +99,30 @@ impl std::fmt::Debug for Initiator { } } -// Make sure that Initiator is not sync so we do not need to worry about what other memory accessor see -// after that we zeroize k is send cause if we send it the original thread can not access -// anymore it -//impl !Sync for Initiator {} -//impl !Copy for Initiator {} - +// Ensures that the `Cipher` type is not `Sync`, which prevents multiple threads from +// simultaneously accessing the same instance of `Cipher`. This eliminates the need to handle +// potential issues related to visibility of changes across threads. +// +// After sending the `k` value, we immediately clear it to prevent the original thread from +// accessing the value again, thereby enhancing security by ensuring the sensitive data is no +// longer available in memory. +// +// The `Cipher` struct is neither `Sync` nor `Copy` due to its `cipher` field, which implements +// the `AeadCipher` trait. This trait requires mutable access, making the entire struct non-`Sync` +// and non-`Copy`, even though the key and nonce are simple types. impl CipherState for Initiator { fn get_k(&mut self) -> &mut Option<[u8; 32]> { &mut self.k } + fn get_n(&self) -> u64 { self.n } + fn set_n(&mut self, n: u64) { self.n = n; } + fn get_cipher(&mut self) -> &mut Option { &mut self.handshake_cipher } @@ -71,6 +136,7 @@ impl HandshakeOp for Initiator { fn name(&self) -> String { "Initiator".to_string() } + fn get_h(&mut self) -> &mut [u8; 32] { &mut self.h } @@ -93,16 +159,11 @@ impl HandshakeOp for Initiator { } impl Initiator { - pub fn from_raw_k(key: [u8; 32]) -> Result, Error> { - let pk = - secp256k1::XOnlyPublicKey::from_slice(&key).map_err(|_| Error::InvalidRawPublicKey)?; - Ok(Self::new(Some(pk))) - } - - pub fn without_pk() -> Result, Error> { - Ok(Self::new(None)) - } - + /// Creates a new [`Initiator`] instance with an optional responder public key. + /// + /// If the responder public key is provided, the initiator uses this key to authenticate the + /// responder during the handshake. The initial initiator state is instantiated with the + /// ephemeral key pair and handshake hash. pub fn new(pk: Option) -> Box { let mut self_ = Self { handshake_cipher: None, @@ -119,23 +180,39 @@ impl Initiator { Box::new(self_) } - /// #### 4.5.1.1 Initiator + /// Creates a new [`Initiator`] instance using a raw 32-byte public key. /// - /// Initiator generates ephemeral keypair and sends the public key to the responder: + /// Constructs a [`XOnlyPublicKey`] from the provided raw key slice and initializes a new + /// [`Initiator`] with the derived public key. If the provided key cannot be converted into a + /// valid [`XOnlyPublicKey`], an [`Error::InvalidRawPublicKey`] error is returned. /// - /// 1. initializes empty output buffer - /// 2. generates ephemeral keypair `e`, appends `e.public_key` to the buffer (64 bytes plaintext public key encoded with ElligatorSwift) - /// 3. calls `MixHash(e.public_key)` - /// 4. calls `EncryptAndHash()` with empty payload and appends the ciphertext to the buffer (note that _k_ is empty at this point, so this effectively reduces down to `MixHash()` on empty data) - /// 5. submits the buffer for sending to the responder in the following format - /// - /// ##### Ephemeral public key message: + /// Typically used when the initiator is aware of the responder's public key in advance. + pub fn from_raw_k(key: [u8; 32]) -> Result, Error> { + let pk = + secp256k1::XOnlyPublicKey::from_slice(&key).map_err(|_| Error::InvalidRawPublicKey)?; + Ok(Self::new(Some(pk))) + } + + /// Creates a new [`Initiator`] without requiring the responder's authority public key. + /// This function initializes the [`Initiator`] with a default empty state and is intended + /// for use when both the initiator and responder are within the same network. In this case, + /// the initiator does not validate the responder's static key from a certificate. However, + /// the connection remains encrypted. + pub fn without_pk() -> Result, Error> { + Ok(Self::new(None)) + } + + /// Executes the initial step of the Noise NX protocol handshake. /// - /// | Field name | Description | - /// | ---------- | -------------------------------- | - /// | PUBKEY | Initiator's ephemeral public key | + /// This step involves generating an ephemeral keypair and encoding the public key using + /// Elligator Swift encoding, which obscures the key to prevent identification. The encoded + /// public key is then mixed into the handshake state, and an empty payload is encrypted. + /// This operation currently only affects the handshake hash, as the key (`k`) is not yet + /// established. The function returns the encoded public key, which is ready to be sent to + /// the responder. /// - /// Message length: 64 bytes + /// On success, the function returns a 64-byte array containing the encoded public key. + /// If an error occurs during encryption, it returns an [`aes_gcm::Error`]. pub fn step_0(&mut self) -> Result<[u8; ELLSWIFT_ENCODING_SIZE], aes_gcm::Error> { let elliswift_enc_pubkey = ElligatorSwift::from_pubkey(self.e.public_key()).to_array(); self.mix_hash(&elliswift_enc_pubkey); @@ -146,23 +223,24 @@ impl Initiator { Ok(message) } - /// #### 4.5.2.2 Initiator - /// - /// 1. receives NX-handshake part 2 message - /// 2. interprets first 64 bytes as ElligatorSwift encoding of `re.public_key` - /// 3. calls `MixHash(re.public_key)` - /// 4. calls `MixKey(ECDH(e.private_key, re.public_key))` - /// 5. decrypts next 80 bytes (64 bytes for ElligatorSwift encoded pubkey + 16 bytes MAC) with `DecryptAndHash()` and stores the results as `rs.public_key` which is **server's static public key**. - /// 6. calls `MixKey(ECDH(e.private_key, rs.public_key)` - /// 7. decrypts next 90 bytes with `DecryptAndHash()` and deserialize plaintext into `SIGNATURE_NOISE_MESSAGE` (74 bytes data + 16 bytes MAC) - /// 8. return pair of CipherState objects, the first for encrypting transport messages from initiator to responder, and the second for messages in the other direction: - /// 1. sets `temp_k1, temp_k2 = HKDF(ck, zerolen, 2)` - /// 2. creates two new CipherState objects `c1` and `c2` - /// 3. calls `c1.InitializeKey(temp_k1)` and `c2.InitializeKey(temp_k2)` - /// 4. returns the pair `(c1, c2)` + /// Processes the second step of the Noise NX protocol handshake for the initiator. /// + /// This method handles the responder's reply in the Noise NX protocol handshake, processing + /// the message to derive shared secrets and authenticate the responder. It interprets the + /// first 64 bytes of the message as the responder's ephemeral public key, decodes it, and + /// mixes it into the handshake state. It then derives a shared secret from the ephemeral keys + /// and updates the state accordingly. /// + /// The next 80 bytes of the message contain the responder's static public key, encrypted and + /// authenticated. The method decrypts this segment and derives another shared secret using the + /// responder's static public key, further securing the handshake state. Finally, the method + /// decrypts and verifies the signature included in the message to ensure the responder's + /// authenticity. /// + /// On success, this method returns a [`NoiseCodec`] instance initialized with session ciphers + /// for secure communication. If the provided `message` has an incorrect length, it returns an + /// [`Error::InvalidMessageLength`]. If decryption or signature verification fails, it returns + /// an [`Error::InvalidCertificate`]. pub fn step_2( &mut self, message: [u8; INITIATOR_EXPECTED_HANDSHAKE_MESSAGE_SIZE], @@ -252,6 +330,12 @@ impl Initiator { } } + // Securely erases sensitive data from the [`Initiator`] memory. + // + // Clears all sensitive cryptographic material within the [`Initiator`] to prevent any + // accidental leakage or misuse. It overwrites the stored keys, chaining key, handshake hash, + // and session ciphers with zeros. This method is typically + // called when the [`Initiator`] instance is no longer needed or before deallocation. fn erase(&mut self) { if let Some(k) = self.k.as_mut() { for b in k { diff --git a/protocols/v2/noise-sv2/src/lib.rs b/protocols/v2/noise-sv2/src/lib.rs index 85ead6e6e6..46316b2edf 100644 --- a/protocols/v2/noise-sv2/src/lib.rs +++ b/protocols/v2/noise-sv2/src/lib.rs @@ -1,6 +1,36 @@ -//! Implement Sv2 noise https://github.com/stratum-mining/sv2-spec/blob/main/04-Protocol-Security.md#4-protocol-security - -// #![feature(negative_impls)] +//! # Noise-SV2: Noise Protocol Implementation for Stratum V2 +//! +//! `noise_sv2` ensures secure communication between Sv2 roles by handling encryption, decryption, +//! and authentication through Noise protocol handshakes and cipher operations. +//! +//! Implementation of the [Sv2 Noise protocol specification](https://github.com/stratum-mining/sv2-spec/blob/main/04-Protocol-Security.md#4-protocol-security). +//! +//! ## Features +//! - Noise Protocol: Establishes secure communication via the Noise protocol handshake between the +//! [`Initiator`] and [`Responder`] roles. +//! - Diffie-Hellman with [`secp256k1`]: Securely establishes a shared secret between two Sv2 +//! roles, using the same elliptic curve used in Bitcoin. +//! - AEAD: Ensures confidentiality and integrity of the data. +//! - `AES-GCM` and `ChaCha20-Poly1305`: Provides encryption, with hardware-optimized and +//! software-optimized options. +//! - Schnorr Signatures: Authenticates messages and verifies the identity of the Sv2 roles. +//! In practice, the primitives exposed by this crate should be used to secure communication +//! channels between Sv2 roles. Securing communication between two Sv2 roles on the same local +//! network (e.g., local mining devices communicating with a local mining proxy) is optional. +//! However, it is mandatory to secure the communication between two Sv2 roles communicating over a +//! remote network (e.g., a local mining proxy communicating with a remote pool sever). +//! +//! The Noise protocol establishes secure communication between two Sv2 roles via a handshake +//! performed at the beginning of the connection. The initiator (e.g., a local mining proxy) and +//! the responder (e.g., a mining pool) establish a shared secret using Elliptic Curve +//! Diffie-Hellman (ECDH) with the [`secp256k1`] elliptic curve (the same elliptic curve used by +//! Bitcoin). Once both Sv2 roles compute the shared secret from the ECDH exchange, the Noise +//! protocol derives symmetric encryption keys for secure communication. These keys are used with +//! AEAD (using either `AES-GCM` or `ChaCha20-Poly1305`) to encrypt and authenticate all +//! communication between the roles. This encryption ensures that sensitive data, such as share +//! submissions, remains confidential and tamper-resistant. Additionally, Schnorr signatures are +//! used to authenticate messages and validate the identities of the Sv2 roles, ensuring that +//! critical messages like job templates and share submissions originate from legitimate sources. use aes_gcm::aead::Buffer; pub use aes_gcm::aead::Error as AeadError; @@ -17,10 +47,22 @@ mod test; pub use const_sv2::{NOISE_HASHED_PROTOCOL_NAME_CHACHA, NOISE_SUPPORTED_CIPHERS_MESSAGE}; +// The parity value used in the Schnorr signature process. +// +// Used to define whether a public key corresponds to an even or odd point on the elliptic curve. +// In this case, `Parity::Even` is used. const PARITY: secp256k1::Parity = secp256k1::Parity::Even; +/// A codec for managing encrypted communication in the Noise protocol. +/// +/// Manages the encryption and decryption of messages between two parties, the [`Initiator`] and +/// [`Responder`], using the Noise protocol. A symmetric cipher is used for both encrypting +/// outgoing messages and decrypting incoming messages. pub struct NoiseCodec { + // Cipher to encrypt outgoing messages. encryptor: GenericCipher, + + // Cipher to decrypt incoming messages. decryptor: GenericCipher, } @@ -31,9 +73,12 @@ impl std::fmt::Debug for NoiseCodec { } impl NoiseCodec { + /// Encrypts a message (`msg`) in place using the stored cipher. pub fn encrypt(&mut self, msg: &mut T) -> Result<(), aes_gcm::Error> { self.encryptor.encrypt(msg) } + + /// Decrypts a message (`msg`) in place using the stored cipher. pub fn decrypt(&mut self, msg: &mut T) -> Result<(), aes_gcm::Error> { self.decryptor.decrypt(msg) } diff --git a/protocols/v2/noise-sv2/src/responder.rs b/protocols/v2/noise-sv2/src/responder.rs index f5a3ddcc2c..7fd6dca722 100644 --- a/protocols/v2/noise-sv2/src/responder.rs +++ b/protocols/v2/noise-sv2/src/responder.rs @@ -1,3 +1,39 @@ +// # Responder Role +// +// Manages the [`Responder`] role in the Noise protocol handshake for secure communication between +// Sv2 roles. The responder is responsible for handling incoming handshake messages from the +// [`crate::Initiator`] (e.g., a mining proxy) and respond with the appropriate cryptographic +// data. +// +// The [`Responder`] role is equipped with utilities for handling elliptic curve Diffie-Hellman +// (ECDH) key exchanges, decrypting messages, and securely managing cryptographic state during the +// handshake phase. The responder's responsibilities include: +// +// - Generating an ephemeral key pair for the handshake. +// - Using the [`secp256k1`] elliptic curve for ECDH to compute a shared secret based on the +// initiator's public key. +// - Decrypting and processing incoming handshake messages from the initiator. +// - Managing state transitions, including updates to the handshake hash, chaining key, and +// encryption key as the session progresses. +// +// ## Usage +// The responder role is typically used by an upstream Sv2 role (e.g., a remote mining pool) to +// respond to an incoming handshake initiated by a downstream role (e.g., a local mining proxy). +// After receiving the initiator's public key, the responder computes a shared secret, which is +// used to securely encrypt further communication. +// +// The [`Responder`] struct implements the [`HandshakeOp`] trait, which defines the core +// cryptographic operations during the handshake. It ensures secure communication by supporting +// both the [`ChaCha20Poly1305`] or `AES-GCM` cipher, providing both confidentiality and message +// authentication for all subsequent communication. +// +// ### Secure Data Erasure +// +// The [`Responder`] includes functionality for securely erasing sensitive cryptographic material, +// ensuring that private keys and other sensitive data are wiped from memory when no longer needed. +// The [`Drop`] trait is implemented to automatically trigger secure erasure when the [`Responder`] +// instance goes out of scope, preventing potential misuse or leakage of cryptographic material. + use std::{ptr, time::Duration}; use crate::{ @@ -17,22 +53,47 @@ use secp256k1::{ellswift::ElligatorSwift, Keypair, Secp256k1, SecretKey}; const VERSION: u16 = 0; +/// Represents the state and operations of the responder in the Noise NX protocol handshake. +/// It handles cryptographic key exchanges, manages handshake state, and securely establishes +/// a connection with the initiator. The responder manages key generation, Diffie-Hellman exchanges, +/// message decryption, and state transitions, ensuring secure communication. Sensitive cryptographic +/// material is securely erased when no longer needed. pub struct Responder { + // Cipher used for encrypting and decrypting messages during the handshake. + // + // It is initialized once enough information is available from the handshake process. handshake_cipher: Option, + // Optional static key used in the handshake. This key may be derived from the pre-shared key + // (PSK) or generated during the handshake. k: Option<[u8; 32]>, + // Current nonce used in the encryption process. + // + // Ensures that the same plaintext encrypted twice will produce different ciphertexts. n: u64, - // Chaining key + // Chaining key used in the key derivation process to generate new keys throughout the + // handshake. ck: [u8; 32], - // Handshake hash + // Handshake hash which accumulates all handshake messages to ensure integrity and prevent + // tampering. h: [u8; 32], - // ephemeral keypair + // Ephemeral key pair generated by the responder for this session, used for generating the + // shared secret with the initiator. e: Keypair, - // Static pub keypair + // Static key pair of the responder, used to establish long-term identity and authenticity. + // + // Remains consistent across handshakes. s: Keypair, - // Authority pub keypair + // Authority key pair, representing the responder's authority credentials. + // + // Used to sign messages and verify the identity of the responder. a: Keypair, + // First [`CipherState`] used for encrypting messages from the initiator to the responder + // after the handshake is complete. c1: Option, + // Second [`CipherState`] used for encrypting messages from the responder to the initiator + // after the handshake is complete. c2: Option, + // Validity duration of the responder's certificate, in seconds. cert_validity: u32, } @@ -42,19 +103,27 @@ impl std::fmt::Debug for Responder { } } -// Make sure that Respoder is not sync so we do not need to worry about what other memory accessor see -// after that we zeroize k is send cause if we send it the original thread can not access -// anymore it -//impl !Sync for Responder {} -//impl !Copy for Responder {} +// Ensures that the `Cipher` type is not `Sync`, which prevents multiple threads from +// simultaneously accessing the same instance of `Cipher`. This eliminates the need to handle +// potential issues related to visibility of changes across threads. +// +// After sending the `k` value, we immediately clear it to prevent the original thread from +// accessing the value again, thereby enhancing security by ensuring the sensitive data is no +// longer available in memory. +// +// The `Cipher` struct is neither `Sync` nor `Copy` due to its `cipher` field, which implements +// the `AeadCipher` trait. This trait requires mutable access, making the entire struct non-`Sync` +// and non-`Copy`, even though the key and nonce are simple types. impl CipherState for Responder { fn get_k(&mut self) -> &mut Option<[u8; 32]> { &mut self.k } + fn get_n(&self) -> u64 { self.n } + fn set_n(&mut self, n: u64) { self.n = n; } @@ -62,6 +131,7 @@ impl CipherState for Responder { fn set_k(&mut self, k: Option<[u8; 32]>) { self.k = k; } + fn get_cipher(&mut self) -> &mut Option { &mut self.handshake_cipher } @@ -71,6 +141,7 @@ impl HandshakeOp for Responder { fn name(&self) -> String { "Responder".to_string() } + fn get_h(&mut self) -> &mut [u8; 32] { &mut self.h } @@ -93,22 +164,12 @@ impl HandshakeOp for Responder { } impl Responder { - pub fn from_authority_kp( - public: &[u8; 32], - private: &[u8; 32], - cert_validity: Duration, - ) -> Result, Error> { - let secp = Secp256k1::new(); - let secret = SecretKey::from_slice(private).map_err(|_| Error::InvalidRawPrivateKey)?; - let kp = Keypair::from_secret_key(&secp, &secret); - let pub_ = kp.x_only_public_key().0.serialize(); - if public == &pub_[..] { - Ok(Self::new(kp, cert_validity.as_secs() as u32)) - } else { - Err(Error::InvalidRawPublicKey) - } - } - + /// Creates a new [`Responder`] instance with the provided authority keypair and certificate + /// validity. + /// + /// Constructs a new [`Responder`] with the necessary cryptographic state for the Noise NX protocol + /// handshake. It generates ephemeral and static key pairs for the responder and prepares the + /// handshake state. The authority keypair and certificate validity period are also configured. pub fn new(a: Keypair, cert_validity: u32) -> Box { let mut self_ = Self { handshake_cipher: None, @@ -127,40 +188,43 @@ impl Responder { Box::new(self_) } - /// #### 4.5.1.2 Responder - /// - /// 1. receives ephemeral public key message with ElligatorSwift encoding (64 bytes plaintext) - /// 2. parses these 64 byte as PubKey and interprets is as `re.public_key` - /// 3. calls `MixHash(re.public_key)` - /// 4. calls `DecryptAndHash()` on remaining bytes (i.e. on empty data with empty _k_, thus effectively only calls `MixHash()` on empty data) + /// Creates a new [`Responder`] instance with the provided 32-byte authority key pair. /// - /// #### 4.5.2.1 Responder - /// - /// 1. initializes empty output buffer - /// 2. generates ephemeral keypair `e`, appends the 64 bytes ElligatorSwift encoding of `e.public_key` to the buffer - /// 3. calls `MixHash(e.public_key)` - /// 4. calls `MixKey(ECDH(e.private_key, re.public_key))` - /// 5. appends `EncryptAndHash(s.public_key)` (80 bytes: 64 bytes encrypted elliswift public key, 16 bytes MAC) - /// 6. calls `MixKey(ECDH(s.private_key, re.public_key))` - /// 7. appends `EncryptAndHash(SIGNATURE_NOISE_MESSAGE)` (74 + 16 bytes) to the buffer - /// 8. submits the buffer for sending to the initiator - /// 9. return pair of CipherState objects, the first for encrypting transport messages from initiator to responder, and the second for messages in the other direction: - /// 1. sets `temp_k1, temp_k2 = HKDF(ck, zerolen, 2)` - /// 2. creates two new CipherState objects `c1` and `c2` - /// 3. calls `c1.InitializeKey(temp_k1)` and `c2.InitializeKey(temp_k2)` - /// 4. returns the pair `(c1, c2)` + /// Constructs a new [`Responder`] with a given public and private key pair, which represents + /// the responder's authority credentials. It verifies that the provided public key matches the + /// corresponding private key, ensuring the authenticity of the authority key pair. The + /// certificate validity duration is also set here. Fails if the key pair is mismatched. + pub fn from_authority_kp( + public: &[u8; 32], + private: &[u8; 32], + cert_validity: Duration, + ) -> Result, Error> { + let secp = Secp256k1::new(); + let secret = SecretKey::from_slice(private).map_err(|_| Error::InvalidRawPrivateKey)?; + let kp = Keypair::from_secret_key(&secp, &secret); + let pub_ = kp.x_only_public_key().0.serialize(); + if public == &pub_[..] { + Ok(Self::new(kp, cert_validity.as_secs() as u32)) + } else { + Err(Error::InvalidRawPublicKey) + } + } + + /// Processes the first step of the Noise NX protocol handshake for the responder. /// - /// ##### Message format of NX-handshake part 2 + /// This function manages the responder's side of the handshake after receiving the initiator's + /// initial message. It processes the ephemeral public key provided by the initiator, derives + /// the necessary shared secrets, and constructs the response message. The response includes + /// the responder's ephemeral public key (in its ElligatorSwift-encoded form), the encrypted + /// static public key, and a signature noise message. Additionally, it establishes the session + /// ciphers for encrypting and decrypting further communication. /// - /// | Field name | Description | - /// | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | - /// | PUBKEY | Responder's plaintext ephemeral public key | - /// | PUBKEY | Responder's encrypted static public key | - /// | MAC | Message authentication code for responder's static public key | - /// | SIGNATURE_NOISE_MESSAGE | Signed message containing Responder's static key. Signature is issued by authority that is generally known to operate the server acting as the noise responder | - /// | MAC | Message authentication code for SIGNATURE_NOISE_MESSAGE | + /// On success, it returns a tuple containing the response message to be sent back to the + /// initiator and a [`NoiseCodec`] instance, which is configured with the session ciphers for + /// secure transmission of subsequent messages. /// - /// Message length: 234 bytes + /// On failure, the method returns an error if there is an issue during encryption, decryption, + /// or any other step of the handshake process. pub fn step_1( &mut self, elligatorswift_theirs_ephemeral_serialized: [u8; ELLSWIFT_ENCODING_SIZE], @@ -258,6 +322,12 @@ impl Responder { Ok((to_send, codec)) } + // Generates a signature noise message for the responder's certificate. + // + // This method creates a signature noise message that includes the protocol version, + // certificate validity period, and a cryptographic signature. The signature is created using + // the responder's static public key and authority keypair, ensuring that the responder's + // identity and certificate validity are cryptographically verifiable. fn get_signature(&self, version: u16, valid_from: u32, not_valid_after: u32) -> [u8; 74] { let mut ret = [0; 74]; let version = version.to_le_bytes(); @@ -277,6 +347,12 @@ impl Responder { ret } + // Securely erases sensitive data in the responder's memory. + // + // Clears all sensitive cryptographic material within the [`Responder`] to prevent any + // accidental leakage or misuse. It overwrites the stored keys, chaining key, handshake hash, + // and session ciphers with zeros. This function is typically + // called when the [`Responder`] instance is no longer needed or before deallocation. fn erase(&mut self) { if let Some(k) = self.k.as_mut() { for b in k { @@ -302,6 +378,8 @@ impl Responder { } impl Drop for Responder { + /// Ensures that sensitive data is securely erased when the [`Responder`] instance is dropped, + /// preventing any potential leakage of cryptographic material. fn drop(&mut self) { self.erase(); } diff --git a/protocols/v2/noise-sv2/src/signature_message.rs b/protocols/v2/noise-sv2/src/signature_message.rs index 827199bed9..df997f7ad7 100644 --- a/protocols/v2/noise-sv2/src/signature_message.rs +++ b/protocols/v2/noise-sv2/src/signature_message.rs @@ -1,14 +1,53 @@ +// # Signature-Based Message Handling +// +// Defines the [`SignatureNoiseMessage`] struct, which represents a signed message used in the +// Noise protocol to authenticate and verify the identity of a party during the handshake. +// +// This module provides utilities for creating, signing, and verifying Noise protocol messages +// using Schnorr signatures over the [`secp256k1`] elliptic curve. It encapsulates signed messages +// along with versioning and validity timestamps. The following capabilities are supported: +// +// - Conversion of raw byte arrays into structured [`SignatureNoiseMessage`] instances. +// - Message signing using Schnorr signatures and the [`secp256k1`] curve. +// - Verification of signed messages, ensuring they fall within valid time periods and are signed +// by an authorized public key. +// +// ## Usage +// +// The [`SignatureNoiseMessage`] is used by both the [`crate::Responder`] and [`crate::Initiator`] +// roles. The [`crate::Responder`] uses the `sign` method to generate a Schnorr signature over the +// initial message sent by the initiator. The [`crate::Initiator`] uses the `verify` method to +// check the validity of the signed message from the responder, comparing it against the provided +// public key and optional authority key, while ensuring the message falls within the specified +// validity period. + use secp256k1::{hashes::sha256, schnorr::Signature, Keypair, Message, Secp256k1, XOnlyPublicKey}; use std::{convert::TryInto, time::SystemTime}; +/// `SignatureNoiseMessage` represents a signed message used in the Noise NX protocol +/// for authentication during the handshake process. It encapsulates the necessary +/// details for signature verification, including protocol versioning, validity periods, +/// and a Schnorr signature over the message. +/// +/// This structure ensures that messages are authenticated and valid only within +/// a specified time window, using Schnorr signatures over the `secp256k1` elliptic curve. pub struct SignatureNoiseMessage { + // Version of the protocol being used. pub version: u16, + // Start of the validity period for the message, expressed as a Unix timestamp. pub valid_from: u32, + // End of the validity period for the message, expressed as a Unix timestamp. pub not_valid_after: u32, + // 64-byte Schnorr signature that authenticates the message. pub signature: [u8; 64], } impl From<[u8; 74]> for SignatureNoiseMessage { + // Converts a 74-byte array into a [`SignatureNoiseMessage`]. + // + // Allows a raw 74-byte array to be converted into a [`SignatureNoiseMessage`], extracting the + // version, validity periods, and signature from the provided data. Panics if the byte array + // cannot be correctly converted into the struct fields. fn from(value: [u8; 74]) -> Self { let version = u16::from_le_bytes(value[0..2].try_into().unwrap()); let valid_from = u32::from_le_bytes(value[2..6].try_into().unwrap()); @@ -24,6 +63,13 @@ impl From<[u8; 74]> for SignatureNoiseMessage { } impl SignatureNoiseMessage { + // Verifies the [`SignatureNoiseMessage`] against the provided public key and an optional + // authority public key. The verification checks that the message is currently valid + // (i.e., within the `valid_from` and `not_valid_after` time window) and that the signature + // is correctly signed by the authority. + // + // If an authority public key is not provided, the function assumes that the signature + // is already valid without further verification. pub fn verify(self, pk: &XOnlyPublicKey, authority_pk: &Option) -> bool { if let Some(authority_pk) = authority_pk { let now = SystemTime::now() @@ -48,6 +94,12 @@ impl SignatureNoiseMessage { true } } + + // Signs a [`SignatureNoiseMessage`] using the provided keypair (`kp`). + // + // Creates a Schnorr signature for the message, combining the version, validity period, and + // the static public key of the server (`static_pk`). The resulting signature is then written + // into the provided message buffer (`msg`). pub fn sign(msg: &mut [u8; 74], static_pk: &XOnlyPublicKey, kp: &Keypair) { let secp = Secp256k1::signing_only(); let m = [&msg[0..10], &static_pk.serialize()].concat(); @@ -58,6 +110,12 @@ impl SignatureNoiseMessage { } } + // Splits the [`SignatureNoiseMessage`] into its component parts: the message hash and the + // signature. + // + // Separates the message into the first 10 bytes (containing the version and validity period) + // and the 64-byte Schnorr signature, returning them in a tuple. Used internally during the + // verification process. fn split(self) -> ([u8; 10], [u8; 64]) { let mut m = [0; 10]; m[0] = self.version.to_le_bytes()[0]; diff --git a/protocols/v2/roles-logic-sv2/Cargo.toml b/protocols/v2/roles-logic-sv2/Cargo.toml index 2b7c34610b..f1b73e7e20 100644 --- a/protocols/v2/roles-logic-sv2/Cargo.toml +++ b/protocols/v2/roles-logic-sv2/Cargo.toml @@ -1,10 +1,15 @@ [package] name = "roles_logic_sv2" version = "1.2.1" +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Common handlers for use within SV2 roles" +documentation = "https://docs.rs/roles_logic_sv2" license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -39,3 +44,6 @@ with_serde = [ "serde", prop_test = ["template_distribution_sv2/prop_test"] # Code coverage tools may conflict with the nopanic logic, so we can disable it when needed disable_nopanic = [] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/roles-logic-sv2/src/parsers.rs b/protocols/v2/roles-logic-sv2/src/parsers.rs index 0274ce785c..d30ca6eced 100644 --- a/protocols/v2/roles-logic-sv2/src/parsers.rs +++ b/protocols/v2/roles-logic-sv2/src/parsers.rs @@ -88,7 +88,7 @@ use tracing::error; pub type AnyMessage<'a> = PoolMessages<'a>; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] #[cfg_attr(feature = "with_serde", derive(Serialize, Deserialize))] pub enum CommonMessages<'a> { ChannelEndpointChanged(ChannelEndpointChanged), diff --git a/protocols/v2/subprotocols/common-messages/Cargo.toml b/protocols/v2/subprotocols/common-messages/Cargo.toml index 783c208dfe..e2343d0de3 100644 --- a/protocols/v2/subprotocols/common-messages/Cargo.toml +++ b/protocols/v2/subprotocols/common-messages/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "common_messages_sv2" version = "2.0.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Sv2 subprotocol common messages" -license = "MIT" +documentation = "https://docs.rs/common_messages_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -21,3 +25,6 @@ serde_repr = {version= "0.1.10", optional=true} no_std = [] with_serde = ["binary_sv2/with_serde", "serde", "serde_repr"] prop_test = ["quickcheck"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/subprotocols/job-declaration/Cargo.toml b/protocols/v2/subprotocols/job-declaration/Cargo.toml index 2ee4313f4c..951131fb1e 100644 --- a/protocols/v2/subprotocols/job-declaration/Cargo.toml +++ b/protocols/v2/subprotocols/job-declaration/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "job_declaration_sv2" version = "1.0.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "SV2 job declaration protocol types" +documentation = "https://docs.rs/job_declaration_sv2" license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] @@ -16,3 +20,6 @@ const_sv2 = {version = "^2.0.0", path = "../../../../protocols/v2/const-sv2"} [features] no_std = [] with_serde = ["binary_sv2/with_serde", "serde"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/subprotocols/mining/Cargo.toml b/protocols/v2/subprotocols/mining/Cargo.toml index c98bb7ec99..020ab39ec9 100644 --- a/protocols/v2/subprotocols/mining/Cargo.toml +++ b/protocols/v2/subprotocols/mining/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "mining_sv2" version = "1.0.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "SV2 mining protocol types" +documentation = "https://docs.rs/mining_sv2" license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -22,3 +26,6 @@ quickcheck_macros = "1" [features] no_std = [] with_serde = ["binary_sv2/with_serde", "serde"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/subprotocols/template-distribution/Cargo.toml b/protocols/v2/subprotocols/template-distribution/Cargo.toml index b0120836dc..20da7f8179 100644 --- a/protocols/v2/subprotocols/template-distribution/Cargo.toml +++ b/protocols/v2/subprotocols/template-distribution/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "template_distribution_sv2" version = "1.0.2" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "Sv2 template distribution subprotocol" -license = "MIT" +documentation = "https://docs.rs/template_distribution_sv2" +license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -20,3 +24,6 @@ quickcheck_macros = { version = "1", optional=true } no_std = [] with_serde = ["binary_sv2/with_serde", "serde"] prop_test = ["quickcheck"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/sv2-ffi/Cargo.toml b/protocols/v2/sv2-ffi/Cargo.toml index 6498b1b5b8..0aa8c45fda 100644 --- a/protocols/v2/sv2-ffi/Cargo.toml +++ b/protocols/v2/sv2-ffi/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "sv2_ffi" version = "1.0.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" +readme = "README.md" description = "SV2 FFI" +documentation = "https://github.com/stratum-mining/stratum" license = "MIT OR Apache-2.0" repository = "https://github.com/stratum-mining/stratum" +homepage = "https://stratumprotocol.org" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [lib] crate-type = ["staticlib"] @@ -25,3 +29,5 @@ quickcheck_macros = "1" with_serde = [] prop_test = ["binary_sv2/prop_test", "common_messages_sv2/prop_test", "template_distribution_sv2/prop_test"] +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/protocols/v2/sv2-ffi/sv2.h b/protocols/v2/sv2-ffi/sv2.h index 23fbc98076..534c12e6e5 100644 --- a/protocols/v2/sv2-ffi/sv2.h +++ b/protocols/v2/sv2-ffi/sv2.h @@ -451,24 +451,32 @@ void free_submit_solution(CSubmitSolution s); #include #include +/// C-compatible enumeration of possible errors in the `codec_sv2` module. +/// +/// This enum mirrors the [`Error`] enum but is designed to be used in C code through FFI. It +/// represents the same set of errors as [`Error`], making them accessible to C programs. struct CError { enum class Tag { - /// Errors from the `binary_sv2` crate + /// AEAD (`snow`) error in the Noise protocol. + AeadError, + /// Binary Sv2 data format error. BinarySv2Error, - /// Errors from the `framing_sv2` crate + /// Framing Sv2 error. + FramingError, + /// Framing Sv2 error. FramingSv2Error, - /// Errors if there are missing bytes in the Noise protocol + /// Invalid step for initiator in the Noise protocol. + InvalidStepForInitiator, + /// Invalid step for responder in the Noise protocol. + InvalidStepForResponder, + /// Missing bytes in the Noise protocol. MissingBytes, - /// Errors from the `noise_sv2` crate + /// Sv2 Noise protocol error. NoiseSv2Error, - /// `snow` errors - AeadError, - /// Error if Noise protocol state is not as expected - UnexpectedNoiseState, - InvalidStepForResponder, - InvalidStepForInitiator, + /// Noise protocol is not in the expected handshake state. NotInHandShakeState, - FramingError, + /// Unexpected state in the Noise protocol. + UnexpectedNoiseState, }; struct MissingBytes_Body { @@ -483,7 +491,11 @@ struct CError { extern "C" { -/// Here only to force cbindgen to create header for CError +/// Force `cbindgen` to create a header for [`CError`]. +/// +/// It ensures that [`CError`] is included in the generated C header file. This function is not meant +/// to be called and will panic if called. Its only purpose is to make [`CError`] visible to +/// `cbindgen`. CError export_cerror(); } // extern "C" diff --git a/roles/Cargo.lock b/roles/Cargo.lock index 721c272a35..f3b8f47d39 100644 --- a/roles/Cargo.lock +++ b/roles/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -69,20 +63,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", @@ -95,33 +80,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -129,9 +114,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arrayvec" @@ -162,22 +147,21 @@ dependencies = [ [[package]] name = "async-channel" -version = "2.2.1" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" dependencies = [ "concurrent-queue", - "event-listener 5.3.0", - "event-listener-strategy 0.5.2", + "event-listener-strategy", "futures-core", "pin-project-lite", ] [[package]] name = "async-compat" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f68a707c1feb095d8c07f8a65b9f506b117d30af431cab89374357de7c11461b" +checksum = "7bab94bde396a3f7b4962e396fdad640e241ed797d4d8d77fc8c237d14c58fc0" dependencies = [ "futures-core", "futures-io", @@ -188,14 +172,14 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.11.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10202063978b3351199d68f8b22c4e47e4b1b822f8d43fd862d5ea8c006b29a" +checksum = "30ca9a001c1e8ba5149f91a74362376cc6bc5b919d92d988668657bd570bdcec" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.1.0", - "futures-lite 2.3.0", + "fastrand", + "futures-lite", "slab", ] @@ -205,71 +189,42 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ - "async-channel 2.2.1", + "async-channel 2.3.1", "async-executor", - "async-io 2.3.2", - "async-lock 3.3.0", + "async-io", + "async-lock", "blocking", - "futures-lite 2.3.0", + "futures-lite", "once_cell", ] [[package]] name = "async-io" -version = "1.13.0" +version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.27", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcccb0f599cfa2f8ace422d3555572f47424da5648a4382a9dd0310ff8210884" -dependencies = [ - "async-lock 3.3.0", + "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "parking", - "polling 3.7.0", - "rustix 0.38.34", + "polling", + "rustix", "slab", "tracing", - "windows-sys 0.52.0", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", + "windows-sys 0.59.0", ] [[package]] name = "async-lock" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 4.0.3", - "event-listener-strategy 0.4.0", + "event-listener 5.3.1", + "event-listener-strategy", "pin-project-lite", ] @@ -292,25 +247,25 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] name = "async-std" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "c634475f29802fde2b8f0b505b1bd00dfe4df7d4a000f0b36f7671197d5c3615" dependencies = [ "async-attributes", "async-channel 1.9.0", "async-global-executor", - "async-io 1.13.0", - "async-lock 2.8.0", + "async-io", + "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 1.13.0", + "futures-lite", "gloo-timers", "kv-log-macro", "log", @@ -330,13 +285,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] @@ -345,36 +300,25 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.2", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets", ] [[package]] @@ -442,9 +386,9 @@ dependencies = [ [[package]] name = "bitcoin" -version = "0.32.2" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea507acc1cd80fc084ace38544bbcf7ced7c2aa65b653b102de0ce718df668f6" +checksum = "0032b0e8ead7074cda7fc4f034409607e3f03a6f71d66ade8a307f79b4d99e73" dependencies = [ "base58ck", "bech32 0.11.0", @@ -454,7 +398,7 @@ dependencies = [ "bitcoin_hashes 0.14.0", "hex-conservative 0.2.1", "hex_lit", - "secp256k1 0.29.0", + "secp256k1 0.29.1", "serde", ] @@ -511,7 +455,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" dependencies = [ "bitcoin-internals 0.2.0", - "hex-conservative 0.1.1", + "hex-conservative 0.1.2", ] [[package]] @@ -544,7 +488,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a" dependencies = [ - "bitcoin 0.32.2", + "bitcoin 0.32.3", "serde", "serde_json", ] @@ -564,15 +508,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" dependencies = [ "serde", ] @@ -597,15 +535,14 @@ dependencies = [ [[package]] name = "blocking" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495f7104e962b7356f0aeb34247aca1fe7d2e783b346582db7f2904cb5717e88" +checksum = "703f41c54fc768e63e091340b424302bb1c29ef4aa0c7f10fe849dfb114d29ea" dependencies = [ - "async-channel 2.2.1", - "async-lock 3.3.0", + "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite", "piper", ] @@ -640,15 +577,18 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.0.97" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -693,9 +633,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" dependencies = [ "clap_builder", "clap_derive", @@ -703,9 +643,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" dependencies = [ "anstream", "anstyle", @@ -715,25 +655,25 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "codec_sv2" -version = "1.2.1" +version = "1.3.0" dependencies = [ "binary_sv2", "buffer_sv2", @@ -746,9 +686,9 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "common_messages_sv2" @@ -809,7 +749,7 @@ dependencies = [ [[package]] name = "const_sv2" -version = "2.0.0" +version = "2.1.0" dependencies = [ "secp256k1 0.28.2", ] @@ -825,9 +765,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -843,9 +783,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -914,19 +854,6 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -935,9 +862,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -955,81 +882,51 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", "pin-project-lite", ] -[[package]] -name = "event-listener-strategy" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" -dependencies = [ - "event-listener 4.0.3", - "pin-project-lite", -] - [[package]] name = "event-listener-strategy" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" dependencies = [ - "event-listener 5.3.0", + "event-listener 5.3.1", "pin-project-lite", ] [[package]] name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - -[[package]] -name = "fastrand" -version = "2.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", + "libredox", + "windows-sys 0.59.0", ] [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -1096,28 +993,13 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" dependencies = [ - "fastrand 2.1.0", + "fastrand", "futures-core", "futures-io", "parking", @@ -1132,7 +1014,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] @@ -1198,15 +1080,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "gloo-timers" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" dependencies = [ "futures-channel", "futures-core", @@ -1216,15 +1098,15 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http", "indexmap", "slab", @@ -1251,9 +1133,9 @@ checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -1263,18 +1145,15 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" [[package]] name = "hex" @@ -1284,9 +1163,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hex-conservative" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed443af458ccb6d81c1e7e661545f94d3176752fb1df2f543b902a1e0f51e2" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" [[package]] name = "hex-conservative" @@ -1325,9 +1204,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -1335,12 +1214,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http", "http-body", "pin-project-lite", @@ -1348,9 +1227,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -1358,20 +1237,11 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error", -] - [[package]] name = "hyper" -version = "1.3.1" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", @@ -1390,9 +1260,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ "bytes", "futures-channel", @@ -1401,21 +1271,20 @@ dependencies = [ "http-body", "hyper", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -1427,41 +1296,32 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "integration-test" version = "0.1.0" dependencies = [ + "async-channel 1.9.0", + "binary_sv2", "bitcoind", + "codec_sv2", + "const_sv2", "flate2", + "key-utils", "minreq", + "network_helpers_sv2", + "once_cell", + "pool_sv2", + "roles_logic_sv2", "tar", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", + "tokio", + "tracing", ] [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" @@ -1531,9 +1391,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" dependencies = [ "wasm-bindgen", ] @@ -1581,33 +1441,38 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.154" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] -name = "linked-hash-map" -version = "0.5.6" +name = "libredox" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] [[package]] -name = "linux-raw-sys" -version = "0.3.8" +name = "linked-hash-map" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" @@ -1621,18 +1486,18 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" dependencies = [ "value-bag", ] [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" @@ -1695,15 +1560,6 @@ dependencies = [ "const_sv2", ] -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1730,13 +1586,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi 0.3.9", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1762,7 +1619,7 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "noise_sv2" -version = "1.1.0" +version = "1.2.0" dependencies = [ "aes-gcm", "chacha20poly1305", @@ -1794,9 +1651,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -1820,30 +1677,23 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "object" -version = "0.32.2" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] [[package]] name = "opaque-debug" @@ -1869,15 +1719,15 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1891,9 +1741,9 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.1", + "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-targets", ] [[package]] @@ -1904,9 +1754,9 @@ checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -1915,9 +1765,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -1925,48 +1775,28 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", "sha2 0.10.8", ] -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.61", -] - [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1981,44 +1811,28 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" dependencies = [ "atomic-waker", - "fastrand 2.1.0", + "fastrand", "futures-io", ] [[package]] name = "polling" -version = "2.8.0" +version = "3.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645493cf344456ef24219d02a768cf1fb92ddf8c92161679ae3d91b91a637be3" +checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.3.9", + "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.34", + "rustix", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2071,41 +1885,34 @@ dependencies = [ ] [[package]] -name = "ppv-lite86" -version = "0.2.17" +name = "portable-atomic" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] -name = "pretty_env_logger" -version = "0.4.0" +name = "ppv-lite86" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "env_logger", - "log", + "zerocopy", ] [[package]] name = "proc-macro2" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -2142,51 +1949,13 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" -dependencies = [ - "bitflags 2.5.0", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "bitflags", ] -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - [[package]] name = "ring" version = "0.17.8" @@ -2227,7 +1996,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.5.0", + "bitflags", "serde", "serde_derive", ] @@ -2264,28 +2033,14 @@ checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustix" -version = "0.37.27" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ - "bitflags 1.3.2", + "bitflags", "errno", - "io-lifetimes", "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys 0.4.13", + "linux-raw-sys", "windows-sys 0.52.0", ] @@ -2356,13 +2111,13 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes 0.13.0", + "bitcoin_hashes 0.14.0", "rand", - "secp256k1-sys 0.10.0", + "secp256k1-sys 0.10.1", "serde", ] @@ -2386,49 +2141,50 @@ dependencies = [ [[package]] name = "secp256k1-sys" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1433bd67156263443f14d603720b082dd3121779323fce20cba2aa07b874bc1b" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] [[package]] name = "serde" -version = "1.0.200" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.200" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -2474,6 +2230,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2504,16 +2266,6 @@ version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.5.7" @@ -2546,9 +2298,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sv1-mining-device" @@ -2573,8 +2325,6 @@ dependencies = [ "bitcoin_hashes 0.3.2", "byteorder", "hex", - "log", - "pretty_env_logger", "serde", "serde_json", "tracing", @@ -2593,9 +2343,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.61" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -2604,9 +2354,9 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb797dad5fb5b76fcf519e702f4a589483b5ef06567f160c392832c1f5e44909" +checksum = "4ff6c40d3aedb5e06b57c6f669ad17ab063dd1e63d977c6a88e7f4dfa4f04020" dependencies = [ "filetime", "libc", @@ -2615,15 +2365,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.11.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", - "fastrand 2.1.0", + "fastrand", "once_cell", - "rustix 0.38.34", - "windows-sys 0.52.0", + "rustix", + "windows-sys 0.59.0", ] [[package]] @@ -2634,33 +2384,24 @@ dependencies = [ "const_sv2", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] @@ -2684,39 +2425,39 @@ dependencies = [ [[package]] name = "tokio" -version = "1.38.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2", "tokio-macros", - "windows-sys 0.48.0", + "tracing", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2748,9 +2489,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -2759,33 +2500,11 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2793,7 +2512,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2807,7 +2525,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", ] [[package]] @@ -2890,21 +2608,21 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "universal-hash" @@ -2924,9 +2642,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" @@ -2942,15 +2660,9 @@ checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" [[package]] name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "waker-fn" -version = "1.1.1" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c4517f54858c779bbcbf228f4fca63d121bf85fbecb2dc578cdf4a39395690" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "want" @@ -2969,34 +2681,35 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" dependencies = [ "cfg-if", "js-sys", @@ -3006,9 +2719,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3016,28 +2729,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.61", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" dependencies = [ "js-sys", "wasm-bindgen", @@ -3058,7 +2771,7 @@ dependencies = [ "either", "home", "once_cell", - "rustix 0.38.34", + "rustix", ] [[package]] @@ -3077,165 +2790,99 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" -dependencies = [ - "windows-sys 0.52.0", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.18" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -3247,8 +2894,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" dependencies = [ "libc", - "linux-raw-sys 0.4.13", - "rustix 0.38.34", + "linux-raw-sys", + "rustix", ] [[package]] @@ -3260,8 +2907,29 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/roles/Cargo.toml b/roles/Cargo.toml index d44909a939..3e9c59fc7a 100644 --- a/roles/Cargo.toml +++ b/roles/Cargo.toml @@ -1,16 +1,3 @@ -name = "stratum_v2_roles" -version = "0.1.0" -authors = ["The Stratum v2 Developers"] -edition = "2021" -description = "The Stratum protocol defines how miners, proxies, and pools communicate to contribute hashrate to the Bitcoin network. Stratum v2 is a robust set of primitives which anyone can use to expand the protocol or implement a role." -documentation = "https://github.com/stratum-mining/stratum" -readme = "README.md" -homepage = "https://stratumprotocol.org" -repository = "https://github.com/stratum-mining/stratum" -license = "MIT + Apache-2.0" -license-file = "LICENSE.md" -keywords = ["stratum", "mining", "bitcoin", "protocol"] - [workspace] resolver="2" diff --git a/roles/jd-client/Cargo.toml b/roles/jd-client/Cargo.toml index dc2d46079d..1cb07ed7c7 100644 --- a/roles/jd-client/Cargo.toml +++ b/roles/jd-client/Cargo.toml @@ -1,10 +1,15 @@ [package] name = "jd_client" version = "0.1.1" +authors = ["The Stratum V2 Developers"] edition = "2021" -description = "TODO" -license = "MIT OR Apache-2.0" +description = "Job Declarator Client (JDC) role" +documentation = "https://docs.rs/jd_client" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [lib] name = "jd_client" diff --git a/roles/jd-client/src/lib/downstream.rs b/roles/jd-client/src/lib/downstream.rs index 92e5a874a7..82a8076022 100644 --- a/roles/jd-client/src/lib/downstream.rs +++ b/roles/jd-client/src/lib/downstream.rs @@ -39,6 +39,7 @@ pub struct DownstreamMiningNode { receiver: Receiver, sender: Sender, pub status: DownstreamMiningNodeStatus, + #[allow(dead_code)] pub prev_job_id: Option, solution_sender: Sender>, withhold: bool, diff --git a/roles/jd-client/src/lib/error.rs b/roles/jd-client/src/lib/error.rs index c3e24bd4d3..f29decb3d6 100644 --- a/roles/jd-client/src/lib/error.rs +++ b/roles/jd-client/src/lib/error.rs @@ -6,6 +6,7 @@ use stratum_common::bitcoin::util::uint::ParseLengthError; pub type ProxyResult<'a, T> = core::result::Result>; +#[allow(dead_code)] #[derive(Debug)] pub enum ChannelSendError<'a> { SubmitSharesExtended( @@ -28,6 +29,7 @@ pub enum ChannelSendError<'a> { #[derive(Debug)] pub enum Error<'a> { + #[allow(dead_code)] VecToSlice32(Vec), /// Errors on bad CLI argument input. BadCliArgs, diff --git a/roles/jd-client/src/lib/proxy_config.rs b/roles/jd-client/src/lib/proxy_config.rs index 89c97a7beb..af03047d8a 100644 --- a/roles/jd-client/src/lib/proxy_config.rs +++ b/roles/jd-client/src/lib/proxy_config.rs @@ -37,6 +37,7 @@ pub struct ProxyConfig { pub cert_validity_sec: u64, pub tp_address: String, pub tp_authority_public_key: Option, + #[allow(dead_code)] pub retry: u32, pub upstreams: Vec, #[serde(deserialize_with = "duration_from_toml")] diff --git a/roles/jd-client/src/lib/upstream_sv2/mod.rs b/roles/jd-client/src/lib/upstream_sv2/mod.rs index 4209e686ae..70adb2dc9d 100644 --- a/roles/jd-client/src/lib/upstream_sv2/mod.rs +++ b/roles/jd-client/src/lib/upstream_sv2/mod.rs @@ -7,11 +7,3 @@ pub use upstream::Upstream; pub type Message = PoolMessages<'static>; pub type StdFrame = StandardSv2Frame; pub type EitherFrame = StandardEitherFrame; - -#[derive(Clone, Copy, Debug)] -pub struct Sv2MiningConnection { - _version: u16, - _setup_connection_flags: u32, - #[allow(dead_code)] - setup_connection_success_flags: u32, -} diff --git a/roles/jd-client/src/lib/upstream_sv2/upstream.rs b/roles/jd-client/src/lib/upstream_sv2/upstream.rs index b04efa3359..7a4b186787 100644 --- a/roles/jd-client/src/lib/upstream_sv2/upstream.rs +++ b/roles/jd-client/src/lib/upstream_sv2/upstream.rs @@ -110,7 +110,9 @@ pub struct Upstream { tx_status: status::Sender, /// Minimum `extranonce2` size. Initially requested in the `jdc-config.toml`, and ultimately /// set by the SV2 Upstream via the SV2 `OpenExtendedMiningChannelSuccess` message. + #[allow(dead_code)] pub min_extranonce_size: u16, + #[allow(dead_code)] pub upstream_extranonce1_size: usize, /// String be included in coinbase tx input scriptsig pub pool_signature: String, @@ -144,7 +146,7 @@ impl Upstream { /// `UpstreamConnection` with a channel to send and receive messages from the SV2 Upstream /// role and uses channels provided in the function arguments to send and receive messages /// from the `Downstream`. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] + #[allow(clippy::too_many_arguments)] pub async fn new( address: SocketAddr, authority_public_key: Secp256k1PublicKey, diff --git a/roles/jd-server/Cargo.toml b/roles/jd-server/Cargo.toml index e8d82438a1..9e5ad8986a 100644 --- a/roles/jd-server/Cargo.toml +++ b/roles/jd-server/Cargo.toml @@ -1,9 +1,16 @@ [package] name = "jd_server" version = "0.1.1" +authors = ["The Stratum V2 Developers"] edition = "2018" -license = "MIT OR Apache-2.0" +description = "Job Declarator Server (JDS) role" +documentation = "https://github.com/stratum-mining/stratum" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] + [lib] name = "jd_server" diff --git a/roles/mining-proxy/Cargo.toml b/roles/mining-proxy/Cargo.toml index 8ffb5d0953..175fca0392 100644 --- a/roles/mining-proxy/Cargo.toml +++ b/roles/mining-proxy/Cargo.toml @@ -1,11 +1,16 @@ [package] name = "mining_proxy_sv2" version = "0.1.1" -authors = ["user"] +authors = ["The Stratum V2 Developers"] edition = "2018" description = "SV2 mining proxy role" -license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/mining_proxy_sv2" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] + [lib] name = "mining_proxy_sv2" diff --git a/roles/mining-proxy/src/lib/error.rs b/roles/mining-proxy/src/lib/error.rs index b5c900bfd9..75ded3a67a 100644 --- a/roles/mining-proxy/src/lib/error.rs +++ b/roles/mining-proxy/src/lib/error.rs @@ -9,6 +9,7 @@ pub type EitherFrame = StandardEitherFrame; #[derive(Debug)] #[allow(clippy::large_enum_variant)] #[allow(clippy::enum_variant_names)] +#[allow(dead_code)] pub enum Error { SendError(SendError), UpstreamNotAvailabe(SocketAddr), diff --git a/roles/pool/Cargo.toml b/roles/pool/Cargo.toml index c253e6b3e9..95c21957bc 100644 --- a/roles/pool/Cargo.toml +++ b/roles/pool/Cargo.toml @@ -1,10 +1,16 @@ [package] name = "pool_sv2" version = "0.1.1" +authors = ["The Stratum V2 Developers"] edition = "2018" description = "SV2 pool role" -license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/pool_sv2" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] + [lib] name = "pool_sv2" diff --git a/roles/pool/src/lib/mod.rs b/roles/pool/src/lib/mod.rs index d7d483a5c1..2603695f88 100644 --- a/roles/pool/src/lib/mod.rs +++ b/roles/pool/src/lib/mod.rs @@ -5,12 +5,14 @@ pub mod template_receiver; use async_channel::{bounded, unbounded}; +use error::PoolError; use mining_pool::{get_coinbase_output, Configuration, Pool}; use template_receiver::TemplateRx; use tracing::{error, info, warn}; use tokio::select; +#[derive(Debug, Clone)] pub struct PoolSv2 { config: Configuration, } @@ -19,7 +21,8 @@ impl PoolSv2 { pub fn new(config: Configuration) -> PoolSv2 { PoolSv2 { config } } - pub async fn start(self) { + + pub async fn start(&self) -> Result<(), PoolError> { let config = self.config.clone(); let (status_tx, status_rx) = unbounded(); let (s_new_t, r_new_t) = bounded(10); @@ -27,15 +30,9 @@ impl PoolSv2 { let (s_solution, r_solution) = bounded(10); let (s_message_recv_signal, r_message_recv_signal) = bounded(10); let coinbase_output_result = get_coinbase_output(&config); - let coinbase_output_len = match coinbase_output_result { - Ok(coinbase_output) => coinbase_output.len() as u32, - Err(err) => { - error!("Failed to get Coinbase output: {:?}", err); - return; - } - }; + let coinbase_output_len = coinbase_output_result?.len() as u32; let tp_authority_public_key = config.tp_authority_public_key; - let template_rx_res = TemplateRx::connect( + TemplateRx::connect( config.tp_address.parse().unwrap(), s_new_t, s_prev_hash, @@ -45,13 +42,7 @@ impl PoolSv2 { coinbase_output_len, tp_authority_public_key, ) - .await; - - if let Err(e) = template_rx_res { - error!("Could not connect to Template Provider: {}", e); - return; - } - + .await?; let pool = Pool::start( config.clone(), r_new_t, @@ -76,7 +67,7 @@ impl PoolSv2 { // we also shut down in case of error }, } - break; + break Ok(()); } }; let task_status: status::Status = task_status.unwrap(); @@ -88,11 +79,11 @@ impl PoolSv2 { "SHUTDOWN from Downstream: {}\nTry to restart the downstream listener", err ); - break; + break Ok(()); } status::State::TemplateProviderShutdown(err) => { error!("SHUTDOWN from Upstream: {}\nTry to reconnecting or connecting to a new upstream", err); - break; + break Ok(()); } status::State::Healthy(msg) => { info!("HEALTHY message: {}", msg); @@ -103,7 +94,7 @@ impl PoolSv2 { .safe_lock(|p| p.remove_downstream(downstream_id)) .is_err() { - break; + break Ok(()); } } } diff --git a/roles/pool/src/main.rs b/roles/pool/src/main.rs index 5129684396..34f5983e59 100644 --- a/roles/pool/src/main.rs +++ b/roles/pool/src/main.rs @@ -99,5 +99,5 @@ async fn main() { return; } }; - PoolSv2::new(config).start().await; + let _ = PoolSv2::new(config).start().await; } diff --git a/roles/roles-utils/network-helpers/Cargo.toml b/roles/roles-utils/network-helpers/Cargo.toml index b4af8f293e..7c35fc38fd 100644 --- a/roles/roles-utils/network-helpers/Cargo.toml +++ b/roles/roles-utils/network-helpers/Cargo.toml @@ -1,11 +1,16 @@ [package] name = "network_helpers_sv2" version = "2.0.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" description = "Networking utils for SV2 roles" -license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/network_helpers_sv2" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -26,3 +31,6 @@ async_std = ["async-std", "async-channel", "binary_sv2", "codec_sv2"] with_tokio = ["tokio", "async-channel", "binary_sv2", "codec_sv2"] with_serde = ["binary_sv2/with_serde", "serde", "codec_sv2/with_serde"] with_buffer_pool = ["codec_sv2/with_buffer_pool"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/roles/roles-utils/rpc/Cargo.toml b/roles/roles-utils/rpc/Cargo.toml index 6ae6e05527..3a17ef02b9 100644 --- a/roles/roles-utils/rpc/Cargo.toml +++ b/roles/roles-utils/rpc/Cargo.toml @@ -1,9 +1,16 @@ [package] name = "rpc_sv2" version = "1.0.0" +authors = ["The Stratum V2 Developers"] edition = "2021" description = "SV2 JD Server RPC" +documentation = "https://docs.rs/rpc_sv2" +readme = "README.md" +homepage = "https://stratumprotocol.org" +repository = "https://github.com/stratum-mining/stratum" license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -16,3 +23,6 @@ base64 = "0.21.5" hyper = { version = "1.1.0", features = ["full"] } hyper-util = { version = "0.1", features = ["full"] } http-body-util = "0.1" + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/roles/tarpaulin.toml b/roles/tarpaulin.toml index 2714d685da..47e6ecba57 100644 --- a/roles/tarpaulin.toml +++ b/roles/tarpaulin.toml @@ -2,7 +2,7 @@ features = "with_buffer_pool async_std tokio with_tokio default" run-types = [ "Lib" ] timeout = "120s" -fail-under = 20 +fail-under = 0 [report] out = ["Xml"] diff --git a/roles/test-utils/mining-device/Cargo.toml b/roles/test-utils/mining-device/Cargo.toml index 775d38f3b2..5fa557db08 100644 --- a/roles/test-utils/mining-device/Cargo.toml +++ b/roles/test-utils/mining-device/Cargo.toml @@ -1,8 +1,16 @@ [package] name = "mining_device" version = "0.1.1" +authors = ["The Stratum V2 Developers"] edition = "2018" publish = false +documentation = "https://github.com/stratum-mining/stratum" +readme = "README.md" +homepage = "https://stratumprotocol.org" +repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/roles/test-utils/mining-device/README.md b/roles/test-utils/mining-device/README.md index 4065c2c30b..03ad7241a5 100644 --- a/roles/test-utils/mining-device/README.md +++ b/roles/test-utils/mining-device/README.md @@ -3,19 +3,58 @@ Header only sv2 cpu miner. ``` -Usage: mining-device [OPTIONS] --address-pool +Usage: mining_device [OPTIONS] --address-pool Options: - -p, --pubkey-pool Pool pub key, when left empty the pool certificate is not checked - -i, --id-device Sometimes used by the pool to identify the device - -a, --address-pool Address of the pool in this format ip:port or domain:port - --handicap This value is used to slow down the cpu miner, it represents the number of micro-seconds that are awaited between hashes [default: 0] - --id-user User id, used when a new channel is opened, it can be used by the pool to identify the miner - -h, --help Print help - -V, --version Print version + -p, --pubkey-pool + Pool pub key, when left empty the pool certificate is not checked + -i, --id-device + Sometimes used by the pool to identify the device + -a, --address-pool + Address of the pool in this format ip:port or domain:port + --handicap + This value is used to slow down the cpu miner, it represents the number of micro-seconds that are awaited between hashes [default: 0] + --id-user + User id, used when a new channel is opened, it can be used by the pool to identify the miner + --nominal-hashrate-multiplier + This floating point number is used to modify the advertised nominal hashrate when opening a channel with the upstream. + If 0.0 < nominal_hashrate_multiplier < 1.0, the CPU miner will advertise a nominal hashrate that is smaller than its real capacity. + If nominal_hashrate_multiplier > 1.0, the CPU miner will advertise a nominal hashrate that is bigger than its real capacity. + If empty, the CPU miner will simply advertise its real capacity. + -h, --help + Print help + -V, --version + Print version ``` Usage example: ``` cargo run --release -- --address-pool 127.0.0.1:20000 --id-device device_id::SOLO::bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh ``` + +## handicap + +CPU mining could damage the system due to excessive heat. + +The `--handicap` parameter should be used as a safety mechanism to slow down the hashrate in order to preserve hardware. + +## nominal hashrate multiplier + +Let's imagine that: +- the upstream wants to receive shares every ~100s (on average) +- the CPU miner nominal hashrate is 1k H/s + +Maybe we want to do a test where we don't want to wait ~100s before a share is submitted by the CPU miner. + +In that case, we need the CPU miner to advertise a smaller hashrate, which will force the upstream to set a lower +difficulty target. + +The `--nominal-hashrate-multiplier` can be used to advertise a custom nominal hashrate. + +In the scenario described above, we could launch the CPU miner with `--nominal-hashrate-multiplier 0.01`. + +The CPU miner would advertise 0.01k H/s, which would cause the upstream to set the difficulty target such that the CPU miner would find a share within ~1s. + +This feature can also be used to advertise a bigger nominal hashrate by using values above `1.0`. + +That can also be useful for testing difficulty adjustment algorithms on Sv2 upstreams. \ No newline at end of file diff --git a/roles/test-utils/mining-device/src/lib/mod.rs b/roles/test-utils/mining-device/src/lib/mod.rs index abfa1d9691..af4404c58c 100644 --- a/roles/test-utils/mining-device/src/lib/mod.rs +++ b/roles/test-utils/mining-device/src/lib/mod.rs @@ -8,6 +8,7 @@ use std::{ atomic::{AtomicBool, Ordering}, Arc, }, + thread::available_parallelism, time::Duration, }; use tokio::net::TcpStream; @@ -42,6 +43,7 @@ pub async fn connect( device_id: Option, user_id: Option, handicap: u32, + nominal_hashrate_multiplier: Option, ) { let address = address .clone() @@ -77,7 +79,16 @@ pub async fn connect( .await .unwrap(); info!("Pool noise connection established at {}", address); - Device::start(receiver, sender, address, device_id, user_id, handicap).await + Device::start( + receiver, + sender, + address, + device_id, + user_id, + handicap, + nominal_hashrate_multiplier, + ) + .await } pub type Message = MiningDeviceMessages<'static>; @@ -193,13 +204,21 @@ pub struct Device { notify_changes_to_mining_thread: NewWorkNotifier, } -fn open_channel(device_id: Option) -> OpenStandardMiningChannel<'static> { +fn open_channel( + device_id: Option, + nominal_hashrate_multiplier: Option, + handicap: u32, +) -> OpenStandardMiningChannel<'static> { let user_identity = device_id.unwrap_or_default().try_into().unwrap(); let id: u32 = 10; info!("Measuring CPU hashrate"); - let p = std::thread::available_parallelism().unwrap().get() as u32 - 3; - let nominal_hash_rate = measure_hashrate(5) as f32 * p as f32; - info!("Pc hashrate is {}", nominal_hash_rate); + let measured_hashrate = measure_hashrate(5, handicap) as f32; + info!("Measured CPU hashrate is {}", measured_hashrate); + let nominal_hash_rate = match nominal_hashrate_multiplier { + Some(m) => measured_hashrate * m, + None => measured_hashrate, + }; + info!("MINING DEVICE: send open channel with request id {}", id); OpenStandardMiningChannel { request_id: id.into(), @@ -217,6 +236,7 @@ impl Device { device_id: Option, user_id: Option, handicap: u32, + nominal_hashrate_multiplier: Option, ) { let setup_connection_handler = Arc::new(Mutex::new(SetupConnectionHandler::new())); SetupConnectionHandler::setup( @@ -244,8 +264,9 @@ impl Device { sender: notify_changes_to_mining_thread, }, }; - let open_channel = - MiningDeviceMessages::Mining(Mining::OpenStandardMiningChannel(open_channel(user_id))); + let open_channel = MiningDeviceMessages::Mining(Mining::OpenStandardMiningChannel( + open_channel(user_id, nominal_hashrate_multiplier, handicap), + )); let frame: StdFrame = open_channel.try_into().unwrap(); self_.sender.send(frame.into()).await.unwrap(); let self_mutex = std::sync::Arc::new(Mutex::new(self_)); @@ -597,7 +618,7 @@ impl NextShareOutcome { } // returns hashrate based on how fast the device hashes over the given duration -fn measure_hashrate(duration_secs: u64) -> f64 { +fn measure_hashrate(duration_secs: u64, handicap: u32) -> f64 { let mut rng = thread_rng(); let prev_hash: [u8; 32] = generate_random_32_byte_array().to_vec().try_into().unwrap(); let prev_hash = Hash::from_inner(prev_hash); @@ -619,7 +640,7 @@ fn measure_hashrate(duration_secs: u64) -> f64 { let start_time = Instant::now(); let mut hashes: u64 = 0; let duration = Duration::from_secs(duration_secs); - let mut miner = Miner::new(0); + let mut miner = Miner::new(handicap); // We put the target to 0 we are only interested in how many hashes per unit of time we can do // and do not want to be botherd by messages about valid shares found. miner.new_target(vec![0_u8; 32]); @@ -631,7 +652,12 @@ fn measure_hashrate(duration_secs: u64) -> f64 { } let elapsed_secs = start_time.elapsed().as_secs_f64(); - hashes as f64 / elapsed_secs + let hashrate_single_thread = hashes as f64 / elapsed_secs; + + // we just measured for a single thread, need to multiply by the available parallelism + let p = available_parallelism().unwrap().get(); + + hashrate_single_thread * p as f64 } fn generate_random_32_byte_array() -> [u8; 32] { let mut rng = thread_rng(); @@ -648,11 +674,7 @@ fn start_mining_threads( tokio::task::spawn(async move { let mut killers: Vec> = vec![]; loop { - let available_parallelism = u32::max( - 2, - std::thread::available_parallelism().unwrap().get() as u32, - ); - let p = available_parallelism - 1; + let p = available_parallelism().unwrap().get() as u32; let unit = u32::MAX / p; while have_new_job.recv().await.is_ok() { while let Some(killer) = killers.pop() { diff --git a/roles/test-utils/mining-device/src/main.rs b/roles/test-utils/mining-device/src/main.rs index 02a19c12f0..74bff1b3f2 100644 --- a/roles/test-utils/mining-device/src/main.rs +++ b/roles/test-utils/mining-device/src/main.rs @@ -39,6 +39,14 @@ struct Args { help = "User id, used when a new channel is opened, it can be used by the pool to identify the miner" )] id_user: Option, + #[arg( + long, + help = "This floating point number is used to modify the advertised nominal hashrate when opening a channel with the upstream.\ + \nIf 0.0 < nominal_hashrate_multiplier < 1.0, the CPU miner will advertise a nominal hashrate that is smaller than its real capacity.\ + \nIf nominal_hashrate_multiplier > 1.0, the CPU miner will advertise a nominal hashrate that is bigger than its real capacity.\ + \nIf empty, the CPU miner will simply advertise its real capacity." + )] + nominal_hashrate_multiplier: Option, } #[tokio::main(flavor = "current_thread")] @@ -52,6 +60,7 @@ async fn main() { args.id_device, args.id_user, args.handicap, + args.nominal_hashrate_multiplier, ) .await; } diff --git a/roles/test-utils/sv1-mining-device/Cargo.toml b/roles/test-utils/sv1-mining-device/Cargo.toml index 55b990c633..edfa6fd1b4 100644 --- a/roles/test-utils/sv1-mining-device/Cargo.toml +++ b/roles/test-utils/sv1-mining-device/Cargo.toml @@ -1,8 +1,15 @@ [package] name = "sv1-mining-device" version = "0.1.0" +authors = ["The Stratum V2 Developers"] edition = "2021" publish = false +documentation = "https://github.com/stratum-mining/stratum" +readme = "README.md" +homepage = "https://stratumprotocol.org" +repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/roles/tests-integration/Cargo.toml b/roles/tests-integration/Cargo.toml index 00eab65fa5..5c077e6370 100644 --- a/roles/tests-integration/Cargo.toml +++ b/roles/tests-integration/Cargo.toml @@ -1,13 +1,31 @@ [package] name = "integration-test" version = "0.1.0" -edition = "2021" +authors = ["The Stratum V2 Developers"] +edition = "2018" +documentation = "https://github.com/stratum-mining/stratum" +readme = "README.md" +homepage = "https://stratumprotocol.org" +repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] +async-channel = "1.5.1" +binary_sv2 = { path = "../../protocols/v2/binary-sv2/binary-sv2" } bitcoind = "0.36.0" +codec_sv2 = { path = "../../protocols/v2/codec-sv2", features = ["noise_sv2"] } +const_sv2 = { path = "../../protocols/v2/const-sv2" } flate2 = "1.0.32" +key-utils = { path = "../../utils/key-utils" } minreq = { version = "2.12.0", features = ["https"] } +once_cell = "1.19.0" +network_helpers_sv2 = { path = "../roles-utils/network-helpers", features =["with_tokio","with_buffer_pool"] } +pool_sv2 = { path = "../pool" } +roles_logic_sv2 = { path = "../../protocols/v2/roles-logic-sv2" } tar = "0.4.41" +tokio = { version="1.36.0",features = ["full","tracing"] } +tracing = "0.1.40" [lib] path = "tests/common/mod.rs" diff --git a/roles/tests-integration/tests/common/mod.rs b/roles/tests-integration/tests/common/mod.rs index 4f5c980093..ea23151229 100644 --- a/roles/tests-integration/tests/common/mod.rs +++ b/roles/tests-integration/tests/common/mod.rs @@ -1,14 +1,28 @@ +mod sniffer; + use bitcoind::{bitcoincore_rpc::RpcApi, BitcoinD, Conf}; use flate2::read::GzDecoder; +use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}; +use once_cell::sync::Lazy; +use pool_sv2::PoolSv2; +use sniffer::Sniffer; use std::{ + collections::HashSet, + convert::TryFrom, env, fs::{create_dir_all, File}, io::{BufReader, Read}, + net::{SocketAddr, TcpListener}, path::{Path, PathBuf}, + str::FromStr, + sync::Mutex, }; use tar::Archive; -const VERSION_TP: &str = "0.1.7"; +// prevents get_available_port from ever returning the same port twice +static UNIQUE_PORTS: Lazy>> = Lazy::new(|| Mutex::new(HashSet::new())); + +const VERSION_TP: &str = "0.1.9"; fn download_bitcoind_tarball(download_url: &str) -> Vec { let response = minreq::get(download_url) @@ -67,7 +81,6 @@ pub struct TemplateProvider { impl TemplateProvider { pub fn start(port: u16) -> Self { let temp_dir = PathBuf::from("/tmp/.template-provider"); - let mut conf = Conf::default(); let staticdir = format!(".bitcoin-{}", port); conf.staticdir = Some(temp_dir.join(staticdir)); @@ -130,11 +143,11 @@ impl TemplateProvider { TemplateProvider { bitcoind } } - pub fn stop(&self) { + fn stop(&self) { let _ = self.bitcoind.client.stop().unwrap(); } - pub fn generate_blocks(&self, n: u64) { + fn generate_blocks(&self, n: u64) { let mining_address = self .bitcoind .client @@ -147,8 +160,125 @@ impl TemplateProvider { .generate_to_address(n, &mining_address) .unwrap(); } +} + +impl Drop for TemplateProvider { + fn drop(&mut self) { + self.stop(); + } +} + +fn is_port_open(address: SocketAddr) -> bool { + TcpListener::bind(address).is_err() +} + +fn get_available_port() -> u16 { + let mut unique_ports = UNIQUE_PORTS.lock().unwrap(); - pub fn get_block_count(&self) -> u64 { - self.bitcoind.client.get_block_count().unwrap() + loop { + let port = TcpListener::bind("127.0.0.1:0") + .unwrap() + .local_addr() + .unwrap() + .port(); + if !unique_ports.contains(&port) { + unique_ports.insert(port); + return port; + } } } + +pub fn get_available_address() -> SocketAddr { + let port = get_available_port(); + SocketAddr::from(([127, 0, 0, 1], port)) +} + +pub async fn start_sniffer(listening_address: SocketAddr, upstream: SocketAddr) -> Sniffer { + let sniffer = Sniffer::new(listening_address, upstream).await; + let sniffer_clone = sniffer.clone(); + tokio::spawn(async move { + sniffer_clone.start().await; + }); + sniffer +} + +#[derive(Debug)] +struct TestPoolSv2 { + pool: PoolSv2, +} + +impl TestPoolSv2 { + fn new( + listening_address: Option, + template_provider_address: Option, + ) -> Self { + use pool_sv2::mining_pool::{CoinbaseOutput, Configuration}; + let pool_port = if let Some(listen_addr) = listening_address { + listen_addr.port() + } else { + get_available_port() + }; + let listening_address = listening_address + .unwrap_or(SocketAddr::from_str(&format!("127.0.0.1:{}", pool_port)).unwrap()); + let is_pool_port_open = is_port_open(listening_address); + assert!(!is_pool_port_open); + let authority_public_key = Secp256k1PublicKey::try_from( + "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72".to_string(), + ) + .expect("failed"); + let authority_secret_key = Secp256k1SecretKey::try_from( + "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n".to_string(), + ) + .expect("failed"); + let cert_validity_sec = 3600; + let coinbase_outputs = vec![CoinbaseOutput::new( + "P2WPKH".to_string(), + "036adc3bdf21e6f9a0f0fb0066bf517e5b7909ed1563d6958a10993849a7554075".to_string(), + )]; + let pool_signature = "Stratum v2 SRI Pool".to_string(); + let tp_address = if let Some(tp_add) = template_provider_address { + tp_add.to_string() + } else { + "127.0.0.1:8442".to_string() + }; + let connection_config = pool_sv2::mining_pool::ConnectionConfig::new( + listening_address.to_string(), + cert_validity_sec, + pool_signature, + ); + let template_provider_config = + pool_sv2::mining_pool::TemplateProviderConfig::new(tp_address, None); + let authority_config = + pool_sv2::mining_pool::AuthorityConfig::new(authority_public_key, authority_secret_key); + let config = Configuration::new( + connection_config, + template_provider_config, + authority_config, + coinbase_outputs, + ); + let pool = PoolSv2::new(config); + + Self { pool } + } +} + +pub async fn start_pool( + listening_address: Option, + template_provider_address: Option, +) -> PoolSv2 { + let test_pool = TestPoolSv2::new(listening_address, template_provider_address); + let pool = test_pool.pool.clone(); + let pool_clone = pool.clone(); + tokio::task::spawn(async move { + assert!(pool_clone.start().await.is_ok()); + }); + // Wait a bit to let the pool exchange initial messages with the TP + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + pool +} + +pub async fn start_template_provider(tp_port: u16) -> TemplateProvider { + let template_provider = TemplateProvider::start(tp_port); + template_provider.generate_blocks(16); + template_provider +} diff --git a/roles/tests-integration/tests/common/sniffer.rs b/roles/tests-integration/tests/common/sniffer.rs new file mode 100644 index 0000000000..46e885e4eb --- /dev/null +++ b/roles/tests-integration/tests/common/sniffer.rs @@ -0,0 +1,497 @@ +use async_channel::{Receiver, Sender}; +use codec_sv2::{ + framing_sv2::framing::Frame, HandshakeRole, Initiator, Responder, StandardEitherFrame, +}; +use key_utils::{Secp256k1PublicKey, Secp256k1SecretKey}; +use network_helpers_sv2::noise_connection_tokio::Connection; +use roles_logic_sv2::{ + parsers::{ + AnyMessage, CommonMessages, + JobDeclaration::{ + AllocateMiningJobToken, AllocateMiningJobTokenSuccess, DeclareMiningJob, + DeclareMiningJobError, DeclareMiningJobSuccess, IdentifyTransactions, + IdentifyTransactionsSuccess, ProvideMissingTransactions, + ProvideMissingTransactionsSuccess, SubmitSolution, + }, + TemplateDistribution, + TemplateDistribution::CoinbaseOutputDataSize, + }, + utils::Mutex, +}; +use std::{collections::VecDeque, convert::TryInto, net::SocketAddr, sync::Arc}; +use tokio::{ + net::{TcpListener, TcpStream}, + select, +}; +type MessageFrame = StandardEitherFrame>; +type MsgType = u8; + +#[derive(Debug, PartialEq)] +enum SnifferError { + DownstreamClosed, + UpstreamClosed, +} + +/// Allows to intercept messages sent between two roles. +/// +/// The downstream (or client) role connects to the [`Sniffer`] `listening_address` and the +/// [`Sniffer`] connects to the `upstream` server. This way, the Sniffer can intercept messages sent +/// between the downstream and upstream roles. The downstream will send its messages to the +/// [`Sniffer`] which will save those in the `downstream_messages` aggregator and forward them to +/// the upstream role. When a response is received it is saved in `upstream_messages` and +/// forwarded to the downstream role. Both `downstream_messages` and `upstream_messages` can be +/// accessed as FIFO queues. +/// +/// It is useful for testing purposes, as it allows to assert that the roles have sent specific +/// messages in a specific order and to inspect the messages details. +#[derive(Debug, Clone)] +pub struct Sniffer { + listening_address: SocketAddr, + upstream_address: SocketAddr, + downstream_messages: MessagesAggregator, + upstream_messages: MessagesAggregator, +} + +impl Sniffer { + /// Creates a new sniffer that listens on the given listening address and connects to the given + /// upstream address. + pub async fn new(listening_address: SocketAddr, upstream_address: SocketAddr) -> Self { + Self { + listening_address, + upstream_address, + downstream_messages: MessagesAggregator::new(), + upstream_messages: MessagesAggregator::new(), + } + } + + /// Starts the sniffer. + /// + /// The sniffer should be started after the upstream role have been initialized and is ready to + /// accept messages and before the downstream role starts sending messages. + pub async fn start(self) { + let (downstream_receiver, downstream_sender) = + Self::create_downstream(Self::wait_for_client(self.listening_address).await) + .await + .expect("Failed to create downstream"); + let (upstream_receiver, upstream_sender) = Self::create_upstream( + TcpStream::connect(self.upstream_address) + .await + .expect("Failed to connect to upstream"), + ) + .await + .expect("Failed to create upstream"); + let downstream_messages = self.downstream_messages.clone(); + let upstream_messages = self.upstream_messages.clone(); + let _ = select! { + r = Self::recv_from_down_send_to_up(downstream_receiver, upstream_sender, downstream_messages) => r, + r = Self::recv_from_up_send_to_down(upstream_receiver, downstream_sender, upstream_messages) => r, + }; + } + + /// Returns the oldest message sent by downstream. + /// + /// The queue is FIFO and once a message is returned it is removed from the queue. + /// + /// This can be used to assert that the downstream sent: + /// - specific message types + /// - specific message fields + pub fn next_downstream_message(&self) -> Option<(MsgType, AnyMessage<'static>)> { + self.downstream_messages.next_message() + } + + /// Returns the oldest message sent by upstream. + /// + /// The queue is FIFO and once a message is returned it is removed from the queue. + /// + /// This can be used to assert that the upstream sent: + /// - specific message types + /// - specific message fields + pub fn next_upstream_message(&self) -> Option<(MsgType, AnyMessage<'static>)> { + self.upstream_messages.next_message() + } + + async fn create_downstream( + stream: TcpStream, + ) -> Option<(Receiver, Sender)> { + let pub_key = "9auqWEzQDVyd2oe1JVGFLMLHZtCo2FFqZwtKA5gd9xbuEu7PH72" + .to_string() + .parse::() + .unwrap() + .into_bytes(); + let prv_key = "mkDLTBBRxdBv998612qipDYoTK3YUrqLe8uWw7gu3iXbSrn2n" + .to_string() + .parse::() + .unwrap() + .into_bytes(); + let responder = + Responder::from_authority_kp(&pub_key, &prv_key, std::time::Duration::from_secs(10000)) + .unwrap(); + if let Ok((receiver_from_client, send_to_client, _, _)) = + Connection::new::<'static, AnyMessage<'static>>( + stream, + HandshakeRole::Responder(responder), + ) + .await + { + Some((receiver_from_client, send_to_client)) + } else { + None + } + } + + async fn create_upstream( + stream: TcpStream, + ) -> Option<(Receiver, Sender)> { + let initiator = Initiator::without_pk().expect("This fn call can not fail"); + if let Ok((receiver_from_client, send_to_client, _, _)) = + Connection::new::<'static, AnyMessage<'static>>( + stream, + HandshakeRole::Initiator(initiator), + ) + .await + { + Some((receiver_from_client, send_to_client)) + } else { + None + } + } + + async fn recv_from_down_send_to_up( + recv: Receiver, + send: Sender, + downstream_messages: MessagesAggregator, + ) -> Result<(), SnifferError> { + while let Ok(mut frame) = recv.recv().await { + let (msg_type, msg) = Self::message_from_frame(&mut frame); + downstream_messages.add_message(msg_type, msg); + if send.send(frame).await.is_err() { + return Err(SnifferError::UpstreamClosed); + }; + } + Err(SnifferError::DownstreamClosed) + } + + async fn recv_from_up_send_to_down( + recv: Receiver, + send: Sender, + upstream_messages: MessagesAggregator, + ) -> Result<(), SnifferError> { + while let Ok(mut frame) = recv.recv().await { + let (msg_type, msg) = Self::message_from_frame(&mut frame); + upstream_messages.add_message(msg_type, msg); + if send.send(frame).await.is_err() { + return Err(SnifferError::DownstreamClosed); + }; + } + Err(SnifferError::UpstreamClosed) + } + + fn message_from_frame(frame: &mut MessageFrame) -> (MsgType, AnyMessage<'static>) { + match frame { + Frame::Sv2(frame) => { + if let Some(header) = frame.get_header() { + let message_type = header.msg_type(); + let mut payload = frame.payload().to_vec(); + let message: Result, _> = + (message_type, payload.as_mut_slice()).try_into(); + match message { + Ok(message) => { + let message = Self::into_static(message); + (message_type, message) + } + _ => { + println!( + "Received frame with invalid payload or message type: {frame:?}" + ); + panic!(); + } + } + } else { + println!("Received frame with invalid header: {frame:?}"); + panic!(); + } + } + Frame::HandShake(f) => { + println!("Received unexpected handshake frame: {f:?}"); + panic!(); + } + } + } + + fn into_static(m: AnyMessage<'_>) -> AnyMessage<'static> { + match m { + AnyMessage::Mining(m) => AnyMessage::Mining(m.into_static()), + AnyMessage::Common(m) => match m { + CommonMessages::ChannelEndpointChanged(m) => { + AnyMessage::Common(CommonMessages::ChannelEndpointChanged(m.into_static())) + } + CommonMessages::SetupConnection(m) => { + AnyMessage::Common(CommonMessages::SetupConnection(m.into_static())) + } + CommonMessages::SetupConnectionError(m) => { + AnyMessage::Common(CommonMessages::SetupConnectionError(m.into_static())) + } + CommonMessages::SetupConnectionSuccess(m) => { + AnyMessage::Common(CommonMessages::SetupConnectionSuccess(m.into_static())) + } + }, + AnyMessage::JobDeclaration(m) => match m { + AllocateMiningJobToken(m) => { + AnyMessage::JobDeclaration(AllocateMiningJobToken(m.into_static())) + } + AllocateMiningJobTokenSuccess(m) => { + AnyMessage::JobDeclaration(AllocateMiningJobTokenSuccess(m.into_static())) + } + DeclareMiningJob(m) => { + AnyMessage::JobDeclaration(DeclareMiningJob(m.into_static())) + } + DeclareMiningJobError(m) => { + AnyMessage::JobDeclaration(DeclareMiningJobError(m.into_static())) + } + DeclareMiningJobSuccess(m) => { + AnyMessage::JobDeclaration(DeclareMiningJobSuccess(m.into_static())) + } + IdentifyTransactions(m) => { + AnyMessage::JobDeclaration(IdentifyTransactions(m.into_static())) + } + IdentifyTransactionsSuccess(m) => { + AnyMessage::JobDeclaration(IdentifyTransactionsSuccess(m.into_static())) + } + ProvideMissingTransactions(m) => { + AnyMessage::JobDeclaration(ProvideMissingTransactions(m.into_static())) + } + ProvideMissingTransactionsSuccess(m) => { + AnyMessage::JobDeclaration(ProvideMissingTransactionsSuccess(m.into_static())) + } + SubmitSolution(m) => AnyMessage::JobDeclaration(SubmitSolution(m.into_static())), + }, + AnyMessage::TemplateDistribution(m) => match m { + CoinbaseOutputDataSize(m) => { + AnyMessage::TemplateDistribution(CoinbaseOutputDataSize(m.into_static())) + } + TemplateDistribution::NewTemplate(m) => AnyMessage::TemplateDistribution( + TemplateDistribution::NewTemplate(m.into_static()), + ), + TemplateDistribution::RequestTransactionData(m) => { + AnyMessage::TemplateDistribution(TemplateDistribution::RequestTransactionData( + m.into_static(), + )) + } + TemplateDistribution::RequestTransactionDataError(m) => { + AnyMessage::TemplateDistribution( + TemplateDistribution::RequestTransactionDataError(m.into_static()), + ) + } + TemplateDistribution::RequestTransactionDataSuccess(m) => { + AnyMessage::TemplateDistribution( + TemplateDistribution::RequestTransactionDataSuccess(m.into_static()), + ) + } + TemplateDistribution::SetNewPrevHash(m) => AnyMessage::TemplateDistribution( + TemplateDistribution::SetNewPrevHash(m.into_static()), + ), + TemplateDistribution::SubmitSolution(m) => AnyMessage::TemplateDistribution( + TemplateDistribution::SubmitSolution(m.into_static()), + ), + }, + } + } + + async fn wait_for_client(client: SocketAddr) -> TcpStream { + let listner = TcpListener::bind(client) + .await + .expect("Impossible to listen on given address"); + if let Ok((stream, _)) = listner.accept().await { + stream + } else { + panic!("Impossible to accept dowsntream connection") + } + } +} + +// Utility macro to assert that the downstream and upstream roles have sent specific messages. +// +// This macro can be called in two ways: +// 1. If you want to assert the message without any of its properties, you can invoke the macro +// with the message group, the nested message group, the message, and the expected message: +// `assert_message!(TemplateDistribution, TemplateDistribution, $msg, $expected_message_variant);`. +// +// 2. If you want to assert the message with its properties, you can invoke the macro with the +// message group, the nested message group, the message, the expected message, and the expected +// properties and values: +// `assert_message!(TemplateDistribution, TemplateDistribution, $msg, $expected_message_variant, +// $expected_property, $expected_property_value, ...);`. +// Note that you can provide any number of properties and values. +// +// In both cases, the `$message_group` could be any variant of `PoolMessages::$message_group` and +// the `$nested_message_group` could be any variant of +// `PoolMessages::$message_group($nested_message_group)`. +// +// If you dont want to provide the `$message_group` and `$nested_message_group` arguments, you can +// utilize `assert_common_message!`, `assert_tp_message!`, `assert_mining_message!`, and +// `assert_jd_message!` macros. All those macros are just wrappers around `assert_message!` macro +// with predefined `$message_group` and `$nested_message_group` arguments. They also can be called +// in two ways, with or without properties validation. +#[macro_export] +macro_rules! assert_message { + ($message_group:ident, $nested_message_group:ident, $msg:expr, $expected_message_variant:ident, + $($expected_property:ident, $expected_property_value:expr),*) => { match $msg { + Some((_, message)) => { + match message { + PoolMessages::$message_group($nested_message_group::$expected_message_variant( + $expected_message_variant { + $($expected_property,)* + .. + }, + )) => { + $( + assert_eq!($expected_property.clone(), $expected_property_value); + )* + } + _ => { + panic!( + "Sent wrong message: {:?}", + message + ); + } + } + } + _ => panic!("No message received"), + } + }; + ($message_group:ident, $nested_message_group:ident, $msg:expr, $expected_message_variant:ident) => { + match $msg { + Some((_, message)) => { + match message { + PoolMessages::$message_group($nested_message_group::$expected_message_variant(_)) => {} + _ => { + panic!( + "Sent wrong message: {:?}", + message + ); + } + } + } + _ => panic!("No message received"), + } + }; +} + +// Assert that the message is a common message and that it has the expected properties and values. +#[macro_export] +macro_rules! assert_common_message { + ($msg:expr, $expected_message_variant:ident, $($expected_property:ident, $expected_property_value:expr),*) => { + assert_message!(Common, CommonMessages, $msg, $expected_message_variant, $($expected_property, $expected_property_value),*); + }; + ($msg:expr, $expected_message_variant:ident) => { + assert_message!(Common, CommonMessages, $msg, $expected_message_variant); + }; +} + +// Assert that the message is a template distribution message and that it has the expected +// properties and values. +#[macro_export] +macro_rules! assert_tp_message { + ($msg:expr, $expected_message_variant:ident, $($expected_property:ident, $expected_property_value:expr),*) => { + assert_message!(TemplateDistribution, TemplateDistribution, $msg, $expected_message_variant, $($expected_property, $expected_property_value),*); + }; + ($msg:expr, $expected_message_variant:ident) => { + assert_message!(TemplateDistribution, TemplateDistribution, $msg, $expected_message_variant); + }; +} + +// Assert that the message is a mining message and that it has the expected properties and values. +#[macro_export] +macro_rules! assert_mining_message { + ($msg:expr, $expected_message_variant:ident, $($expected_property:ident, $expected_property_value:expr),*) => { + assert_message!(Mining, Mining, $msg, $expected_message_variant, $($expected_property, $expected_property_value),*); + }; + ($msg:expr, $expected_message_variant:ident) => { + assert_message!(Mining, Mining, $msg, $expected_message_variant); + }; +} + +// Assert that the message is a job declaration message and that it has the expected properties and +// values. +#[macro_export] +macro_rules! assert_jd_message { + ($msg:expr, $expected_message_variant:ident, $($expected_property:ident, $expected_property_value:expr),*) => { + assert_message!(JobDeclaration, JobDeclaration, $msg, $expected_message_variant, $($expected_property, $expected_property_value),*); + }; + ($msg:expr, $expected_message_variant:ident) => { + assert_message!(JobDeclaration, JobDeclaration, $msg, $expected_message_variant); + }; +} + +// This implementation is used in order to check if a test has handled all messages sent by the +// downstream and upstream roles. If not, the test will panic. +// +// This is useful to ensure that the test has checked all exchanged messages between the roles. +impl Drop for Sniffer { + fn drop(&mut self) { + // Don't print backtrace on panic + std::panic::set_hook(Box::new(|_| { + println!(); + })); + if !self.downstream_messages.is_empty() { + println!( + "You didn't handle all downstream messages: {:?}", + self.downstream_messages + ); + panic!(); + } + if !self.upstream_messages.is_empty() { + println!( + "You didn't handle all upstream messages: {:?}", + self.upstream_messages + ); + panic!(); + } + } +} + +#[derive(Debug, Clone)] +struct MessagesAggregator { + messages: Arc)>>>, +} + +impl MessagesAggregator { + fn new() -> Self { + Self { + messages: Arc::new(Mutex::new(VecDeque::new())), + } + } + + // Adds a message to the end of the queue. + fn add_message(&self, msg_type: MsgType, message: AnyMessage<'static>) { + self.messages + .safe_lock(|messages| messages.push_back((msg_type, message))) + .unwrap(); + } + + fn is_empty(&self) -> bool { + self.messages + .safe_lock(|messages| messages.is_empty()) + .unwrap() + } + + // The aggregator queues messages in FIFO order, so this function returns the oldest message in + // the queue. + // + // The returned message is removed from the queue. + fn next_message(&self) -> Option<(MsgType, AnyMessage<'static>)> { + let is_state = self + .messages + .safe_lock(|messages| { + let mut cloned = messages.clone(); + if let Some((msg_type, msg)) = cloned.pop_front() { + *messages = cloned; + Some((msg_type, msg)) + } else { + None + } + }) + .unwrap(); + is_state + } +} diff --git a/roles/tests-integration/tests/pool_integration.rs b/roles/tests-integration/tests/pool_integration.rs new file mode 100644 index 0000000000..9754ed1d94 --- /dev/null +++ b/roles/tests-integration/tests/pool_integration.rs @@ -0,0 +1,40 @@ +mod common; + +use roles_logic_sv2::{ + common_messages_sv2::{Protocol, SetupConnection}, + parsers::{CommonMessages, PoolMessages, TemplateDistribution}, +}; + +// This test starts a Template Provider and a Pool, and checks if they exchange the correct +// messages upon connection. +// The Sniffer is used as a proxy between the Upstream(Template Provider) and Downstream(Pool). The +// Pool will connect to the Sniffer, and the Sniffer will connect to the Template Provider. +#[tokio::test] +async fn success_pool_template_provider_connection() { + let sniffer_addr = common::get_available_address(); + let tp_addr = common::get_available_address(); + let pool_addr = common::get_available_address(); + let _tp = common::start_template_provider(tp_addr.port()).await; + let sniffer = common::start_sniffer(sniffer_addr, tp_addr).await; + let _ = common::start_pool(Some(pool_addr), Some(sniffer_addr)).await; + // here we assert that the downstream(pool in this case) have sent `SetupConnection` message + // with the correct parameters, protocol, flags, min_version and max_version. Note that the + // macro can take any number of arguments after the message argument, but the order is + // important where a property should be followed by its value. + assert_common_message!( + &sniffer.next_downstream_message(), + SetupConnection, + protocol, + Protocol::TemplateDistributionProtocol, + flags, + 0, + min_version, + 2, + max_version, + 2 + ); + assert_common_message!(&sniffer.next_upstream_message(), SetupConnectionSuccess); + assert_tp_message!(&sniffer.next_downstream_message(), CoinbaseOutputDataSize); + assert_tp_message!(&sniffer.next_upstream_message(), NewTemplate); + assert_tp_message!(sniffer.next_upstream_message(), SetNewPrevHash); +} diff --git a/roles/translator/Cargo.toml b/roles/translator/Cargo.toml index a7c762a058..cf58e40930 100644 --- a/roles/translator/Cargo.toml +++ b/roles/translator/Cargo.toml @@ -1,10 +1,15 @@ [package] name = "translator_sv2" version = "0.1.1" +authors = ["The Stratum V2 Developers"] edition = "2021" description = "Server used to bridge SV1 miners to SV2 pools" -license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/translator_sv2" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [lib] name = "translator_sv2" diff --git a/roles/translator/src/lib/mod.rs b/roles/translator/src/lib/mod.rs index 5a95bcde71..2c5a21c4d0 100644 --- a/roles/translator/src/lib/mod.rs +++ b/roles/translator/src/lib/mod.rs @@ -276,10 +276,6 @@ impl TranslatorSv2 { let _ = task_collector.safe_lock(|t| t.push((task.abort_handle(), "init task".to_string()))); } - - pub fn upstream_address(&self) -> SocketAddr { - self.config.upstream_address.clone().parse().unwrap() - } } fn kill_tasks(task_collector: Arc>>) { diff --git a/roles/translator/src/lib/proxy/bridge.rs b/roles/translator/src/lib/proxy/bridge.rs index 74db21111e..93f4efdeb2 100644 --- a/roles/translator/src/lib/proxy/bridge.rs +++ b/roles/translator/src/lib/proxy/bridge.rs @@ -540,6 +540,7 @@ mod test { pub mod test_utils { use super::*; + #[allow(dead_code)] pub struct BridgeInterface { pub tx_sv1_submit: Sender, pub rx_sv2_submit_shares_ext: Receiver>, diff --git a/roles/translator/src/lib/upstream_sv2/upstream.rs b/roles/translator/src/lib/upstream_sv2/upstream.rs index 6135174328..6e2ddf646c 100644 --- a/roles/translator/src/lib/upstream_sv2/upstream.rs +++ b/roles/translator/src/lib/upstream_sv2/upstream.rs @@ -115,7 +115,7 @@ impl Upstream { /// `UpstreamConnection` with a channel to send and receive messages from the SV2 Upstream /// role and uses channels provided in the function arguments to send and receive messages /// from the `Downstream`. - #[cfg_attr(feature = "cargo-clippy", allow(clippy::too_many_arguments))] + #[allow(clippy::too_many_arguments)] pub async fn new( address: SocketAddr, authority_public_key: Secp256k1PublicKey, diff --git a/scripts/coverage-protocols.sh b/scripts/coverage-protocols.sh new file mode 100755 index 0000000000..124db36b62 --- /dev/null +++ b/scripts/coverage-protocols.sh @@ -0,0 +1,36 @@ +#!/bin/bash +tarpaulin() { + crate_name=$1 + output_dir="target/tarpaulin-reports/$crate_name" + mkdir -p "$output_dir" + cargo +nightly tarpaulin --verbose --out Xml --output-dir "$output_dir" +} + +cd protocols +tarpaulin + +crates=( + "v1" + "v2/binary-sv2/serde-sv2" + "v2/binary-sv2/no-serde-sv2/codec" + "v2/binary-sv2/no-serde-sv2/derive_codec" + "v2/binary-sv2/binary-sv2" + "v2/noise-sv2" + "v2/framing-sv2" + "v2/codec-sv2" + "v2/const-sv2" + "v2/subprotocols/common-messages" + "v2/subprotocols/template-distribution" + "v2/subprotocols/mining" + "v2/subprotocols/job-declaration" + "v2/sv2-ffi" + "v2/roles-logic-sv2" +) + +for crate in "${crates[@]}"; do + echo "Running Tarpaulin for $crate..." + crate_name=$(basename "$crate") + cd "$crate" || exit 1 + tarpaulin "$crate_name-coverage" + cd - || exit 1 +done \ No newline at end of file diff --git a/scripts/coverage-roles.sh b/scripts/coverage-roles.sh new file mode 100755 index 0000000000..50fecb521f --- /dev/null +++ b/scripts/coverage-roles.sh @@ -0,0 +1,28 @@ +#!/bin/bash +tarpaulin() { + crate_name=$1 + output_dir="target/tarpaulin-reports/$crate_name" + mkdir -p "$output_dir" + cargo +nightly tarpaulin --verbose --out Xml --output-dir "$output_dir" --all-features +} + +cd roles +tarpaulin + +crates=( + "mining-proxy" + "pool" + "test-utils/mining-device" + "test-utils/sv1-mining-device" + "translator" + "jd-client" + "jd-server" +) + +for crate in "${crates[@]}"; do + echo "Running Tarpaulin for $crate..." + crate_name=$(basename "$crate") + cd "$crate" || exit 1 + tarpaulin "$crate_name-coverage" + cd - || exit 1 +done diff --git a/scripts/coverage-utils.sh b/scripts/coverage-utils.sh new file mode 100755 index 0000000000..6f32742984 --- /dev/null +++ b/scripts/coverage-utils.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +tarpaulin() { + crate_name=$1 + output_dir="target/tarpaulin-reports/$crate_name" + mkdir -p "$output_dir" + cargo +nightly tarpaulin --verbose --out Xml --output-dir "$output_dir" --all-features +} + +cd utils +tarpaulin + +crates=( + "buffer" + "error-handling" + "key-utils" + "bip32-key-derivation" +) + +for crate in "${crates[@]}"; do + echo "Running Tarpaulin for $crate..." + crate_name=$(basename "$crate") + cd "$crate" || exit 1 + tarpaulin "$crate_name-coverage" + cd - || exit 1 +done diff --git a/scripts/tarpaulin.sh b/scripts/tarpaulin.sh deleted file mode 100755 index 9e0f226f35..0000000000 --- a/scripts/tarpaulin.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/sh - -tarpaulin() -{ - cargo +nightly tarpaulin --verbose -} - -cd protocols -tarpaulin -cd ../roles -tarpaulin -cd ../utils -tarpaulin diff --git a/utils/Cargo.toml b/utils/Cargo.toml index aa22ee1aef..43e3c95fe8 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -1,16 +1,3 @@ -name = "stratum_v2_utils" -version = "1.0.0" -authors = ["The Stratum v2 Developers"] -edition = "2021" -description = "The Stratum protocol defines how miners, proxies, and pools communicate to contribute hashrate to the Bitcoin network. Stratum v2 is a robust set of primitives which anyone can use to expand the protocol or implement a role." -documentation = "https://github.com/stratum-mining/stratum" -readme = "README.md" -homepage = "https://stratumprotocol.org" -repository = "https://github.com/stratum-mining/stratum" -license = "MIT + Apache-2.0" -license-file = "LICENSE.md" -keywords = ["stratum", "mining", "bitcoin", "protocol"] - [workspace] resolver="2" diff --git a/utils/bip32-key-derivation/Cargo.toml b/utils/bip32-key-derivation/Cargo.toml index a2d0681e4c..2bcdd6fe12 100644 --- a/utils/bip32-key-derivation/Cargo.toml +++ b/utils/bip32-key-derivation/Cargo.toml @@ -1,10 +1,15 @@ [package] name = "bip32_derivation" version = "1.0.0" +authors = ["The Stratum V2 Developers"] edition = "2021" description = "bip32_derivation" -license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/bip32_derivation" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [lib] name = "bip32_derivation" @@ -20,3 +25,6 @@ slip132 = "0.10" [dev-dependencies] toml = { version = "0.5.6", git = "https://github.com/diondokter/toml-rs", default-features = false, rev = "c4161aa" } + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/utils/buffer/Cargo.toml b/utils/buffer/Cargo.toml index b3a8f543d9..e7802be093 100644 --- a/utils/buffer/Cargo.toml +++ b/utils/buffer/Cargo.toml @@ -1,11 +1,15 @@ [package] name = "buffer_sv2" version = "1.1.0" -authors = ["fi3 "] +authors = ["The Stratum V2 Developers"] edition = "2018" description = "buffer" -license = "MIT" +documentation = "https://docs.rs/buffer_sv2" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] [dependencies] criterion = {version = "0.3", optional = true} @@ -29,3 +33,6 @@ harness = false debug = [] fuzz = [] with_serde = ["serde"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/utils/error-handling/Cargo.toml b/utils/error-handling/Cargo.toml index 6d3cb4a157..8d9c7fd072 100644 --- a/utils/error-handling/Cargo.toml +++ b/utils/error-handling/Cargo.toml @@ -1,7 +1,15 @@ [package] name = "error_handling" version = "1.0.0" +authors = ["The Stratum V2 Developers"] edition = "2021" description = "Macro used to clean and centralize error handling within async processes" -license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/error_handling" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/utils/key-utils/Cargo.toml b/utils/key-utils/Cargo.toml index 55c5bba15f..7d0213bbaa 100644 --- a/utils/key-utils/Cargo.toml +++ b/utils/key-utils/Cargo.toml @@ -1,10 +1,15 @@ [package] name = "key-utils" version = "1.1.0" +authors = ["The Stratum V2 Developers"] edition = "2021" description = "Key utils" -license = "MIT OR Apache-2.0" +documentation = "https://docs.rs/key-utils" +readme = "README.md" +homepage = "https://stratumprotocol.org" repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -24,3 +29,6 @@ serde = { version = "1.0.89", features = ["derive","alloc"], default-features = [dev-dependencies] toml = { version = "0.5.6", git = "https://github.com/diondokter/toml-rs", default-features = false, rev = "c4161aa" } + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/utils/message-generator/Cargo.toml b/utils/message-generator/Cargo.toml index f97d74f0d9..55f17d365e 100644 --- a/utils/message-generator/Cargo.toml +++ b/utils/message-generator/Cargo.toml @@ -1,7 +1,15 @@ [package] name = "message_generator_sv2" version = "1.0.1" +authors = ["The Stratum V2 Developers"] edition = "2021" +description = "message generator" +documentation = "https://github.com/stratum-mining/stratum" +readme = "README.md" +homepage = "https://stratumprotocol.org" +repository = "https://github.com/stratum-mining/stratum" +license = "MIT OR Apache-2.0" +keywords = ["stratum", "mining", "bitcoin", "protocol"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/utils/tarpaulin.toml b/utils/tarpaulin.toml index c704c0f94d..8607047202 100644 --- a/utils/tarpaulin.toml +++ b/utils/tarpaulin.toml @@ -1,7 +1,7 @@ [default] run-types = [ "Lib" ] timeout = "120s" -fail-under = 7 +fail-under = 0 [report] out = ["Xml"]