diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000..e724ea0856 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +RUST_BACKTRACE = "1" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..12f32366ab --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +# EditorConfig is awesome:http://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*.md] +indent_style = space diff --git a/.gitattributes b/.gitattributes index e5a028af21..9d5816faad 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ zcash_client_backend/src/proto/compact_formats.rs linguist-generated=true zcash_client_backend/src/proto/service.rs linguist-generated=true +zcash_client_backend/src/proto/proposal.rs linguist-generated=true diff --git a/.github/actions/prepare/action.yml b/.github/actions/prepare/action.yml new file mode 100644 index 0000000000..9ac597d495 --- /dev/null +++ b/.github/actions/prepare/action.yml @@ -0,0 +1,39 @@ +name: 'Prepare CI' +description: 'Prepares feature flags' +inputs: + extra-features: + description: 'Extra feature flags to enable' + required: false + default: '' + test-dependencies: + description: 'Include test dependencies' + required: false + default: true +outputs: + feature-flags: + description: 'Feature flags' + value: ${{ steps.prepare.outputs.flags }} +runs: + using: 'composite' + steps: + - id: test + shell: bash + run: echo "feature=test-dependencies" >> $GITHUB_OUTPUT + if: inputs.test-dependencies == 'true' + - name: Prepare feature flags + id: prepare + shell: bash + run: > + echo "flags=--features ' + bundled-prover + download-params + lightwalletd-tonic + sync + temporary-zcashd + transparent-inputs + unstable + unstable-serialization + unstable-spanning-tree + ${{ inputs.extra-features }} + ${{ steps.test.outputs.feature }} + '" >> $GITHUB_OUTPUT diff --git a/.github/workflows/aggregate-audits.yml b/.github/workflows/aggregate-audits.yml new file mode 100644 index 0000000000..546d37d3b6 --- /dev/null +++ b/.github/workflows/aggregate-audits.yml @@ -0,0 +1,24 @@ +name: Aggregate audits + +on: + push: + branches: main + paths: + - '.github/workflows/aggregate-audits.yml' + - 'supply-chain/audits.toml' + +permissions: + contents: read + +jobs: + trigger: + name: Trigger + runs-on: ubuntu-latest + steps: + - name: Trigger aggregation in zcash/rust-ecosystem + run: > + gh api repos/zcash/rust-ecosystem/dispatches + --field event_type="aggregate-audits" + --field client_payload[sha]="$GITHUB_SHA" + env: + GH_TOKEN: ${{ github.token }} diff --git a/.github/workflows/audits.yml b/.github/workflows/audits.yml new file mode 100644 index 0000000000..f6a3bbc738 --- /dev/null +++ b/.github/workflows/audits.yml @@ -0,0 +1,42 @@ +name: Audits + +on: + pull_request: + push: + branches: main + +permissions: + contents: read + +jobs: + cargo-vet: + name: Vet Rust dependencies + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + id: toolchain + - run: rustup override set ${{steps.toolchain.outputs.name}} + - run: cargo install cargo-vet --version ~0.10 + - run: cargo vet --locked + + cargo-deny: + name: Check licenses + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: EmbarkStudios/cargo-deny-action@v2 + with: + command: check licenses + + required-checks: + name: Required status checks have passed + needs: + - cargo-vet + - cargo-deny + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Determine whether all required-pass steps succeeded + run: | + echo '${{ toJSON(needs) }}' | jq -e '[ .[] | .result == "success" ] | all' diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index f62273e552..c649b89417 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -3,19 +3,25 @@ name: librustzcash documentation on: push: branches: - - master + - main jobs: deploy: - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare - uses: dtolnay/rust-toolchain@nightly id: toolchain - run: rustup override set ${{steps.toolchain.outputs.name}} - name: Build latest rustdocs - run: cargo doc --no-deps --workspace --all-features + run: > + cargo doc + --no-deps + --workspace + ${{ steps.prepare.outputs.feature-flags }} env: RUSTDOCFLAGS: -Z unstable-options --enable-index-page --cfg docsrs @@ -25,7 +31,7 @@ jobs: mv ./target/doc ./book/book/rustdoc/latest - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./book/book diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0611a39c6c..4aab93a21b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,40 +1,173 @@ -name: CI checks +name: CI -on: [push, pull_request] +on: + pull_request: + push: + branches: main + merge_group: jobs: test: - name: Test on ${{ matrix.os }} + name: > + Test${{ + matrix.state != 'NOT_A_PUZZLE' && format(' {0}', matrix.state) || '' + }} on ${{ matrix.target }} runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.state != 'NOT_A_PUZZLE' }} strategy: matrix: - os: [ubuntu-latest, windows-latest, macOS-latest] + target: + - Linux + - macOS + - Windows + state: + - NOT_A_PUZZLE + - Orchard + - NU7 + + include: + - target: Linux + os: ubuntu-latest-8cores + - target: macOS + os: macOS-latest + - target: Windows + os: windows-latest-8cores + + - state: Orchard + extra_flags: orchard + - state: NU7 + rustflags: '--cfg zcash_unstable="nu7"' + + exclude: + - target: macOS + state: NU7 + + env: + RUSTFLAGS: ${{ matrix.rustflags }} + RUSTDOCFLAGS: ${{ matrix.rustflags }} steps: - - uses: actions/checkout@v3 - - name: Fetch path to Zcash parameters - working-directory: ./zcash_proofs - shell: bash - run: echo "ZCASH_PARAMS=$(cargo run --release --example get-params-path --features directories)" >> $GITHUB_ENV - - name: Cache Zcash parameters - id: cache-params - uses: actions/cache@v3.3.1 + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare with: - path: ${{ env.ZCASH_PARAMS }} - key: ${{ runner.os }}-params - - name: Fetch Zcash parameters - if: steps.cache-params.outputs.cache-hit != 'true' - working-directory: ./zcash_proofs - run: cargo run --release --example download-params --features download-params - + extra-features: ${{ matrix.state != 'NOT_A_PUZZLE' && matrix.extra_flags || '' }} + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-msrv-${{ hashFiles('**/Cargo.lock') }} - name: Run tests - run: cargo test --all-features --verbose --release --all + run: > + cargo test + --workspace + ${{ steps.prepare.outputs.feature-flags }} - name: Run slow tests - run: cargo test --all-features --verbose --release --all -- --ignored + run: > + cargo test + --workspace + ${{ steps.prepare.outputs.feature-flags }} + --features expensive-tests + -- --ignored + - name: Verify working directory is clean + run: git diff --exit-code + + # States that we want to ensure can be built, but that we don't actively run tests for. + check-msrv: + name: > + Check${{ + matrix.state != 'NOT_A_PUZZLE' && format(' {0}', matrix.state) || '' + }} build on ${{ matrix.target }} + runs-on: ${{ matrix.os }} + continue-on-error: ${{ matrix.state != 'NOT_A_PUZZLE' }} + strategy: + matrix: + target: + - Linux + - macOS + - Windows + state: + - ZFuture + + include: + - target: Linux + os: ubuntu-latest + - target: macOS + os: macOS-latest + - target: Windows + os: windows-latest + + - state: ZFuture + rustflags: '--cfg zcash_unstable="zfuture"' + + env: + RUSTFLAGS: ${{ matrix.rustflags }} + RUSTDOCFLAGS: ${{ matrix.rustflags }} + + steps: + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare + with: + extra-features: ${{ matrix.state != 'NOT_A_PUZZLE' && matrix.extra_flags || '' }} + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-msrv-${{ hashFiles('**/Cargo.lock') }} + - name: Run check + run: > + cargo check + --release + --workspace + --tests + ${{ steps.prepare.outputs.feature-flags }} - name: Verify working directory is clean run: git diff --exit-code - build: + build-latest: + name: Latest build on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + steps: + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare + - uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: ${{ runner.os }}-cargo-latest + - uses: dtolnay/rust-toolchain@stable + id: toolchain + - run: rustup override set ${{steps.toolchain.outputs.name}} + - name: Remove lockfile to build with latest dependencies + run: rm Cargo.lock + - name: Build crates + run: > + cargo build + --workspace + --all-targets + ${{ steps.prepare.outputs.feature-flags }} + --verbose + - name: Verify working directory is clean (excluding lockfile) + run: git diff --exit-code ':!Cargo.lock' + + build-nodefault: name: Build target ${{ matrix.target }} runs-on: ubuntu-latest strategy: @@ -43,46 +176,69 @@ jobs: - wasm32-wasi steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + path: crates + # We use a synthetic crate to ensure no dev-dependencies are enabled, which can + # be incompatible with some of these targets. + - name: Create synthetic crate for testing + run: cargo init --lib ci-build + - name: Copy Rust version into synthetic crate + run: cp crates/rust-toolchain.toml ci-build/ + - name: Copy patch directives into synthetic crate + run: | + echo "[patch.crates-io]" >> ./ci-build/Cargo.toml + cat ./crates/Cargo.toml | sed "0,/.\+\(patch.crates.\+\)/d" >> ./ci-build/Cargo.toml + - name: Add zcash_proofs as a dependency of the synthetic crate + working-directory: ./ci-build + run: cargo add --no-default-features --path ../crates/zcash_proofs + - name: Add zcash_client_backend as a dependency of the synthetic crate + working-directory: ./ci-build + run: cargo add --path ../crates/zcash_client_backend + - name: Copy pinned dependencies into synthetic crate + run: cp crates/Cargo.lock ci-build/ - name: Add target + working-directory: ./ci-build run: rustup target add ${{ matrix.target }} - - run: cargo fetch - - name: Build zcash_proofs for target - working-directory: ./zcash_proofs - run: cargo build --verbose --no-default-features --target ${{ matrix.target }} - - name: Build zcash_client_backend for target - working-directory: ./zcash_client_backend - run: cargo build --verbose --no-default-features --target ${{ matrix.target }} + - name: Build for target + working-directory: ./ci-build + run: cargo build --verbose --target ${{ matrix.target }} bitrot: name: Bitrot check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # Build benchmarks to prevent bitrot - name: Build benchmarks run: cargo build --all --benches clippy: name: Clippy (MSRV) - timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare - name: Run clippy uses: actions-rs/clippy-check@v1 with: name: Clippy (MSRV) token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features --all-targets -- -D warnings + args: > + ${{ steps.prepare.outputs.feature-flags }} + --all-targets + -- + -D warnings clippy-beta: name: Clippy (beta) - timeout-minutes: 30 runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare - uses: dtolnay/rust-toolchain@beta id: toolchain - run: rustup override set ${{steps.toolchain.outputs.name}} @@ -92,7 +248,11 @@ jobs: with: name: Clippy (beta) token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features --all-targets -- -W clippy::all + args: > + ${{ steps.prepare.outputs.feature-flags }} + --all-targets + -- + -W clippy::all codecov: name: Code coverage @@ -102,42 +262,125 @@ jobs: options: --security-opt seccomp=unconfined steps: - - uses: actions/checkout@v3 - - name: Fetch path to Zcash parameters - working-directory: ./zcash_proofs - shell: bash - run: echo "ZCASH_PARAMS=$(cargo run --release --example get-params-path --features directories)" >> $GITHUB_ENV - - name: Cache Zcash parameters - id: cache-params - uses: actions/cache@v3.3.1 + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare + - uses: actions/cache@v4 with: - path: ${{ env.ZCASH_PARAMS }} - key: ${{ runner.os }}-params - - name: Fetch Zcash parameters - if: steps.cache-params.outputs.cache-hit != 'true' - working-directory: ./zcash_proofs - run: cargo run --release --example download-params --features download-params - + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: codecov-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Generate coverage report - run: cargo tarpaulin --engine llvm --all-features --release --timeout 600 --out Xml + run: > + cargo tarpaulin + --engine llvm + ${{ steps.prepare.outputs.feature-flags }} + --release + --timeout 600 + --out xml - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3.1.3 + uses: codecov/codecov-action@v4.6.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} doc-links: name: Intra-doc links runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare - run: cargo fetch # Requires #![deny(rustdoc::broken_intra_doc_links)] in crates. - name: Check intra-doc links - run: cargo doc --all --document-private-items + run: > + cargo doc + --all + ${{ steps.prepare.outputs.feature-flags }} + --document-private-items fmt: name: Rustfmt - timeout-minutes: 30 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check formatting run: cargo fmt --all -- --check + + protobuf: + name: protobuf consistency + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - id: prepare + uses: ./.github/actions/prepare + - name: Install protoc + uses: supplypike/setup-bin@v4 + with: + uri: 'https://github.com/protocolbuffers/protobuf/releases/download/v25.1/protoc-25.1-linux-x86_64.zip' + name: 'protoc' + version: '25.1' + subPath: 'bin' + - name: Trigger protobuf regeneration + run: > + cargo check + --workspace + ${{ steps.prepare.outputs.feature-flags }} + - name: Verify working directory is clean + run: git diff --exit-code + + uuid: + name: UUID validity + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Extract UUIDs + id: extract + run: | + { + echo 'UUIDS<> "$GITHUB_OUTPUT" + - name: Check UUID validity + env: + UUIDS: ${{ steps.extract.outputs.UUIDS }} + run: uuidparse -n -o type $UUIDS | xargs -L 1 test "invalid" != + - name: Check UUID type + env: + UUIDS: ${{ steps.extract.outputs.UUIDS }} + run: uuidparse -n -o type $UUIDS | xargs -L 1 test "random" = + - name: Check UUID uniqueness + env: + UUIDS: ${{ steps.extract.outputs.UUIDS }} + run: > + test $( + uuidparse -n -o uuid $U4 | wc -l + ) -eq $( + uuidparse -n -o uuid $U4 | sort | uniq | wc -l + ) + + required-checks: + name: Required status checks have passed + needs: + - test + - check-msrv + - build-latest + - build-nodefault + - bitrot + - clippy + - doc-links + - fmt + - protobuf + - uuid + if: ${{ always() }} + runs-on: ubuntu-latest + steps: + - name: Determine whether all required-pass steps succeeded + run: | + echo '${{ toJSON(needs) }}' | jq -e '[ .[] | .result == "success" ] | all' diff --git a/.gitignore b/.gitignore index fa8d85ac52..eb5a316cbd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -Cargo.lock target diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 001f415bb4..0000000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,66 +0,0 @@ - -# /************************************************************************ - # File: .gitlab-ci.yml - # Author: mdr0id - # Date: 9/10/2018 - # Description: Used to setup runners/jobs for librustzcash - # Usage: Commit source and the pipeline will trigger the according jobs. - # For now the build and test are done in the same jobs. - # - # Known bugs/missing features: - # - # ************************************************************************/ - -stages: - - build - - test - - deploy - -rust-latest: - stage: build - image: rust:latest - script: - - cargo --verbose --version - - time cargo build --verbose - -rust-nightly: - stage: build - image: rustlang/rust:nightly - script: - - cargo --verbose --version - - cargo build --verbose - allow_failure: true - -librustzcash-test-latest: - stage: test - image: rust:latest - script: - - cargo --verbose --version - - time cargo test --release --verbose - -librustzcash-test-rust-nightly: - stage: test - image: rustlang/rust:nightly - script: - - cargo --verbose --version - - cargo test --release --verbose - allow_failure: true - -#used to manually deploy a given release -librustzcash-rust-rc: - stage: deploy - image: rust:latest - script: - - cargo --verbose --version - - time cargo build --release --verbose - when: manual - -#used to manually deploy a given release -librustzcash-rust-nightly-rc: - stage: deploy - image: rustlang/rust:nightly - script: - - cargo --verbose --version - - cargo build --release --verbose - allow_failure: true - when: manual diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..b31e2934f4 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "rust-analyzer.cargo.features": "all", + "rust-analyzer.server.extraEnv": { "RUSTUP_TOOLCHAIN": "stable" } +} diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..a0390d68d1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,6384 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "ambassador" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b27ba24e4d8a188489d5a03c7fabc167a60809a383cdb4d15feb37479cd2a48" +dependencies = [ + "itertools 0.10.5", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "amplify" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e711289a6cb28171b4f0e6c8019c69ff9476050508dc082167575d458ff74d0" +dependencies = [ + "amplify_derive", + "amplify_num", + "ascii", + "wasm-bindgen", +] + +[[package]] +name = "amplify_derive" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759dcbfaf94d838367a86d493ec34ccc8aa6fe365cb7880d6bf89006de24d9c1" +dependencies = [ + "amplify_syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "amplify_num" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c009c5c4de814911b177e2ea59e4930bb918978ed3cce4900d846a6ceb0838" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "amplify_syn" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7736fb8d473c0d83098b5bac44df6a561e20470375cd8bcae30516dc889fd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "anyhow" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "arti-client" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a726d39f22ddf0dcf0ea903d900592167d60a32e46f202e83651a1be8c452e" +dependencies = [ + "async-trait", + "cfg-if", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "educe", + "fs-mistrust", + "futures", + "hostname-validator", + "humantime", + "humantime-serde", + "libc", + "postage", + "rand", + "safelog", + "serde", + "thiserror", + "tor-async-utils", + "tor-basic-utils", + "tor-chanmgr", + "tor-circmgr", + "tor-config", + "tor-dirmgr", + "tor-error", + "tor-guardmgr", + "tor-keymgr", + "tor-linkspec", + "tor-llcrypto", + "tor-memquota", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "async-compression" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +dependencies = [ + "flate2", + "futures-core", + "futures-io", + "memchr", + "pin-project-lite", + "xz2", + "zstd", + "zstd-safe", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "async_executors" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a982d2f86de6137cc05c9db9a915a19886c97911f9790d04f174cede74be01a5" +dependencies = [ + "blanket", + "futures-core", + "futures-task", + "futures-util", + "pin-project", + "rustc_version", + "tokio", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "axum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 1.0.1", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 0.1.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + +[[package]] +name = "bellman" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afceed28bac7f9f5a508bca8aeeff51cdfa4770c0b967ac55c621e2ddfd6171" +dependencies = [ + "bitvec", + "blake2s_simd", + "byteorder", + "crossbeam-channel", + "ff", + "group", + "lazy_static", + "log", + "num_cpus", + "pairing", + "rand_core", + "rayon", + "subtle", +] + +[[package]] +name = "bip0039" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68a5a99c65851e7be249f5cf510c0a136f18c9bca32139576d59bd3f577b043" +dependencies = [ + "hmac", + "pbkdf2", + "rand", + "sha2", + "unicode-normalization", + "zeroize", +] + +[[package]] +name = "bip32" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e141fb0f8be1c7b45887af94c88b182472b57c96b56773250ae00cd6a14a164" +dependencies = [ + "bs58", + "hmac", + "rand_core", + "ripemd", + "secp256k1", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blake2s_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "blanket" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "group", + "pairing", + "rand_core", + "subtle", +] + +[[package]] +name = "bounded-vec-deque" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2225b558afc76c596898f5f1b3fc35cfce0eb1b13635cbd7d1b2a7177dc10ccd" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "sha2", + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "by_address" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" + +[[package]] +name = "bytemuck" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "caret" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df55dc0c84d5a555c4b8b84ecf3cff724df77a7b1a8c4a70cd66a981524cff0" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "coarsetime" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +dependencies = [ + "libc", + "wasix", + "wasm-bindgen", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpp_demangle" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8227005286ec39567949b33df9896bcadfa6051bccca2488129f108ca23119" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "daggy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a9304e55e9d601a39ae4deaba85406d5c0980e106f65afcf0460e9af1e7602" +dependencies = [ + "petgraph", +] + +[[package]] +name = "darling" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" +dependencies = [ + "darling_core 0.14.4", + "darling_macro 0.14.4", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core 0.20.10", + "darling_macro 0.20.10", +] + +[[package]] +name = "darling_core" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.63", +] + +[[package]] +name = "darling_macro" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" +dependencies = [ + "darling_core 0.14.4", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core 0.20.10", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive-adhoc" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5283ac2881753c76c0892406705553f0d9ab30649f81e18964d3408f4501edb8" +dependencies = [ + "derive-adhoc-macros", + "heck 0.4.1", +] + +[[package]] +name = "derive-adhoc-macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c21b673a9b8c78c34908e6fcb42b922e11c4df2de5237f1c3f58d3285904a84b" +dependencies = [ + "heck 0.4.1", + "itertools 0.11.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "sha3", + "strum 0.25.0", + "syn 1.0.109", + "void", +] + +[[package]] +name = "derive-deftly" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f9bc3564f74be6c35d49a7efee54380d7946ccc631323067f33fabb9246027" +dependencies = [ + "derive-deftly-macros", + "heck 0.5.0", +] + +[[package]] +name = "derive-deftly-macros" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b84d32b18d9a256d81e4fec2e4cfd0ab6dde5e5ff49be1713ae0adbd0060c2" +dependencies = [ + "heck 0.5.0", + "indexmap 2.6.0", + "itertools 0.13.0", + "proc-macro-crate", + "proc-macro2", + "quote", + "sha3", + "strum 0.26.3", + "syn 2.0.63", + "void", +] + +[[package]] +name = "derive_builder_core_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24c1b715c79be6328caa9a5e1a387a196ea503740f0722ec3dd8f67a9e72314d" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3eae24d595f4d0ecc90a9a5a6d11c2bd8dafe2375ec4a1ec63250e5ade7d228" +dependencies = [ + "derive_builder_macro_fork_arti", +] + +[[package]] +name = "derive_builder_macro_fork_arti" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69887769a2489cd946bf782eb2b1bb2cb7bc88551440c94a765d4f040c08ebf3" +dependencies = [ + "derive_builder_core_fork_arti", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.63", + "unicode-xid", +] + +[[package]] +name = "devtools" +version = "0.0.0" +dependencies = [ + "anyhow", + "bech32", + "bellman", + "bip0039", + "blake2b_simd", + "ed25519-zebra", + "equihash", + "group", + "gumdrop", + "hex", + "jubjub", + "lazy_static", + "orchard", + "sapling-crypto", + "secp256k1", + "secrecy", + "serde", + "serde_json", + "sha2", + "tokio", + "tonic", + "uint", + "zcash_address", + "zcash_client_backend", + "zcash_encoding", + "zcash_keys", + "zcash_note_encryption", + "zcash_primitives", + "zcash_proofs", + "zcash_protocol", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "directories" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "displaydoc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "document-features" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +dependencies = [ + "litrs", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "dynosaur" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47baef31f408cc1a54eadcb0160ffe2b935b5963bd2b519719f3a101749af524" +dependencies = [ + "dynosaur_derive", + "trait-variant", +] + +[[package]] +name = "dynosaur_derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320c6861ca45ea23faee2bd86a6c7f20ec4b7a64a2773971cd4f52ae61615463" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "serde", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "merlin", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.14.5", + "hex", + "rand_core", + "serde", + "sha2", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f0042ff8246a363dbe77d2ceedb073339e85a804b9a47636c6e016a9a32c05f" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "3.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf1fa3f06bbff1ea5b1a9c7b14aa992a39657db60a2759457328d7e058f49ee" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "equihash" +version = "0.2.0" +dependencies = [ + "blake2b_simd", + "byteorder", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "f4jumble" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "hex", + "proptest", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "figment" +version = "0.10.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" +dependencies = [ + "atomic 0.6.0", + "serde", + "toml", + "uncased", + "version_check", +] + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys 0.59.0", +] + +[[package]] +name = "findshlibs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b9e59cd0f7e0806cca4be089683ecb6434e602038df21fe6bf6711b2f07f64" +dependencies = [ + "cc", + "lazy_static", + "libc", + "winapi", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fluid-let" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749cff877dc1af878a0b31a41dd221a753634401ea0ef2f87b62d3171522485a" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fpe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4b37de5ae15812a764c958297cfc50f5c010438f60c6ce75d11b802abd404" +dependencies = [ + "cbc", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "fs-mistrust" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf84fbaf375e6a485fa126f6335c0cfa7741114aa4f86ba37960a42cd1994b8" +dependencies = [ + "derive_builder_fork_arti", + "dirs", + "libc", + "once_cell", + "pwd-grp", + "serde", + "thiserror", + "walkdir", +] + +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls 0.23.12", + "rustls-pki-types", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob-match" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985c9503b412198aa4197559e9a318524ebc4519c229bfa05a535828c950b9d" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "memuse", + "rand_core", + "subtle", +] + +[[package]] +name = "gumdrop" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +dependencies = [ + "gumdrop_derive", +] + +[[package]] +name = "gumdrop_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.6.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + +[[package]] +name = "halo2_gadgets" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126a150072b0c38c7b573fe3eaf0af944a7fed09e154071bf2436d3f016f7230" +dependencies = [ + "arrayvec", + "bitvec", + "ff", + "group", + "halo2_proofs", + "lazy_static", + "pasta_curves", + "rand", + "subtle", + "uint", +] + +[[package]] +name = "halo2_legacy_pdqsort" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47716fe1ae67969c5e0b2ef826f32db8c3be72be325e1aa3c1951d06b5575ec5" + +[[package]] +name = "halo2_proofs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b867a8d9bbb85fca76fff60652b5cd19b853a1c4d0665cb89bee68b18d2caf0" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2_legacy_pdqsort", + "maybe-rayon", + "pasta_curves", + "rand_core", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "hostname-validator" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "humantime-serde" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" +dependencies = [ + "humantime", + "serde", +] + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "incrementalmerkletree" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45063fbc4b0a37837f6bfe0445f269d13d730ad0aa3b5a7f74aa7bf27a0f4df" +dependencies = [ + "either", + "proptest", + "rand", + "rand_core", +] + +[[package]] +name = "incrementalmerkletree-testing" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc7fb094e413bc6daea7b30a6f2c749e47fd07e98691c6ef3b3423d4ef4b7fb6" +dependencies = [ + "incrementalmerkletree", + "proptest", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", + "serde", +] + +[[package]] +name = "inferno" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "321f0f839cd44a4686e9504b0a62b4d69a50b62072144c71c68f5873c167b8d9" +dependencies = [ + "ahash", + "indexmap 2.6.0", + "is-terminal", + "itoa", + "log", + "num-format", + "once_cell", + "quick-xml", + "rgb", + "str_stack", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "jubjub" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core", + "subtle", +] + +[[package]] +name = "keccak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "known-folders" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4397c789f2709d23cfcb703b316e0766a8d4b17db2d47b0ab096ef6047cae1d8" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lzma-sys" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "memuse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" +dependencies = [ + "nonempty", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "minreq" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0" +dependencies = [ + "log", + "once_cell", + "rustls 0.21.12", + "rustls-webpki 0.101.7", + "webpki-roots 0.25.4", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.6.0", + "filetime", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "oneshot-fused-workaround" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f49cbc8293c5ba37516d29aba392a94a34638367d17d67617cea34e4f9acd05" +dependencies = [ + "futures", +] + +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "orchard" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f18e997fa121de5c73e95cdc7e8512ae43b7de38904aeea5e5713cc48f3c0ba" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "ff", + "fpe", + "group", + "halo2_gadgets", + "halo2_proofs", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "proptest", + "rand", + "reddsa", + "serde", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" +dependencies = [ + "base16ct", + "ecdsa", + "elliptic-curve", + "primeorder", + "rand_core", + "sha2", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "password-hash" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand", + "static_assertions", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest", + "password-hash", +] + +[[package]] +name = "pczt" +version = "0.0.0" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap 2.6.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[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.63", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "postage" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af3fb618632874fb76937c2361a7f22afd393c982a2165595407edc75b06d3c1" +dependencies = [ + "atomic 0.5.3", + "crossbeam-queue", + "futures", + "parking_lot", + "pin-project", + "static_assertions", + "thiserror", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "pprof" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef5c97c51bd34c7e742402e216abdeb44d415fbe6ae41d56b114723e953711cb" +dependencies = [ + "backtrace", + "cfg-if", + "criterion", + "findshlibs", + "inferno", + "libc", + "log", + "nix", + "once_cell", + "parking_lot", + "smallvec", + "symbolic-demangle", + "tempfile", + "thiserror", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.63", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "uint", +] + +[[package]] +name = "priority-queue" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" +dependencies = [ + "autocfg", + "equivalent", + "indexmap 2.6.0", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.6.0", + "lazy_static", + "num-traits", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax 0.8.4", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13db3d3fde688c61e2446b4d843bc27a7e8af269a69440c0308021dc92333cc" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb182580f71dd070f88d01ce3de9f4da5021db7115d2e1c3605a754153b77c1" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.13.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.63", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18bec9b0adc4eba778b33684b7ba3e7137789434769ee3ce3930463ef904cfca" +dependencies = [ + "anyhow", + "itertools 0.13.0", + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "prost-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee5168b05f49d4b0ca581206eb14a7b22fafd963efe729ac48eb03266e25cc2" +dependencies = [ + "prost", +] + +[[package]] +name = "pwd-grp" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6955c41fd7e4283bdf6ff3e7218b7e3f8ef24c4236b31d22be050f4cfd5e2a2c" +dependencies = [ + "derive-adhoc", + "libc", + "paste", + "thiserror", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "reddsa" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a5191930e84973293aa5f532b513404460cd2216c1cfb76d08748c15b40b02" +dependencies = [ + "blake2b_simd", + "byteorder", + "group", + "hex", + "jubjub", + "pasta_curves", + "rand_core", + "serde", + "thiserror", + "zeroize", +] + +[[package]] +name = "redjubjub" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a60db2c3bc9c6fd1e8631fee75abc008841d27144be744951d6b9b75f9b569c" +dependencies = [ + "rand_core", + "reddsa", + "serde", + "thiserror", + "zeroize", +] + +[[package]] +name = "redox_syscall" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + +[[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 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "retry-error" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ef93545b13f6dd83a9f98c8a656ccbd7bc2b95a747844346c70f7e6f5a5932" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rgb" +version = "0.8.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05aaa8004b64fd573fc9d002f4e632d51ad4f026c2b5ba95fcb6c2f32c2c47d8" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted 0.7.1", + "web-sys", + "winapi", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest", +] + +[[package]] +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "sha2", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rusqlite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7753b721174eb8ff87a9a0e799e2d7bc3749323e773db92e0984debb00019d6e" +dependencies = [ + "bitflags 2.6.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", + "time", + "uuid", +] + +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "num-traits", + "serde", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "log", + "once_cell", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.6", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safelog" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3939f5e7c65f96a54e7d2a6853e3994d691c95f54f5263916a2d1877dad70789" +dependencies = [ + "derive_more", + "educe", + "either", + "fluid-let", + "thiserror", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sanitize-filename" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "sapling-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfff8cfce16aeb38da50b8e2ed33c9018f30552beff2210c266662a021b17f38" +dependencies = [ + "aes", + "bellman", + "bitvec", + "blake2b_simd", + "blake2s_simd", + "bls12_381", + "byteorder", + "document-features", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "lazy_static", + "memuse", + "proptest", + "rand", + "rand_core", + "redjubjub", + "subtle", + "tracing", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + +[[package]] +name = "schemerz" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e82960ac11ccabd77d53c933532612079e01205b5873ec5095f4b3426493434" +dependencies = [ + "daggy", + "indexmap 1.9.3", + "log", + "thiserror", + "uuid", +] + +[[package]] +name = "schemerz-rusqlite" +version = "0.320.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ff99b7d9e8790fb20a7e52a482f66fddb3c28c3ce700c6c2665cacbf1b5529" +dependencies = [ + "rusqlite", + "schemerz", + "uuid", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring 0.17.8", + "untrusted 0.9.0", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25996b82292a7a57ed3508f052cfff8640d38d32018784acd714758b43da9c8f" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a129b9e9efbfb223753b9163c4ab3b13cff7fd9c7f010fbac25ab4099fa07e" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "serde_ignored" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e319a36d1b52126a0d608f24e93b2d81297091818cd70625fcf50a15d84ddf" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_with" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.6.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +dependencies = [ + "darling 0.20.10", + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shardtree" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f2390975ebfe8838f9e861f7a588123d49a7a7a0a08568ea831d8ad53fc9b4" +dependencies = [ + "assert_matches", + "bitflags 2.6.0", + "either", + "incrementalmerkletree", + "incrementalmerkletree-testing", + "proptest", + "tracing", +] + +[[package]] +name = "shellexpand" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +dependencies = [ + "dirs", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ssh-cipher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caac132742f0d33c3af65bfcde7f6aa8f62f0e991d80db99149eb9d44708784f" +dependencies = [ + "cipher", + "ssh-encoding", +] + +[[package]] +name = "ssh-encoding" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" +dependencies = [ + "base64ct", + "pem-rfc7468", + "sha2", +] + +[[package]] +name = "ssh-key" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" +dependencies = [ + "p256", + "p384", + "p521", + "rand_core", + "rsa", + "sec1", + "sha2", + "signature", + "ssh-cipher", + "ssh-encoding", + "subtle", + "zeroize", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros 0.25.3", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.63", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.63", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "symbolic-common" +version = "12.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71297dc3e250f7dbdf8adb99e235da783d690f5819fdeb4cce39d9cfb0aca9f1" +dependencies = [ + "debugid", + "memmap2", + "stable_deref_trait", + "uuid", +] + +[[package]] +name = "symbolic-demangle" +version = "12.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424fa2c9bf2c862891b9cfd354a752751a6730fd838a4691e7f6c2c7957b9daf" +dependencies = [ + "cpp_demangle", + "rustc-demangle", + "symbolic-common", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "tokio-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.22", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.6.0", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.6.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.20", +] + +[[package]] +name = "tonic" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38659f4a91aba8598d27821589f5db7dddd94601e7a01b1e485a50e5484c7401" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "flate2", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-pemfile", + "socket2", + "tokio", + "tokio-rustls 0.26.0", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", + "webpki-roots 0.26.3", +] + +[[package]] +name = "tonic-build" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "568392c5a2bd0020723e3f387891176aabafe36fd9fcd074ad309dfa0c8eb964" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "tor-async-utils" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2adb0fa957cad5a1f408e357a4450931366a9d35b78235d72260b6842518ba" +dependencies = [ + "educe", + "futures", + "oneshot-fused-workaround", + "pin-project", + "postage", + "void", +] + +[[package]] +name = "tor-basic-utils" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d080757c5d7404813f936147e3d72f7c035eba541c1422de29acd2242b98f36" +dependencies = [ + "derive_more", + "hex", + "itertools 0.13.0", + "libc", + "paste", + "rand", + "rand_chacha", + "serde", + "slab", + "thiserror", +] + +[[package]] +name = "tor-bytes" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23db3a71001bc0a3f25b0f6ee824b559d128768275811de0d913369990f9a83b" +dependencies = [ + "bytes", + "derive-deftly", + "digest", + "educe", + "getrandom", + "safelog", + "thiserror", + "tor-error", + "tor-llcrypto", + "zeroize", +] + +[[package]] +name = "tor-cell" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "637cf998679d463138d9bffe07c55bec1c3ccc43a66e4a587d8952ceaa6ee7d4" +dependencies = [ + "bitflags 2.6.0", + "bytes", + "caret", + "derive_more", + "educe", + "paste", + "rand", + "smallvec", + "thiserror", + "tor-basic-utils", + "tor-bytes", + "tor-cert", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-units", +] + +[[package]] +name = "tor-cert" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "437567f6b43fab396478d01ff15ce717587bda41a15f06c4de87f8fee1fefbb0" +dependencies = [ + "caret", + "derive_more", + "digest", + "thiserror", + "tor-bytes", + "tor-checkable", + "tor-llcrypto", +] + +[[package]] +name = "tor-chanmgr" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc267bf4aba055859d51c34dcab067c109b89cefed9501409377772cbb296d8b" +dependencies = [ + "async-trait", + "derive_builder_fork_arti", + "derive_more", + "educe", + "futures", + "oneshot-fused-workaround", + "postage", + "rand", + "safelog", + "serde", + "thiserror", + "tor-async-utils", + "tor-basic-utils", + "tor-cell", + "tor-config", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdir", + "tor-proto", + "tor-rtcompat", + "tor-socksproto", + "tor-units", + "tracing", + "void", +] + +[[package]] +name = "tor-checkable" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a052723dc4fc53605232652d75997940cd51abc4cd3ff74daca746c150f5ac7" +dependencies = [ + "humantime", + "signature", + "thiserror", + "tor-llcrypto", +] + +[[package]] +name = "tor-circmgr" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00458ecadba3e0f8566f6be9fcf41bd2a50b3f45f0a349057510b2d37322c21" +dependencies = [ + "amplify", + "async-trait", + "bounded-vec-deque", + "cfg-if", + "derive_builder_fork_arti", + "derive_more", + "downcast-rs", + "dyn-clone", + "educe", + "futures", + "humantime-serde", + "itertools 0.13.0", + "once_cell", + "oneshot-fused-workaround", + "pin-project", + "rand", + "retry-error", + "safelog", + "serde", + "static_assertions", + "thiserror", + "tor-async-utils", + "tor-basic-utils", + "tor-chanmgr", + "tor-config", + "tor-error", + "tor-guardmgr", + "tor-linkspec", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-protover", + "tor-relay-selection", + "tor-rtcompat", + "tracing", + "void", + "weak-table", +] + +[[package]] +name = "tor-config" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7281aaa74794b23cf570547ff7706fbca5f8564d421921943420f2c70ddecfa" +dependencies = [ + "amplify", + "derive-deftly", + "derive_builder_fork_arti", + "directories", + "educe", + "either", + "figment", + "fs-mistrust", + "futures", + "itertools 0.13.0", + "notify", + "once_cell", + "paste", + "postage", + "regex", + "serde", + "serde-value", + "serde_ignored", + "shellexpand", + "strum 0.26.3", + "thiserror", + "toml", + "tor-basic-utils", + "tor-error", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-consdiff" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0da0ead1be2509af9558ff9b8004d25e7e4795a3dfa4d78968d8c43d231e76c7" +dependencies = [ + "digest", + "hex", + "thiserror", + "tor-llcrypto", +] + +[[package]] +name = "tor-dirclient" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b3fb55282536a5aaf998ceaa1d2f5a268119c3b3cf8de741eaf98f5ed5af079" +dependencies = [ + "async-compression", + "base64ct", + "derive_more", + "futures", + "hex", + "http", + "httparse", + "httpdate", + "itertools 0.13.0", + "memchr", + "thiserror", + "tor-circmgr", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdoc", + "tor-proto", + "tor-rtcompat", + "tracing", +] + +[[package]] +name = "tor-dirmgr" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf47d53f83a55f4cc61fa77bcd051c283d1a1da60f67e9370d845df3480256" +dependencies = [ + "async-trait", + "base64ct", + "derive_builder_fork_arti", + "derive_more", + "digest", + "educe", + "event-listener", + "fs-mistrust", + "fslock", + "futures", + "hex", + "humantime", + "humantime-serde", + "itertools 0.13.0", + "memmap2", + "once_cell", + "oneshot-fused-workaround", + "paste", + "postage", + "rand", + "rusqlite", + "safelog", + "scopeguard", + "serde", + "signature", + "strum 0.26.3", + "thiserror", + "time", + "tor-async-utils", + "tor-basic-utils", + "tor-checkable", + "tor-circmgr", + "tor-config", + "tor-consdiff", + "tor-dirclient", + "tor-error", + "tor-guardmgr", + "tor-llcrypto", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-rtcompat", + "tracing", +] + +[[package]] +name = "tor-error" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d764640168b176da6c44e4d5ceda97db3aca89d6f7e5ee398f454c56ba6a34c2" +dependencies = [ + "derive_more", + "futures", + "once_cell", + "paste", + "retry-error", + "static_assertions", + "strum 0.26.3", + "thiserror", + "tracing", + "void", +] + +[[package]] +name = "tor-guardmgr" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82444165f5f3a2587e4c93258c7a554718a4985780d262ae158a5e24fcbee019" +dependencies = [ + "amplify", + "base64ct", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "dyn-clone", + "educe", + "futures", + "humantime", + "humantime-serde", + "itertools 0.13.0", + "num_enum", + "oneshot-fused-workaround", + "pin-project", + "postage", + "rand", + "safelog", + "serde", + "strum 0.26.3", + "thiserror", + "tor-async-utils", + "tor-basic-utils", + "tor-config", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdir", + "tor-netdoc", + "tor-persist", + "tor-proto", + "tor-relay-selection", + "tor-rtcompat", + "tor-units", + "tracing", +] + +[[package]] +name = "tor-hscrypto" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eda7d9ec707605c7bdd71c29a94e5cf6c1a61ed42f815b5dd2da3f1546393ca1" +dependencies = [ + "data-encoding", + "derive_more", + "digest", + "itertools 0.13.0", + "paste", + "rand", + "safelog", + "signature", + "subtle", + "thiserror", + "tor-basic-utils", + "tor-bytes", + "tor-error", + "tor-llcrypto", + "tor-units", +] + +[[package]] +name = "tor-key-forge" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c2a026f0a21f9870799eb3ac24340bfa71efb6126d9c7294a6d6c401a8ba67" +dependencies = [ + "derive-deftly", + "derive_more", + "downcast-rs", + "paste", + "rand", + "signature", + "ssh-key", + "thiserror", + "tor-error", + "tor-hscrypto", + "tor-llcrypto", +] + +[[package]] +name = "tor-keymgr" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f66ea6684cd1110c8b405443611b1c8f24cca4d8ca05a41ee747ea4479ad4c5c" +dependencies = [ + "amplify", + "arrayvec", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "downcast-rs", + "dyn-clone", + "fs-mistrust", + "glob-match", + "humantime", + "inventory", + "itertools 0.13.0", + "rand", + "serde", + "ssh-key", + "thiserror", + "tor-basic-utils", + "tor-config", + "tor-error", + "tor-hscrypto", + "tor-key-forge", + "tor-llcrypto", + "tor-persist", + "tracing", + "walkdir", + "zeroize", +] + +[[package]] +name = "tor-linkspec" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53ccbd9372da197987699399c88695ecd1737c2b0e6267c540a3febdf92b5643" +dependencies = [ + "base64ct", + "by_address", + "caret", + "derive-deftly", + "derive_builder_fork_arti", + "derive_more", + "hex", + "itertools 0.13.0", + "safelog", + "serde", + "serde_with", + "strum 0.26.3", + "thiserror", + "tor-basic-utils", + "tor-bytes", + "tor-config", + "tor-llcrypto", + "tor-protover", +] + +[[package]] +name = "tor-llcrypto" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b0c146b5c77f609dda6992f60940586fee2aa711dab5d59b3ecc7c8c958d787" +dependencies = [ + "aes", + "base64ct", + "ctr", + "curve25519-dalek", + "derive_more", + "digest", + "ed25519-dalek", + "educe", + "getrandom", + "hex", + "rand_core", + "rsa", + "safelog", + "serde", + "sha1", + "sha2", + "sha3", + "signature", + "simple_asn1", + "subtle", + "thiserror", + "visibility", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "tor-log-ratelim" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6fada5f9dae169bd0caf2d61932c528e8136790a5a553eac9de30d6290fe7e" +dependencies = [ + "futures", + "humantime", + "once_cell", + "thiserror", + "tor-error", + "tor-rtcompat", + "tracing", + "weak-table", +] + +[[package]] +name = "tor-memquota" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12551616f15f9d1fb4e37ee14ffc0b6743d0c31affbb4643374f456c0a820073" +dependencies = [ + "derive-deftly", + "derive_more", + "educe", + "futures", + "pin-project", + "serde", + "slotmap", + "static_assertions", + "thiserror", + "tor-async-utils", + "tor-basic-utils", + "tor-config", + "tor-error", + "tor-log-ratelim", + "tor-rtcompat", + "tracing", + "void", +] + +[[package]] +name = "tor-netdir" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9b832338169a882cb895c79ea60089641749e1c2d6d4f7b8e23cc010185dfa" +dependencies = [ + "bitflags 2.6.0", + "derive_more", + "futures", + "humantime", + "itertools 0.13.0", + "num_enum", + "rand", + "serde", + "static_assertions", + "strum 0.26.3", + "thiserror", + "tor-basic-utils", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-netdoc", + "tor-protover", + "tor-units", + "tracing", + "typed-index-collections", +] + +[[package]] +name = "tor-netdoc" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a5fbfe1444d96e08bb97cdf717a62738e5f1bd5a594c5520a05f683b8a516e3" +dependencies = [ + "amplify", + "base64ct", + "bitflags 2.6.0", + "cipher", + "derive_builder_fork_arti", + "derive_more", + "digest", + "educe", + "hex", + "humantime", + "itertools 0.13.0", + "once_cell", + "phf", + "serde", + "serde_with", + "signature", + "smallvec", + "subtle", + "thiserror", + "time", + "tinystr", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-cert", + "tor-checkable", + "tor-error", + "tor-llcrypto", + "tor-protover", + "void", + "weak-table", + "zeroize", +] + +[[package]] +name = "tor-persist" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5acf1de8a8ad4817d8ebdf1250bddbe9efa4b25dea0a339ae8dbb1f7d7f28583" +dependencies = [ + "derive-deftly", + "derive_more", + "filetime", + "fs-mistrust", + "fslock", + "futures", + "itertools 0.13.0", + "oneshot-fused-workaround", + "paste", + "sanitize-filename", + "serde", + "serde_json", + "thiserror", + "tor-async-utils", + "tor-basic-utils", + "tor-error", + "tracing", + "void", +] + +[[package]] +name = "tor-proto" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "073a996250f73d7a814d9049de321a1f59a710dc18b92a4da8d26f002a5735dd" +dependencies = [ + "asynchronous-codec", + "bitvec", + "bytes", + "cipher", + "coarsetime", + "derive_builder_fork_arti", + "derive_more", + "digest", + "educe", + "futures", + "hkdf", + "hmac", + "oneshot-fused-workaround", + "pin-project", + "rand", + "rand_core", + "safelog", + "subtle", + "thiserror", + "tokio", + "tokio-util", + "tor-async-utils", + "tor-basic-utils", + "tor-bytes", + "tor-cell", + "tor-cert", + "tor-checkable", + "tor-config", + "tor-error", + "tor-linkspec", + "tor-llcrypto", + "tor-log-ratelim", + "tor-rtcompat", + "tor-rtmock", + "tor-units", + "tracing", + "typenum", + "void", + "zeroize", +] + +[[package]] +name = "tor-protover" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae806e57efcbd59694fd3c3c8100bc7dabc2dfd0bda70f122c7489fc0e889704" +dependencies = [ + "caret", + "thiserror", +] + +[[package]] +name = "tor-relay-selection" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca1a4d767cd52ee8723dfe9dc3ec640b7a37527e009bc0860301a1aad249871" +dependencies = [ + "rand", + "serde", + "tor-basic-utils", + "tor-linkspec", + "tor-netdir", + "tor-netdoc", +] + +[[package]] +name = "tor-rtcompat" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df693852fb9a1c1a8bca1673fa3a8e1bca929fb2753165b1efce70f9381323b0" +dependencies = [ + "async-trait", + "async_executors", + "coarsetime", + "derive_more", + "educe", + "futures", + "futures-rustls", + "paste", + "pin-project", + "rustls-pki-types", + "thiserror", + "tokio", + "tokio-util", + "tor-error", + "tracing", + "void", + "x509-signature", +] + +[[package]] +name = "tor-rtmock" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f540f59b3194f4ad697d6ffa13013e2b92e1058cd1b3beb9c8a500d2b805a92" +dependencies = [ + "amplify", + "async-trait", + "derive-deftly", + "derive_more", + "educe", + "futures", + "humantime", + "itertools 0.13.0", + "oneshot-fused-workaround", + "pin-project", + "priority-queue", + "slotmap", + "strum 0.26.3", + "thiserror", + "tor-error", + "tor-rtcompat", + "tracing", + "tracing-test", + "void", +] + +[[package]] +name = "tor-socksproto" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308849623b9aa2a604c42a68081f7956ea6d4b5f378dd94ef0a36f05e86399ff" +dependencies = [ + "caret", + "subtle", + "thiserror", + "tor-bytes", + "tor-error", +] + +[[package]] +name = "tor-units" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de60d366ac1128f4cd3e1bae601a3d0393cd0c4e9e0e05eee605d28a8a5ce704" +dependencies = [ + "derive_more", + "thiserror", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +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", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +dependencies = [ + "quote", + "syn 2.0.63", +] + +[[package]] +name = "trait-variant" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-index-collections" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183496e014253d15abbe6235677b1392dba2d40524c88938991226baa38ac7c4" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "wagyu-zcash-parameters" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c904628658374e651288f000934c33ef738b2d8b3e65d4100b70b395dbe2bb" +dependencies = [ + "wagyu-zcash-parameters-1", + "wagyu-zcash-parameters-2", + "wagyu-zcash-parameters-3", + "wagyu-zcash-parameters-4", + "wagyu-zcash-parameters-5", + "wagyu-zcash-parameters-6", +] + +[[package]] +name = "wagyu-zcash-parameters-1" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bf2e21bb027d3f8428c60d6a720b54a08bf6ce4e6f834ef8e0d38bb5695da8" + +[[package]] +name = "wagyu-zcash-parameters-2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616ab2e51e74cc48995d476e94de810fb16fc73815f390bf2941b046cc9ba2c" + +[[package]] +name = "wagyu-zcash-parameters-3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14da1e2e958ff93c0830ee68e91884069253bf3462a67831b02b367be75d6147" + +[[package]] +name = "wagyu-zcash-parameters-4" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f058aeef03a2070e8666ffb5d1057d8bb10313b204a254a6e6103eb958e9a6d6" + +[[package]] +name = "wagyu-zcash-parameters-5" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffe916b30e608c032ae1b734f02574a3e12ec19ab5cc5562208d679efe4969d" + +[[package]] +name = "wagyu-zcash-parameters-6" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasix" +version = "0.12.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +dependencies = [ + "wasi", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.63", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "weak-table" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix", + "winsafe", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +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-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[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.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +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", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "x509-signature" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb2bc2a902d992cd5f471ee3ab0ffd6603047a4207384562755b9d6de977518" +dependencies = [ + "ring 0.16.20", + "untrusted 0.7.1", +] + +[[package]] +name = "xdg" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" + +[[package]] +name = "xz2" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +dependencies = [ + "lzma-sys", +] + +[[package]] +name = "zcash" +version = "0.1.0" +dependencies = [ + "document-features", + "zcash_primitives", +] + +[[package]] +name = "zcash_address" +version = "0.6.0" +dependencies = [ + "assert_matches", + "bech32", + "bs58", + "f4jumble", + "proptest", + "zcash_encoding", + "zcash_protocol", +] + +[[package]] +name = "zcash_client_backend" +version = "0.15.0" +dependencies = [ + "ambassador", + "arti-client", + "assert_matches", + "async-trait", + "base64", + "bech32", + "bip32", + "bls12_381", + "bs58", + "byteorder", + "crossbeam-channel", + "document-features", + "dynosaur", + "futures-util", + "group", + "gumdrop", + "hex", + "http-body-util", + "hyper", + "hyper-util", + "incrementalmerkletree", + "jubjub", + "memuse", + "nom", + "nonempty", + "orchard", + "pasta_curves", + "percent-encoding", + "proptest", + "prost", + "rand", + "rand_chacha", + "rand_core", + "rayon", + "rust_decimal", + "sapling-crypto", + "secrecy", + "serde", + "serde_json", + "shardtree", + "subtle", + "time", + "tokio", + "tokio-rustls 0.24.0", + "tonic", + "tonic-build", + "tor-rtcompat", + "tower", + "tracing", + "trait-variant", + "webpki-roots 0.25.4", + "which", + "zcash_address", + "zcash_encoding", + "zcash_keys", + "zcash_note_encryption", + "zcash_primitives", + "zcash_proofs", + "zcash_protocol", + "zip32", + "zip321", +] + +[[package]] +name = "zcash_client_sqlite" +version = "0.13.0" +dependencies = [ + "ambassador", + "assert_matches", + "bip32", + "bls12_381", + "bs58", + "byteorder", + "document-features", + "group", + "incrementalmerkletree", + "incrementalmerkletree-testing", + "jubjub", + "maybe-rayon", + "nonempty", + "orchard", + "pasta_curves", + "proptest", + "prost", + "rand_chacha", + "rand_core", + "regex", + "rusqlite", + "sapling-crypto", + "schemerz", + "schemerz-rusqlite", + "secrecy", + "shardtree", + "static_assertions", + "subtle", + "tempfile", + "time", + "tracing", + "uuid", + "zcash_address", + "zcash_client_backend", + "zcash_encoding", + "zcash_keys", + "zcash_note_encryption", + "zcash_primitives", + "zcash_proofs", + "zcash_protocol", + "zip32", + "zip321", +] + +[[package]] +name = "zcash_encoding" +version = "0.2.1" +dependencies = [ + "byteorder", + "nonempty", +] + +[[package]] +name = "zcash_extensions" +version = "0.1.0" +dependencies = [ + "blake2b_simd", + "ff", + "jubjub", + "orchard", + "rand_core", + "sapling-crypto", + "zcash_address", + "zcash_primitives", + "zcash_proofs", +] + +[[package]] +name = "zcash_history" +version = "0.4.0" +dependencies = [ + "assert_matches", + "blake2b_simd", + "byteorder", + "primitive-types", + "proptest", +] + +[[package]] +name = "zcash_keys" +version = "0.5.0" +dependencies = [ + "bech32", + "bip32", + "blake2b_simd", + "bls12_381", + "bs58", + "byteorder", + "document-features", + "group", + "hex", + "jubjub", + "memuse", + "nonempty", + "orchard", + "proptest", + "rand_core", + "sapling-crypto", + "secrecy", + "subtle", + "tracing", + "zcash_address", + "zcash_encoding", + "zcash_primitives", + "zcash_protocol", + "zip32", +] + +[[package]] +name = "zcash_note_encryption" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b4580cd6cee12e44421dac43169be8d23791650816bdb34e6ddfa70ac89c1c5" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher", + "rand_core", + "subtle", +] + +[[package]] +name = "zcash_primitives" +version = "0.20.0" +dependencies = [ + "aes", + "assert_matches", + "bip32", + "blake2b_simd", + "bs58", + "byteorder", + "chacha20poly1305", + "criterion", + "document-features", + "equihash", + "ff", + "fpe", + "group", + "hex", + "incrementalmerkletree", + "jubjub", + "memuse", + "nonempty", + "orchard", + "pprof", + "proptest", + "rand", + "rand_core", + "rand_xorshift", + "redjubjub", + "ripemd", + "sapling-crypto", + "secp256k1", + "sha2", + "subtle", + "tracing", + "zcash_address", + "zcash_encoding", + "zcash_note_encryption", + "zcash_protocol", + "zcash_spec", + "zip32", +] + +[[package]] +name = "zcash_proofs" +version = "0.20.0" +dependencies = [ + "bellman", + "blake2b_simd", + "bls12_381", + "byteorder", + "document-features", + "group", + "home", + "jubjub", + "known-folders", + "lazy_static", + "minreq", + "rand_core", + "redjubjub", + "sapling-crypto", + "tracing", + "wagyu-zcash-parameters", + "xdg", + "zcash_primitives", +] + +[[package]] +name = "zcash_protocol" +version = "0.4.1" +dependencies = [ + "document-features", + "incrementalmerkletree", + "incrementalmerkletree-testing", + "memuse", + "proptest", +] + +[[package]] +name = "zcash_spec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3bf58b673cb3dacd8ae09ba345998923a197ab0da70d6239d8e8838949e9b" +dependencies = [ + "blake2b_simd", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "zip32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4226d0aee9c9407c27064dfeec9d7b281c917de3374e1e5a2e2cfad9e09de19e" +dependencies = [ + "blake2b_simd", + "memuse", + "subtle", +] + +[[package]] +name = "zip321" +version = "0.2.0" +dependencies = [ + "base64", + "nom", + "percent-encoding", + "proptest", + "zcash_address", + "zcash_protocol", +] + +[[package]] +name = "zstd" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.10+zstd.1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index 044d879e93..727f2dd701 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,16 +4,180 @@ members = [ "components/f4jumble", "components/zcash_address", "components/zcash_encoding", - "components/zcash_note_encryption", + "components/zcash_protocol", + "components/zip321", + "devtools", + "pczt", + "zcash", "zcash_client_backend", "zcash_client_sqlite", "zcash_extensions", "zcash_history", + "zcash_keys", "zcash_primitives", "zcash_proofs", ] +[workspace.package] +edition = "2021" +rust-version = "1.77" +repository = "https://github.com/zcash/librustzcash" +license = "MIT OR Apache-2.0" +categories = ["cryptography::cryptocurrencies"] + +# Common dependencies across all of our crates. Dependencies used only by a single crate +# (and that don't have cross-crate versioning needs) are specified by the crate itself. +# +# See the individual crate `Cargo.toml` files for information about which dependencies are +# part of a public API, and which can be updated without a SemVer bump. +[workspace.dependencies] +# Intra-workspace dependencies +equihash = { version = "0.2", path = "components/equihash" } +zcash_address = { version = "0.6", path = "components/zcash_address" } +zcash_client_backend = { version = "0.15", path = "zcash_client_backend" } +zcash_encoding = { version = "0.2.1", path = "components/zcash_encoding" } +zcash_keys = { version = "0.5", path = "zcash_keys" } +zcash_protocol = { version = "0.4.1", path = "components/zcash_protocol" } +zip321 = { version = "0.2", path = "components/zip321" } + +zcash_note_encryption = "0.4" +zcash_primitives = { version = "0.20", path = "zcash_primitives", default-features = false } +zcash_proofs = { version = "0.20", path = "zcash_proofs", default-features = false } + +# Shielded protocols +bellman = { version = "0.14", default-features = false, features = ["groth16"] } +ff = "0.13" +group = "0.13" +incrementalmerkletree = "0.7" +shardtree = "0.5" +zcash_spec = "0.1" + +# Payment protocols +# - Sapling +bitvec = "1" +blake2s_simd = "1" +bls12_381 = "0.8" +jubjub = "0.10" +sapling = { package = "sapling-crypto", version = "0.3", default-features = false } + +# - Orchard +nonempty = "0.7" +orchard = { version = "0.10", default-features = false } +pasta_curves = "0.5" + +# - Transparent +bip32 = { version = "0.5", default-features = false, features = ["secp256k1-ffi"] } +ripemd = "0.1" +secp256k1 = "0.27" + +# CSPRNG +rand = "0.8" +rand_core = "0.6" + +# Currency conversions +rust_decimal = { version = "1.35", default-features = false, features = ["serde"] } + +# Digests +blake2b_simd = "1" +sha2 = "0.10" + +# Documentation +document-features = "0.2" + +# Encodings +base64 = "0.22" +bech32 = "0.9" +bs58 = { version = "0.5", features = ["check"] } +byteorder = "1" +hex = "0.4" +percent-encoding = "2.1.0" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +# HTTP +hyper = "1" +http-body-util = "0.1" +hyper-util = { version = "0.1.1", features = ["tokio"] } +tokio-rustls = "0.24" +webpki-roots = "0.25" + +# Logging and metrics +memuse = "0.2.1" +tracing = "0.1" + +# Parallel processing +crossbeam-channel = "0.5" +maybe-rayon = { version = "0.1.0", default-features = false } +rayon = "1.5" + +# Protobuf and gRPC +prost = "0.13" +tonic = { version = "0.12", default-features = false } +tonic-build = { version = "0.12", default-features = false } + +# Secret management +secrecy = "0.8" +subtle = "2.2.3" + +# SQLite databases +# - Warning: One of the downstream consumers requires that SQLite be available through +# CocoaPods, due to being bound to React Native. We need to ensure that the SQLite +# version required for `rusqlite` is a version that is available through CocoaPods. +rusqlite = { version = "0.32", features = ["bundled"] } +schemerz = "0.2" +schemerz-rusqlite = "0.320" +time = "0.3.22" +uuid = "1.1" + +# Static constants and assertions +lazy_static = "1" +static_assertions = "1" + +# Tests and benchmarks +ambassador = "0.4" +assert_matches = "1.5" +criterion = "0.5" +proptest = "1" +rand_chacha = "0.3" +rand_xorshift = "0.3" +incrementalmerkletree-testing = "0.2" + +# Tor +# - `arti-client` depends on `rusqlite`, and a version mismatch there causes a compilation +# failure due to incompatible `libsqlite3-sys` versions. +arti-client = { version = "0.23", default-features = false, features = ["compression", "rustls", "tokio"] } +dynosaur = "0.1.1" +tokio = "1" +tor-rtcompat = "0.23" +tower = "0.4" +trait-variant = "0.1" + +# ZIP 32 +aes = "0.8" +fpe = "0.6" +zip32 = "0.1.1" + [profile.release] lto = true panic = 'abort' codegen-units = 1 + +[profile.test] +# Since we have many computationally expensive tests, this changes the test profile to +# compile with optimizations by default, but keep full debug info. +# +# This differs from the release profile in the following ways: +# - it does not set `lto = true`, which increases compile times without substantially +# speeding up tests; +# - it does not set `codegen-units = 1`, which increases compile times and is only +# useful to improve determinism of release builds; +# - it does not set `panic = 'abort'`, which is in any case ignored for tests. +# +# To get results as close as possible to a release build, use `cargo test --release`. +# To speed up compilation and avoid optimizations potentially resulting in lower-quality +# debug info, use `cargo test --profile=dev`. +opt-level = 3 +debug = true + +[workspace.lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zcash_unstable, values("zfuture", "tze", "nsm"))'] } diff --git a/README.md b/README.md index c10053bd85..696e4caddd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # Zcash Rust crates -This repository contains a (work-in-progress) set of Rust crates for -working with Zcash. +This repository contains a (work-in-progress) set of Rust crates for working +with Zcash. ## Security Warnings -These libraries are currently under development and have not been fully-reviewed. +These libraries are under development and have not been fully reviewed. ## License @@ -16,20 +16,8 @@ All code in this workspace is licensed under either of at your option. -Downstream code forks should note that some (but not all) of these crates -and components depend on the 'orchard' crate, which is licensed under the -[Bootstrap Open Source License](https://github.com/zcash/orchard/blob/main/LICENSE-BOSL). -A license exception is provided allowing some derived works that are linked or -combined with the 'orchard' crate to be copied or distributed under the original -licenses (in this case MIT / Apache 2.0), provided that the included portions of -the 'orchard' code remain subject to BOSL. -See for details of which -derived works can make use of this exception, and the `README.md` files in -subdirectories for which crates and components this applies to. - ### Contribution -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/components/equihash/src/lib.rs b/components/equihash/src/lib.rs index fc23642063..cb6131ca3b 100644 --- a/components/equihash/src/lib.rs +++ b/components/equihash/src/lib.rs @@ -20,6 +20,8 @@ // Catch documentation errors caused by code changes. #![deny(rustdoc::broken_intra_doc_links)] +mod minimal; +mod params; mod verify; #[cfg(test)] diff --git a/components/equihash/src/minimal.rs b/components/equihash/src/minimal.rs new file mode 100644 index 0000000000..81da63e657 --- /dev/null +++ b/components/equihash/src/minimal.rs @@ -0,0 +1,190 @@ +use std::io::Cursor; +use std::mem::size_of; + +use byteorder::{BigEndian, ReadBytesExt}; + +use crate::params::Params; + +pub(crate) fn expand_array(vin: &[u8], bit_len: usize, byte_pad: usize) -> Vec { + assert!(bit_len >= 8); + assert!(u32::BITS as usize >= 7 + bit_len); + + let out_width = (bit_len + 7) / 8 + byte_pad; + let out_len = 8 * out_width * vin.len() / bit_len; + + // Shortcut for parameters where expansion is a no-op + if out_len == vin.len() { + return vin.to_vec(); + } + + let mut vout: Vec = vec![0; out_len]; + let bit_len_mask: u32 = (1 << bit_len) - 1; + + // The acc_bits least-significant bits of acc_value represent a bit sequence + // in big-endian order. + let mut acc_bits = 0; + let mut acc_value: u32 = 0; + + let mut j = 0; + for b in vin { + acc_value = (acc_value << 8) | u32::from(*b); + acc_bits += 8; + + // When we have bit_len or more bits in the accumulator, write the next + // output element. + if acc_bits >= bit_len { + acc_bits -= bit_len; + for x in byte_pad..out_width { + vout[j + x] = (( + // Big-endian + acc_value >> (acc_bits + (8 * (out_width - x - 1))) + ) & ( + // Apply bit_len_mask across byte boundaries + (bit_len_mask >> (8 * (out_width - x - 1))) & 0xFF + )) as u8; + } + j += out_width; + } + } + + vout +} + +/// Returns `None` if the parameters are invalid for this minimal encoding. +pub(crate) fn indices_from_minimal(p: Params, minimal: &[u8]) -> Option> { + let c_bit_len = p.collision_bit_length(); + // Division is exact because k >= 3. + if minimal.len() != ((1 << p.k) * (c_bit_len + 1)) / 8 { + return None; + } + + assert!(((c_bit_len + 1) + 7) / 8 <= size_of::()); + let len_indices = u32::BITS as usize * minimal.len() / (c_bit_len + 1); + let byte_pad = size_of::() - ((c_bit_len + 1) + 7) / 8; + + let mut csr = Cursor::new(expand_array(minimal, c_bit_len + 1, byte_pad)); + let mut ret = Vec::with_capacity(len_indices); + + // Big-endian so that lexicographic array comparison is equivalent to integer + // comparison + while let Ok(i) = csr.read_u32::() { + ret.push(i); + } + + Some(ret) +} + +#[cfg(test)] +mod tests { + use super::{expand_array, indices_from_minimal, Params}; + + #[test] + fn array_expansion() { + let check_array = |(bit_len, byte_pad), compact, expanded| { + assert_eq!(expand_array(compact, bit_len, byte_pad), expanded); + }; + + // 8 11-bit chunks, all-ones + check_array( + (11, 0), + &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ], + &[ + 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, + 0x07, 0xff, + ][..], + ); + // 8 21-bit chunks, alternating 1s and 0s + check_array( + (21, 0), + &[ + 0xaa, 0xaa, 0xad, 0x55, 0x55, 0x6a, 0xaa, 0xab, 0x55, 0x55, 0x5a, 0xaa, 0xaa, 0xd5, + 0x55, 0x56, 0xaa, 0xaa, 0xb5, 0x55, 0x55, + ], + &[ + 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, + 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, + ][..], + ); + // 8 21-bit chunks, based on example in the spec + check_array( + (21, 0), + &[ + 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x12, 0x30, 0x22, 0xb3, 0x82, + 0x26, 0xac, 0x19, 0xbd, 0xf2, 0x34, 0x56, + ], + &[ + 0x00, 0x00, 0x44, 0x00, 0x00, 0x29, 0x1f, 0xff, 0xff, 0x00, 0x01, 0x23, 0x00, 0x45, + 0x67, 0x00, 0x89, 0xab, 0x00, 0xcd, 0xef, 0x12, 0x34, 0x56, + ][..], + ); + // 16 14-bit chunks, alternating 11s and 00s + check_array( + (14, 0), + &[ + 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, + 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, + ], + &[ + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, + ][..], + ); + // 8 11-bit chunks, all-ones, 2-byte padding + check_array( + (11, 2), + &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ], + &[ + 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, + 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, + 0x00, 0x00, 0x07, 0xff, + ][..], + ); + } + + #[test] + fn minimal_solution_repr() { + let check_repr = |minimal, indices| { + assert_eq!( + indices_from_minimal(Params { n: 80, k: 3 }, minimal).unwrap(), + indices, + ); + }; + + // The solutions here are not intended to be valid. + check_repr( + &[ + 0x00, 0x00, 0x08, 0x00, 0x00, 0x40, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x80, + 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x01, + ], + &[1, 1, 1, 1, 1, 1, 1, 1], + ); + check_repr( + &[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ], + &[ + 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, + ], + ); + check_repr( + &[ + 0x0f, 0xff, 0xf8, 0x00, 0x20, 0x03, 0xff, 0xfe, 0x00, 0x08, 0x00, 0xff, 0xff, 0x80, + 0x02, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x80, + ], + &[131071, 128, 131071, 128, 131071, 128, 131071, 128], + ); + check_repr( + &[ + 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x4d, 0x10, 0x01, 0x4c, 0x80, + 0x0f, 0xfc, 0x00, 0x00, 0x2f, 0xff, 0xff, + ], + &[68, 41, 2097151, 1233, 665, 1023, 1, 1048575], + ); + } +} diff --git a/components/equihash/src/params.rs b/components/equihash/src/params.rs new file mode 100644 index 0000000000..2e17700065 --- /dev/null +++ b/components/equihash/src/params.rs @@ -0,0 +1,37 @@ +#[derive(Clone, Copy)] +pub(crate) struct Params { + pub(crate) n: u32, + pub(crate) k: u32, +} + +impl Params { + /// Returns `None` if the parameters are invalid. + pub(crate) fn new(n: u32, k: u32) -> Option { + // We place the following requirements on the parameters: + // - n is a multiple of 8, so the hash output has an exact byte length. + // - k >= 3 so the encoded solutions have an exact byte length. + // - k < n, so the collision bit length is at least 1. + // - n is a multiple of k + 1, so we have an integer collision bit length. + if (n % 8 == 0) && (k >= 3) && (k < n) && (n % (k + 1) == 0) { + Some(Params { n, k }) + } else { + None + } + } + pub(crate) fn indices_per_hash_output(&self) -> u32 { + 512 / self.n + } + pub(crate) fn hash_output(&self) -> u8 { + (self.indices_per_hash_output() * self.n / 8) as u8 + } + pub(crate) fn collision_bit_length(&self) -> usize { + (self.n / (self.k + 1)) as usize + } + pub(crate) fn collision_byte_length(&self) -> usize { + (self.collision_bit_length() + 7) / 8 + } + #[cfg(test)] + pub(crate) fn hash_length(&self) -> usize { + ((self.k as usize) + 1) * self.collision_byte_length() + } +} diff --git a/components/equihash/src/test_vectors/invalid.rs b/components/equihash/src/test_vectors/invalid.rs index 11da849e0d..5dbec5a33d 100644 --- a/components/equihash/src/test_vectors/invalid.rs +++ b/components/equihash/src/test_vectors/invalid.rs @@ -1,4 +1,4 @@ -use crate::verify::{Kind, Params}; +use crate::{params::Params, verify::Kind}; pub(crate) struct TestVector { pub(crate) params: Params, diff --git a/components/equihash/src/test_vectors/valid.rs b/components/equihash/src/test_vectors/valid.rs index a55de1b96a..4df20c642a 100644 --- a/components/equihash/src/test_vectors/valid.rs +++ b/components/equihash/src/test_vectors/valid.rs @@ -1,4 +1,4 @@ -use crate::verify::Params; +use crate::params::Params; pub(crate) struct TestVector { pub(crate) params: Params, diff --git a/components/equihash/src/verify.rs b/components/equihash/src/verify.rs index 2015008838..53071ddc01 100644 --- a/components/equihash/src/verify.rs +++ b/components/equihash/src/verify.rs @@ -3,16 +3,13 @@ //! [Equihash]: https://zips.z.cash/protocol/protocol.pdf#equihash use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams, State as Blake2bState}; -use byteorder::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{LittleEndian, WriteBytesExt}; use std::fmt; -use std::io::Cursor; -use std::mem::size_of; -#[derive(Clone, Copy)] -pub(crate) struct Params { - pub(crate) n: u32, - pub(crate) k: u32, -} +use crate::{ + minimal::{expand_array, indices_from_minimal}, + params::Params, +}; #[derive(Clone)] struct Node { @@ -20,37 +17,6 @@ struct Node { indices: Vec, } -impl Params { - fn new(n: u32, k: u32) -> Result { - // We place the following requirements on the parameters: - // - n is a multiple of 8, so the hash output has an exact byte length. - // - k >= 3 so the encoded solutions have an exact byte length. - // - k < n, so the collision bit length is at least 1. - // - n is a multiple of k + 1, so we have an integer collision bit length. - if (n % 8 == 0) && (k >= 3) && (k < n) && (n % (k + 1) == 0) { - Ok(Params { n, k }) - } else { - Err(Error(Kind::InvalidParams)) - } - } - fn indices_per_hash_output(&self) -> u32 { - 512 / self.n - } - fn hash_output(&self) -> u8 { - (self.indices_per_hash_output() * self.n / 8) as u8 - } - fn collision_bit_length(&self) -> usize { - (self.n / (self.k + 1)) as usize - } - fn collision_byte_length(&self) -> usize { - (self.collision_bit_length() + 7) / 8 - } - #[cfg(test)] - fn hash_length(&self) -> usize { - ((self.k as usize) + 1) * self.collision_byte_length() - } -} - impl Node { fn new(p: &Params, state: &Blake2bState, i: u32) -> Self { let hash = generate_hash(state, i / p.indices_per_hash_output()); @@ -168,74 +134,6 @@ fn generate_hash(base_state: &Blake2bState, i: u32) -> Blake2bHash { state.finalize() } -fn expand_array(vin: &[u8], bit_len: usize, byte_pad: usize) -> Vec { - assert!(bit_len >= 8); - assert!(u32::BITS as usize >= 7 + bit_len); - - let out_width = (bit_len + 7) / 8 + byte_pad; - let out_len = 8 * out_width * vin.len() / bit_len; - - // Shortcut for parameters where expansion is a no-op - if out_len == vin.len() { - return vin.to_vec(); - } - - let mut vout: Vec = vec![0; out_len]; - let bit_len_mask: u32 = (1 << bit_len) - 1; - - // The acc_bits least-significant bits of acc_value represent a bit sequence - // in big-endian order. - let mut acc_bits = 0; - let mut acc_value: u32 = 0; - - let mut j = 0; - for b in vin { - acc_value = (acc_value << 8) | u32::from(*b); - acc_bits += 8; - - // When we have bit_len or more bits in the accumulator, write the next - // output element. - if acc_bits >= bit_len { - acc_bits -= bit_len; - for x in byte_pad..out_width { - vout[j + x] = (( - // Big-endian - acc_value >> (acc_bits + (8 * (out_width - x - 1))) - ) & ( - // Apply bit_len_mask across byte boundaries - (bit_len_mask >> (8 * (out_width - x - 1))) & 0xFF - )) as u8; - } - j += out_width; - } - } - - vout -} - -fn indices_from_minimal(p: Params, minimal: &[u8]) -> Result, Error> { - let c_bit_len = p.collision_bit_length(); - // Division is exact because k >= 3. - if minimal.len() != ((1 << p.k) * (c_bit_len + 1)) / 8 { - return Err(Error(Kind::InvalidParams)); - } - - assert!(((c_bit_len + 1) + 7) / 8 <= size_of::()); - let len_indices = u32::BITS as usize * minimal.len() / (c_bit_len + 1); - let byte_pad = size_of::() - ((c_bit_len + 1) + 7) / 8; - - let mut csr = Cursor::new(expand_array(minimal, c_bit_len + 1, byte_pad)); - let mut ret = Vec::with_capacity(len_indices); - - // Big-endian so that lexicographic array comparison is equivalent to integer - // comparison - while let Ok(i) = csr.read_u32::() { - ret.push(i); - } - - Ok(ret) -} - fn has_collision(a: &Node, b: &Node, len: usize) -> bool { a.hash .iter() @@ -347,8 +245,8 @@ pub fn is_valid_solution( nonce: &[u8], soln: &[u8], ) -> Result<(), Error> { - let p = Params::new(n, k)?; - let indices = indices_from_minimal(p, soln)?; + let p = Params::new(n, k).ok_or(Error(Kind::InvalidParams))?; + let indices = indices_from_minimal(p, soln).ok_or(Error(Kind::InvalidParams))?; // Recursive validation is faster is_valid_solution_recursive(p, input, nonce, &indices) @@ -356,122 +254,9 @@ pub fn is_valid_solution( #[cfg(test)] mod tests { - use super::{ - expand_array, indices_from_minimal, is_valid_solution, is_valid_solution_iterative, - is_valid_solution_recursive, Params, - }; + use super::{is_valid_solution, is_valid_solution_iterative, is_valid_solution_recursive}; use crate::test_vectors::{INVALID_TEST_VECTORS, VALID_TEST_VECTORS}; - #[test] - fn array_expansion() { - let check_array = |(bit_len, byte_pad), compact, expanded| { - assert_eq!(expand_array(compact, bit_len, byte_pad), expanded); - }; - - // 8 11-bit chunks, all-ones - check_array( - (11, 0), - &[ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ], - &[ - 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, 0x07, 0xff, - 0x07, 0xff, - ][..], - ); - // 8 21-bit chunks, alternating 1s and 0s - check_array( - (21, 0), - &[ - 0xaa, 0xaa, 0xad, 0x55, 0x55, 0x6a, 0xaa, 0xab, 0x55, 0x55, 0x5a, 0xaa, 0xaa, 0xd5, - 0x55, 0x56, 0xaa, 0xaa, 0xb5, 0x55, 0x55, - ], - &[ - 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, - 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, 0x15, 0x55, 0x55, - ][..], - ); - // 8 21-bit chunks, based on example in the spec - check_array( - (21, 0), - &[ - 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x12, 0x30, 0x22, 0xb3, 0x82, - 0x26, 0xac, 0x19, 0xbd, 0xf2, 0x34, 0x56, - ], - &[ - 0x00, 0x00, 0x44, 0x00, 0x00, 0x29, 0x1f, 0xff, 0xff, 0x00, 0x01, 0x23, 0x00, 0x45, - 0x67, 0x00, 0x89, 0xab, 0x00, 0xcd, 0xef, 0x12, 0x34, 0x56, - ][..], - ); - // 16 14-bit chunks, alternating 11s and 00s - check_array( - (14, 0), - &[ - 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, - 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, 0xcc, 0xcf, 0x33, 0x3c, 0xcc, 0xf3, 0x33, - ], - &[ - 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, - 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, - 0x33, 0x33, 0x33, 0x33, - ][..], - ); - // 8 11-bit chunks, all-ones, 2-byte padding - check_array( - (11, 2), - &[ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ], - &[ - 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, - 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, 0x00, 0x00, 0x07, 0xff, - 0x00, 0x00, 0x07, 0xff, - ][..], - ); - } - - #[test] - fn minimal_solution_repr() { - let check_repr = |minimal, indices| { - assert_eq!( - indices_from_minimal(Params { n: 80, k: 3 }, minimal).unwrap(), - indices, - ); - }; - - // The solutions here are not intended to be valid. - check_repr( - &[ - 0x00, 0x00, 0x08, 0x00, 0x00, 0x40, 0x00, 0x02, 0x00, 0x00, 0x10, 0x00, 0x00, 0x80, - 0x00, 0x04, 0x00, 0x00, 0x20, 0x00, 0x01, - ], - &[1, 1, 1, 1, 1, 1, 1, 1], - ); - check_repr( - &[ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, - ], - &[ - 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, 2097151, - ], - ); - check_repr( - &[ - 0x0f, 0xff, 0xf8, 0x00, 0x20, 0x03, 0xff, 0xfe, 0x00, 0x08, 0x00, 0xff, 0xff, 0x80, - 0x02, 0x00, 0x3f, 0xff, 0xe0, 0x00, 0x80, - ], - &[131071, 128, 131071, 128, 131071, 128, 131071, 128], - ); - check_repr( - &[ - 0x00, 0x02, 0x20, 0x00, 0x0a, 0x7f, 0xff, 0xfe, 0x00, 0x4d, 0x10, 0x01, 0x4c, 0x80, - 0x0f, 0xfc, 0x00, 0x00, 0x2f, 0xff, 0xff, - ], - &[68, 41, 2097151, 1233, 665, 1023, 1, 1048575], - ); - } - #[test] fn valid_test_vectors() { for tv in VALID_TEST_VECTORS { diff --git a/components/zcash_address/CHANGELOG.md b/components/zcash_address/CHANGELOG.md index a7bf6f6385..7eeb56b994 100644 --- a/components/zcash_address/CHANGELOG.md +++ b/components/zcash_address/CHANGELOG.md @@ -7,6 +7,37 @@ and this library adheres to Rust's notion of ## [Unreleased] +## [0.6.0] - 2024-10-02 +### Changed +- Migrated to `zcash_protocol 0.4`. + +## [0.5.0] - 2024-08-26 +### Changed +- Updated `zcash_protocol` dependency to version `0.3` + +## [0.4.0] - 2024-08-19 +### Added +- `zcash_address::ZcashAddress::{can_receive_memo, can_receive_as, matches_receiver}` +- `zcash_address::unified::Address::{can_receive_memo, has_receiver_of_type, contains_receiver}` +- Module `zcash_address::testing` under the `test-dependencies` feature. +- Module `zcash_address::unified::address::testing` under the + `test-dependencies` feature. + +### Changed +- Updated `zcash_protocol` dependency to version `0.2` + +## [0.3.2] - 2024-03-06 +### Added +- `zcash_address::convert`: + - `TryFromRawAddress::try_from_raw_tex` + - `TryFromAddress::try_from_tex` + - `ToAddress::from_tex` + +## [0.3.1] - 2024-01-12 +### Fixed +- Stubs for `zcash_address::convert` traits that are created by `rust-analyzer` + and similar LSPs no longer reference crate-private type aliases. + ## [0.3.0] - 2023-06-06 ### Changed - Bumped bs58 dependency to `0.5`. diff --git a/components/zcash_address/Cargo.toml b/components/zcash_address/Cargo.toml index 3ccef17a46..f2201d2612 100644 --- a/components/zcash_address/Cargo.toml +++ b/components/zcash_address/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zcash_address" description = "Zcash address parsing and serialization" -version = "0.3.0" +version = "0.6.0" authors = [ "Jack Grigg ", ] @@ -14,18 +14,24 @@ rust-version = "1.52" categories = ["cryptography::cryptocurrencies", "encoding"] keywords = ["zcash", "address", "sapling", "unified"] +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + [dependencies] -bech32 = "0.9" -bs58 = { version = "0.5", features = ["check"] } +bech32.workspace = true +bs58.workspace = true f4jumble = { version = "0.1", path = "../f4jumble" } -zcash_encoding = { version = "0.2", path = "../zcash_encoding" } +zcash_protocol.workspace = true +zcash_encoding.workspace = true +proptest = { workspace = true, optional = true } [dev-dependencies] -assert_matches = "1.3.0" -proptest = "1" +assert_matches.workspace = true +proptest.workspace = true [features] -test-dependencies = [] +test-dependencies = ["dep:proptest"] [lib] bench = false diff --git a/components/zcash_address/src/convert.rs b/components/zcash_address/src/convert.rs index 38b04f374a..225b8775bb 100644 --- a/components/zcash_address/src/convert.rs +++ b/components/zcash_address/src/convert.rs @@ -2,7 +2,7 @@ use std::{error::Error, fmt}; use crate::{kind::*, AddressKind, Network, ZcashAddress}; -/// An address type is not supported for conversion. +/// An error indicating that an address type is not supported for conversion. #[derive(Debug)] pub struct UnsupportedAddress(&'static str); @@ -107,12 +107,12 @@ pub trait TryFromRawAddress: Sized { /// [`Self::try_from_raw_sapling`] as a valid Sapling address). type Error; - fn try_from_raw_sprout(data: sprout::Data) -> Result> { + fn try_from_raw_sprout(data: [u8; 64]) -> Result> { let _ = data; Err(ConversionError::Unsupported(UnsupportedAddress("Sprout"))) } - fn try_from_raw_sapling(data: sapling::Data) -> Result> { + fn try_from_raw_sapling(data: [u8; 43]) -> Result> { let _ = data; Err(ConversionError::Unsupported(UnsupportedAddress("Sapling"))) } @@ -123,7 +123,7 @@ pub trait TryFromRawAddress: Sized { } fn try_from_raw_transparent_p2pkh( - data: p2pkh::Data, + data: [u8; 20], ) -> Result> { let _ = data; Err(ConversionError::Unsupported(UnsupportedAddress( @@ -131,14 +131,19 @@ pub trait TryFromRawAddress: Sized { ))) } - fn try_from_raw_transparent_p2sh( - data: p2sh::Data, - ) -> Result> { + fn try_from_raw_transparent_p2sh(data: [u8; 20]) -> Result> { let _ = data; Err(ConversionError::Unsupported(UnsupportedAddress( "transparent P2SH", ))) } + + fn try_from_raw_tex(data: [u8; 20]) -> Result> { + let _ = data; + Err(ConversionError::Unsupported(UnsupportedAddress( + "transparent-source restricted P2PKH", + ))) + } } /// A helper trait for converting a [`ZcashAddress`] into another type. @@ -187,17 +192,14 @@ pub trait TryFromAddress: Sized { /// [`Self::try_from_sapling`] as a valid Sapling address). type Error; - fn try_from_sprout( - net: Network, - data: sprout::Data, - ) -> Result> { + fn try_from_sprout(net: Network, data: [u8; 64]) -> Result> { let _ = (net, data); Err(ConversionError::Unsupported(UnsupportedAddress("Sprout"))) } fn try_from_sapling( net: Network, - data: sapling::Data, + data: [u8; 43], ) -> Result> { let _ = (net, data); Err(ConversionError::Unsupported(UnsupportedAddress("Sapling"))) @@ -213,7 +215,7 @@ pub trait TryFromAddress: Sized { fn try_from_transparent_p2pkh( net: Network, - data: p2pkh::Data, + data: [u8; 20], ) -> Result> { let _ = (net, data); Err(ConversionError::Unsupported(UnsupportedAddress( @@ -223,28 +225,32 @@ pub trait TryFromAddress: Sized { fn try_from_transparent_p2sh( net: Network, - data: p2sh::Data, + data: [u8; 20], ) -> Result> { let _ = (net, data); Err(ConversionError::Unsupported(UnsupportedAddress( "transparent P2SH", ))) } + + fn try_from_tex(net: Network, data: [u8; 20]) -> Result> { + let _ = (net, data); + Err(ConversionError::Unsupported(UnsupportedAddress( + "transparent-source restricted P2PKH", + ))) + } } impl TryFromAddress for (Network, T) { type Error = T::Error; - fn try_from_sprout( - net: Network, - data: sprout::Data, - ) -> Result> { + fn try_from_sprout(net: Network, data: [u8; 64]) -> Result> { T::try_from_raw_sprout(data).map(|addr| (net, addr)) } fn try_from_sapling( net: Network, - data: sapling::Data, + data: [u8; 43], ) -> Result> { T::try_from_raw_sapling(data).map(|addr| (net, addr)) } @@ -258,17 +264,21 @@ impl TryFromAddress for (Network, T) { fn try_from_transparent_p2pkh( net: Network, - data: p2pkh::Data, + data: [u8; 20], ) -> Result> { T::try_from_raw_transparent_p2pkh(data).map(|addr| (net, addr)) } fn try_from_transparent_p2sh( net: Network, - data: p2sh::Data, + data: [u8; 20], ) -> Result> { T::try_from_raw_transparent_p2sh(data).map(|addr| (net, addr)) } + + fn try_from_tex(net: Network, data: [u8; 20]) -> Result> { + T::try_from_raw_tex(data).map(|addr| (net, addr)) + } } /// A helper trait for converting another type into a [`ZcashAddress`]. @@ -303,19 +313,21 @@ impl TryFromAddress for (Network, T) { /// ); /// ``` pub trait ToAddress: private::Sealed { - fn from_sprout(net: Network, data: sprout::Data) -> Self; + fn from_sprout(net: Network, data: [u8; 64]) -> Self; - fn from_sapling(net: Network, data: sapling::Data) -> Self; + fn from_sapling(net: Network, data: [u8; 43]) -> Self; fn from_unified(net: Network, data: unified::Address) -> Self; - fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self; + fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Self; + + fn from_transparent_p2sh(net: Network, data: [u8; 20]) -> Self; - fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self; + fn from_tex(net: Network, data: [u8; 20]) -> Self; } impl ToAddress for ZcashAddress { - fn from_sprout(net: Network, data: sprout::Data) -> Self { + fn from_sprout(net: Network, data: [u8; 64]) -> Self { ZcashAddress { net: if let Network::Regtest = net { Network::Test @@ -326,7 +338,7 @@ impl ToAddress for ZcashAddress { } } - fn from_sapling(net: Network, data: sapling::Data) -> Self { + fn from_sapling(net: Network, data: [u8; 43]) -> Self { ZcashAddress { net, kind: AddressKind::Sapling(data), @@ -340,7 +352,7 @@ impl ToAddress for ZcashAddress { } } - fn from_transparent_p2pkh(net: Network, data: p2pkh::Data) -> Self { + fn from_transparent_p2pkh(net: Network, data: [u8; 20]) -> Self { ZcashAddress { net: if let Network::Regtest = net { Network::Test @@ -351,7 +363,7 @@ impl ToAddress for ZcashAddress { } } - fn from_transparent_p2sh(net: Network, data: p2sh::Data) -> Self { + fn from_transparent_p2sh(net: Network, data: [u8; 20]) -> Self { ZcashAddress { net: if let Network::Regtest = net { Network::Test @@ -361,6 +373,13 @@ impl ToAddress for ZcashAddress { kind: AddressKind::P2sh(data), } } + + fn from_tex(net: Network, data: [u8; 20]) -> Self { + ZcashAddress { + net, + kind: AddressKind::Tex(data), + } + } } mod private { diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 9e5e422ce6..a0d3c117d6 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -1,9 +1,11 @@ use std::{convert::TryInto, error::Error, fmt, str::FromStr}; use bech32::{self, FromBase32, ToBase32, Variant}; +use zcash_protocol::consensus::{NetworkConstants, NetworkType}; +use zcash_protocol::constants::{mainnet, regtest, testnet}; use crate::kind::unified::Encoding; -use crate::{kind::*, AddressKind, Network, ZcashAddress}; +use crate::{kind::*, AddressKind, ZcashAddress}; /// An error while attempting to parse a string as a Zcash address. #[derive(Debug, PartialEq, Eq)] @@ -54,55 +56,87 @@ impl FromStr for ZcashAddress { kind: AddressKind::Unified(data), }); } - Err(unified::ParseError::NotUnified) => { - // allow decoding to fall through to Sapling/Transparent + Err(unified::ParseError::NotUnified | unified::ParseError::UnknownPrefix(_)) => { + // allow decoding to fall through to Sapling/TEX/Transparent } Err(e) => { return Err(ParseError::from(e)); } } - // Try decoding as a Sapling address (Bech32) - if let Ok((hrp, data, Variant::Bech32)) = bech32::decode(s) { - // If we reached this point, the encoding is supposed to be valid Bech32. + // Try decoding as a Sapling or TEX address (Bech32/Bech32m) + if let Ok((hrp, data, variant)) = bech32::decode(s) { + // If we reached this point, the encoding is found to be valid Bech32 or Bech32m. let data = Vec::::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?; - let net = match hrp.as_str() { - sapling::MAINNET => Network::Main, - sapling::TESTNET => Network::Test, - sapling::REGTEST => Network::Regtest, - // We will not define new Bech32 address encodings. - _ => { - return Err(ParseError::NotZcash); + match variant { + Variant::Bech32 => { + let net = match hrp.as_str() { + mainnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Main, + testnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Test, + regtest::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Regtest, + // We will not define new Bech32 address encodings. + _ => { + return Err(ParseError::NotZcash); + } + }; + + return data[..] + .try_into() + .map(AddressKind::Sapling) + .map_err(|_| ParseError::InvalidEncoding) + .map(|kind| ZcashAddress { net, kind }); } - }; + Variant::Bech32m => { + // Try decoding as a TEX address (Bech32m) + let net = match hrp.as_str() { + mainnet::HRP_TEX_ADDRESS => NetworkType::Main, + testnet::HRP_TEX_ADDRESS => NetworkType::Test, + regtest::HRP_TEX_ADDRESS => NetworkType::Regtest, + // Not recognized as a Zcash address type + _ => { + return Err(ParseError::NotZcash); + } + }; - return data[..] - .try_into() - .map(AddressKind::Sapling) - .map_err(|_| ParseError::InvalidEncoding) - .map(|kind| ZcashAddress { net, kind }); + return data[..] + .try_into() + .map(AddressKind::Tex) + .map_err(|_| ParseError::InvalidEncoding) + .map(|kind| ZcashAddress { net, kind }); + } + } } // The rest use Base58Check. if let Ok(decoded) = bs58::decode(s).with_check(None).into_vec() { - let net = match decoded[..2].try_into().unwrap() { - sprout::MAINNET | p2pkh::MAINNET | p2sh::MAINNET => Network::Main, - sprout::TESTNET | p2pkh::TESTNET | p2sh::TESTNET => Network::Test, - // We will not define new Base58Check address encodings. - _ => return Err(ParseError::NotZcash), - }; + if decoded.len() >= 2 { + let (prefix, net) = match decoded[..2].try_into().unwrap() { + prefix @ (mainnet::B58_PUBKEY_ADDRESS_PREFIX + | mainnet::B58_SCRIPT_ADDRESS_PREFIX + | mainnet::B58_SPROUT_ADDRESS_PREFIX) => (prefix, NetworkType::Main), + prefix @ (testnet::B58_PUBKEY_ADDRESS_PREFIX + | testnet::B58_SCRIPT_ADDRESS_PREFIX + | testnet::B58_SPROUT_ADDRESS_PREFIX) => (prefix, NetworkType::Test), + // We will not define new Base58Check address encodings. + _ => return Err(ParseError::NotZcash), + }; - return match decoded[..2].try_into().unwrap() { - sprout::MAINNET | sprout::TESTNET => { - decoded[2..].try_into().map(AddressKind::Sprout) + return match prefix { + mainnet::B58_SPROUT_ADDRESS_PREFIX | testnet::B58_SPROUT_ADDRESS_PREFIX => { + decoded[2..].try_into().map(AddressKind::Sprout) + } + mainnet::B58_PUBKEY_ADDRESS_PREFIX | testnet::B58_PUBKEY_ADDRESS_PREFIX => { + decoded[2..].try_into().map(AddressKind::P2pkh) + } + mainnet::B58_SCRIPT_ADDRESS_PREFIX | testnet::B58_SCRIPT_ADDRESS_PREFIX => { + decoded[2..].try_into().map(AddressKind::P2sh) + } + _ => unreachable!(), } - p2pkh::MAINNET | p2pkh::TESTNET => decoded[2..].try_into().map(AddressKind::P2pkh), - p2sh::MAINNET | p2sh::TESTNET => decoded[2..].try_into().map(AddressKind::P2sh), - _ => unreachable!(), + .map_err(|_| ParseError::InvalidEncoding) + .map(|kind| ZcashAddress { kind, net }); } - .map_err(|_| ParseError::InvalidEncoding) - .map(|kind| ZcashAddress { kind, net }); }; // If it's not valid Bech32, Bech32m, or Base58Check, it's not a Zcash address. @@ -110,8 +144,8 @@ impl FromStr for ZcashAddress { } } -fn encode_bech32(hrp: &str, data: &[u8]) -> String { - bech32::encode(hrp, data.to_base32(), Variant::Bech32).expect("hrp is invalid") +fn encode_bech32(hrp: &str, data: &[u8], variant: Variant) -> String { + bech32::encode(hrp, data.to_base32(), variant).expect("hrp is invalid") } fn encode_b58(prefix: [u8; 2], data: &[u8]) -> String { @@ -124,36 +158,18 @@ fn encode_b58(prefix: [u8; 2], data: &[u8]) -> String { impl fmt::Display for ZcashAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let encoded = match &self.kind { - AddressKind::Sprout(data) => encode_b58( - match self.net { - Network::Main => sprout::MAINNET, - Network::Test | Network::Regtest => sprout::TESTNET, - }, - data, - ), + AddressKind::Sprout(data) => encode_b58(self.net.b58_sprout_address_prefix(), data), AddressKind::Sapling(data) => encode_bech32( - match self.net { - Network::Main => sapling::MAINNET, - Network::Test => sapling::TESTNET, - Network::Regtest => sapling::REGTEST, - }, + self.net.hrp_sapling_payment_address(), data, + Variant::Bech32, ), AddressKind::Unified(addr) => addr.encode(&self.net), - AddressKind::P2pkh(data) => encode_b58( - match self.net { - Network::Main => p2pkh::MAINNET, - Network::Test | Network::Regtest => p2pkh::TESTNET, - }, - data, - ), - AddressKind::P2sh(data) => encode_b58( - match self.net { - Network::Main => p2sh::MAINNET, - Network::Test | Network::Regtest => p2sh::TESTNET, - }, - data, - ), + AddressKind::P2pkh(data) => encode_b58(self.net.b58_pubkey_address_prefix(), data), + AddressKind::P2sh(data) => encode_b58(self.net.b58_script_address_prefix(), data), + AddressKind::Tex(data) => { + encode_bech32(self.net.hrp_tex_address(), data, Variant::Bech32m) + } }; write!(f, "{}", encoded) } @@ -161,8 +177,10 @@ impl fmt::Display for ZcashAddress { #[cfg(test)] mod tests { + use assert_matches::assert_matches; + use super::*; - use crate::kind::unified; + use crate::{kind::unified, Network}; fn encoding(encoded: &str, decoded: ZcashAddress) { assert_eq!(decoded.to_string(), encoded); @@ -269,6 +287,74 @@ mod tests { ); } + #[test] + fn tex() { + let p2pkh_str = "t1VmmGiyjVNeCjxDZzg7vZmd99WyzVby9yC"; + let tex_str = "tex1s2rt77ggv6q989lr49rkgzmh5slsksa9khdgte"; + + // Transcode P2PKH to TEX + let p2pkh_zaddr: ZcashAddress = p2pkh_str.parse().unwrap(); + assert_matches!(p2pkh_zaddr.net, Network::Main); + if let AddressKind::P2pkh(zaddr_data) = p2pkh_zaddr.kind { + let tex_zaddr = ZcashAddress { + net: p2pkh_zaddr.net, + kind: AddressKind::Tex(zaddr_data), + }; + + assert_eq!(tex_zaddr.to_string(), tex_str); + } else { + panic!("Decoded address should have been a P2PKH address."); + } + + // Transcode TEX to P2PKH + let tex_zaddr: ZcashAddress = tex_str.parse().unwrap(); + assert_matches!(tex_zaddr.net, Network::Main); + if let AddressKind::Tex(zaddr_data) = tex_zaddr.kind { + let p2pkh_zaddr = ZcashAddress { + net: tex_zaddr.net, + kind: AddressKind::P2pkh(zaddr_data), + }; + + assert_eq!(p2pkh_zaddr.to_string(), p2pkh_str); + } else { + panic!("Decoded address should have been a TEX address."); + } + } + + #[test] + fn tex_testnet() { + let p2pkh_str = "tm9ofD7kHR7AF8MsJomEzLqGcrLCBkD9gDj"; + let tex_str = "textest1qyqszqgpqyqszqgpqyqszqgpqyqszqgpfcjgfy"; + + // Transcode P2PKH to TEX + let p2pkh_zaddr: ZcashAddress = p2pkh_str.parse().unwrap(); + assert_matches!(p2pkh_zaddr.net, Network::Test); + if let AddressKind::P2pkh(zaddr_data) = p2pkh_zaddr.kind { + let tex_zaddr = ZcashAddress { + net: p2pkh_zaddr.net, + kind: AddressKind::Tex(zaddr_data), + }; + + assert_eq!(tex_zaddr.to_string(), tex_str); + } else { + panic!("Decoded address should have been a P2PKH address."); + } + + // Transcode TEX to P2PKH + let tex_zaddr: ZcashAddress = tex_str.parse().unwrap(); + assert_matches!(tex_zaddr.net, Network::Test); + if let AddressKind::Tex(zaddr_data) = tex_zaddr.kind { + let p2pkh_zaddr = ZcashAddress { + net: tex_zaddr.net, + kind: AddressKind::P2pkh(zaddr_data), + }; + + assert_eq!(p2pkh_zaddr.to_string(), p2pkh_str); + } else { + panic!("Decoded address should have been a TEX address."); + } + } + #[test] fn whitespace() { assert_eq!( diff --git a/components/zcash_address/src/kind.rs b/components/zcash_address/src/kind.rs index 5397c027f8..38b4557a6e 100644 --- a/components/zcash_address/src/kind.rs +++ b/components/zcash_address/src/kind.rs @@ -1,7 +1 @@ pub mod unified; - -pub(crate) mod sapling; -pub(crate) mod sprout; - -pub(crate) mod p2pkh; -pub(crate) mod p2sh; diff --git a/components/zcash_address/src/kind/p2pkh.rs b/components/zcash_address/src/kind/p2pkh.rs deleted file mode 100644 index 0120e2c39f..0000000000 --- a/components/zcash_address/src/kind/p2pkh.rs +++ /dev/null @@ -1,7 +0,0 @@ -/// The prefix for a Base58Check-encoded mainnet transparent P2PKH address. -pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xb8]; - -/// The prefix for a Base58Check-encoded testnet transparent P2PKH address. -pub(crate) const TESTNET: [u8; 2] = [0x1d, 0x25]; - -pub(crate) type Data = [u8; 20]; diff --git a/components/zcash_address/src/kind/p2sh.rs b/components/zcash_address/src/kind/p2sh.rs deleted file mode 100644 index 5059513182..0000000000 --- a/components/zcash_address/src/kind/p2sh.rs +++ /dev/null @@ -1,7 +0,0 @@ -/// The prefix for a Base58Check-encoded mainnet transparent P2SH address. -pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xbd]; - -/// The prefix for a Base58Check-encoded testnet transparent P2SH address. -pub(crate) const TESTNET: [u8; 2] = [0x1c, 0xba]; - -pub(crate) type Data = [u8; 20]; diff --git a/components/zcash_address/src/kind/sapling.rs b/components/zcash_address/src/kind/sapling.rs deleted file mode 100644 index 2cbf914d61..0000000000 --- a/components/zcash_address/src/kind/sapling.rs +++ /dev/null @@ -1,22 +0,0 @@ -/// The HRP for a Bech32-encoded mainnet Sapling address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.4][saplingpaymentaddrencoding]. -/// -/// [saplingpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding -pub(crate) const MAINNET: &str = "zs"; - -/// The HRP for a Bech32-encoded testnet Sapling address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.4][saplingpaymentaddrencoding]. -/// -/// [saplingpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding -pub(crate) const TESTNET: &str = "ztestsapling"; - -/// The HRP for a Bech32-encoded regtest Sapling address. -/// -/// It is defined in [the `zcashd` codebase]. -/// -/// [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L493 -pub(crate) const REGTEST: &str = "zregtestsapling"; - -pub(crate) type Data = [u8; 43]; diff --git a/components/zcash_address/src/kind/sprout.rs b/components/zcash_address/src/kind/sprout.rs deleted file mode 100644 index fae74aab28..0000000000 --- a/components/zcash_address/src/kind/sprout.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// The prefix for a Base58Check-encoded mainnet Sprout address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. -/// -/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding -pub(crate) const MAINNET: [u8; 2] = [0x16, 0x9a]; - -/// The prefix for a Base58Check-encoded testnet Sprout address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. -/// -/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding -pub(crate) const TESTNET: [u8; 2] = [0x16, 0xb6]; - -pub(crate) type Data = [u8; 64]; diff --git a/components/zcash_address/src/kind/unified.rs b/components/zcash_address/src/kind/unified.rs index 98bfa54130..fd7e4d5703 100644 --- a/components/zcash_address/src/kind/unified.rs +++ b/components/zcash_address/src/kind/unified.rs @@ -1,3 +1,5 @@ +//! Implementation of [ZIP 316](https://zips.z.cash/zip-0316) Unified Addresses and Viewing Keys. + use bech32::{self, FromBase32, ToBase32, Variant}; use std::cmp; use std::convert::{TryFrom, TryInto}; @@ -17,12 +19,23 @@ pub use ivk::{Ivk, Uivk}; const PADDING_LEN: usize = 16; +/// The known Receiver and Viewing Key types. +/// +/// The typecodes `0xFFFA..=0xFFFF` reserved for experiments are currently not +/// distinguished from unknown values, and will be parsed as [`Typecode::Unknown`]. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Typecode { + /// A transparent P2PKH address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316). P2pkh, + /// A transparent P2SH address. + /// + /// This typecode cannot occur in a [`Ufvk`] or [`Uivk`]. P2sh, + /// A Sapling raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316). Sapling, + /// An Orchard raw address, FVK, or IVK encoding as specified in [ZIP 316](https://zips.z.cash/zip-0316). Orchard, + /// An unknown or experimental typecode. Unknown(u32), } @@ -396,7 +409,7 @@ pub trait Encoding: private::SealedContainer { } } -/// Trait for for Unified containers, that exposes the items within them. +/// Trait for Unified containers, that exposes the items within them. pub trait Container { /// The type of item in this unified container. type Item: SealedItem; diff --git a/components/zcash_address/src/kind/unified/address.rs b/components/zcash_address/src/kind/unified/address.rs index addba7d186..9b50e9b1ec 100644 --- a/components/zcash_address/src/kind/unified/address.rs +++ b/components/zcash_address/src/kind/unified/address.rs @@ -1,5 +1,6 @@ +use zcash_protocol::PoolType; + use super::{private::SealedItem, ParseError, Typecode}; -use crate::kind; use std::convert::{TryFrom, TryInto}; @@ -7,9 +8,9 @@ use std::convert::{TryFrom, TryInto}; #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Receiver { Orchard([u8; 43]), - Sapling(kind::sapling::Data), - P2pkh(kind::p2pkh::Data), - P2sh(kind::p2sh::Data), + Sapling([u8; 43]), + P2pkh([u8; 20]), + P2sh([u8; 20]), Unknown { typecode: u32, data: Vec }, } @@ -56,9 +57,76 @@ impl SealedItem for Receiver { } /// A Unified Address. +/// +/// # Examples +/// +/// ``` +/// # use std::convert::Infallible; +/// # use std::error::Error; +/// use zcash_address::{ +/// unified::{self, Container, Encoding}, +/// ConversionError, TryFromRawAddress, ZcashAddress, +/// }; +/// +/// # fn main() -> Result<(), Box> { +/// # let address_from_user = || "u1pg2aaph7jp8rpf6yhsza25722sg5fcn3vaca6ze27hqjw7jvvhhuxkpcg0ge9xh6drsgdkda8qjq5chpehkcpxf87rnjryjqwymdheptpvnljqqrjqzjwkc2ma6hcq666kgwfytxwac8eyex6ndgr6ezte66706e3vaqrd25dzvzkc69kw0jgywtd0cmq52q5lkw6uh7hyvzjse8ksx"; +/// let example_ua: &str = address_from_user(); +/// +/// // We can parse this directly as a `unified::Address`: +/// let (network, ua) = unified::Address::decode(example_ua)?; +/// +/// // Or we can parse via `ZcashAddress` (which you should do): +/// struct MyUnifiedAddress(unified::Address); +/// impl TryFromRawAddress for MyUnifiedAddress { +/// // In this example we aren't checking the validity of the +/// // inner Unified Address, but your code should do so! +/// type Error = Infallible; +/// +/// fn try_from_raw_unified(ua: unified::Address) -> Result> { +/// Ok(MyUnifiedAddress(ua)) +/// } +/// } +/// let addr: ZcashAddress = example_ua.parse()?; +/// let parsed = addr.convert_if_network::(network)?; +/// assert_eq!(parsed.0, ua); +/// +/// // We can obtain the receivers for the UA in preference order +/// // (the order in which wallets should prefer to use them): +/// let receivers: Vec = ua.items(); +/// +/// // And we can create the UA from a list of receivers: +/// let new_ua = unified::Address::try_from_items(receivers)?; +/// assert_eq!(new_ua, ua); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Address(pub(crate) Vec); +impl Address { + /// Returns whether this address has the ability to receive transfers of the given pool type. + pub fn has_receiver_of_type(&self, pool_type: PoolType) -> bool { + self.0.iter().any(|r| match r { + Receiver::Orchard(_) => pool_type == PoolType::ORCHARD, + Receiver::Sapling(_) => pool_type == PoolType::SAPLING, + Receiver::P2pkh(_) | Receiver::P2sh(_) => pool_type == PoolType::TRANSPARENT, + Receiver::Unknown { .. } => false, + }) + } + + /// Returns whether this address contains the given receiver. + pub fn contains_receiver(&self, receiver: &Receiver) -> bool { + self.0.contains(receiver) + } + + /// Returns whether this address can receive a memo. + pub fn can_receive_memo(&self) -> bool { + self.0 + .iter() + .any(|r| matches!(r, Receiver::Sapling(_) | Receiver::Orchard(_))) + } +} + impl super::private::SealedContainer for Address { /// The HRP for a Bech32m-encoded mainnet Unified Address. /// @@ -92,26 +160,18 @@ impl super::Container for Address { } #[cfg(any(test, feature = "test-dependencies"))] -pub mod test_vectors; - -#[cfg(test)] -mod tests { - use assert_matches::assert_matches; - use zcash_encoding::MAX_COMPACT_SIZE; - - use crate::{ - kind::unified::{private::SealedContainer, Container, Encoding}, - Network, - }; - +pub mod testing { use proptest::{ array::{uniform11, uniform20, uniform32}, collection::vec, prelude::*, sample::select, + strategy::Strategy, }; + use zcash_encoding::MAX_COMPACT_SIZE; - use super::{Address, ParseError, Receiver, Typecode}; + use super::{Address, Receiver}; + use crate::unified::Typecode; prop_compose! { fn uniform43()(a in uniform11(0u8..), b in uniform32(0u8..)) -> [u8; 43] { @@ -122,11 +182,13 @@ mod tests { } } - fn arb_transparent_typecode() -> impl Strategy { + /// A strategy to generate an arbitrary transparent typecode. + pub fn arb_transparent_typecode() -> impl Strategy { select(vec![Typecode::P2pkh, Typecode::P2sh]) } - fn arb_shielded_typecode() -> impl Strategy { + /// A strategy to generate an arbitrary shielded (Sapling, Orchard, or unknown) typecode. + pub fn arb_shielded_typecode() -> impl Strategy { prop_oneof![ Just(Typecode::Sapling), Just(Typecode::Orchard), @@ -137,7 +199,7 @@ mod tests { /// A strategy to generate an arbitrary valid set of typecodes without /// duplication and containing only one of P2sh and P2pkh transparent /// typecodes. The resulting vector will be sorted in encoding order. - fn arb_typecodes() -> impl Strategy> { + pub fn arb_typecodes() -> impl Strategy> { prop::option::of(arb_transparent_typecode()).prop_flat_map(|transparent| { prop::collection::hash_set(arb_shielded_typecode(), 1..4).prop_map(move |xs| { let mut typecodes: Vec<_> = xs.into_iter().chain(transparent).collect(); @@ -147,7 +209,11 @@ mod tests { }) } - fn arb_unified_address_for_typecodes( + /// Generates an arbitrary Unified address containing receivers corresponding to the provided + /// set of typecodes. The receivers of this address are likely to not represent valid protocol + /// receivers, and should only be used for testing parsing and/or encoding functions that do + /// not concern themselves with the validity of the underlying receivers. + pub fn arb_unified_address_for_typecodes( typecodes: Vec, ) -> impl Strategy> { typecodes @@ -164,11 +230,33 @@ mod tests { .collect::>() } - fn arb_unified_address() -> impl Strategy { + /// Generates an arbitrary Unified address. The receivers of this address are likely to not + /// represent valid protocol receivers, and should only be used for testing parsing and/or + /// encoding functions that do not concern themselves with the validity of the underlying + /// receivers. + pub fn arb_unified_address() -> impl Strategy { arb_typecodes() .prop_flat_map(arb_unified_address_for_typecodes) .prop_map(Address) } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod test_vectors; + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use crate::{ + kind::unified::{private::SealedContainer, Container, Encoding}, + unified::address::testing::arb_unified_address, + Network, + }; + + use proptest::{prelude::*, sample::select}; + + use super::{Address, ParseError, Receiver, Typecode}; proptest! { #[test] @@ -296,7 +384,7 @@ mod tests { #[test] fn only_transparent() { // Encoding of `Address(vec![Receiver::P2pkh([0; 20])])`. - let encoded = vec![ + let encoded = [ 0xf0, 0x9e, 0x9d, 0x6e, 0xf5, 0xa6, 0xac, 0x16, 0x50, 0xf0, 0xdb, 0xe1, 0x2c, 0xa5, 0x36, 0x22, 0xa2, 0x04, 0x89, 0x86, 0xe9, 0x6a, 0x9b, 0xf3, 0xff, 0x6d, 0x2f, 0xe6, 0xea, 0xdb, 0xc5, 0x20, 0x62, 0xf9, 0x6f, 0xa9, 0x86, 0xcc, diff --git a/components/zcash_address/src/kind/unified/fvk.rs b/components/zcash_address/src/kind/unified/fvk.rs index 2afc80de66..2133c54d0e 100644 --- a/components/zcash_address/src/kind/unified/fvk.rs +++ b/components/zcash_address/src/kind/unified/fvk.rs @@ -78,6 +78,30 @@ impl SealedItem for Fvk { } /// A Unified Full Viewing Key. +/// +/// # Examples +/// +/// ``` +/// # use std::error::Error; +/// use zcash_address::unified::{self, Container, Encoding}; +/// +/// # fn main() -> Result<(), Box> { +/// # let ufvk_from_user = || "uview1cgrqnry478ckvpr0f580t6fsahp0a5mj2e9xl7hv2d2jd4ldzy449mwwk2l9yeuts85wjls6hjtghdsy5vhhvmjdw3jxl3cxhrg3vs296a3czazrycrr5cywjhwc5c3ztfyjdhmz0exvzzeyejamyp0cr9z8f9wj0953fzht0m4lenk94t70ruwgjxag2tvp63wn9ftzhtkh20gyre3w5s24f6wlgqxnjh40gd2lxe75sf3z8h5y2x0atpxcyf9t3em4h0evvsftluruqne6w4sm066sw0qe5y8qg423grple5fftxrqyy7xmqmatv7nzd7tcjadu8f7mqz4l83jsyxy4t8pkayytyk7nrp467ds85knekdkvnd7hqkfer8mnqd7pv"; +/// let example_ufvk: &str = ufvk_from_user(); +/// +/// let (network, ufvk) = unified::Ufvk::decode(example_ufvk)?; +/// +/// // We can obtain the pool-specific Full Viewing Keys for the UFVK in preference +/// // order (the order in which wallets should prefer to use their corresponding +/// // address receivers): +/// let fvks: Vec = ufvk.items(); +/// +/// // And we can create the UFVK from a list of FVKs: +/// let new_ufvk = unified::Ufvk::try_from_items(fvks)?; +/// assert_eq!(new_ufvk, ufvk); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Ufvk(pub(crate) Vec); @@ -210,7 +234,7 @@ mod tests { // The test cases below use `Ufvk(vec![Fvk::Orchard([1; 96])])` as base. // Invalid padding ([0xff; 16] instead of [b'u', 0x00, 0x00, 0x00...]) - let invalid_padding = vec![ + let invalid_padding = [ 0x6b, 0x32, 0x44, 0xf1, 0xb, 0x67, 0xe9, 0x8f, 0x6, 0x57, 0xe3, 0x5, 0x17, 0xa0, 0x7, 0x5c, 0xb0, 0xc9, 0x23, 0xcc, 0xb7, 0x54, 0xac, 0x55, 0x6a, 0x65, 0x99, 0x95, 0x32, 0x97, 0xd5, 0x34, 0xa7, 0xc8, 0x6f, 0xc, 0xd7, 0x3b, 0xe0, 0x88, 0x19, 0xf3, 0x3e, @@ -228,7 +252,7 @@ mod tests { ); // Short padding (padded to 15 bytes instead of 16) - let truncated_padding = vec![ + let truncated_padding = [ 0xdf, 0xea, 0x84, 0x55, 0xc3, 0x4a, 0x7c, 0x6e, 0x9f, 0x83, 0x3, 0x21, 0x14, 0xb0, 0xcf, 0xb0, 0x60, 0x84, 0x75, 0x3a, 0xdc, 0xb9, 0x93, 0x16, 0xc0, 0x8f, 0x28, 0x5f, 0x61, 0x5e, 0xf0, 0x8e, 0x44, 0xae, 0xa6, 0x74, 0xc5, 0x64, 0xad, 0xfa, 0xdc, 0x7d, @@ -276,7 +300,7 @@ mod tests { ); // - Truncated after the typecode of the Sapling fvk. - let truncated_after_sapling_typecode = vec![ + let truncated_after_sapling_typecode = [ 0xac, 0x26, 0x5b, 0x19, 0x8f, 0x88, 0xb0, 0x7, 0xb3, 0x0, 0x91, 0x19, 0x52, 0xe1, 0x73, 0x48, 0xff, 0x66, 0x7a, 0xef, 0xcf, 0x57, 0x9c, 0x65, 0xe4, 0x6a, 0x7a, 0x1d, 0x19, 0x75, 0x6b, 0x43, 0xdd, 0xcf, 0xb9, 0x9a, 0xf3, 0x7a, 0xf8, 0xb, 0x23, 0x96, 0x64, @@ -306,7 +330,7 @@ mod tests { #[test] fn only_transparent() { // Raw encoding of `Ufvk(vec![Fvk::P2pkh([0; 65])])`. - let encoded = vec![ + let encoded = [ 0xc4, 0x70, 0xc8, 0x7a, 0xcc, 0xe6, 0x6b, 0x1a, 0x62, 0xc7, 0xcd, 0x5f, 0x76, 0xd8, 0xcc, 0x9c, 0x50, 0xbd, 0xce, 0x85, 0x80, 0xd7, 0x78, 0x25, 0x3e, 0x47, 0x9, 0x57, 0x7d, 0x6a, 0xdb, 0x10, 0xb4, 0x11, 0x80, 0x13, 0x4c, 0x83, 0x76, 0xb4, 0x6b, 0xbd, diff --git a/components/zcash_address/src/kind/unified/ivk.rs b/components/zcash_address/src/kind/unified/ivk.rs index 31c2ad56f0..2a4ebb4fa2 100644 --- a/components/zcash_address/src/kind/unified/ivk.rs +++ b/components/zcash_address/src/kind/unified/ivk.rs @@ -83,6 +83,30 @@ impl SealedItem for Ivk { } /// A Unified Incoming Viewing Key. +/// +/// # Examples +/// +/// ``` +/// # use std::error::Error; +/// use zcash_address::unified::{self, Container, Encoding}; +/// +/// # fn main() -> Result<(), Box> { +/// # let uivk_from_user = || "uivk1djetqg3fws7y7qu5tekynvcdhz69gsyq07ewvppmzxdqhpfzdgmx8urnkqzv7ylz78ez43ux266pqjhecd59fzhn7wpe6zarnzh804hjtkyad25ryqla5pnc8p5wdl3phj9fczhz64zprun3ux7y9jc08567xryumuz59rjmg4uuflpjqwnq0j0tzce0x74t4tv3gfjq7nczkawxy6y7hse733ae3vw7qfjd0ss0pytvezxp42p6rrpzeh6t2zrz7zpjk0xhngcm6gwdppxs58jkx56gsfflugehf5vjlmu7vj3393gj6u37wenavtqyhdvcdeaj86s6jczl4zq"; +/// let example_uivk: &str = uivk_from_user(); +/// +/// let (network, uivk) = unified::Uivk::decode(example_uivk)?; +/// +/// // We can obtain the pool-specific Incoming Viewing Keys for the UIVK in +/// // preference order (the order in which wallets should prefer to use their +/// // corresponding address receivers): +/// let ivks: Vec = uivk.items(); +/// +/// // And we can create the UIVK from a list of IVKs: +/// let new_uivk = unified::Uivk::try_from_items(ivks)?; +/// assert_eq!(new_uivk, uivk); +/// # Ok(()) +/// # } +/// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Uivk(pub(crate) Vec); @@ -203,7 +227,7 @@ mod tests { // The test cases below use `Uivk(vec![Ivk::Orchard([1; 64])])` as base. // Invalid padding ([0xff; 16] instead of [b'u', 0x00, 0x00, 0x00...]) - let invalid_padding = vec![ + let invalid_padding = [ 0xba, 0xbc, 0xc0, 0x71, 0xcd, 0x3b, 0xfd, 0x9a, 0x32, 0x19, 0x7e, 0xeb, 0x8a, 0xa7, 0x6e, 0xd4, 0xac, 0xcb, 0x59, 0xc2, 0x54, 0x26, 0xc6, 0xab, 0x71, 0xc7, 0xc3, 0x72, 0xc, 0xa9, 0xad, 0xa4, 0xad, 0x8c, 0x9e, 0x35, 0x7b, 0x4c, 0x5d, 0xc7, 0x66, 0x12, @@ -219,7 +243,7 @@ mod tests { ); // Short padding (padded to 15 bytes instead of 16) - let truncated_padding = vec![ + let truncated_padding = [ 0x96, 0x73, 0x6a, 0x56, 0xbc, 0x44, 0x38, 0xe2, 0x47, 0x41, 0x1c, 0x70, 0xe4, 0x6, 0x87, 0xbe, 0xb6, 0x90, 0xbd, 0xab, 0x1b, 0xd8, 0x27, 0x10, 0x0, 0x21, 0x30, 0x2, 0x77, 0x87, 0x0, 0x25, 0x96, 0x94, 0x8f, 0x1e, 0x39, 0xd2, 0xd8, 0x65, 0xb4, 0x3c, 0x72, @@ -242,7 +266,7 @@ mod tests { // with the ivk data truncated, but valid padding. // - Missing the last data byte of the Sapling ivk. - let truncated_sapling_data = vec![ + let truncated_sapling_data = [ 0xce, 0xbc, 0xfe, 0xc5, 0xef, 0x2d, 0xe, 0x66, 0xc2, 0x8c, 0x34, 0xdc, 0x2e, 0x24, 0xd2, 0xc7, 0x4b, 0xac, 0x36, 0xe0, 0x43, 0x72, 0xa7, 0x33, 0xa4, 0xe, 0xe0, 0x52, 0x15, 0x64, 0x66, 0x92, 0x36, 0xa7, 0x60, 0x8e, 0x48, 0xe8, 0xb0, 0x30, 0x4d, 0xcb, @@ -261,7 +285,7 @@ mod tests { ); // - Truncated after the typecode of the Sapling ivk. - let truncated_after_sapling_typecode = vec![ + let truncated_after_sapling_typecode = [ 0xf7, 0x3, 0xd8, 0xbe, 0x6a, 0x27, 0xfa, 0xa1, 0xd3, 0x11, 0xea, 0x25, 0x94, 0xe2, 0xb, 0xde, 0xed, 0x6a, 0xaa, 0x8, 0x46, 0x7d, 0xe4, 0xb1, 0xe, 0xf1, 0xde, 0x61, 0xd7, 0x95, 0xf7, 0x82, 0x62, 0x32, 0x7a, 0x73, 0x8c, 0x55, 0x93, 0xa1, 0x63, 0x75, 0xe2, 0xca, @@ -288,7 +312,7 @@ mod tests { #[test] fn only_transparent() { // Raw Encoding of `Uivk(vec![Ivk::P2pkh([0; 65])])`. - let encoded = vec![ + let encoded = [ 0x12, 0x51, 0x37, 0xc7, 0xac, 0x8c, 0xd, 0x13, 0x3a, 0x5f, 0xc6, 0x84, 0x53, 0x90, 0xf8, 0xe7, 0x23, 0x34, 0xfb, 0xda, 0x49, 0x3c, 0x87, 0x1c, 0x8f, 0x1a, 0xe1, 0x63, 0xba, 0xdf, 0x77, 0x64, 0x43, 0xcf, 0xdc, 0x37, 0x1f, 0xd2, 0x89, 0x60, 0xe3, 0x77, diff --git a/components/zcash_address/src/lib.rs b/components/zcash_address/src/lib.rs index c0f7fbc62c..32a3c05f4d 100644 --- a/components/zcash_address/src/lib.rs +++ b/components/zcash_address/src/lib.rs @@ -1,3 +1,134 @@ +//! *Parser for all defined Zcash address types.* +//! +//! This crate implements address parsing as a two-phase process, built around the opaque +//! [`ZcashAddress`] type. +//! +//! - [`ZcashAddress`] can be parsed from, and encoded to, strings. +//! - [`ZcashAddress::convert`] or [`ZcashAddress::convert_if_network`] can be used to +//! convert a parsed address into custom types that implement the [`TryFromAddress`] or +//! [`TryFromRawAddress`] traits. +//! - Custom types can be converted into a [`ZcashAddress`] via its implementation of the +//! [`ToAddress`] trait. +//! +//! ```text +//! s.parse() .convert() +//! --------> ---------> +//! Strings ZcashAddress Custom types +//! <-------- <--------- +//! .encode() ToAddress +//! ``` +//! +//! It is important to note that this crate does not depend on any of the Zcash protocol +//! crates (e.g. `sapling-crypto` or `orchard`). This crate has minimal dependencies by +//! design; it focuses solely on parsing, handling those concerns for you, while exposing +//! APIs that enable you to convert the parsed data into the Rust types you want to use. +//! +//! # Using this crate +//! +//! ## I just need to validate Zcash addresses +//! +//! ``` +//! # use zcash_address::ZcashAddress; +//! fn is_valid_zcash_address(addr_string: &str) -> bool { +//! addr_string.parse::().is_ok() +//! } +//! ``` +//! +//! ## I want to parse Zcash addresses in a Rust wallet app that uses the `zcash_primitives` transaction builder +//! +//! Use `zcash_client_backend::address::RecipientAddress`, which implements the traits in +//! this crate to parse address strings into protocol types that work with the transaction +//! builder in the `zcash_primitives` crate (as well as the wallet functionality in the +//! `zcash_client_backend` crate itself). +//! +//! > We intend to refactor the key and address types from the `zcash_client_backend` and +//! > `zcash_primitives` crates into a separate crate focused on dealing with Zcash key +//! > material. That crate will then be what you should use. +//! +//! ## I want to parse Unified Addresses +//! +//! See the [`unified::Address`] documentation for examples. +//! +//! While the [`unified::Address`] type does have parsing methods, you should still parse +//! your address strings with [`ZcashAddress`] and then convert; this will ensure that for +//! other Zcash address types you get a [`ConversionError::Unsupported`], which is a +//! better error for your users. +//! +//! ## I want to parse mainnet Zcash addresses in a language that supports C FFI +//! +//! As an example, you could use static functions to create the address types in the +//! target language from the parsed data. +//! +//! ``` +//! use std::ffi::{CStr, c_char, c_void}; +//! use std::ptr; +//! +//! use zcash_address::{ConversionError, Network, TryFromRawAddress, ZcashAddress}; +//! +//! // Functions that return a pointer to a heap-allocated address of the given kind in +//! // the target language. These should be augmented to return any relevant errors. +//! extern { +//! fn addr_from_sapling(data: *const u8) -> *mut c_void; +//! fn addr_from_transparent_p2pkh(data: *const u8) -> *mut c_void; +//! } +//! +//! struct ParsedAddress(*mut c_void); +//! +//! impl TryFromRawAddress for ParsedAddress { +//! type Error = &'static str; +//! +//! fn try_from_raw_sapling( +//! data: [u8; 43], +//! ) -> Result> { +//! let parsed = unsafe { addr_from_sapling(data[..].as_ptr()) }; +//! if parsed.is_null() { +//! Err("Reason for the failure".into()) +//! } else { +//! Ok(Self(parsed)) +//! } +//! } +//! +//! fn try_from_raw_transparent_p2pkh( +//! data: [u8; 20], +//! ) -> Result> { +//! let parsed = unsafe { addr_from_transparent_p2pkh(data[..].as_ptr()) }; +//! if parsed.is_null() { +//! Err("Reason for the failure".into()) +//! } else { +//! Ok(Self(parsed)) +//! } +//! } +//! } +//! +//! pub extern "C" fn parse_zcash_address(encoded: *const c_char) -> *mut c_void { +//! let encoded = unsafe { CStr::from_ptr(encoded) }.to_str().expect("valid"); +//! +//! let addr = match ZcashAddress::try_from_encoded(encoded) { +//! Ok(addr) => addr, +//! Err(e) => { +//! // This was either an invalid address encoding, or not a Zcash address. +//! // You should pass this error back across the FFI. +//! return ptr::null_mut(); +//! } +//! }; +//! +//! match addr.convert_if_network::(Network::Main) { +//! Ok(parsed) => parsed.0, +//! Err(e) => { +//! // We didn't implement all of the methods of `TryFromRawAddress`, so if an +//! // address with one of those kinds is parsed, it will result in an error +//! // here that should be passed back across the FFI. +//! ptr::null_mut() +//! } +//! } +//! } +//! ``` + +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// Catch documentation errors caused by code changes. +#![deny(rustdoc::broken_intra_doc_links)] + mod convert; mod encoding; mod kind; @@ -10,6 +141,9 @@ pub use convert::{ }; pub use encoding::ParseError; pub use kind::unified; +use kind::unified::Receiver; +pub use zcash_protocol::consensus::NetworkType as Network; +use zcash_protocol::PoolType; /// A Zcash address. #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -18,28 +152,15 @@ pub struct ZcashAddress { kind: AddressKind, } -/// The Zcash network for which an address is encoded. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Network { - /// Zcash Mainnet. - Main, - /// Zcash Testnet. - Test, - /// Private integration / regression testing, used in `zcashd`. - /// - /// For some address types there is no distinction between test and regtest encodings; - /// those will always be parsed as `Network::Test`. - Regtest, -} - /// Known kinds of Zcash addresses. #[derive(Clone, Debug, PartialEq, Eq, Hash)] enum AddressKind { - Sprout(kind::sprout::Data), - Sapling(kind::sapling::Data), + Sprout([u8; 64]), + Sapling([u8; 43]), Unified(unified::Address), - P2pkh(kind::p2pkh::Data), - P2sh(kind::p2sh::Data), + P2pkh([u8; 20]), + P2sh([u8; 20]), + Tex([u8; 20]), } impl ZcashAddress { @@ -104,6 +225,7 @@ impl ZcashAddress { AddressKind::Unified(data) => T::try_from_unified(self.net, data), AddressKind::P2pkh(data) => T::try_from_transparent_p2pkh(self.net, data), AddressKind::P2sh(data) => T::try_from_transparent_p2sh(self.net, data), + AddressKind::Tex(data) => T::try_from_tex(self.net, data), } } @@ -139,10 +261,123 @@ impl ZcashAddress { T::try_from_raw_transparent_p2pkh(data) } AddressKind::P2sh(data) if regtest_exception => T::try_from_raw_transparent_p2sh(data), + AddressKind::Tex(data) if network_matches => T::try_from_raw_tex(data), _ => Err(ConversionError::IncorrectNetwork { expected: net, actual: self.net, }), } } + + /// Returns whether this address has the ability to receive transfers of the given pool type. + pub fn can_receive_as(&self, pool_type: PoolType) -> bool { + use AddressKind::*; + match &self.kind { + Sprout(_) => false, + Sapling(_) => pool_type == PoolType::SAPLING, + Unified(addr) => addr.has_receiver_of_type(pool_type), + P2pkh(_) | P2sh(_) | Tex(_) => pool_type == PoolType::TRANSPARENT, + } + } + + /// Returns whether this address can receive a memo. + pub fn can_receive_memo(&self) -> bool { + use AddressKind::*; + match &self.kind { + Sprout(_) | Sapling(_) => true, + Unified(addr) => addr.can_receive_memo(), + P2pkh(_) | P2sh(_) | Tex(_) => false, + } + } + + /// Returns whether or not this address contains or corresponds to the given unified address + /// receiver. + pub fn matches_receiver(&self, receiver: &Receiver) -> bool { + match (&self.kind, receiver) { + (AddressKind::Unified(ua), r) => ua.contains_receiver(r), + (AddressKind::Sapling(d), Receiver::Sapling(r)) => r == d, + (AddressKind::P2pkh(d), Receiver::P2pkh(r)) => r == d, + (AddressKind::Tex(d), Receiver::P2pkh(r)) => r == d, + (AddressKind::P2sh(d), Receiver::P2sh(r)) => r == d, + _ => false, + } + } +} + +#[cfg(feature = "test-dependencies")] +pub mod testing { + use std::convert::TryInto; + + use proptest::{array::uniform20, collection::vec, prelude::any, prop_compose, prop_oneof}; + + use crate::{unified::address::testing::arb_unified_address, AddressKind, ZcashAddress}; + use zcash_protocol::consensus::NetworkType; + + prop_compose! { + fn arb_sprout_addr_kind()( + r_bytes in vec(any::(), 64) + ) -> AddressKind { + AddressKind::Sprout(r_bytes.try_into().unwrap()) + } + } + + prop_compose! { + fn arb_sapling_addr_kind()( + r_bytes in vec(any::(), 43) + ) -> AddressKind { + AddressKind::Sapling(r_bytes.try_into().unwrap()) + } + } + + prop_compose! { + fn arb_p2pkh_addr_kind()( + r_bytes in uniform20(any::()) + ) -> AddressKind { + AddressKind::P2pkh(r_bytes) + } + } + + prop_compose! { + fn arb_p2sh_addr_kind()( + r_bytes in uniform20(any::()) + ) -> AddressKind { + AddressKind::P2sh(r_bytes) + } + } + + prop_compose! { + fn arb_unified_addr_kind()( + uaddr in arb_unified_address() + ) -> AddressKind { + AddressKind::Unified(uaddr) + } + } + + prop_compose! { + fn arb_tex_addr_kind()( + r_bytes in uniform20(any::()) + ) -> AddressKind { + AddressKind::Tex(r_bytes) + } + } + + prop_compose! { + /// Create an arbitrary, structurally-valid `ZcashAddress` value. + /// + /// Note that the data contained in the generated address does _not_ necessarily correspond + /// to a valid address according to the Zcash protocol; binary data in the resulting value + /// is entirely random. + pub fn arb_address(net: NetworkType)( + kind in prop_oneof!( + arb_sprout_addr_kind(), + arb_sapling_addr_kind(), + arb_p2pkh_addr_kind(), + arb_p2sh_addr_kind(), + arb_unified_addr_kind(), + arb_tex_addr_kind() + ) + ) -> ZcashAddress { + ZcashAddress { net, kind } + } + } } diff --git a/components/zcash_encoding/CHANGELOG.md b/components/zcash_encoding/CHANGELOG.md index da71b50f7c..f8733c1bd2 100644 --- a/components/zcash_encoding/CHANGELOG.md +++ b/components/zcash_encoding/CHANGELOG.md @@ -7,6 +7,11 @@ and this library adheres to Rust's notion of ## [Unreleased] +## [0.2.1] - 2024-08-19 +### Added +- `zcash_encoding::CompactSize::serialized_size` +- `zcash_encoding::Vector::serialized_size_of_u8_vec` + ## [0.2.0] - 2022-10-19 ### Changed - MSRV is now 1.56.1 diff --git a/components/zcash_encoding/Cargo.toml b/components/zcash_encoding/Cargo.toml index b287a47656..02f41a0a4b 100644 --- a/components/zcash_encoding/Cargo.toml +++ b/components/zcash_encoding/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zcash_encoding" description = "Binary encodings used throughout the Zcash ecosystem." -version = "0.2.0" +version = "0.2.1" authors = [ "Jack Grigg ", "Kris Nuttycombe ", @@ -16,8 +16,8 @@ categories = ["cryptography::cryptocurrencies", "encoding"] keywords = ["zcash"] [dependencies] -byteorder = "1" -nonempty = "0.7" +byteorder.workspace = true +nonempty.workspace = true [lib] bench = false diff --git a/components/zcash_encoding/src/lib.rs b/components/zcash_encoding/src/lib.rs index 8e23f7b68f..69f1c5c63c 100644 --- a/components/zcash_encoding/src/lib.rs +++ b/components/zcash_encoding/src/lib.rs @@ -93,6 +93,16 @@ impl CompactSize { } } } + + /// Returns the number of bytes needed to encode the given size in compact form. + pub fn serialized_size(size: usize) -> usize { + match size { + s if s < 253 => 1, + s if s <= 0xFFFF => 3, + s if s <= 0xFFFFFFFF => 5, + _ => 9, + } + } } /// Namespace for functions that perform encoding of vectors. @@ -171,6 +181,12 @@ impl Vector { CompactSize::write(&mut writer, items.len())?; items.try_for_each(|e| func(&mut writer, e)) } + + /// Returns the serialized size of a vector of `u8` as written by `[Vector::write]`. + pub fn serialized_size_of_u8_vec(vec: &[u8]) -> usize { + let length = vec.len(); + CompactSize::serialized_size(length) + length + } } /// Namespace for functions that perform encoding of array contents. @@ -279,8 +295,11 @@ mod tests { >::Error: Debug, { let mut data = vec![]; - CompactSize::write(&mut data, value.try_into().unwrap()).unwrap(); + let value_usize: usize = value.try_into().unwrap(); + CompactSize::write(&mut data, value_usize).unwrap(); assert_eq!(&data[..], expected); + let serialized_size = CompactSize::serialized_size(value_usize); + assert_eq!(serialized_size, expected.len()); let result: io::Result = CompactSize::read_t(&data[..]); match result { Ok(n) => assert_eq!(n, value), @@ -308,6 +327,8 @@ mod tests { let mut data = vec![]; CompactSize::write(&mut data, value).unwrap(); assert_eq!(&data[..], encoded); + let serialized_size = CompactSize::serialized_size(value); + assert_eq!(serialized_size, encoded.len()); assert!(CompactSize::read(encoded).is_err()); } } @@ -320,6 +341,8 @@ mod tests { let mut data = vec![]; Vector::write(&mut data, &$value, |w, e| w.write_u8(*e)).unwrap(); assert_eq!(&data[..], &$expected[..]); + let serialized_size = Vector::serialized_size_of_u8_vec(&$value); + assert_eq!(serialized_size, $expected.len()); match Vector::read(&data[..], |r| r.read_u8()) { Ok(v) => assert_eq!(v, $value), Err(e) => panic!("Unexpected error: {:?}", e), diff --git a/components/zcash_note_encryption/CHANGELOG.md b/components/zcash_note_encryption/CHANGELOG.md deleted file mode 100644 index cedc180c69..0000000000 --- a/components/zcash_note_encryption/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# Changelog -All notable changes to this library will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this library adheres to Rust's notion of -[Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.4.0] - 2023-06-06 -### Changed -- The `esk` and `ephemeral_key` arguments have been removed from - `Domain::parse_note_plaintext_without_memo_ovk`. It is therefore no longer - necessary (or possible) to ensure that `ephemeral_key` is derived from `esk` - and the diversifier within the note plaintext. We have analyzed the safety of - this change in the context of callers within `zcash_note_encryption` and - `orchard`. See https://github.com/zcash/librustzcash/pull/848 and the - associated issue https://github.com/zcash/librustzcash/issues/802 for - additional detail. - -## [0.3.0] - 2023-03-22 -### Changed -- The `recipient` parameter has been removed from `Domain::note_plaintext_bytes`. -- The `recipient` parameter has been removed from `NoteEncryption::new`. Since - the `Domain::Note` type is now expected to contain information about the - recipient of the note, there is no longer any need to pass this information - in via the encryption context. - -## [0.2.0] - 2022-10-13 -### Added -- `zcash_note_encryption::Domain`: - - `Domain::PreparedEphemeralPublicKey` associated type. - - `Domain::prepare_epk` method, which produces the above type. - -### Changed -- MSRV is now 1.56.1. -- `zcash_note_encryption::Domain` now requires `epk` to be converted to - `Domain::PreparedEphemeralPublicKey` before being passed to - `Domain::ka_agree_dec`. -- Changes to batch decryption APIs: - - The return types of `batch::try_note_decryption` and - `batch::try_compact_note_decryption` have changed. Now, instead of - returning entries corresponding to the cartesian product of the IVKs used for - decryption with the outputs being decrypted, this now returns a vector of - decryption results of the same length and in the same order as the `outputs` - argument to the function. Each successful result includes the index of the - entry in `ivks` used to decrypt the value. - -## [0.1.0] - 2021-12-17 -Initial release. diff --git a/components/zcash_note_encryption/Cargo.toml b/components/zcash_note_encryption/Cargo.toml deleted file mode 100644 index 34d359ef7f..0000000000 --- a/components/zcash_note_encryption/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "zcash_note_encryption" -description = "Note encryption for Zcash transactions" -version = "0.4.0" -authors = [ - "Jack Grigg ", - "Kris Nuttycombe " -] -homepage = "https://github.com/zcash/librustzcash" -repository = "https://github.com/zcash/librustzcash" -readme = "README.md" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.56.1" -categories = ["cryptography::cryptocurrencies"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -cipher = { version = "0.4", default-features = false } -chacha20 = { version = "0.9", default-features = false } -chacha20poly1305 = { version = "0.10", default-features = false } -rand_core = { version = "0.6", default-features = false } -subtle = { version = "2.3", default-features = false } - -[features] -default = ["alloc"] -alloc = [] -pre-zip-212 = [] - -[lib] -bench = false diff --git a/components/zcash_note_encryption/README.md b/components/zcash_note_encryption/README.md deleted file mode 100644 index 612b7a64fb..0000000000 --- a/components/zcash_note_encryption/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# zcash_note_encryption - -This crate implements the [in-band secret distribution scheme] for the Sapling and -Orchard protocols. It provides reusable methods that implement common note encryption -and trial decryption logic, and enforce protocol-agnostic verification requirements. - -Protocol-specific logic is handled via the `Domain` trait. Implementations of this -trait are provided in the [`zcash_primitives`] (for Sapling) and [`orchard`] crates; -users with their own existing types can similarly implement the trait themselves. - -[in-band secret distribution scheme]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband -[`zcash_primitives`]: https://crates.io/crates/zcash_primitives -[`orchard`]: https://crates.io/crates/orchard - -## License - -Licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. diff --git a/components/zcash_note_encryption/src/batch.rs b/components/zcash_note_encryption/src/batch.rs deleted file mode 100644 index ad704167c2..0000000000 --- a/components/zcash_note_encryption/src/batch.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! APIs for batch trial decryption. - -use alloc::vec::Vec; // module is alloc only - -use crate::{ - try_compact_note_decryption_inner, try_note_decryption_inner, BatchDomain, EphemeralKeyBytes, - ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, -}; - -/// Trial decryption of a batch of notes with a set of recipients. -/// -/// This is the batched version of [`crate::try_note_decryption`]. -/// -/// Returns a vector containing the decrypted result for each output, -/// with the same length and in the same order as the outputs were -/// provided, along with the index in the `ivks` slice associated with -/// the IVK that successfully decrypted the output. -#[allow(clippy::type_complexity)] -pub fn try_note_decryption>( - ivks: &[D::IncomingViewingKey], - outputs: &[(D, Output)], -) -> Vec> { - batch_note_decryption(ivks, outputs, try_note_decryption_inner) -} - -/// Trial decryption of a batch of notes for light clients with a set of recipients. -/// -/// This is the batched version of [`crate::try_compact_note_decryption`]. -/// -/// Returns a vector containing the decrypted result for each output, -/// with the same length and in the same order as the outputs were -/// provided, along with the index in the `ivks` slice associated with -/// the IVK that successfully decrypted the output. -#[allow(clippy::type_complexity)] -pub fn try_compact_note_decryption>( - ivks: &[D::IncomingViewingKey], - outputs: &[(D, Output)], -) -> Vec> { - batch_note_decryption(ivks, outputs, try_compact_note_decryption_inner) -} - -fn batch_note_decryption, F, FR, const CS: usize>( - ivks: &[D::IncomingViewingKey], - outputs: &[(D, Output)], - decrypt_inner: F, -) -> Vec> -where - F: Fn(&D, &D::IncomingViewingKey, &EphemeralKeyBytes, &Output, &D::SymmetricKey) -> Option, -{ - if ivks.is_empty() { - return (0..outputs.len()).map(|_| None).collect(); - }; - - // Fetch the ephemeral keys for each output, and batch-parse and prepare them. - let ephemeral_keys = D::batch_epk(outputs.iter().map(|(_, output)| output.ephemeral_key())); - - // Derive the shared secrets for all combinations of (ivk, output). - // The scalar multiplications cannot benefit from batching. - let items = ephemeral_keys.iter().flat_map(|(epk, ephemeral_key)| { - ivks.iter().map(move |ivk| { - ( - epk.as_ref().map(|epk| D::ka_agree_dec(ivk, epk)), - ephemeral_key, - ) - }) - }); - - // Run the batch-KDF to obtain the symmetric keys from the shared secrets. - let keys = D::batch_kdf(items); - - // Finish the trial decryption! - keys.chunks(ivks.len()) - .zip(ephemeral_keys.iter().zip(outputs.iter())) - .map(|(key_chunk, ((_, ephemeral_key), (domain, output)))| { - key_chunk - .iter() - .zip(ivks.iter().enumerate()) - .filter_map(|(key, (i, ivk))| { - key.as_ref() - .and_then(|key| decrypt_inner(domain, ivk, ephemeral_key, output, key)) - .map(|out| (out, i)) - }) - .next() - }) - .collect::>>() -} diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs deleted file mode 100644 index fb8049d40c..0000000000 --- a/components/zcash_note_encryption/src/lib.rs +++ /dev/null @@ -1,675 +0,0 @@ -//! Note encryption for Zcash transactions. -//! -//! This crate implements the [in-band secret distribution scheme] for the Sapling and -//! Orchard protocols. It provides reusable methods that implement common note encryption -//! and trial decryption logic, and enforce protocol-agnostic verification requirements. -//! -//! Protocol-specific logic is handled via the [`Domain`] trait. Implementations of this -//! trait are provided in the [`zcash_primitives`] (for Sapling) and [`orchard`] crates; -//! users with their own existing types can similarly implement the trait themselves. -//! -//! [in-band secret distribution scheme]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband -//! [`zcash_primitives`]: https://crates.io/crates/zcash_primitives -//! [`orchard`]: https://crates.io/crates/orchard - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -// Catch documentation errors caused by code changes. -#![deny(rustdoc::broken_intra_doc_links)] -#![deny(unsafe_code)] -// TODO: #![deny(missing_docs)] - -#[cfg(feature = "alloc")] -extern crate alloc; -#[cfg(feature = "alloc")] -use alloc::vec::Vec; - -use chacha20::{ - cipher::{StreamCipher, StreamCipherSeek}, - ChaCha20, -}; -use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit}; -use cipher::KeyIvInit; - -use rand_core::RngCore; -use subtle::{Choice, ConstantTimeEq}; - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub mod batch; - -/// The size of a compact note. -pub const COMPACT_NOTE_SIZE: usize = 1 + // version - 11 + // diversifier - 8 + // value - 32; // rseed (or rcm prior to ZIP 212) -/// The size of [`NotePlaintextBytes`]. -pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; -/// The size of [`OutPlaintextBytes`]. -pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d - 32; // esk -const AEAD_TAG_SIZE: usize = 16; -/// The size of an encrypted note plaintext. -pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE; -/// The size of an encrypted outgoing plaintext. -pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + AEAD_TAG_SIZE; - -/// A symmetric key that can be used to recover a single Sapling or Orchard output. -pub struct OutgoingCipherKey(pub [u8; 32]); - -impl From<[u8; 32]> for OutgoingCipherKey { - fn from(ock: [u8; 32]) -> Self { - OutgoingCipherKey(ock) - } -} - -impl AsRef<[u8]> for OutgoingCipherKey { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -/// Newtype representing the byte encoding of an [`EphemeralPublicKey`]. -/// -/// [`EphemeralPublicKey`]: Domain::EphemeralPublicKey -#[derive(Clone, Debug)] -pub struct EphemeralKeyBytes(pub [u8; 32]); - -impl AsRef<[u8]> for EphemeralKeyBytes { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl From<[u8; 32]> for EphemeralKeyBytes { - fn from(value: [u8; 32]) -> EphemeralKeyBytes { - EphemeralKeyBytes(value) - } -} - -impl ConstantTimeEq for EphemeralKeyBytes { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } -} - -/// Newtype representing the byte encoding of a note plaintext. -pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); -/// Newtype representing the byte encoding of a outgoing plaintext. -pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]); - -#[derive(Copy, Clone, PartialEq, Eq)] -enum NoteValidity { - Valid, - Invalid, -} - -/// Trait that encapsulates protocol-specific note encryption types and logic. -/// -/// This trait enables most of the note encryption logic to be shared between Sapling and -/// Orchard, as well as between different implementations of those protocols. -pub trait Domain { - type EphemeralSecretKey: ConstantTimeEq; - type EphemeralPublicKey; - type PreparedEphemeralPublicKey; - type SharedSecret; - type SymmetricKey: AsRef<[u8]>; - type Note; - type Recipient; - type DiversifiedTransmissionKey; - type IncomingViewingKey; - type OutgoingViewingKey; - type ValueCommitment; - type ExtractedCommitment; - type ExtractedCommitmentBytes: Eq + for<'a> From<&'a Self::ExtractedCommitment>; - type Memo; - - /// Derives the `EphemeralSecretKey` corresponding to this note. - /// - /// Returns `None` if the note was created prior to [ZIP 212], and doesn't have a - /// deterministic `EphemeralSecretKey`. - /// - /// [ZIP 212]: https://zips.z.cash/zip-0212 - fn derive_esk(note: &Self::Note) -> Option; - - /// Extracts the `DiversifiedTransmissionKey` from the note. - fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey; - - /// Prepare an ephemeral public key for more efficient scalar multiplication. - fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey; - - /// Derives `EphemeralPublicKey` from `esk` and the note's diversifier. - fn ka_derive_public( - note: &Self::Note, - esk: &Self::EphemeralSecretKey, - ) -> Self::EphemeralPublicKey; - - /// Derives the `SharedSecret` from the sender's information during note encryption. - fn ka_agree_enc( - esk: &Self::EphemeralSecretKey, - pk_d: &Self::DiversifiedTransmissionKey, - ) -> Self::SharedSecret; - - /// Derives the `SharedSecret` from the recipient's information during note trial - /// decryption. - fn ka_agree_dec( - ivk: &Self::IncomingViewingKey, - epk: &Self::PreparedEphemeralPublicKey, - ) -> Self::SharedSecret; - - /// Derives the `SymmetricKey` used to encrypt the note plaintext. - /// - /// `secret` is the `SharedSecret` obtained from [`Self::ka_agree_enc`] or - /// [`Self::ka_agree_dec`]. - /// - /// `ephemeral_key` is the byte encoding of the [`EphemeralPublicKey`] used to derive - /// `secret`. During encryption it is derived via [`Self::epk_bytes`]; during trial - /// decryption it is obtained from [`ShieldedOutput::ephemeral_key`]. - /// - /// [`EphemeralPublicKey`]: Self::EphemeralPublicKey - /// [`EphemeralSecretKey`]: Self::EphemeralSecretKey - fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey; - - /// Encodes the given `Note` and `Memo` as a note plaintext. - fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes; - - /// Derives the [`OutgoingCipherKey`] for an encrypted note, given the note-specific - /// public data and an `OutgoingViewingKey`. - fn derive_ock( - ovk: &Self::OutgoingViewingKey, - cv: &Self::ValueCommitment, - cmstar_bytes: &Self::ExtractedCommitmentBytes, - ephemeral_key: &EphemeralKeyBytes, - ) -> OutgoingCipherKey; - - /// Encodes the outgoing plaintext for the given note. - fn outgoing_plaintext_bytes( - note: &Self::Note, - esk: &Self::EphemeralSecretKey, - ) -> OutPlaintextBytes; - - /// Returns the byte encoding of the given `EphemeralPublicKey`. - fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; - - /// Attempts to parse `ephemeral_key` as an `EphemeralPublicKey`. - /// - /// Returns `None` if `ephemeral_key` is not a valid byte encoding of an - /// `EphemeralPublicKey`. - fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option; - - /// Derives the `ExtractedCommitment` for this note. - fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment; - - /// Parses the given note plaintext from the recipient's perspective. - /// - /// The implementation of this method must check that: - /// - The note plaintext version is valid (for the given decryption domain's context, - /// which may be passed via `self`). - /// - The note plaintext contains valid encodings of its various fields. - /// - Any domain-specific requirements are satisfied. - /// - /// `&self` is passed here to enable the implementation to enforce contextual checks, - /// such as rules like [ZIP 212] that become active at a specific block height. - /// - /// [ZIP 212]: https://zips.z.cash/zip-0212 - /// - /// # Panics - /// - /// Panics if `plaintext` is shorter than [`COMPACT_NOTE_SIZE`]. - fn parse_note_plaintext_without_memo_ivk( - &self, - ivk: &Self::IncomingViewingKey, - plaintext: &[u8], - ) -> Option<(Self::Note, Self::Recipient)>; - - /// Parses the given note plaintext from the sender's perspective. - /// - /// The implementation of this method must check that: - /// - The note plaintext version is valid (for the given decryption domain's context, - /// which may be passed via `self`). - /// - The note plaintext contains valid encodings of its various fields. - /// - Any domain-specific requirements are satisfied. - /// - /// `&self` is passed here to enable the implementation to enforce contextual checks, - /// such as rules like [ZIP 212] that become active at a specific block height. - /// - /// [ZIP 212]: https://zips.z.cash/zip-0212 - fn parse_note_plaintext_without_memo_ovk( - &self, - pk_d: &Self::DiversifiedTransmissionKey, - plaintext: &NotePlaintextBytes, - ) -> Option<(Self::Note, Self::Recipient)>; - - /// Extracts the memo field from the given note plaintext. - /// - /// # Compatibility - /// - /// `&self` is passed here in anticipation of future changes to memo handling, where - /// the memos may no longer be part of the note plaintext. - fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo; - - /// Parses the `DiversifiedTransmissionKey` field of the outgoing plaintext. - /// - /// Returns `None` if `out_plaintext` does not contain a valid byte encoding of a - /// `DiversifiedTransmissionKey`. - fn extract_pk_d(out_plaintext: &OutPlaintextBytes) -> Option; - - /// Parses the `EphemeralSecretKey` field of the outgoing plaintext. - /// - /// Returns `None` if `out_plaintext` does not contain a valid byte encoding of an - /// `EphemeralSecretKey`. - fn extract_esk(out_plaintext: &OutPlaintextBytes) -> Option; -} - -/// Trait that encapsulates protocol-specific batch trial decryption logic. -/// -/// Each batchable operation has a default implementation that calls through to the -/// non-batched implementation. Domains can override whichever operations benefit from -/// batched logic. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub trait BatchDomain: Domain { - /// Computes `Self::kdf` on a batch of items. - /// - /// For each item in the batch, if the shared secret is `None`, this returns `None` at - /// that position. - fn batch_kdf<'a>( - items: impl Iterator, &'a EphemeralKeyBytes)>, - ) -> Vec> { - // Default implementation: do the non-batched thing. - items - .map(|(secret, ephemeral_key)| secret.map(|secret| Self::kdf(secret, ephemeral_key))) - .collect() - } - - /// Computes `Self::epk` on a batch of ephemeral keys. - /// - /// This is useful for protocols where the underlying curve requires an inversion to - /// parse an encoded point. - /// - /// For usability, this returns tuples of the ephemeral keys and the result of parsing - /// them. - fn batch_epk( - ephemeral_keys: impl Iterator, - ) -> Vec<(Option, EphemeralKeyBytes)> { - // Default implementation: do the non-batched thing. - ephemeral_keys - .map(|ephemeral_key| { - ( - Self::epk(&ephemeral_key).map(Self::prepare_epk), - ephemeral_key, - ) - }) - .collect() - } -} - -/// Trait that provides access to the components of an encrypted transaction output. -/// -/// Implementations of this trait are required to define the length of their ciphertext -/// field. In order to use the trial decryption APIs in this crate, the length must be -/// either [`ENC_CIPHERTEXT_SIZE`] or [`COMPACT_NOTE_SIZE`]. -pub trait ShieldedOutput { - /// Exposes the `ephemeral_key` field of the output. - fn ephemeral_key(&self) -> EphemeralKeyBytes; - - /// Exposes the `cmu_bytes` or `cmx_bytes` field of the output. - fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes; - - /// Exposes the note ciphertext of the output. - fn enc_ciphertext(&self) -> &[u8; CIPHERTEXT_SIZE]; -} - -/// A struct containing context required for encrypting Sapling and Orchard notes. -/// -/// This struct provides a safe API for encrypting Sapling and Orchard notes. In particular, it -/// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts are -/// consistent with each other. -/// -/// Implements section 4.19 of the -/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband) -pub struct NoteEncryption { - epk: D::EphemeralPublicKey, - esk: D::EphemeralSecretKey, - note: D::Note, - memo: D::Memo, - /// `None` represents the `ovk = ⊥` case. - ovk: Option, -} - -impl NoteEncryption { - /// Construct a new note encryption context for the specified note, - /// recipient, and memo. - pub fn new(ovk: Option, note: D::Note, memo: D::Memo) -> Self { - let esk = D::derive_esk(¬e).expect("ZIP 212 is active."); - NoteEncryption { - epk: D::ka_derive_public(¬e, &esk), - esk, - note, - memo, - ovk, - } - } - - /// For use only with Sapling. This method is preserved in order that test code - /// be able to generate pre-ZIP-212 ciphertexts so that tests can continue to - /// cover pre-ZIP-212 transaction decryption. - #[cfg(feature = "pre-zip-212")] - #[cfg_attr(docsrs, doc(cfg(feature = "pre-zip-212")))] - pub fn new_with_esk( - esk: D::EphemeralSecretKey, - ovk: Option, - note: D::Note, - memo: D::Memo, - ) -> Self { - NoteEncryption { - epk: D::ka_derive_public(¬e, &esk), - esk, - note, - memo, - ovk, - } - } - - /// Exposes the ephemeral secret key being used to encrypt this note. - pub fn esk(&self) -> &D::EphemeralSecretKey { - &self.esk - } - - /// Exposes the encoding of the ephemeral public key being used to encrypt this note. - pub fn epk(&self) -> &D::EphemeralPublicKey { - &self.epk - } - - /// Generates `encCiphertext` for this note. - pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { - let pk_d = D::get_pk_d(&self.note); - let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); - let key = D::kdf(shared_secret, &D::epk_bytes(&self.epk)); - let input = D::note_plaintext_bytes(&self.note, &self.memo); - - let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; - output[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&input.0); - let tag = ChaCha20Poly1305::new(key.as_ref().into()) - .encrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut output[..NOTE_PLAINTEXT_SIZE], - ) - .unwrap(); - output[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag); - - output - } - - /// Generates `outCiphertext` for this note. - pub fn encrypt_outgoing_plaintext( - &self, - cv: &D::ValueCommitment, - cmstar: &D::ExtractedCommitment, - rng: &mut R, - ) -> [u8; OUT_CIPHERTEXT_SIZE] { - let (ock, input) = if let Some(ovk) = &self.ovk { - let ock = D::derive_ock(ovk, cv, &cmstar.into(), &D::epk_bytes(&self.epk)); - let input = D::outgoing_plaintext_bytes(&self.note, &self.esk); - - (ock, input) - } else { - // ovk = ⊥ - let mut ock = OutgoingCipherKey([0; 32]); - let mut input = [0u8; OUT_PLAINTEXT_SIZE]; - - rng.fill_bytes(&mut ock.0); - rng.fill_bytes(&mut input); - - (ock, OutPlaintextBytes(input)) - }; - - let mut output = [0u8; OUT_CIPHERTEXT_SIZE]; - output[..OUT_PLAINTEXT_SIZE].copy_from_slice(&input.0); - let tag = ChaCha20Poly1305::new(ock.as_ref().into()) - .encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut output[..OUT_PLAINTEXT_SIZE]) - .unwrap(); - output[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag); - - output - } -} - -/// Trial decryption of the full note plaintext by the recipient. -/// -/// Attempts to decrypt and validate the given shielded output using the given `ivk`. -/// If successful, the corresponding note and memo are returned, along with the address to -/// which the note was sent. -/// -/// Implements section 4.19.2 of the -/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk). -pub fn try_note_decryption>( - domain: &D, - ivk: &D::IncomingViewingKey, - output: &Output, -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let ephemeral_key = output.ephemeral_key(); - - let epk = D::prepare_epk(D::epk(&ephemeral_key)?); - let shared_secret = D::ka_agree_dec(ivk, &epk); - let key = D::kdf(shared_secret, &ephemeral_key); - - try_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key) -} - -fn try_note_decryption_inner>( - domain: &D, - ivk: &D::IncomingViewingKey, - ephemeral_key: &EphemeralKeyBytes, - output: &Output, - key: &D::SymmetricKey, -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let enc_ciphertext = output.enc_ciphertext(); - - let mut plaintext = - NotePlaintextBytes(enc_ciphertext[..NOTE_PLAINTEXT_SIZE].try_into().unwrap()); - - ChaCha20Poly1305::new(key.as_ref().into()) - .decrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut plaintext.0, - enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), - ) - .ok()?; - - let (note, to) = parse_note_plaintext_without_memo_ivk( - domain, - ivk, - ephemeral_key, - &output.cmstar_bytes(), - &plaintext.0, - )?; - let memo = domain.extract_memo(&plaintext); - - Some((note, to, memo)) -} - -fn parse_note_plaintext_without_memo_ivk( - domain: &D, - ivk: &D::IncomingViewingKey, - ephemeral_key: &EphemeralKeyBytes, - cmstar_bytes: &D::ExtractedCommitmentBytes, - plaintext: &[u8], -) -> Option<(D::Note, D::Recipient)> { - let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, plaintext)?; - - if let NoteValidity::Valid = check_note_validity::(¬e, ephemeral_key, cmstar_bytes) { - Some((note, to)) - } else { - None - } -} - -fn check_note_validity( - note: &D::Note, - ephemeral_key: &EphemeralKeyBytes, - cmstar_bytes: &D::ExtractedCommitmentBytes, -) -> NoteValidity { - if &D::ExtractedCommitmentBytes::from(&D::cmstar(note)) == cmstar_bytes { - // In the case corresponding to specification section 4.19.3, we check that `esk` is equal - // to `D::derive_esk(note)` prior to calling this method. - if let Some(derived_esk) = D::derive_esk(note) { - if D::epk_bytes(&D::ka_derive_public(note, &derived_esk)) - .ct_eq(ephemeral_key) - .into() - { - NoteValidity::Valid - } else { - NoteValidity::Invalid - } - } else { - // Before ZIP 212 - NoteValidity::Valid - } - } else { - // Published commitment doesn't match calculated commitment - NoteValidity::Invalid - } -} - -/// Trial decryption of the compact note plaintext by the recipient for light clients. -/// -/// Attempts to decrypt and validate the given compact shielded output using the -/// given `ivk`. If successful, the corresponding note is returned, along with the address -/// to which the note was sent. -/// -/// Implements the procedure specified in [`ZIP 307`]. -/// -/// [`ZIP 307`]: https://zips.z.cash/zip-0307 -pub fn try_compact_note_decryption>( - domain: &D, - ivk: &D::IncomingViewingKey, - output: &Output, -) -> Option<(D::Note, D::Recipient)> { - let ephemeral_key = output.ephemeral_key(); - - let epk = D::prepare_epk(D::epk(&ephemeral_key)?); - let shared_secret = D::ka_agree_dec(ivk, &epk); - let key = D::kdf(shared_secret, &ephemeral_key); - - try_compact_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key) -} - -fn try_compact_note_decryption_inner>( - domain: &D, - ivk: &D::IncomingViewingKey, - ephemeral_key: &EphemeralKeyBytes, - output: &Output, - key: &D::SymmetricKey, -) -> Option<(D::Note, D::Recipient)> { - // Start from block 1 to skip over Poly1305 keying output - let mut plaintext = [0; COMPACT_NOTE_SIZE]; - plaintext.copy_from_slice(output.enc_ciphertext()); - let mut keystream = ChaCha20::new(key.as_ref().into(), [0u8; 12][..].into()); - keystream.seek(64); - keystream.apply_keystream(&mut plaintext); - - parse_note_plaintext_without_memo_ivk( - domain, - ivk, - ephemeral_key, - &output.cmstar_bytes(), - &plaintext, - ) -} - -/// Recovery of the full note plaintext by the sender. -/// -/// Attempts to decrypt and validate the given shielded output using the given `ovk`. -/// If successful, the corresponding note and memo are returned, along with the address to -/// which the note was sent. -/// -/// Implements [Zcash Protocol Specification section 4.19.3][decryptovk]. -/// -/// [decryptovk]: https://zips.z.cash/protocol/nu5.pdf#decryptovk -pub fn try_output_recovery_with_ovk>( - domain: &D, - ovk: &D::OutgoingViewingKey, - output: &Output, - cv: &D::ValueCommitment, - out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let ock = D::derive_ock(ovk, cv, &output.cmstar_bytes(), &output.ephemeral_key()); - try_output_recovery_with_ock(domain, &ock, output, out_ciphertext) -} - -/// Recovery of the full note plaintext by the sender. -/// -/// Attempts to decrypt and validate the given shielded output using the given `ock`. -/// If successful, the corresponding note and memo are returned, along with the address to -/// which the note was sent. -/// -/// Implements part of section 4.19.3 of the -/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk). -/// For decryption using a Full Viewing Key see [`try_output_recovery_with_ovk`]. -pub fn try_output_recovery_with_ock>( - domain: &D, - ock: &OutgoingCipherKey, - output: &Output, - out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let enc_ciphertext = output.enc_ciphertext(); - - let mut op = OutPlaintextBytes([0; OUT_PLAINTEXT_SIZE]); - op.0.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]); - - ChaCha20Poly1305::new(ock.as_ref().into()) - .decrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut op.0, - out_ciphertext[OUT_PLAINTEXT_SIZE..].into(), - ) - .ok()?; - - let pk_d = D::extract_pk_d(&op)?; - let esk = D::extract_esk(&op)?; - - let ephemeral_key = output.ephemeral_key(); - let shared_secret = D::ka_agree_enc(&esk, &pk_d); - // The small-order point check at the point of output parsing rejects - // non-canonical encodings, so reencoding here for the KDF should - // be okay. - let key = D::kdf(shared_secret, &ephemeral_key); - - let mut plaintext = NotePlaintextBytes([0; NOTE_PLAINTEXT_SIZE]); - plaintext - .0 - .copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]); - - ChaCha20Poly1305::new(key.as_ref().into()) - .decrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut plaintext.0, - enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), - ) - .ok()?; - - let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &plaintext)?; - let memo = domain.extract_memo(&plaintext); - - // ZIP 212: Check that the esk provided to this function is consistent with the esk we can - // derive from the note. This check corresponds to `ToScalar(PRF^{expand}_{rseed}([4]) = esk` - // in https://zips.z.cash/protocol/protocol.pdf#decryptovk. (`ρ^opt = []` for Sapling.) - if let Some(derived_esk) = D::derive_esk(¬e) { - if (!derived_esk.ct_eq(&esk)).into() { - return None; - } - } - - if let NoteValidity::Valid = - check_note_validity::(¬e, &ephemeral_key, &output.cmstar_bytes()) - { - Some((note, to, memo)) - } else { - None - } -} diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md new file mode 100644 index 0000000000..7a03fb52ee --- /dev/null +++ b/components/zcash_protocol/CHANGELOG.md @@ -0,0 +1,103 @@ +# Changelog +All notable changes to this library will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this library adheres to Rust's notion of +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [0.4.1] - 2024-11-13 +### Added +- `zcash_protocol::value::QuotRem` +- `zcash_protocol::value::Zatoshis::div_with_remainder` +- `impl Mul for zcash_protocol::value::Zatoshis` +- `impl Div for zcash_protocol::value::Zatoshis` + +## [0.4.0] - 2024-10-02 +### Added +- `impl Sub for BlockHeight` unlike the implementation that was + removed in version `0.3.0`, a saturating subtraction for block heights having + a return type of `u32` makes sense for `BlockHeight`. Subtracting one block + height from another yields the delta between them. + +### Changed +- Mainnet activation height has been set for `consensus::BranchId::Nu6`. +- Adding a delta to a `BlockHeight` now uses saturating addition. +- Subtracting a delta to a `BlockHeight` now uses saturating subtraction. + +## [0.3.0] - 2024-08-26 +### Changed +- Testnet activation height has been set for `consensus::BranchId::Nu6`. + +### Removed +- `impl {Add, Sub} for BlockHeight` - these operations were unused, and it + does not make sense to add block heights (it is not a monoid.) + +## [0.2.0] - 2024-08-19 +### Added +- `zcash_protocol::PoolType::{TRANSPARENT, SAPLING, ORCHARD}` + +### Changed +- MSRV is now 1.70.0. +- `consensus::BranchId` now has an additional `Nu6` variant. + +## [0.1.1] - 2024-03-25 +### Added +- `zcash_protocol::memo`: + - `impl TryFrom<&MemoBytes> for Memo` + +### Removed +- `unstable-nu6` and `zfuture` feature flags (use `--cfg zcash_unstable=\"nu6\"` + or `--cfg zcash_unstable=\"zfuture\"` in `RUSTFLAGS` and `RUSTDOCFLAGS` + instead). + +## [0.1.0] - 2024-03-06 +The entries below are relative to the `zcash_primitives` crate as of the tag +`zcash_primitives-0.14.0`. + +### Added +- The following modules have been extracted from `zcash_primitives` and + moved to this crate: + - `consensus` + - `constants` + - `zcash_protocol::value` replaces `zcash_primitives::transaction::components::amount` +- `zcash_protocol::consensus`: + - `NetworkConstants` has been extracted from the `Parameters` trait. Relative to the + state prior to the extraction: + - The Bech32 prefixes now return `&'static str` instead of `&str`. + - Added `NetworkConstants::hrp_tex_address`. + - `NetworkType` + - `Parameters::b58_sprout_address_prefix` +- `zcash_protocol::consensus`: + - `impl Hash for LocalNetwork` +- `zcash_protocol::constants::{mainnet, testnet}::B58_SPROUT_ADDRESS_PREFIX` +- Added in `zcash_protocol::value`: + - `Zatoshis` + - `ZatBalance` + - `MAX_BALANCE` has been added to replace previous instances where + `zcash_protocol::value::MAX_MONEY` was used as a signed value. + +### Changed +- `zcash_protocol::value::COIN` has been changed from an `i64` to a `u64` +- `zcash_protocol::value::MAX_MONEY` has been changed from an `i64` to a `u64` +- `zcash_protocol::consensus::Parameters` has been split into two traits, with + the newly added `NetworkConstants` trait providing all network constant + accessors. Also, the `address_network` method has been replaced with a new + `network_type` method that serves the same purpose. A blanket impl of + `NetworkConstants` is provided for all types that implement `Parameters`, + so call sites for methods that have moved to `NetworkConstants` should + remain unchanged (though they may require an additional `use` statement.) + +### Removed +- From `zcash_protocol::value`: + - `NonNegativeAmount` (use `Zatoshis` instead.) + - `Amount` (use `ZatBalance` instead.) + - The following conversions have been removed relative to `zcash_primitives-0.14.0`, + as `zcash_protocol` does not depend on the `orchard` or `sapling-crypto` crates. + - `From for orchard::NoteValue>` + - `TryFrom for Amount` + - `From for sapling::value::NoteValue>` + - `TryFrom for NonNegativeAmount` + - `impl AddAssign for NonNegativeAmount` + - `impl SubAssign for NonNegativeAmount` diff --git a/components/zcash_protocol/Cargo.toml b/components/zcash_protocol/Cargo.toml new file mode 100644 index 0000000000..c7b046f857 --- /dev/null +++ b/components/zcash_protocol/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "zcash_protocol" +description = "Zcash protocol network constants and value types." +version = "0.4.1" +authors = [ + "Jack Grigg ", + "Kris Nuttycombe ", +] +homepage = "https://github.com/zcash/librustzcash" +repository.workspace = true +readme = "README.md" +license.workspace = true +edition.workspace = true +rust-version = "1.70" +categories = ["cryptography::cryptocurrencies"] +keywords = ["zcash"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +# - Logging and metrics +memuse.workspace = true + +# Dependencies used internally: +# (Breaking upgrades to these are usually backwards-compatible, but check MSRVs.) +# - Documentation +document-features.workspace = true + +# - Test dependencies +proptest = { workspace = true, optional = true } +incrementalmerkletree = { workspace = true, optional = true } +incrementalmerkletree-testing = { workspace = true, optional = true } + +[dev-dependencies] +proptest.workspace = true + +[features] +## Exposes APIs that are useful for testing, such as `proptest` strategies. +test-dependencies = [ + "dep:incrementalmerkletree", + "dep:incrementalmerkletree-testing", + "dep:proptest", + "incrementalmerkletree?/test-dependencies", +] + +## Exposes support for working with a local consensus (e.g. regtest). +local-consensus = [] + +[lints] +workspace = true diff --git a/components/zcash_note_encryption/LICENSE-APACHE b/components/zcash_protocol/LICENSE-APACHE similarity index 100% rename from components/zcash_note_encryption/LICENSE-APACHE rename to components/zcash_protocol/LICENSE-APACHE diff --git a/components/zcash_note_encryption/LICENSE-MIT b/components/zcash_protocol/LICENSE-MIT similarity index 95% rename from components/zcash_note_encryption/LICENSE-MIT rename to components/zcash_protocol/LICENSE-MIT index 9500c140cc..c869731ad4 100644 --- a/components/zcash_note_encryption/LICENSE-MIT +++ b/components/zcash_protocol/LICENSE-MIT @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021 Electric Coin Company +Copyright (c) 2021-2024 Electric Coin Company Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/components/zcash_protocol/README.md b/components/zcash_protocol/README.md new file mode 100644 index 0000000000..862adf0a84 --- /dev/null +++ b/components/zcash_protocol/README.md @@ -0,0 +1,20 @@ +# zcash_protocol + +Zcash network constants and value types. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. diff --git a/zcash_primitives/src/consensus.rs b/components/zcash_protocol/src/consensus.rs similarity index 67% rename from zcash_primitives/src/consensus.rs rename to components/zcash_protocol/src/consensus.rs index 02ceffa165..73b48d3f24 100644 --- a/zcash_primitives/src/consensus.rs +++ b/components/zcash_protocol/src/consensus.rs @@ -5,24 +5,32 @@ use std::cmp::{Ord, Ordering}; use std::convert::TryFrom; use std::fmt; use std::ops::{Add, Bound, RangeBounds, Sub}; -use zcash_address; -use crate::constants; +use crate::constants::{mainnet, regtest, testnet}; -/// A wrapper type representing blockchain heights. Safe conversion from -/// various integer types, as well as addition and subtraction, are provided. +/// A wrapper type representing blockchain heights. +/// +/// Safe conversion from various integer types, as well as addition and subtraction, are +/// provided. #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct BlockHeight(u32); memuse::impl_no_dynamic_usage!(BlockHeight); +/// The height of the genesis block on a network. pub const H0: BlockHeight = BlockHeight(0); impl BlockHeight { pub const fn from_u32(v: u32) -> BlockHeight { BlockHeight(v) } + + /// Subtracts the provided value from this height, returning `H0` if this would result in + /// underflow of the wrapped `u32`. + pub fn saturating_sub(self, v: u32) -> BlockHeight { + BlockHeight(self.0.saturating_sub(v)) + } } impl fmt::Display for BlockHeight { @@ -95,15 +103,7 @@ impl Add for BlockHeight { type Output = Self; fn add(self, other: u32) -> Self { - BlockHeight(self.0 + other) - } -} - -impl Add for BlockHeight { - type Output = Self; - - fn add(self, other: Self) -> Self { - self + other.0 + BlockHeight(self.0.saturating_add(other)) } } @@ -111,248 +111,300 @@ impl Sub for BlockHeight { type Output = Self; fn sub(self, other: u32) -> Self { - if other > self.0 { - panic!("Subtraction resulted in negative block height."); - } - - BlockHeight(self.0 - other) + BlockHeight(self.0.saturating_sub(other)) } } -impl Sub for BlockHeight { - type Output = Self; +impl Sub for BlockHeight { + type Output = u32; - fn sub(self, other: Self) -> Self { - self - other.0 + fn sub(self, other: BlockHeight) -> u32 { + self.0.saturating_sub(other.0) } } -/// Zcash consensus parameters. -pub trait Parameters: Clone { - /// Returns the activation height for a particular network upgrade, - /// if an activation height has been set. - fn activation_height(&self, nu: NetworkUpgrade) -> Option; - - /// Determines whether the specified network upgrade is active as of the - /// provided block height on the network to which this Parameters value applies. - fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { - self.activation_height(nu).map_or(false, |h| h <= height) - } - +/// Constants associated with a given Zcash network. +pub trait NetworkConstants: Clone { /// The coin type for ZEC, as defined by [SLIP 44]. /// /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md fn coin_type(&self) -> u32; - /// Returns the standard network constant for address encoding. Returns - /// 'None' for nonstandard networks. - fn address_network(&self) -> Option; - /// Returns the human-readable prefix for Bech32-encoded Sapling extended spending keys - /// the network to which this Parameters value applies. + /// for the network to which this NetworkConstants value applies. /// /// Defined in [ZIP 32]. /// /// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst - fn hrp_sapling_extended_spending_key(&self) -> &str; + fn hrp_sapling_extended_spending_key(&self) -> &'static str; /// Returns the human-readable prefix for Bech32-encoded Sapling extended full - /// viewing keys for the network to which this Parameters value applies. + /// viewing keys for the network to which this NetworkConstants value applies. /// /// Defined in [ZIP 32]. /// /// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst - fn hrp_sapling_extended_full_viewing_key(&self) -> &str; + fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str; /// Returns the Bech32-encoded human-readable prefix for Sapling payment addresses - /// viewing keys for the network to which this Parameters value applies. + /// for the network to which this NetworkConstants value applies. /// /// Defined in section 5.6.4 of the [Zcash Protocol Specification]. /// /// [`PaymentAddress`]: zcash_primitives::primitives::PaymentAddress /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf - fn hrp_sapling_payment_address(&self) -> &str; + fn hrp_sapling_payment_address(&self) -> &'static str; + + /// Returns the human-readable prefix for Base58Check-encoded Sprout + /// payment addresses for the network to which this NetworkConstants value + /// applies. + /// + /// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. + /// + /// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding + fn b58_sprout_address_prefix(&self) -> [u8; 2]; /// Returns the human-readable prefix for Base58Check-encoded transparent - /// pay-to-public-key-hash payment addresses for the network to which this Parameters value + /// pay-to-public-key-hash payment addresses for the network to which this NetworkConstants value /// applies. /// /// [`TransparentAddress::PublicKey`]: zcash_primitives::legacy::TransparentAddress::PublicKey fn b58_pubkey_address_prefix(&self) -> [u8; 2]; /// Returns the human-readable prefix for Base58Check-encoded transparent pay-to-script-hash - /// payment addresses for the network to which this Parameters value applies. + /// payment addresses for the network to which this NetworkConstants value applies. /// /// [`TransparentAddress::Script`]: zcash_primitives::legacy::TransparentAddress::Script fn b58_script_address_prefix(&self) -> [u8; 2]; -} -/// Marker struct for the production network. -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub struct MainNetwork; + /// Returns the Bech32-encoded human-readable prefix for TEX addresses, for the + /// network to which this `NetworkConstants` value applies. + /// + /// Defined in [ZIP 320]. + /// + /// [ZIP 320]: https://zips.z.cash/zip-0320 + fn hrp_tex_address(&self) -> &'static str; +} -memuse::impl_no_dynamic_usage!(MainNetwork); +/// The enumeration of known Zcash network types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NetworkType { + /// Zcash Mainnet. + Main, + /// Zcash Testnet. + Test, + /// Private integration / regression testing, used in `zcashd`. + /// + /// For some address types there is no distinction between test and regtest encodings; + /// those will always be parsed as `Network::Test`. + Regtest, +} -pub const MAIN_NETWORK: MainNetwork = MainNetwork; - -impl Parameters for MainNetwork { - fn activation_height(&self, nu: NetworkUpgrade) -> Option { - match nu { - NetworkUpgrade::Overwinter => Some(BlockHeight(347_500)), - NetworkUpgrade::Sapling => Some(BlockHeight(419_200)), - NetworkUpgrade::Blossom => Some(BlockHeight(653_600)), - NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)), - NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)), - NetworkUpgrade::Nu5 => Some(BlockHeight(1_687_104)), - #[cfg(feature = "zfuture")] - NetworkUpgrade::ZFuture => None, - } - } +memuse::impl_no_dynamic_usage!(NetworkType); +impl NetworkConstants for NetworkType { fn coin_type(&self) -> u32 { - constants::mainnet::COIN_TYPE + match self { + NetworkType::Main => mainnet::COIN_TYPE, + NetworkType::Test => testnet::COIN_TYPE, + NetworkType::Regtest => regtest::COIN_TYPE, + } } - fn address_network(&self) -> Option { - Some(zcash_address::Network::Main) + fn hrp_sapling_extended_spending_key(&self) -> &'static str { + match self { + NetworkType::Main => mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + NetworkType::Test => testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + NetworkType::Regtest => regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY, + } } - fn hrp_sapling_extended_spending_key(&self) -> &str { - constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY + fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str { + match self { + NetworkType::Main => mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + NetworkType::Test => testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + NetworkType::Regtest => regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + } } - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY + fn hrp_sapling_payment_address(&self) -> &'static str { + match self { + NetworkType::Main => mainnet::HRP_SAPLING_PAYMENT_ADDRESS, + NetworkType::Test => testnet::HRP_SAPLING_PAYMENT_ADDRESS, + NetworkType::Regtest => regtest::HRP_SAPLING_PAYMENT_ADDRESS, + } } - fn hrp_sapling_payment_address(&self) -> &str { - constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS + fn b58_sprout_address_prefix(&self) -> [u8; 2] { + match self { + NetworkType::Main => mainnet::B58_SPROUT_ADDRESS_PREFIX, + NetworkType::Test => testnet::B58_SPROUT_ADDRESS_PREFIX, + NetworkType::Regtest => regtest::B58_SPROUT_ADDRESS_PREFIX, + } } fn b58_pubkey_address_prefix(&self) -> [u8; 2] { - constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX + match self { + NetworkType::Main => mainnet::B58_PUBKEY_ADDRESS_PREFIX, + NetworkType::Test => testnet::B58_PUBKEY_ADDRESS_PREFIX, + NetworkType::Regtest => regtest::B58_PUBKEY_ADDRESS_PREFIX, + } } fn b58_script_address_prefix(&self) -> [u8; 2] { - constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX + match self { + NetworkType::Main => mainnet::B58_SCRIPT_ADDRESS_PREFIX, + NetworkType::Test => testnet::B58_SCRIPT_ADDRESS_PREFIX, + NetworkType::Regtest => regtest::B58_SCRIPT_ADDRESS_PREFIX, + } } -} -/// Marker struct for the test network. -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub struct TestNetwork; + fn hrp_tex_address(&self) -> &'static str { + match self { + NetworkType::Main => mainnet::HRP_TEX_ADDRESS, + NetworkType::Test => testnet::HRP_TEX_ADDRESS, + NetworkType::Regtest => regtest::HRP_TEX_ADDRESS, + } + } +} -memuse::impl_no_dynamic_usage!(TestNetwork); +/// Zcash consensus parameters. +pub trait Parameters: Clone { + /// Returns the type of network configured by this set of consensus parameters. + fn network_type(&self) -> NetworkType; -pub const TEST_NETWORK: TestNetwork = TestNetwork; + /// Returns the activation height for a particular network upgrade, + /// if an activation height has been set. + fn activation_height(&self, nu: NetworkUpgrade) -> Option; -impl Parameters for TestNetwork { - fn activation_height(&self, nu: NetworkUpgrade) -> Option { - match nu { - NetworkUpgrade::Overwinter => Some(BlockHeight(207_500)), - NetworkUpgrade::Sapling => Some(BlockHeight(280_000)), - NetworkUpgrade::Blossom => Some(BlockHeight(584_000)), - NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)), - NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)), - NetworkUpgrade::Nu5 => Some(BlockHeight(1_842_420)), - #[cfg(feature = "zfuture")] - NetworkUpgrade::ZFuture => None, - } + /// Determines whether the specified network upgrade is active as of the + /// provided block height on the network to which this Parameters value applies. + fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { + self.activation_height(nu).map_or(false, |h| h <= height) } +} +impl NetworkConstants for P { fn coin_type(&self) -> u32 { - constants::testnet::COIN_TYPE + self.network_type().coin_type() } - fn address_network(&self) -> Option { - Some(zcash_address::Network::Test) + fn hrp_sapling_extended_spending_key(&self) -> &'static str { + self.network_type().hrp_sapling_extended_spending_key() } - fn hrp_sapling_extended_spending_key(&self) -> &str { - constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY + fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str { + self.network_type().hrp_sapling_extended_full_viewing_key() } - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY + fn hrp_sapling_payment_address(&self) -> &'static str { + self.network_type().hrp_sapling_payment_address() } - fn hrp_sapling_payment_address(&self) -> &str { - constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS + fn b58_sprout_address_prefix(&self) -> [u8; 2] { + self.network_type().b58_sprout_address_prefix() } fn b58_pubkey_address_prefix(&self) -> [u8; 2] { - constants::testnet::B58_PUBKEY_ADDRESS_PREFIX + self.network_type().b58_pubkey_address_prefix() } fn b58_script_address_prefix(&self) -> [u8; 2] { - constants::testnet::B58_SCRIPT_ADDRESS_PREFIX + self.network_type().b58_script_address_prefix() + } + + fn hrp_tex_address(&self) -> &'static str { + self.network_type().hrp_tex_address() } } +/// Marker struct for the production network. #[derive(PartialEq, Eq, Copy, Clone, Debug)] -pub enum Network { - MainNetwork, - TestNetwork, -} +pub struct MainNetwork; -memuse::impl_no_dynamic_usage!(Network); +memuse::impl_no_dynamic_usage!(MainNetwork); -impl Parameters for Network { - fn activation_height(&self, nu: NetworkUpgrade) -> Option { - match self { - Network::MainNetwork => MAIN_NETWORK.activation_height(nu), - Network::TestNetwork => TEST_NETWORK.activation_height(nu), - } - } +/// The production network. +pub const MAIN_NETWORK: MainNetwork = MainNetwork; - fn coin_type(&self) -> u32 { - match self { - Network::MainNetwork => MAIN_NETWORK.coin_type(), - Network::TestNetwork => TEST_NETWORK.coin_type(), - } +impl Parameters for MainNetwork { + fn network_type(&self) -> NetworkType { + NetworkType::Main } - fn address_network(&self) -> Option { - match self { - Network::MainNetwork => Some(zcash_address::Network::Main), - Network::TestNetwork => Some(zcash_address::Network::Test), + fn activation_height(&self, nu: NetworkUpgrade) -> Option { + match nu { + NetworkUpgrade::Overwinter => Some(BlockHeight(347_500)), + NetworkUpgrade::Sapling => Some(BlockHeight(419_200)), + NetworkUpgrade::Blossom => Some(BlockHeight(653_600)), + NetworkUpgrade::Heartwood => Some(BlockHeight(903_000)), + NetworkUpgrade::Canopy => Some(BlockHeight(1_046_400)), + NetworkUpgrade::Nu5 => Some(BlockHeight(1_687_104)), + NetworkUpgrade::Nu6 => Some(BlockHeight(2_726_400)), + //TODO: update this value + NetworkUpgrade::Nu7 => Some(BlockHeight(3_000_000)), + #[cfg(zcash_unstable = "zfuture")] + NetworkUpgrade::ZFuture => None, } } +} - fn hrp_sapling_extended_spending_key(&self) -> &str { - match self { - Network::MainNetwork => MAIN_NETWORK.hrp_sapling_extended_spending_key(), - Network::TestNetwork => TEST_NETWORK.hrp_sapling_extended_spending_key(), - } - } +/// Marker struct for the test network. +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +pub struct TestNetwork; - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - match self { - Network::MainNetwork => MAIN_NETWORK.hrp_sapling_extended_full_viewing_key(), - Network::TestNetwork => TEST_NETWORK.hrp_sapling_extended_full_viewing_key(), - } +memuse::impl_no_dynamic_usage!(TestNetwork); + +/// The test network. +pub const TEST_NETWORK: TestNetwork = TestNetwork; + +impl Parameters for TestNetwork { + fn network_type(&self) -> NetworkType { + NetworkType::Test } - fn hrp_sapling_payment_address(&self) -> &str { - match self { - Network::MainNetwork => MAIN_NETWORK.hrp_sapling_payment_address(), - Network::TestNetwork => TEST_NETWORK.hrp_sapling_payment_address(), + fn activation_height(&self, nu: NetworkUpgrade) -> Option { + match nu { + NetworkUpgrade::Overwinter => Some(BlockHeight(207_500)), + NetworkUpgrade::Sapling => Some(BlockHeight(280_000)), + NetworkUpgrade::Blossom => Some(BlockHeight(584_000)), + NetworkUpgrade::Heartwood => Some(BlockHeight(903_800)), + NetworkUpgrade::Canopy => Some(BlockHeight(1_028_500)), + NetworkUpgrade::Nu5 => Some(BlockHeight(1_842_420)), + NetworkUpgrade::Nu6 => Some(BlockHeight(2_976_000)), + //TODO: update this value + NetworkUpgrade::Nu7 => Some(BlockHeight(3_000_000)), + #[cfg(zcash_unstable = "zfuture")] + NetworkUpgrade::ZFuture => None, } } +} - fn b58_pubkey_address_prefix(&self) -> [u8; 2] { +/// The enumeration of known Zcash networks. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum Network { + /// Zcash Mainnet. + MainNetwork, + /// Zcash Testnet. + TestNetwork, +} + +memuse::impl_no_dynamic_usage!(Network); + +impl Parameters for Network { + fn network_type(&self) -> NetworkType { match self { - Network::MainNetwork => MAIN_NETWORK.b58_pubkey_address_prefix(), - Network::TestNetwork => TEST_NETWORK.b58_pubkey_address_prefix(), + Network::MainNetwork => NetworkType::Main, + Network::TestNetwork => NetworkType::Test, } } - fn b58_script_address_prefix(&self) -> [u8; 2] { + fn activation_height(&self, nu: NetworkUpgrade) -> Option { match self { - Network::MainNetwork => MAIN_NETWORK.b58_script_address_prefix(), - Network::TestNetwork => TEST_NETWORK.b58_script_address_prefix(), + Network::MainNetwork => MAIN_NETWORK.activation_height(nu), + Network::TestNetwork => TEST_NETWORK.activation_height(nu), } } } @@ -361,7 +413,7 @@ impl Parameters for Network { /// consensus rules enforced by the network are altered. /// /// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum NetworkUpgrade { /// The [Overwinter] network upgrade. /// @@ -387,12 +439,20 @@ pub enum NetworkUpgrade { /// /// [Nu5]: https://z.cash/upgrade/nu5/ Nu5, + /// The [Nu6] network upgrade. + /// + /// [Nu6]: https://z.cash/upgrade/nu6/ + Nu6, + /// The [Nu7] network upgrade. + /// + /// [Nu7]: https://z.cash/upgrade/nu7/ + Nu7, /// The ZFUTURE network upgrade. /// /// This upgrade is expected never to activate on mainnet; /// it is intended for use in integration testing of functionality /// that is a candidate for integration in a future network upgrade. - #[cfg(feature = "zfuture")] + #[cfg(zcash_unstable = "zfuture")] ZFuture, } @@ -407,7 +467,9 @@ impl fmt::Display for NetworkUpgrade { NetworkUpgrade::Heartwood => write!(f, "Heartwood"), NetworkUpgrade::Canopy => write!(f, "Canopy"), NetworkUpgrade::Nu5 => write!(f, "Nu5"), - #[cfg(feature = "zfuture")] + NetworkUpgrade::Nu6 => write!(f, "Nu6"), + NetworkUpgrade::Nu7 => write!(f, "Nu7"), + #[cfg(zcash_unstable = "zfuture")] NetworkUpgrade::ZFuture => write!(f, "ZFUTURE"), } } @@ -422,7 +484,9 @@ impl NetworkUpgrade { NetworkUpgrade::Heartwood => BranchId::Heartwood, NetworkUpgrade::Canopy => BranchId::Canopy, NetworkUpgrade::Nu5 => BranchId::Nu5, - #[cfg(feature = "zfuture")] + NetworkUpgrade::Nu6 => BranchId::Nu6, + NetworkUpgrade::Nu7 => BranchId::Nu7, + #[cfg(zcash_unstable = "zfuture")] NetworkUpgrade::ZFuture => BranchId::ZFuture, } } @@ -439,8 +503,15 @@ const UPGRADES_IN_ORDER: &[NetworkUpgrade] = &[ NetworkUpgrade::Heartwood, NetworkUpgrade::Canopy, NetworkUpgrade::Nu5, + NetworkUpgrade::Nu6, + NetworkUpgrade::Nu7, + #[cfg(zcash_unstable = "zfuture")] + NetworkUpgrade::ZFuture, ]; +/// The "grace period" defined in [ZIP 212]. +/// +/// [ZIP 212]: https://zips.z.cash/zip-0212#changes-to-the-process-of-receiving-sapling-or-orchard-notes pub const ZIP212_GRACE_PERIOD: u32 = 32256; /// A globally-unique identifier for a set of consensus rules within the Zcash chain. @@ -455,7 +526,7 @@ pub const ZIP212_GRACE_PERIOD: u32 = 32256; /// /// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. /// -/// [`signature_hash`]: crate::transaction::sighash::signature_hash +/// [`signature_hash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/transaction/sighash/fn.signature_hash.html #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BranchId { /// The consensus rules at the launch of Zcash. @@ -472,9 +543,13 @@ pub enum BranchId { Canopy, /// The consensus rules deployed by [`NetworkUpgrade::Nu5`]. Nu5, + /// The consensus rules deployed by [`NetworkUpgrade::Nu6`]. + Nu6, + /// The consensus rules deployed by [`NetworkUpgrade::Nu7`]. + Nu7, /// Candidates for future consensus rules; this branch will never /// activate on mainnet. - #[cfg(feature = "zfuture")] + #[cfg(zcash_unstable = "zfuture")] ZFuture, } @@ -492,7 +567,10 @@ impl TryFrom for BranchId { 0xf5b9_230b => Ok(BranchId::Heartwood), 0xe9ff_75a6 => Ok(BranchId::Canopy), 0xc2d6_d0b4 => Ok(BranchId::Nu5), - #[cfg(feature = "zfuture")] + 0xc8e7_1055 => Ok(BranchId::Nu6), + //TODO: update this value + 0xffff_ffff => Ok(BranchId::Nu7), + #[cfg(zcash_unstable = "zfuture")] 0xffff_ffff => Ok(BranchId::ZFuture), _ => Err("Unknown consensus branch ID"), } @@ -509,7 +587,10 @@ impl From for u32 { BranchId::Heartwood => 0xf5b9_230b, BranchId::Canopy => 0xe9ff_75a6, BranchId::Nu5 => 0xc2d6_d0b4, - #[cfg(feature = "zfuture")] + BranchId::Nu6 => 0xc8e7_1055, + //TODO: update this value + BranchId::Nu7 => 0xffff_ffff, + #[cfg(zcash_unstable = "zfuture")] BranchId::ZFuture => 0xffff_ffff, } } @@ -574,14 +655,20 @@ impl BranchId { BranchId::Canopy => params .activation_height(NetworkUpgrade::Canopy) .map(|lower| (lower, params.activation_height(NetworkUpgrade::Nu5))), - BranchId::Nu5 => params.activation_height(NetworkUpgrade::Nu5).map(|lower| { - #[cfg(feature = "zfuture")] + BranchId::Nu5 => params + .activation_height(NetworkUpgrade::Nu5) + .map(|lower| (lower, params.activation_height(NetworkUpgrade::Nu6))), + BranchId::Nu6 => params + .activation_height(NetworkUpgrade::Nu6) + .map(|lower| (lower, params.activation_height(NetworkUpgrade::Nu7))), + BranchId::Nu7 => params.activation_height(NetworkUpgrade::Nu7).map(|lower| { + #[cfg(zcash_unstable = "zfuture")] let upper = params.activation_height(NetworkUpgrade::ZFuture); - #[cfg(not(feature = "zfuture"))] + #[cfg(not(zcash_unstable = "zfuture"))] let upper = None; (lower, upper) }), - #[cfg(feature = "zfuture")] + #[cfg(zcash_unstable = "zfuture")] BranchId::ZFuture => params .activation_height(NetworkUpgrade::ZFuture) .map(|lower| (lower, None)), @@ -609,7 +696,9 @@ pub mod testing { BranchId::Heartwood, BranchId::Canopy, BranchId::Nu5, - #[cfg(feature = "zfuture")] + BranchId::Nu6, + BranchId::Nu7, + #[cfg(zcash_unstable = "zfuture")] BranchId::ZFuture, ]) } @@ -627,15 +716,21 @@ pub mod testing { ) }) } + + #[cfg(feature = "test-dependencies")] + impl incrementalmerkletree_testing::TestCheckpoint for BlockHeight { + fn from_u64(value: u64) -> Self { + BlockHeight(u32::try_from(value).expect("Test checkpoint ids do not exceed 32 bits")) + } + } } #[cfg(test)] mod tests { - use std::convert::TryFrom; - use super::{ BlockHeight, BranchId, NetworkUpgrade, Parameters, MAIN_NETWORK, UPGRADES_IN_ORDER, }; + use std::convert::TryFrom; #[test] fn nu_ordering() { @@ -697,8 +792,16 @@ mod tests { BranchId::Nu5, ); assert_eq!( - BranchId::for_height(&MAIN_NETWORK, BlockHeight(5_000_000)), + BranchId::for_height(&MAIN_NETWORK, BlockHeight(2_726_399)), BranchId::Nu5, ); + assert_eq!( + BranchId::for_height(&MAIN_NETWORK, BlockHeight(2_726_400)), + BranchId::Nu6, + ); + assert_eq!( + BranchId::for_height(&MAIN_NETWORK, BlockHeight(5_000_000)), + BranchId::Nu7, + ); } } diff --git a/components/zcash_protocol/src/constants.rs b/components/zcash_protocol/src/constants.rs new file mode 100644 index 0000000000..fc56eca937 --- /dev/null +++ b/components/zcash_protocol/src/constants.rs @@ -0,0 +1,5 @@ +//! Network-specific Zcash constants. + +pub mod mainnet; +pub mod regtest; +pub mod testnet; diff --git a/components/zcash_protocol/src/constants/mainnet.rs b/components/zcash_protocol/src/constants/mainnet.rs new file mode 100644 index 0000000000..98c81caa25 --- /dev/null +++ b/components/zcash_protocol/src/constants/mainnet.rs @@ -0,0 +1,52 @@ +//! Constants for the Zcash main network. + +/// The mainnet coin type for ZEC, as defined by [SLIP 44]. +/// +/// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md +pub const COIN_TYPE: u32 = 133; + +/// The HRP for a Bech32-encoded mainnet Sapling [`ExtendedSpendingKey`]. +/// +/// Defined in [ZIP 32]. +/// +/// [`ExtendedSpendingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedSpendingKey.html +/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst +pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-main"; + +/// The HRP for a Bech32-encoded mainnet [`ExtendedFullViewingKey`]. +/// +/// Defined in [ZIP 32]. +/// +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html +/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst +pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviews"; + +/// The HRP for a Bech32-encoded mainnet Sapling [`PaymentAddress`]. +/// +/// Defined in section 5.6.4 of the [Zcash Protocol Specification]. +/// +/// [`PaymentAddress`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/struct.PaymentAddress.html +/// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf +pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zs"; + +/// The prefix for a Base58Check-encoded mainnet Sprout address. +/// +/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. +/// +/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding +pub const B58_SPROUT_ADDRESS_PREFIX: [u8; 2] = [0x16, 0x9a]; + +/// The prefix for a Base58Check-encoded mainnet [`PublicKeyHash`]. +/// +/// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html +pub const B58_PUBKEY_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xb8]; + +/// The prefix for a Base58Check-encoded mainnet [`ScriptHash`]. +/// +/// [`ScriptHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html +pub const B58_SCRIPT_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xbd]; + +/// The HRP for a Bech32m-encoded mainnet [ZIP 320] TEX address. +/// +/// [ZIP 320]: https://zips.z.cash/zip-0320 +pub const HRP_TEX_ADDRESS: &str = "tex"; diff --git a/components/zcash_protocol/src/constants/regtest.rs b/components/zcash_protocol/src/constants/regtest.rs new file mode 100644 index 0000000000..001baa7ea4 --- /dev/null +++ b/components/zcash_protocol/src/constants/regtest.rs @@ -0,0 +1,59 @@ +//! # Regtest constants +//! +//! `regtest` is a `zcashd`-specific environment used for local testing. They mostly reuse +//! the testnet constants. +//! These constants are defined in [the `zcashd` codebase]. +//! +//! [the `zcashd` codebase]: + +/// The regtest cointype reuses the testnet cointype +pub const COIN_TYPE: u32 = 1; + +/// The HRP for a Bech32-encoded regtest Sapling [`ExtendedSpendingKey`]. +/// +/// It is defined in [the `zcashd` codebase]. +/// +/// [`ExtendedSpendingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedSpendingKey.html +/// [the `zcashd` codebase]: +pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest"; + +/// The HRP for a Bech32-encoded regtest Sapling [`ExtendedFullViewingKey`]. +/// +/// It is defined in [the `zcashd` codebase]. +/// +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html +/// [the `zcashd` codebase]: +pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling"; + +/// The HRP for a Bech32-encoded regtest Sapling [`PaymentAddress`]. +/// +/// It is defined in [the `zcashd` codebase]. +/// +/// [`PaymentAddress`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/struct.PaymentAddress.html +/// [the `zcashd` codebase]: +pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zregtestsapling"; + +/// The prefix for a Base58Check-encoded regtest Sprout address. +/// +/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. +/// Same as the testnet prefix. +/// +/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding +pub const B58_SPROUT_ADDRESS_PREFIX: [u8; 2] = [0x16, 0xb6]; + +/// The prefix for a Base58Check-encoded regtest transparent [`PublicKeyHash`]. +/// Same as the testnet prefix. +/// +/// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html +pub const B58_PUBKEY_ADDRESS_PREFIX: [u8; 2] = [0x1d, 0x25]; + +/// The prefix for a Base58Check-encoded regtest transparent [`ScriptHash`]. +/// Same as the testnet prefix. +/// +/// [`ScriptHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html +pub const B58_SCRIPT_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xba]; + +/// The HRP for a Bech32m-encoded regtest [ZIP 320] TEX address. +/// +/// [ZIP 320]: https://zips.z.cash/zip-0320 +pub const HRP_TEX_ADDRESS: &str = "texregtest"; diff --git a/components/zcash_protocol/src/constants/testnet.rs b/components/zcash_protocol/src/constants/testnet.rs new file mode 100644 index 0000000000..023926546e --- /dev/null +++ b/components/zcash_protocol/src/constants/testnet.rs @@ -0,0 +1,52 @@ +//! Constants for the Zcash test network. + +/// The testnet coin type for ZEC, as defined by [SLIP 44]. +/// +/// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md +pub const COIN_TYPE: u32 = 1; + +/// The HRP for a Bech32-encoded testnet Sapling [`ExtendedSpendingKey`]. +/// +/// Defined in [ZIP 32]. +/// +/// [`ExtendedSpendingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedSpendingKey.html +/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst +pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-test"; + +/// The HRP for a Bech32-encoded testnet Sapling [`ExtendedFullViewingKey`]. +/// +/// Defined in [ZIP 32]. +/// +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html +/// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst +pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling"; + +/// The HRP for a Bech32-encoded testnet Sapling [`PaymentAddress`]. +/// +/// Defined in section 5.6.4 of the [Zcash Protocol Specification]. +/// +/// [`PaymentAddress`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/struct.PaymentAddress.html +/// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf +pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "ztestsapling"; + +/// The prefix for a Base58Check-encoded testnet Sprout address. +/// +/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. +/// +/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding +pub const B58_SPROUT_ADDRESS_PREFIX: [u8; 2] = [0x16, 0xb6]; + +/// The prefix for a Base58Check-encoded testnet transparent [`PublicKeyHash`]. +/// +/// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html +pub const B58_PUBKEY_ADDRESS_PREFIX: [u8; 2] = [0x1d, 0x25]; + +/// The prefix for a Base58Check-encoded testnet transparent [`ScriptHash`]. +/// +/// [`ScriptHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html +pub const B58_SCRIPT_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xba]; + +/// The HRP for a Bech32m-encoded testnet [ZIP 320] TEX address. +/// +/// [ZIP 320]: https://zips.z.cash/zip-0320 +pub const HRP_TEX_ADDRESS: &str = "textest"; diff --git a/components/zcash_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs new file mode 100644 index 0000000000..f73564751d --- /dev/null +++ b/components/zcash_protocol/src/lib.rs @@ -0,0 +1,59 @@ +//! *A crate for Zcash protocol constants and value types.* +//! +//! `zcash_protocol` contains Rust structs, traits and functions that provide the network constants +//! for the Zcash main and test networks, as well types for representing ZEC amounts and value +//! balances. +//! +//! ## Feature flags +#![doc = document_features::document_features!()] +//! + +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// Catch documentation errors caused by code changes. +#![deny(rustdoc::broken_intra_doc_links)] +// Temporary until we have addressed all Result cases. +#![allow(clippy::result_unit_err)] + +use core::fmt; + +pub mod consensus; +pub mod constants; +#[cfg(feature = "local-consensus")] +pub mod local_consensus; +pub mod memo; +pub mod value; + +/// A Zcash shielded transfer protocol. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum ShieldedProtocol { + /// The Sapling protocol + Sapling, + /// The Orchard protocol + Orchard, +} + +/// A value pool in the Zcash protocol. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PoolType { + /// The transparent value pool + Transparent, + /// A shielded value pool. + Shielded(ShieldedProtocol), +} + +impl PoolType { + pub const TRANSPARENT: PoolType = PoolType::Transparent; + pub const SAPLING: PoolType = PoolType::Shielded(ShieldedProtocol::Sapling); + pub const ORCHARD: PoolType = PoolType::Shielded(ShieldedProtocol::Orchard); +} + +impl fmt::Display for PoolType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PoolType::Transparent => f.write_str("Transparent"), + PoolType::Shielded(ShieldedProtocol::Sapling) => f.write_str("Sapling"), + PoolType::Shielded(ShieldedProtocol::Orchard) => f.write_str("Orchard"), + } + } +} diff --git a/components/zcash_protocol/src/local_consensus.rs b/components/zcash_protocol/src/local_consensus.rs new file mode 100644 index 0000000000..115e070f45 --- /dev/null +++ b/components/zcash_protocol/src/local_consensus.rs @@ -0,0 +1,221 @@ +use crate::consensus::{BlockHeight, NetworkType, NetworkUpgrade, Parameters}; + +/// a `LocalNetwork` setup should define the activation heights +/// of network upgrades. `None` is considered as "not activated" +/// These heights are not validated. Callers shall initialized +/// them according to the settings used on the Full Nodes they +/// are connecting to. +/// +/// Example: +/// Regtest Zcashd using the following `zcash.conf` +/// ``` +/// ## NUPARAMS +/// nuparams=5ba81b19:1 # Overwinter +/// nuparams=76b809bb:1 # Sapling +/// nuparams=2bb40e60:1 # Blossom +/// nuparams=f5b9230b:1 # Heartwood +/// nuparams=e9ff75a6:1 # Canopy +/// nuparams=c2d6d0b4:1 # NU5 +/// nuparams=c8e71055:1 # NU6 +/// ``` +/// would use the following `LocalNetwork` struct +/// ``` +/// let regtest = LocalNetwork { +/// overwinter: Some(BlockHeight::from_u32(1)), +/// sapling: Some(BlockHeight::from_u32(1)), +/// blossom: Some(BlockHeight::from_u32(1)), +/// heartwood: Some(BlockHeight::from_u32(1)), +/// canopy: Some(BlockHeight::from_u32(1)), +/// nu5: Some(BlockHeight::from_u32(1)), +/// nu6: Some(BlockHeight::from_u32(1)), +/// }; +/// ``` +/// +#[derive(Clone, PartialEq, Eq, Copy, Debug, Hash)] +pub struct LocalNetwork { + pub overwinter: Option, + pub sapling: Option, + pub blossom: Option, + pub heartwood: Option, + pub canopy: Option, + pub nu5: Option, + pub nu6: Option, + pub nu7: Option, + #[cfg(zcash_unstable = "zfuture")] + pub z_future: Option, +} + +/// Parameters implementation for `LocalNetwork` +impl Parameters for LocalNetwork { + fn network_type(&self) -> NetworkType { + NetworkType::Regtest + } + + fn activation_height(&self, nu: NetworkUpgrade) -> Option { + match nu { + NetworkUpgrade::Overwinter => self.overwinter, + NetworkUpgrade::Sapling => self.sapling, + NetworkUpgrade::Blossom => self.blossom, + NetworkUpgrade::Heartwood => self.heartwood, + NetworkUpgrade::Canopy => self.canopy, + NetworkUpgrade::Nu5 => self.nu5, + NetworkUpgrade::Nu6 => self.nu6, + NetworkUpgrade::Nu7 => self.nu7, + #[cfg(zcash_unstable = "zfuture")] + NetworkUpgrade::ZFuture => self.z_future, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + consensus::{BlockHeight, NetworkConstants, NetworkUpgrade, Parameters}, + constants, + local_consensus::LocalNetwork, + }; + + #[test] + fn regtest_nu_activation() { + let expected_overwinter = BlockHeight::from_u32(1); + let expected_sapling = BlockHeight::from_u32(2); + let expected_blossom = BlockHeight::from_u32(3); + let expected_heartwood = BlockHeight::from_u32(4); + let expected_canopy = BlockHeight::from_u32(5); + let expected_nu5 = BlockHeight::from_u32(6); + let expected_nu6 = BlockHeight::from_u32(7); + let expected_nu7 = BlockHeight::from_u32(8); + #[cfg(zcash_unstable = "zfuture")] + let expected_z_future = BlockHeight::from_u32(8); + + let regtest = LocalNetwork { + overwinter: Some(expected_overwinter), + sapling: Some(expected_sapling), + blossom: Some(expected_blossom), + heartwood: Some(expected_heartwood), + canopy: Some(expected_canopy), + nu5: Some(expected_nu5), + nu6: Some(expected_nu6), + nu7: Some(expected_nu7), + #[cfg(zcash_unstable = "zfuture")] + z_future: Some(expected_z_future), + }; + + assert!(regtest.is_nu_active(NetworkUpgrade::Overwinter, expected_overwinter)); + assert!(regtest.is_nu_active(NetworkUpgrade::Sapling, expected_sapling)); + assert!(regtest.is_nu_active(NetworkUpgrade::Blossom, expected_blossom)); + assert!(regtest.is_nu_active(NetworkUpgrade::Heartwood, expected_heartwood)); + assert!(regtest.is_nu_active(NetworkUpgrade::Canopy, expected_canopy)); + assert!(regtest.is_nu_active(NetworkUpgrade::Nu5, expected_nu5)); + assert!(regtest.is_nu_active(NetworkUpgrade::Nu6, expected_nu6)); + assert!(regtest.is_nu_active(NetworkUpgrade::Nu7, expected_nu7)); + #[cfg(zcash_unstable = "zfuture")] + assert!(!regtest.is_nu_active(NetworkUpgrade::ZFuture, expected_nu5)); + } + + #[test] + fn regtest_activation_heights() { + let expected_overwinter = BlockHeight::from_u32(1); + let expected_sapling = BlockHeight::from_u32(2); + let expected_blossom = BlockHeight::from_u32(3); + let expected_heartwood = BlockHeight::from_u32(4); + let expected_canopy = BlockHeight::from_u32(5); + let expected_nu5 = BlockHeight::from_u32(6); + let expected_nu6 = BlockHeight::from_u32(7); + let expected_nu7 = BlockHeight::from_u32(8); + #[cfg(zcash_unstable = "zfuture")] + let expected_z_future = BlockHeight::from_u32(8); + + let regtest = LocalNetwork { + overwinter: Some(expected_overwinter), + sapling: Some(expected_sapling), + blossom: Some(expected_blossom), + heartwood: Some(expected_heartwood), + canopy: Some(expected_canopy), + nu5: Some(expected_nu5), + nu6: Some(expected_nu6), + nu7: Some(expected_nu7), + #[cfg(zcash_unstable = "zfuture")] + z_future: Some(expected_z_future), + }; + + assert_eq!( + regtest.activation_height(NetworkUpgrade::Overwinter), + Some(expected_overwinter) + ); + assert_eq!( + regtest.activation_height(NetworkUpgrade::Sapling), + Some(expected_sapling) + ); + assert_eq!( + regtest.activation_height(NetworkUpgrade::Blossom), + Some(expected_blossom) + ); + assert_eq!( + regtest.activation_height(NetworkUpgrade::Heartwood), + Some(expected_heartwood) + ); + assert_eq!( + regtest.activation_height(NetworkUpgrade::Canopy), + Some(expected_canopy) + ); + assert_eq!( + regtest.activation_height(NetworkUpgrade::Nu5), + Some(expected_nu5) + ); + #[cfg(zcash_unstable = "zfuture")] + assert_eq!( + regtest.activation_height(NetworkUpgrade::ZFuture), + Some(expected_z_future) + ); + } + + #[test] + fn regtests_constants() { + let expected_overwinter = BlockHeight::from_u32(1); + let expected_sapling = BlockHeight::from_u32(2); + let expected_blossom = BlockHeight::from_u32(3); + let expected_heartwood = BlockHeight::from_u32(4); + let expected_canopy = BlockHeight::from_u32(5); + let expected_nu5 = BlockHeight::from_u32(6); + let expected_nu6 = BlockHeight::from_u32(7); + let expected_nu7 = BlockHeight::from_u32(8); + #[cfg(zcash_unstable = "zfuture")] + let expected_z_future = BlockHeight::from_u32(8); + + let regtest = LocalNetwork { + overwinter: Some(expected_overwinter), + sapling: Some(expected_sapling), + blossom: Some(expected_blossom), + heartwood: Some(expected_heartwood), + canopy: Some(expected_canopy), + nu5: Some(expected_nu5), + nu6: Some(expected_nu6), + nu7: Some(expected_nu7), + #[cfg(zcash_unstable = "zfuture")] + z_future: Some(expected_z_future), + }; + + assert_eq!(regtest.coin_type(), constants::regtest::COIN_TYPE); + assert_eq!( + regtest.hrp_sapling_extended_spending_key(), + constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY + ); + assert_eq!( + regtest.hrp_sapling_extended_full_viewing_key(), + constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY + ); + assert_eq!( + regtest.hrp_sapling_payment_address(), + constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS + ); + assert_eq!( + regtest.b58_pubkey_address_prefix(), + constants::regtest::B58_PUBKEY_ADDRESS_PREFIX + ); + assert_eq!( + regtest.b58_script_address_prefix(), + constants::regtest::B58_SCRIPT_ADDRESS_PREFIX + ); + } +} diff --git a/zcash_primitives/src/memo.rs b/components/zcash_protocol/src/memo.rs similarity index 97% rename from zcash_primitives/src/memo.rs rename to components/zcash_protocol/src/memo.rs index 8143eec69d..10258a52d9 100644 --- a/zcash_primitives/src/memo.rs +++ b/components/zcash_protocol/src/memo.rs @@ -28,7 +28,7 @@ where } /// Errors that may result from attempting to construct an invalid memo. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Error { InvalidUtf8(std::str::Utf8Error), TooLong(usize), @@ -144,9 +144,10 @@ impl Deref for TextMemo { } /// An unencrypted memo received alongside a shielded note in a Zcash transaction. -#[derive(Clone)] +#[derive(Clone, Default)] pub enum Memo { /// An empty memo field. + #[default] Empty, /// A memo field containing a UTF-8 string. Text(TextMemo), @@ -171,12 +172,6 @@ impl fmt::Debug for Memo { } } -impl Default for Memo { - fn default() -> Self { - Memo::Empty - } -} - impl PartialEq for Memo { fn eq(&self, rhs: &Memo) -> bool { match (self, rhs) { @@ -197,13 +192,25 @@ impl TryFrom for Memo { /// Returns an error if the provided slice does not represent a valid `Memo` (for /// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical). fn try_from(bytes: MemoBytes) -> Result { + Self::try_from(&bytes) + } +} + +impl TryFrom<&MemoBytes> for Memo { + type Error = Error; + + /// Parses a `Memo` from its ZIP 302 serialization. + /// + /// Returns an error if the provided slice does not represent a valid `Memo` (for + /// example, if the slice is not 512 bytes, or the encoded `Memo` is non-canonical). + fn try_from(bytes: &MemoBytes) -> Result { match bytes.0[0] { 0xF6 if bytes.0.iter().skip(1).all(|&b| b == 0) => Ok(Memo::Empty), 0xFF => Ok(Memo::Arbitrary(Box::new(bytes.0[1..].try_into().unwrap()))), b if b <= 0xF4 => str::from_utf8(bytes.as_slice()) .map(|r| Memo::Text(TextMemo(r.to_owned()))) .map_err(Error::InvalidUtf8), - _ => Ok(Memo::Future(bytes)), + _ => Ok(Memo::Future(bytes.clone())), } } } diff --git a/components/zcash_protocol/src/value.rs b/components/zcash_protocol/src/value.rs new file mode 100644 index 0000000000..e7e5001dbc --- /dev/null +++ b/components/zcash_protocol/src/value.rs @@ -0,0 +1,569 @@ +use std::convert::{Infallible, TryFrom}; +use std::error; +use std::iter::Sum; +use std::num::NonZeroU64; +use std::ops::{Add, Div, Mul, Neg, Sub}; + +use memuse::DynamicUsage; + +pub const COIN: u64 = 1_0000_0000; +pub const MAX_MONEY: u64 = 21_000_000 * COIN; +pub const MAX_BALANCE: i64 = MAX_MONEY as i64; + +/// A type-safe representation of a Zcash value delta, in zatoshis. +/// +/// An ZatBalance can only be constructed from an integer that is within the valid monetary +/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis), +/// and this is preserved as an invariant internally. (A [`Transaction`] containing serialized +/// invalid ZatBalances would also be rejected by the network consensus rules.) +/// +/// [`Transaction`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/transaction/struct.Transaction.html +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct ZatBalance(i64); + +memuse::impl_no_dynamic_usage!(ZatBalance); + +impl ZatBalance { + /// Returns a zero-valued ZatBalance. + pub const fn zero() -> Self { + ZatBalance(0) + } + + /// Creates a constant ZatBalance from an i64. + /// + /// Panics: if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. + pub const fn const_from_i64(amount: i64) -> Self { + assert!(-MAX_BALANCE <= amount && amount <= MAX_BALANCE); // contains is not const + ZatBalance(amount) + } + + /// Creates a constant ZatBalance from a u64. + /// + /// Panics: if the amount is outside the range `{0..MAX_BALANCE}`. + pub const fn const_from_u64(amount: u64) -> Self { + assert!(amount <= MAX_MONEY); // contains is not const + ZatBalance(amount as i64) + } + + /// Creates an ZatBalance from an i64. + /// + /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. + pub fn from_i64(amount: i64) -> Result { + if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) { + Ok(ZatBalance(amount)) + } else if amount < -MAX_BALANCE { + Err(BalanceError::Underflow) + } else { + Err(BalanceError::Overflow) + } + } + + /// Creates a non-negative ZatBalance from an i64. + /// + /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. + pub fn from_nonnegative_i64(amount: i64) -> Result { + if (0..=MAX_BALANCE).contains(&amount) { + Ok(ZatBalance(amount)) + } else if amount < 0 { + Err(BalanceError::Underflow) + } else { + Err(BalanceError::Overflow) + } + } + + /// Creates an ZatBalance from a u64. + /// + /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + pub fn from_u64(amount: u64) -> Result { + if amount <= MAX_MONEY { + Ok(ZatBalance(amount as i64)) + } else { + Err(BalanceError::Overflow) + } + } + + /// Reads an ZatBalance from a signed 64-bit little-endian integer. + /// + /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. + pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result { + let amount = i64::from_le_bytes(bytes); + ZatBalance::from_i64(amount) + } + + /// Reads a non-negative ZatBalance from a signed 64-bit little-endian integer. + /// + /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. + pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { + let amount = i64::from_le_bytes(bytes); + ZatBalance::from_nonnegative_i64(amount) + } + + /// Reads an ZatBalance from an unsigned 64-bit little-endian integer. + /// + /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. + pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { + let amount = u64::from_le_bytes(bytes); + ZatBalance::from_u64(amount) + } + + /// Returns the ZatBalance encoded as a signed 64-bit little-endian integer. + pub fn to_i64_le_bytes(self) -> [u8; 8] { + self.0.to_le_bytes() + } + + /// Returns `true` if `self` is positive and `false` if the ZatBalance is zero or + /// negative. + pub const fn is_positive(self) -> bool { + self.0.is_positive() + } + + /// Returns `true` if `self` is negative and `false` if the ZatBalance is zero or + /// positive. + pub const fn is_negative(self) -> bool { + self.0.is_negative() + } + + pub fn sum>(values: I) -> Option { + let mut result = ZatBalance::zero(); + for value in values { + result = (result + value)?; + } + Some(result) + } +} + +impl TryFrom for ZatBalance { + type Error = BalanceError; + + fn try_from(value: i64) -> Result { + ZatBalance::from_i64(value) + } +} + +impl From for i64 { + fn from(amount: ZatBalance) -> i64 { + amount.0 + } +} + +impl From<&ZatBalance> for i64 { + fn from(amount: &ZatBalance) -> i64 { + amount.0 + } +} + +impl TryFrom for u64 { + type Error = BalanceError; + + fn try_from(value: ZatBalance) -> Result { + value.0.try_into().map_err(|_| BalanceError::Underflow) + } +} + +impl Add for ZatBalance { + type Output = Option; + + fn add(self, rhs: ZatBalance) -> Option { + ZatBalance::from_i64(self.0 + rhs.0).ok() + } +} + +impl Add for Option { + type Output = Self; + + fn add(self, rhs: ZatBalance) -> Option { + self.and_then(|lhs| lhs + rhs) + } +} + +impl Sub for ZatBalance { + type Output = Option; + + fn sub(self, rhs: ZatBalance) -> Option { + ZatBalance::from_i64(self.0 - rhs.0).ok() + } +} + +impl Sub for Option { + type Output = Self; + + fn sub(self, rhs: ZatBalance) -> Option { + self.and_then(|lhs| lhs - rhs) + } +} + +impl Sum for Option { + fn sum>(mut iter: I) -> Self { + iter.try_fold(ZatBalance::zero(), |acc, a| acc + a) + } +} + +impl<'a> Sum<&'a ZatBalance> for Option { + fn sum>(mut iter: I) -> Self { + iter.try_fold(ZatBalance::zero(), |acc, a| acc + *a) + } +} + +impl Neg for ZatBalance { + type Output = Self; + + fn neg(self) -> Self { + ZatBalance(-self.0) + } +} + +impl Mul for ZatBalance { + type Output = Option; + + fn mul(self, rhs: usize) -> Option { + let rhs: i64 = rhs.try_into().ok()?; + self.0 + .checked_mul(rhs) + .and_then(|i| ZatBalance::try_from(i).ok()) + } +} + +/// A type-safe representation of some nonnegative amount of Zcash. +/// +/// A Zatoshis can only be constructed from an integer that is within the valid monetary +/// range of `{0..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis). +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] +pub struct Zatoshis(u64); + +/// A struct that provides both the quotient and remainder of a division operation. +pub struct QuotRem { + quotient: A, + remainder: A, +} + +impl QuotRem { + /// Returns the quotient portion of the value. + pub fn quotient(&self) -> &A { + &self.quotient + } + + /// Returns the remainder portion of the value. + pub fn remainder(&self) -> &A { + &self.remainder + } +} + +impl Zatoshis { + /// Returns the identity `Zatoshis` + pub const ZERO: Self = Zatoshis(0); + + /// Returns this Zatoshis as a u64. + pub fn into_u64(self) -> u64 { + self.0 + } + + /// Creates a Zatoshis from a u64. + /// + /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + pub fn from_u64(amount: u64) -> Result { + if (0..=MAX_MONEY).contains(&amount) { + Ok(Zatoshis(amount)) + } else { + Err(BalanceError::Overflow) + } + } + + /// Creates a constant Zatoshis from a u64. + /// + /// Panics: if the amount is outside the range `{0..MAX_MONEY}`. + pub const fn const_from_u64(amount: u64) -> Self { + assert!(amount <= MAX_MONEY); // contains is not const + Zatoshis(amount) + } + + /// Creates a Zatoshis from an i64. + /// + /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + pub fn from_nonnegative_i64(amount: i64) -> Result { + u64::try_from(amount) + .map_err(|_| BalanceError::Underflow) + .and_then(Self::from_u64) + } + + /// Reads an Zatoshis from an unsigned 64-bit little-endian integer. + /// + /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { + let amount = u64::from_le_bytes(bytes); + Self::from_u64(amount) + } + + /// Reads a Zatoshis from a signed integer represented as a two's + /// complement 64-bit little-endian value. + /// + /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { + let amount = i64::from_le_bytes(bytes); + Self::from_nonnegative_i64(amount) + } + + /// Returns this Zatoshis encoded as a signed two's complement 64-bit + /// little-endian value. + pub fn to_i64_le_bytes(self) -> [u8; 8] { + (self.0 as i64).to_le_bytes() + } + + /// Returns whether or not this `Zatoshis` is the zero value. + pub fn is_zero(&self) -> bool { + self == &Zatoshis::ZERO + } + + /// Returns whether or not this `Zatoshis` is positive. + pub fn is_positive(&self) -> bool { + self > &Zatoshis::ZERO + } + + /// Divides this `Zatoshis` value by the given divisor and returns the quotient and remainder. + pub fn div_with_remainder(&self, divisor: NonZeroU64) -> QuotRem { + let divisor = u64::from(divisor); + // `self` is already bounds-checked, and both the quotient and remainder + // are <= self, so we don't need to re-check them in division. + QuotRem { + quotient: Zatoshis(self.0 / divisor), + remainder: Zatoshis(self.0 % divisor), + } + } +} + +impl From for ZatBalance { + fn from(n: Zatoshis) -> Self { + ZatBalance(n.0 as i64) + } +} + +impl From<&Zatoshis> for ZatBalance { + fn from(n: &Zatoshis) -> Self { + ZatBalance(n.0 as i64) + } +} + +impl From for u64 { + fn from(n: Zatoshis) -> Self { + n.into_u64() + } +} + +impl TryFrom for Zatoshis { + type Error = BalanceError; + + fn try_from(value: u64) -> Result { + Zatoshis::from_u64(value) + } +} + +impl TryFrom for Zatoshis { + type Error = BalanceError; + + fn try_from(value: ZatBalance) -> Result { + Zatoshis::from_nonnegative_i64(value.0) + } +} + +impl Add for Zatoshis { + type Output = Option; + + fn add(self, rhs: Zatoshis) -> Option { + Self::from_u64(self.0.checked_add(rhs.0)?).ok() + } +} + +impl Add for Option { + type Output = Self; + + fn add(self, rhs: Zatoshis) -> Option { + self.and_then(|lhs| lhs + rhs) + } +} + +impl Sub for Zatoshis { + type Output = Option; + + fn sub(self, rhs: Zatoshis) -> Option { + Zatoshis::from_u64(self.0.checked_sub(rhs.0)?).ok() + } +} + +impl Sub for Option { + type Output = Self; + + fn sub(self, rhs: Zatoshis) -> Option { + self.and_then(|lhs| lhs - rhs) + } +} + +impl Mul for Zatoshis { + type Output = Option; + + fn mul(self, rhs: u64) -> Option { + Zatoshis::from_u64(self.0.checked_mul(rhs)?).ok() + } +} + +impl Mul for Zatoshis { + type Output = Option; + + fn mul(self, rhs: usize) -> Option { + self * u64::try_from(rhs).ok()? + } +} + +impl Sum for Option { + fn sum>(mut iter: I) -> Self { + iter.try_fold(Zatoshis::ZERO, |acc, a| acc + a) + } +} + +impl<'a> Sum<&'a Zatoshis> for Option { + fn sum>(mut iter: I) -> Self { + iter.try_fold(Zatoshis::ZERO, |acc, a| acc + *a) + } +} + +impl Div for Zatoshis { + type Output = Zatoshis; + + fn div(self, rhs: NonZeroU64) -> Zatoshis { + // `self` is already bounds-checked and the quotient is <= self, so + // we don't need to re-check it + Zatoshis(self.0 / u64::from(rhs)) + } +} + +/// A type for balance violations in amount addition and subtraction +/// (overflow and underflow of allowed ranges) +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum BalanceError { + Overflow, + Underflow, +} + +impl error::Error for BalanceError {} + +impl std::fmt::Display for BalanceError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match &self { + BalanceError::Overflow => { + write!( + f, + "ZatBalance addition resulted in a value outside the valid range." + ) + } + BalanceError::Underflow => write!( + f, + "ZatBalance subtraction resulted in a value outside the valid range." + ), + } + } +} + +impl From for BalanceError { + fn from(_value: Infallible) -> Self { + unreachable!() + } +} + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use proptest::prelude::prop_compose; + + use super::{ZatBalance, Zatoshis, MAX_BALANCE, MAX_MONEY}; + + prop_compose! { + pub fn arb_zat_balance()(amt in -MAX_BALANCE..MAX_BALANCE) -> ZatBalance { + ZatBalance::from_i64(amt).unwrap() + } + } + + prop_compose! { + pub fn arb_positive_zat_balance()(amt in 1i64..MAX_BALANCE) -> ZatBalance { + ZatBalance::from_i64(amt).unwrap() + } + } + + prop_compose! { + pub fn arb_nonnegative_zat_balance()(amt in 0i64..MAX_BALANCE) -> ZatBalance { + ZatBalance::from_i64(amt).unwrap() + } + } + + prop_compose! { + pub fn arb_zatoshis()(amt in 0u64..MAX_MONEY) -> Zatoshis { + Zatoshis::from_u64(amt).unwrap() + } + } +} + +#[cfg(test)] +mod tests { + use crate::value::MAX_BALANCE; + + use super::ZatBalance; + + #[test] + fn amount_in_range() { + let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00"; + assert_eq!(ZatBalance::from_u64_le_bytes(*zero).unwrap(), ZatBalance(0)); + assert_eq!( + ZatBalance::from_nonnegative_i64_le_bytes(*zero).unwrap(), + ZatBalance(0) + ); + assert_eq!(ZatBalance::from_i64_le_bytes(*zero).unwrap(), ZatBalance(0)); + + let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff"; + assert!(ZatBalance::from_u64_le_bytes(*neg_one).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_one).is_err()); + assert_eq!( + ZatBalance::from_i64_le_bytes(*neg_one).unwrap(), + ZatBalance(-1) + ); + + let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00"; + assert_eq!( + ZatBalance::from_u64_le_bytes(*max_money).unwrap(), + ZatBalance(MAX_BALANCE) + ); + assert_eq!( + ZatBalance::from_nonnegative_i64_le_bytes(*max_money).unwrap(), + ZatBalance(MAX_BALANCE) + ); + assert_eq!( + ZatBalance::from_i64_le_bytes(*max_money).unwrap(), + ZatBalance(MAX_BALANCE) + ); + + let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00"; + assert!(ZatBalance::from_u64_le_bytes(*max_money_p1).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*max_money_p1).is_err()); + assert!(ZatBalance::from_i64_le_bytes(*max_money_p1).is_err()); + + let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff"; + assert!(ZatBalance::from_u64_le_bytes(*neg_max_money).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money).is_err()); + assert_eq!( + ZatBalance::from_i64_le_bytes(*neg_max_money).unwrap(), + ZatBalance(-MAX_BALANCE) + ); + + let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff"; + assert!(ZatBalance::from_u64_le_bytes(*neg_max_money_m1).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money_m1).is_err()); + assert!(ZatBalance::from_i64_le_bytes(*neg_max_money_m1).is_err()); + } + + #[test] + fn add_overflow() { + let v = ZatBalance(MAX_BALANCE); + assert_eq!(v + ZatBalance(1), None) + } + + #[test] + fn sub_underflow() { + let v = ZatBalance(-MAX_BALANCE); + assert_eq!(v - ZatBalance(1), None) + } +} diff --git a/components/zip321/CHANGELOG.md b/components/zip321/CHANGELOG.md new file mode 100644 index 0000000000..d1c0d8c97d --- /dev/null +++ b/components/zip321/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog +All notable changes to this library will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this library adheres to Rust's notion of +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Changed +- MSRV is now 1.77.0. + +## [0.2.0] 2024-10-04 + +### Changed +- Migrated to `zcash_address 0.6` + +## [0.1.0] 2024-08-20 + +The contents of this crate were factored out from `zcash_client_backend` to +provide a better separation of concerns and simpler integration with WASM +builds. The entries below are relative to the `zcash_client_backend` crate as +of `zcash_client_backend-0.10.0`. + +### Added +- `zip321::Payment::new` +- `impl From> for Zip321Error` + +### Changed +- Fields of `zip321::Payment` are now private. Accessors have been provided for + the fields that are no longer public, and `Payment::new` has been added to + serve the needs of payment construction. +- `zip321::Payment::recipient_address()` returns `zcash_address::ZcashAddress` +- `zip321::Payment::without_memo` now takes a `zcash_address::ZcashAddress` for + its `recipient_address` argument. +- Uses of `zcash_primitives::transaction::components::amount::NonNegartiveAmount` + have been replace with `zcash_protocol::value::Zatoshis`. Also, some incorrect + uses of the signed `zcash_primitives::transaction::components::Amount` + type have been corrected via replacement with the `Zatoshis` type. +- The following methods that previously required a + `zcash_primitives::consensus::Parameters` argument to facilitate address + parsing no longer take such an argument. + - `zip321::TransactionRequest::{to_uri, from_uri}` + - `zip321::render::addr_param` + - `zip321::parse::{lead_addr, zcashparam}` +- `zip321::Param::Memo` now boxes its argument. +- `zip321::Param::Addr` now wraps a `zcash_address::ZcashAddress` +- MSRV is now 1.70.0. diff --git a/components/zip321/Cargo.toml b/components/zip321/Cargo.toml new file mode 100644 index 0000000000..be67284a39 --- /dev/null +++ b/components/zip321/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "zip321" +description = "Parsing functions and data types for Zcash ZIP 321 Payment Request URIs" +version = "0.2.0" +authors = [ + "Kris Nuttycombe " +] +homepage = "https://github.com/zcash/librustzcash" +repository.workspace = true +readme = "README.md" +license.workspace = true +edition.workspace = true +rust-version.workspace = true +categories.workspace = true + +[dependencies] +zcash_address.workspace = true +zcash_protocol.workspace = true + +# - Parsing and Encoding +nom = "7" +base64.workspace = true +percent-encoding.workspace = true + +# - Test dependencies +proptest = { workspace = true, optional = true } + +[dev-dependencies] +zcash_address = { workspace = true, features = ["test-dependencies"] } +zcash_protocol = { workspace = true, features = ["test-dependencies"] } +proptest.workspace = true + +[features] +## Exposes APIs that are useful for testing, such as `proptest` strategies. +test-dependencies = ["dep:proptest"] diff --git a/components/zip321/LICENSE-APACHE b/components/zip321/LICENSE-APACHE new file mode 100644 index 0000000000..1e5006dc14 --- /dev/null +++ b/components/zip321/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/components/zip321/LICENSE-MIT b/components/zip321/LICENSE-MIT new file mode 100644 index 0000000000..35ad1aa6e9 --- /dev/null +++ b/components/zip321/LICENSE-MIT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017-2024 Electric Coin Company + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/components/zip321/README.md b/components/zip321/README.md new file mode 100644 index 0000000000..bccff5b0ad --- /dev/null +++ b/components/zip321/README.md @@ -0,0 +1,22 @@ +# zip321 + +This library contains Rust parsing functions and data types for working with +Zcash ZIP 321 Payment Request URIs. + +## License + +Licensed under either of + + * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or + http://www.apache.org/licenses/LICENSE-2.0) + * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual licensed as above, without any additional terms or +conditions. + diff --git a/zcash_client_backend/src/zip321.rs b/components/zip321/src/lib.rs similarity index 55% rename from zcash_client_backend/src/zip321.rs rename to components/zip321/src/lib.rs index a26c3cdf48..42b81fb786 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/components/zip321/src/lib.rs @@ -1,30 +1,30 @@ //! Reference implementation of the ZIP-321 standard for payment requests. //! -//! This module provides data structures, parsing, and rendering functions +//! This crate provides data structures, parsing, and rendering functions //! for interpreting and producing valid ZIP 321 URIs. //! //! The specification for ZIP 321 URIs may be found at use core::fmt::Debug; -use std::collections::HashMap; +use std::{ + collections::BTreeMap, + fmt::{self, Display}, +}; use base64::{prelude::BASE64_URL_SAFE_NO_PAD, Engine}; use nom::{ character::complete::char, combinator::all_consuming, multi::separated_list0, sequence::preceded, }; -use zcash_primitives::{ - consensus, + +use zcash_address::{ConversionError, ZcashAddress}; +use zcash_protocol::{ memo::{self, MemoBytes}, - transaction::components::Amount, + value::BalanceError, + value::Zatoshis, }; -#[cfg(any(test, feature = "test-dependencies"))] -use std::cmp::Ordering; - -use crate::address::RecipientAddress; - /// Errors that may be produced in decoding of payment requests. -#[derive(Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Zip321Error { /// A memo field in the ZIP 321 URI was not properly base-64 encoded InvalidBase64(base64::DecodeError), @@ -45,16 +45,67 @@ pub enum Zip321Error { ParseError(String), } +impl From> for Zip321Error { + fn from(value: ConversionError) -> Self { + Zip321Error::ParseError(format!("Address parsing failed: {}", value)) + } +} + +impl Display for Zip321Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Zip321Error::InvalidBase64(err) => { + write!(f, "Memo value was not correctly base64-encoded: {:?}", err) + } + Zip321Error::MemoBytesError(err) => write!( + f, + "Memo exceeded maximum length or violated UTF-8 encoding restrictions: {:?}", + err + ), + Zip321Error::TooManyPayments(n) => write!( + f, + "Cannot create a Zcash transaction containing {} payments", + n + ), + Zip321Error::DuplicateParameter(param, idx) => write!( + f, + "There is a duplicate {} parameter at index {}", + param.name(), + idx + ), + Zip321Error::TransparentMemo(idx) => write!( + f, + "Payment {} is invalid: cannot send a memo to a transparent recipient address", + idx + ), + Zip321Error::RecipientMissing(idx) => { + write!(f, "Payment {} is missing its recipient address", idx) + } + Zip321Error::ParseError(s) => write!(f, "Parse failure: {}", s), + } + } +} + +impl std::error::Error for Zip321Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Zip321Error::InvalidBase64(err) => Some(err), + Zip321Error::MemoBytesError(err) => Some(err), + _ => None, + } + } +} + /// Converts a [`MemoBytes`] value to a ZIP 321 compatible base64-encoded string. /// -/// [`MemoBytes`]: zcash_primitives::memo::MemoBytes +/// [`MemoBytes`]: zcash_protocol::memo::MemoBytes pub fn memo_to_base64(memo: &MemoBytes) -> String { BASE64_URL_SAFE_NO_PAD.encode(memo.as_slice()) } /// Parse a [`MemoBytes`] value from a ZIP 321 compatible base64-encoded string. /// -/// [`MemoBytes`]: zcash_primitives::memo::MemoBytes +/// [`MemoBytes`]: zcash_protocol::memo::MemoBytes pub fn memo_from_base64(s: &str) -> Result { BASE64_URL_SAFE_NO_PAD .decode(s) @@ -63,56 +114,104 @@ pub fn memo_from_base64(s: &str) -> Result { } /// A single payment being requested. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Payment { - /// The payment address to which the payment should be sent. - pub recipient_address: RecipientAddress, + /// The address to which the payment should be sent. + recipient_address: ZcashAddress, /// The amount of the payment that is being requested. - pub amount: Amount, + amount: Zatoshis, /// A memo that, if included, must be provided with the payment. /// If a memo is present and [`recipient_address`] is not a shielded /// address, the wallet should report an error. /// /// [`recipient_address`]: #structfield.recipient_address - pub memo: Option, + memo: Option, /// A human-readable label for this payment within the larger structure /// of the transaction request. - pub label: Option, + label: Option, /// A human-readable message to be displayed to the user describing the /// purpose of this payment. - pub message: Option, + message: Option, /// A list of other arbitrary key/value pairs associated with this payment. - pub other_params: Vec<(String, String)>, + other_params: Vec<(String, String)>, } impl Payment { - /// A utility for use in tests to help check round-trip serialization properties. - #[cfg(any(test, feature = "test-dependencies"))] - pub(in crate::zip321) fn normalize(&mut self) { - self.other_params.sort(); + /// Constructs a new [`Payment`] from its constituent parts. + /// + /// Returns `None` if the payment requests that a memo be sent to a recipient that cannot + /// receive a memo. + pub fn new( + recipient_address: ZcashAddress, + amount: Zatoshis, + memo: Option, + label: Option, + message: Option, + other_params: Vec<(String, String)>, + ) -> Option { + if memo.is_none() || recipient_address.can_receive_memo() { + Some(Self { + recipient_address, + amount, + memo, + label, + message, + other_params, + }) + } else { + None + } } - /// Returns a function which compares two normalized payments, with addresses sorted by their - /// string representation given the specified network. This does not perform normalization - /// internally, so payments must be normalized prior to being passed to the comparison function - /// returned from this method. - #[cfg(any(test, feature = "test-dependencies"))] - pub(in crate::zip321) fn compare_normalized( - params: &P, - ) -> impl Fn(&Payment, &Payment) -> Ordering + '_ { - move |a: &Payment, b: &Payment| { - let a_addr = a.recipient_address.encode(params); - let b_addr = b.recipient_address.encode(params); - - a_addr - .cmp(&b_addr) - .then(a.amount.cmp(&b.amount)) - .then(a.memo.cmp(&b.memo)) - .then(a.label.cmp(&b.label)) - .then(a.message.cmp(&b.message)) - .then(a.other_params.cmp(&b.other_params)) + /// Constructs a new [`Payment`] paying the given address the specified amount. + pub fn without_memo(recipient_address: ZcashAddress, amount: Zatoshis) -> Self { + Self { + recipient_address, + amount, + memo: None, + label: None, + message: None, + other_params: vec![], } } + + /// Returns the payment address to which the payment should be sent. + pub fn recipient_address(&self) -> &ZcashAddress { + &self.recipient_address + } + + /// Returns the value of the payment that is being requested, in zatoshis. + pub fn amount(&self) -> Zatoshis { + self.amount + } + + /// Returns the memo that, if included, must be provided with the payment. + pub fn memo(&self) -> Option<&MemoBytes> { + self.memo.as_ref() + } + + /// A human-readable label for this payment within the larger structure + /// of the transaction request. + pub fn label(&self) -> Option<&String> { + self.label.as_ref() + } + + /// A human-readable message to be displayed to the user describing the + /// purpose of this payment. + pub fn message(&self) -> Option<&String> { + self.message.as_ref() + } + + /// A list of other arbitrary key/value pairs associated with this payment. + pub fn other_params(&self) -> &[(String, String)] { + self.other_params.as_ref() + } + + /// A utility for use in tests to help check round-trip serialization properties. + #[cfg(any(test, feature = "test-dependencies"))] + pub(crate) fn normalize(&mut self) { + self.other_params.sort(); + } } /// A ZIP321 transaction request. @@ -121,57 +220,88 @@ impl Payment { /// When constructing a transaction in response to such a request, /// a separate output should be added to the transaction for each /// payment value in the request. -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TransactionRequest { - payments: Vec, + payments: BTreeMap, } impl TransactionRequest { /// Constructs a new empty transaction request. pub fn empty() -> Self { - Self { payments: vec![] } + Self { + payments: BTreeMap::new(), + } } - /// Constructs a new transaction request that obeys the ZIP-321 invariants + /// Constructs a new transaction request that obeys the ZIP-321 invariants. pub fn new(payments: Vec) -> Result { - let request = TransactionRequest { payments }; + // Payment indices are limited to 4 digits + if payments.len() > 9999 { + return Err(Zip321Error::TooManyPayments(payments.len())); + } + + let request = TransactionRequest { + payments: payments.into_iter().enumerate().collect(), + }; // Enforce validity requirements. if !request.payments.is_empty() { - // It doesn't matter what params we use here, as none of the validity - // requirements depend on them. - let params = consensus::MAIN_NETWORK; - TransactionRequest::from_uri(¶ms, &request.to_uri(¶ms).unwrap())?; + TransactionRequest::from_uri(&request.to_uri())?; } Ok(request) } - /// Returns the slice of payments that make up this request. - pub fn payments(&self) -> &[Payment] { - &self.payments[..] + /// Constructs a new transaction request from the provided map from payment + /// index to payment. + /// + /// Payment index 0 will be mapped to the empty payment index. + pub fn from_indexed( + payments: BTreeMap, + ) -> Result { + if let Some(k) = payments.keys().find(|k| **k > 9999) { + // This is not quite the correct error, but close enough. + return Err(Zip321Error::TooManyPayments(*k)); + } + + Ok(TransactionRequest { payments }) + } + + /// Returns the map of payments that make up this request. + /// + /// This is a map from payment index to payment. Payment index `0` is used to denote + /// the empty payment index in the returned values. + pub fn payments(&self) -> &BTreeMap { + &self.payments + } + + /// Returns the total value of payments to be made. + /// + /// Returns `Err` in the case of overflow, or if the value is + /// outside the range `0..=MAX_MONEY` zatoshis. + pub fn total(&self) -> Result { + self.payments + .values() + .map(|p| p.amount) + .try_fold(Zatoshis::ZERO, |acc, a| { + (acc + a).ok_or(BalanceError::Overflow) + }) } /// A utility for use in tests to help check round-trip serialization properties. #[cfg(any(test, feature = "test-dependencies"))] - pub(in crate::zip321) fn normalize(&mut self, params: &P) { - for p in &mut self.payments { + pub(crate) fn normalize(&mut self) { + for p in &mut self.payments.values_mut() { p.normalize(); } - - self.payments.sort_by(Payment::compare_normalized(params)); } /// A utility for use in tests to help check round-trip serialization properties. /// by comparing a two transaction requests for equality after normalization. - #[cfg(all(test, feature = "test-dependencies"))] - pub(in crate::zip321) fn normalize_and_eq( - params: &P, - a: &mut TransactionRequest, - b: &mut TransactionRequest, - ) -> bool { - a.normalize(params); - b.normalize(params); + #[cfg(test)] + pub(crate) fn normalize_and_eq(a: &mut TransactionRequest, b: &mut TransactionRequest) -> bool { + a.normalize(); + b.normalize(); a == b } @@ -179,13 +309,13 @@ impl TransactionRequest { /// Convert this request to a URI string. /// /// Returns None if the payment request is empty. - pub fn to_uri(&self, params: &P) -> Option { + pub fn to_uri(&self) -> String { fn payment_params( payment: &Payment, payment_index: Option, ) -> impl IntoIterator + '_ { std::iter::empty() - .chain(render::amount_param(payment.amount, payment_index)) + .chain(Some(render::amount_param(payment.amount, payment_index))) .chain( payment .memo @@ -212,42 +342,44 @@ impl TransactionRequest { ) } - match &self.payments[..] { - [] => None, - [payment] => { + match self.payments.len() { + 0 => "zcash:".to_string(), + 1 if *self.payments.iter().next().unwrap().0 == 0 => { + let (_, payment) = self.payments.iter().next().unwrap(); let query_params = payment_params(payment, None) .into_iter() .collect::>(); - Some(format!( - "zcash:{}?{}", - payment.recipient_address.encode(params), + format!( + "zcash:{}{}{}", + payment.recipient_address.encode(), + if query_params.is_empty() { "" } else { "?" }, query_params.join("&") - )) + ) } _ => { let query_params = self .payments .iter() - .enumerate() .flat_map(|(i, payment)| { + let idx = if *i == 0 { None } else { Some(*i) }; let primary_address = payment.recipient_address.clone(); std::iter::empty() - .chain(Some(render::addr_param(params, &primary_address, Some(i)))) - .chain(payment_params(payment, Some(i))) + .chain(Some(render::addr_param(&primary_address, idx))) + .chain(payment_params(payment, idx)) }) .collect::>(); - Some(format!("zcash:?{}", query_params.join("&"))) + format!("zcash:?{}", query_params.join("&")) } } } /// Parse the provided URI to a payment request value. - pub fn from_uri(params: &P, uri: &str) -> Result { + pub fn from_uri(uri: &str) -> Result { // Parse the leading zcash:
- let (rest, primary_addr_param) = - parse::lead_addr(params)(uri).map_err(|e| Zip321Error::ParseError(e.to_string()))?; + let (rest, primary_addr_param) = parse::lead_addr(uri) + .map_err(|e| Zip321Error::ParseError(format!("Error parsing lead address: {}", e)))?; // Parse the remaining parameters as an undifferentiated list let (_, xs) = if rest.is_empty() { @@ -255,13 +387,15 @@ impl TransactionRequest { } else { all_consuming(preceded( char('?'), - separated_list0(char('&'), parse::zcashparam(params)), + separated_list0(char('&'), parse::zcashparam), ))(rest) - .map_err(|e| Zip321Error::ParseError(e.to_string()))? + .map_err(|e| { + Zip321Error::ParseError(format!("Error parsing query parameters: {}", e)) + })? }; // Construct sets of payment parameters, keyed by the payment index. - let mut params_by_index: HashMap> = HashMap::new(); + let mut params_by_index: BTreeMap> = BTreeMap::new(); // Add the primary address, if any, to the index. if let Some(p) = primary_addr_param { @@ -288,20 +422,21 @@ impl TransactionRequest { // Build the actual payment values from the index. params_by_index .into_iter() - .map(|(i, params)| parse::to_payment(params, i)) - .collect::, _>>() + .map(|(i, params)| parse::to_payment(params, i).map(|payment| (i, payment))) + .collect::, _>>() .map(|payments| TransactionRequest { payments }) } } mod render { use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; - - use zcash_primitives::{ - consensus, transaction::components::amount::COIN, transaction::components::Amount, + use zcash_address::ZcashAddress; + use zcash_protocol::{ + memo::MemoBytes, + value::{Zatoshis, COIN}, }; - use super::{memo_to_base64, MemoBytes, RecipientAddress}; + use super::memo_to_base64; /// The set of ASCII characters that must be percent-encoded according /// to the definition of ZIP 321. This is the complement of the subset of @@ -341,36 +476,28 @@ mod render { /// Constructs an "address" key/value pair containing the encoded recipient address /// at the specified parameter index. - pub fn addr_param( - params: &P, - addr: &RecipientAddress, - idx: Option, - ) -> String { - format!("address{}={}", param_index(idx), addr.encode(params)) + pub fn addr_param(addr: &ZcashAddress, idx: Option) -> String { + format!("address{}={}", param_index(idx), addr.encode()) } - /// Converts an [`Amount`] value to a correctly formatted decimal ZEC + /// Converts a [`Zatoshis`] value to a correctly formatted decimal ZEC /// value for inclusion in a ZIP 321 URI. - pub fn amount_str(amount: Amount) -> Option { - if amount.is_positive() { - let coins = i64::from(amount) / COIN; - let zats = i64::from(amount) % COIN; - Some(if zats == 0 { - format!("{}", coins) - } else { - format!("{}.{:0>8}", coins, zats) - .trim_end_matches('0') - .to_string() - }) + pub fn amount_str(amount: Zatoshis) -> String { + let coins = u64::from(amount) / COIN; + let zats = u64::from(amount) % COIN; + if zats == 0 { + format!("{}", coins) } else { - None + format!("{}.{:0>8}", coins, zats) + .trim_end_matches('0') + .to_string() } } /// Constructs an "amount" key/value pair containing the encoded ZEC amount /// at the specified parameter index. - pub fn amount_param(amount: Amount, idx: Option) -> Option { - amount_str(amount).map(|s| format!("amount{}={}", param_index(idx), s)) + pub fn amount_param(amount: Zatoshis, idx: Option) -> String { + format!("amount{}={}", param_index(idx), amount_str(amount)) } /// Constructs a "memo" key/value pair containing the base64URI-encoded memo @@ -397,33 +524,48 @@ mod parse { use nom::{ bytes::complete::{tag, take_till}, character::complete::{alpha1, char, digit0, digit1, one_of}, - combinator::{map_opt, map_res, opt, recognize}, + combinator::{all_consuming, map_opt, map_res, opt, recognize}, sequence::{preceded, separated_pair, tuple}, AsChar, IResult, InputTakeAtPosition, }; use percent_encoding::percent_decode; - use zcash_primitives::{ - consensus, transaction::components::amount::COIN, transaction::components::Amount, + use zcash_address::ZcashAddress; + use zcash_protocol::value::BalanceError; + use zcash_protocol::{ + memo::MemoBytes, + value::{Zatoshis, COIN}, }; - use crate::address::RecipientAddress; - - use super::{memo_from_base64, MemoBytes, Payment, Zip321Error}; + use super::{memo_from_base64, Payment, Zip321Error}; /// A data type that defines the possible parameter types which may occur within a /// ZIP 321 URI. - #[derive(Debug, PartialEq, Eq)] + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Param { - Addr(Box), - Amount(Amount), - Memo(MemoBytes), + Addr(Box), + Amount(Zatoshis), + Memo(Box), Label(String), Message(String), Other(String, String), } + impl Param { + /// Returns the name of the parameter from which this value was parsed. + pub fn name(&self) -> String { + match self { + Param::Addr(_) => "address".to_owned(), + Param::Amount(_) => "amount".to_owned(), + Param::Memo(_) => "memo".to_owned(), + Param::Label(_) => "label".to_owned(), + Param::Message(_) => "message".to_owned(), + Param::Other(name, _) => name.clone(), + } + } + } + /// A [`Param`] value with its associated index. - #[derive(Debug)] + #[derive(Debug, Clone, PartialEq, Eq)] pub struct IndexedParam { pub param: Param, pub payment_index: usize, @@ -462,7 +604,7 @@ mod parse { let mut payment = Payment { recipient_address: *addr.ok_or(Zip321Error::RecipientMissing(i))?, - amount: Amount::zero(), + amount: Zatoshis::ZERO, memo: None, label: None, message: None, @@ -472,15 +614,13 @@ mod parse { for v in vs { match v { Param::Amount(a) => payment.amount = a, - Param::Memo(m) => match payment.recipient_address { - RecipientAddress::Shielded(_) | RecipientAddress::Unified(_) => { - payment.memo = Some(m) - } - RecipientAddress::Transparent(_) => { - return Err(Zip321Error::TransparentMemo(i)) + Param::Memo(m) => { + if payment.recipient_address.can_receive_memo() { + payment.memo = Some(*m); + } else { + return Err(Zip321Error::TransparentMemo(i)); } - }, - + } Param::Label(m) => payment.label = Some(m), Param::Message(m) => payment.message = Some(m), Param::Other(n, m) => payment.other_params.push((n, m)), @@ -492,40 +632,34 @@ mod parse { } /// Parses and consumes the leading "zcash:\[address\]" from a ZIP 321 URI. - pub fn lead_addr( - params: &P, - ) -> impl Fn(&str) -> IResult<&str, Option> + '_ { - move |input: &str| { - map_opt( - preceded(tag("zcash:"), take_till(|c| c == '?')), - |addr_str: &str| { - if addr_str.is_empty() { - Some(None) // no address is ok, so wrap in `Some` - } else { - // `decode` returns `None` on error, which we want to - // then cause `map_opt` to fail. - RecipientAddress::decode(params, addr_str).map(|a| { + pub fn lead_addr(input: &str) -> IResult<&str, Option> { + map_opt( + preceded(tag("zcash:"), take_till(|c| c == '?')), + |addr_str: &str| { + if addr_str.is_empty() { + Some(None) // no address is ok, so wrap in `Some` + } else { + // `try_from_encoded(..).ok()` returns `None` on error, which we want to then + // cause `map_opt` to fail. + ZcashAddress::try_from_encoded(addr_str) + .map(|a| { Some(IndexedParam { param: Param::Addr(Box::new(a)), payment_index: 0, }) }) - } - }, - )(input) - } + .ok() + } + }, + )(input) } - /// The primary parser for = query-string parameter pair. - pub fn zcashparam( - params: &P, - ) -> impl Fn(&str) -> IResult<&str, IndexedParam> + '_ { - move |input| { - map_res( - separated_pair(indexed_name, char('='), recognize(qchars)), - move |r| to_indexed_param(params, r), - )(input) - } + /// The primary parser for `name=value` query-string parameter pairs. + pub fn zcashparam(input: &str) -> IResult<&str, IndexedParam> { + map_res( + separated_pair(indexed_name, char('='), recognize(qchars)), + to_indexed_param, + )(input) } /// Extension for the `alphanumeric0` parser which extends that parser @@ -567,59 +701,55 @@ mod parse { } /// Parses a value in decimal ZEC. - pub fn parse_amount(input: &str) -> IResult<&str, Amount> { + pub fn parse_amount(input: &str) -> IResult<&str, Zatoshis> { map_res( - tuple(( + all_consuming(tuple(( digit1, opt(preceded( char('.'), - map_opt(digit0, |s: &str| if s.len() > 8 { None } else { Some(s) }), + map_opt(digit1, |s: &str| if s.len() > 8 { None } else { Some(s) }), )), - )), + ))), |(whole_s, decimal_s): (&str, Option<&str>)| { - let coins: i64 = whole_s + let coins: u64 = whole_s .to_string() - .parse::() + .parse::() .map_err(|e| e.to_string())?; - let zats: i64 = match decimal_s { + let zats: u64 = match decimal_s { Some(d) => format!("{:0<8}", d) - .parse::() + .parse::() .map_err(|e| e.to_string())?, None => 0, }; - if coins >= 21000000 && (coins > 21000000 || zats > 0) { - return Err(format!( - "{} coins exceeds the maximum possible Zcash value.", - coins - )); - } - - let amt = coins * COIN + zats; - - Amount::from_nonnegative_i64(amt) - .map_err(|_| format!("Not a valid zat amount: {}", amt)) + coins + .checked_mul(COIN) + .and_then(|coin_zats| coin_zats.checked_add(zats)) + .ok_or(BalanceError::Overflow) + .and_then(Zatoshis::from_u64) + .map_err(|_| format!("Not a valid zat amount: {}.{}", coins, zats)) }, )(input) } - fn to_indexed_param<'a, P: consensus::Parameters>( - params: &'a P, + fn to_indexed_param( ((name, iopt), value): ((&str, Option<&str>), &str), ) -> Result { let param = match name { - "address" => RecipientAddress::decode(params, value) + "address" => ZcashAddress::try_from_encoded(value) .map(Box::new) .map(Param::Addr) - .ok_or(format!( - "Could not interpret {} as a valid Zcash address.", - value - )), + .map_err(|err| { + format!( + "Could not interpret {} as a valid Zcash address: {}", + value, err + ) + }), "amount" => parse_amount(value) - .map(|(_, a)| Param::Amount(a)) - .map_err(|e| e.to_string()), + .map_err(|e| e.to_string()) + .map(|(_, a)| Param::Amount(a)), "label" => percent_decode(value.as_bytes()) .decode_utf8() @@ -632,6 +762,7 @@ mod parse { .map_err(|e| e.to_string()), "memo" => memo_from_base64(value) + .map(Box::new) .map(Param::Memo) .map_err(|e| format!("Decoded memo was invalid: {:?}", e)), @@ -657,40 +788,17 @@ mod parse { } } -#[cfg(feature = "test-dependencies")] +#[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::collection::btree_map; use proptest::collection::vec; use proptest::option; - use proptest::prelude::{any, prop_compose, prop_oneof}; - use proptest::strategy::Strategy; - use zcash_primitives::{ - consensus::TEST_NETWORK, legacy::testing::arb_transparent_addr, - sapling::testing::arb_payment_address, - transaction::components::amount::testing::arb_nonnegative_amount, - }; + use proptest::prelude::{any, prop_compose}; - use crate::address::{RecipientAddress, UnifiedAddress}; + use zcash_address::testing::arb_address; + use zcash_protocol::{consensus::NetworkType, value::testing::arb_zatoshis}; use super::{MemoBytes, Payment, TransactionRequest}; - - prop_compose! { - fn arb_unified_addr()( - sapling in arb_payment_address(), - transparent in option::of(arb_transparent_addr()), - ) -> UnifiedAddress { - UnifiedAddress::from_receivers(None, Some(sapling), transparent).unwrap() - } - } - - pub fn arb_addr() -> impl Strategy { - prop_oneof![ - arb_payment_address().prop_map(RecipientAddress::Shielded), - arb_transparent_addr().prop_map(RecipientAddress::Transparent), - arb_unified_addr().prop_map(RecipientAddress::Unified), - ] - } - pub const VALID_PARAMNAME: &str = "[a-zA-Z][a-zA-Z0-9+-]*"; prop_compose! { @@ -700,25 +808,20 @@ pub mod testing { } prop_compose! { - pub fn arb_zip321_payment()( - recipient_address in arb_addr(), - amount in arb_nonnegative_amount(), + pub fn arb_zip321_payment(network: NetworkType)( + recipient_address in arb_address(network), + amount in arb_zatoshis(), memo in option::of(arb_valid_memo()), message in option::of(any::()), label in option::of(any::()), // prevent duplicates by generating a set rather than a vec other_params in btree_map(VALID_PARAMNAME, any::(), 0..3), - ) -> Payment { - - let is_shielded = match recipient_address { - RecipientAddress::Transparent(_) => false, - RecipientAddress::Shielded(_) | RecipientAddress::Unified(_) => true, - }; - + ) -> Payment { + let memo = memo.filter(|_| recipient_address.can_receive_memo()); Payment { recipient_address, amount, - memo: memo.filter(|_| is_shielded), + memo, label, message, other_params: other_params.into_iter().collect(), @@ -727,64 +830,67 @@ pub mod testing { } prop_compose! { - pub fn arb_zip321_request()(payments in vec(arb_zip321_payment(), 1..10)) -> TransactionRequest { - let mut req = TransactionRequest { payments }; - req.normalize(&TEST_NETWORK); // just to make test comparisons easier + pub fn arb_zip321_request(network: NetworkType)( + payments in btree_map(0usize..10000, arb_zip321_payment(network), 1..10) + ) -> TransactionRequest { + let mut req = TransactionRequest::from_indexed(payments).unwrap(); + req.normalize(); // just to make test comparisons easier + req + } + } + + prop_compose! { + pub fn arb_zip321_request_sequential(network: NetworkType)( + payments in vec(arb_zip321_payment(network), 1..10) + ) -> TransactionRequest { + let mut req = TransactionRequest::new(payments).unwrap(); + req.normalize(); // just to make test comparisons easier req } } prop_compose! { - pub fn arb_zip321_uri()(req in arb_zip321_request()) -> String { - req.to_uri(&TEST_NETWORK).unwrap() + pub fn arb_zip321_uri(network: NetworkType)(req in arb_zip321_request(network)) -> String { + req.to_uri() } } prop_compose! { - pub fn arb_addr_str()(addr in arb_addr()) -> String { - addr.encode(&TEST_NETWORK) + pub fn arb_addr_str(network: NetworkType)( + recipient_address in arb_address(network) + ) -> String { + recipient_address.encode() } } } #[cfg(test)] mod tests { + use proptest::prelude::{any, proptest}; use std::str::FromStr; - use zcash_primitives::{ - consensus::{Parameters, TEST_NETWORK}, - memo::Memo, - transaction::components::Amount, + + use zcash_address::{testing::arb_address, ZcashAddress}; + use zcash_protocol::{ + consensus::NetworkType, + memo::{Memo, MemoBytes}, + value::{testing::arb_zatoshis, Zatoshis}, }; - use crate::address::RecipientAddress; + #[cfg(feature = "local-consensus")] + use zcash_protocol::{local_consensus::LocalNetwork, BlockHeight}; use super::{ memo_from_base64, memo_to_base64, parse::{parse_amount, zcashparam, Param}, - render::amount_str, - MemoBytes, Payment, TransactionRequest, - }; - use crate::encoding::decode_payment_address; - - #[cfg(all(test, feature = "test-dependencies"))] - use proptest::prelude::{any, proptest}; - - #[cfg(all(test, feature = "test-dependencies"))] - use zcash_primitives::transaction::components::amount::testing::arb_nonnegative_amount; - - #[cfg(all(test, feature = "test-dependencies"))] - use super::{ - render::{memo_param, str_param}, - testing::{arb_addr, arb_addr_str, arb_valid_memo, arb_zip321_request, arb_zip321_uri}, + render::{amount_str, memo_param, str_param}, + testing::{arb_addr_str, arb_valid_memo, arb_zip321_request, arb_zip321_uri}, + Payment, TransactionRequest, }; fn check_roundtrip(req: TransactionRequest) { - if let Some(req_uri) = req.to_uri(&TEST_NETWORK) { - let parsed = TransactionRequest::from_uri(&TEST_NETWORK, &req_uri).unwrap(); - assert_eq!(parsed, req); - } else { - panic!("Generated invalid payment request: {:?}", req); - } + let req_uri = req.to_uri(); + let parsed = TransactionRequest::from_uri(&req_uri).unwrap(); + assert_eq!(parsed, req); } #[test] @@ -792,8 +898,8 @@ mod tests { let amounts = vec![1u64, 1000u64, 100000u64, 100000000u64, 100000000000u64]; for amt_u64 in amounts { - let amt = Amount::from_u64(amt_u64).unwrap(); - let amt_str = amount_str(amt).unwrap(); + let amt = Zatoshis::const_from_u64(amt_u64); + let amt_str = amount_str(amt); assert_eq!(amt, parse_amount(&amt_str).unwrap().1); } } @@ -802,27 +908,27 @@ mod tests { fn test_zip321_parse_empty_message() { let fragment = "message="; - let result = zcashparam(&TEST_NETWORK)(fragment).unwrap().1.param; + let result = zcashparam(fragment).unwrap().1.param; assert_eq!(result, Param::Message("".to_string())); } #[test] fn test_zip321_parse_simple() { let uri = "zcash:ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k?amount=3768769.02796286&message="; - let parse_result = TransactionRequest::from_uri(&TEST_NETWORK, uri).unwrap(); + let parse_result = TransactionRequest::from_uri(uri).unwrap(); - let expected = TransactionRequest { - payments: vec![ + let expected = TransactionRequest::new( + vec![ Payment { - recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), - amount: Amount::from_u64(376876902796286).unwrap(), + recipient_address: ZcashAddress::try_from_encoded("ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap(), + amount: Zatoshis::const_from_u64(376876902796286), memo: None, label: None, message: Some("".to_string()), other_params: vec![], } ] - }; + ).unwrap(); assert_eq!(parse_result, expected); } @@ -830,38 +936,38 @@ mod tests { #[test] fn test_zip321_parse_no_query_params() { let uri = "zcash:ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k"; - let parse_result = TransactionRequest::from_uri(&TEST_NETWORK, uri).unwrap(); + let parse_result = TransactionRequest::from_uri(uri).unwrap(); - let expected = TransactionRequest { - payments: vec![ + let expected = TransactionRequest::new( + vec![ Payment { - recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), - amount: Amount::from_u64(0).unwrap(), + recipient_address: ZcashAddress::try_from_encoded("ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap(), + amount: Zatoshis::ZERO, memo: None, label: None, message: None, other_params: vec![], } ] - }; + ).unwrap(); assert_eq!(parse_result, expected); } #[test] fn test_zip321_roundtrip_empty_message() { - let req = TransactionRequest { - payments: vec![ + let req = TransactionRequest::new( + vec![ Payment { - recipient_address: RecipientAddress::Shielded(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), - amount: Amount::from_u64(0).unwrap(), + recipient_address: ZcashAddress::try_from_encoded("ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap(), + amount: Zatoshis::ZERO, memo: None, label: None, message: Some("".to_string()), other_params: vec![] } ] - }; + ).unwrap(); check_roundtrip(req); } @@ -887,125 +993,163 @@ mod tests { #[test] fn test_zip321_spec_valid_examples() { + let valid_0 = "zcash:"; + let v0r = TransactionRequest::from_uri(valid_0).unwrap(); + assert!(v0r.payments.is_empty()); + + let valid_0 = "zcash:?"; + let v0r = TransactionRequest::from_uri(valid_0).unwrap(); + assert!(v0r.payments.is_empty()); + let valid_1 = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=1&memo=VGhpcyBpcyBhIHNpbXBsZSBtZW1vLg&message=Thank%20you%20for%20your%20purchase"; - let v1r = TransactionRequest::from_uri(&TEST_NETWORK, valid_1).unwrap(); + let v1r = TransactionRequest::from_uri(valid_1).unwrap(); assert_eq!( - v1r.payments.get(0).map(|p| p.amount), - Some(Amount::from_u64(100000000).unwrap()) + v1r.payments.get(&0).map(|p| p.amount), + Some(Zatoshis::const_from_u64(100000000)) ); let valid_2 = "zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=123.456&address.1=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez&amount.1=0.789&memo.1=VGhpcyBpcyBhIHVuaWNvZGUgbWVtbyDinKjwn6aE8J-PhvCfjok"; - let mut v2r = TransactionRequest::from_uri(&TEST_NETWORK, valid_2).unwrap(); - v2r.normalize(&TEST_NETWORK); + let mut v2r = TransactionRequest::from_uri(valid_2).unwrap(); + v2r.normalize(); assert_eq!( - v2r.payments.get(0).map(|p| p.amount), - Some(Amount::from_u64(12345600000).unwrap()) + v2r.payments.get(&0).map(|p| p.amount), + Some(Zatoshis::const_from_u64(12345600000)) ); assert_eq!( - v2r.payments.get(1).map(|p| p.amount), - Some(Amount::from_u64(78900000).unwrap()) + v2r.payments.get(&1).map(|p| p.amount), + Some(Zatoshis::const_from_u64(78900000)) ); // valid; amount just less than MAX_MONEY // 20999999.99999999 let valid_3 = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=20999999.99999999"; - let v3r = TransactionRequest::from_uri(&TEST_NETWORK, valid_3).unwrap(); + let v3r = TransactionRequest::from_uri(valid_3).unwrap(); assert_eq!( - v3r.payments.get(0).map(|p| p.amount), - Some(Amount::from_u64(2099999999999999u64).unwrap()) + v3r.payments.get(&0).map(|p| p.amount), + Some(Zatoshis::const_from_u64(2099999999999999)) ); // valid; MAX_MONEY // 21000000 let valid_4 = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=21000000"; - let v4r = TransactionRequest::from_uri(&TEST_NETWORK, valid_4).unwrap(); + let v4r = TransactionRequest::from_uri(valid_4).unwrap(); assert_eq!( - v4r.payments.get(0).map(|p| p.amount), - Some(Amount::from_u64(2100000000000000u64).unwrap()) + v4r.payments.get(&0).map(|p| p.amount), + Some(Zatoshis::const_from_u64(2100000000000000)) + ); + } + + #[cfg(feature = "local-consensus")] + #[test] + fn test_zip321_spec_regtest_valid_examples() { + let params = LocalNetwork { + overwinter: Some(BlockHeight::from_u32(1)), + sapling: Some(BlockHeight::from_u32(1)), + blossom: Some(BlockHeight::from_u32(1)), + heartwood: Some(BlockHeight::from_u32(1)), + canopy: Some(BlockHeight::from_u32(1)), + nu5: Some(BlockHeight::from_u32(1)), + nu6: Some(BlockHeight::from_u32(1)), + z_future: Some(BlockHeight::from_u32(1)), + }; + let valid_1 = "zcash:zregtestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle7505hlz3?amount=1&memo=VGhpcyBpcyBhIHNpbXBsZSBtZW1vLg&message=Thank%20you%20for%20your%20purchase"; + let v1r = TransactionRequest::from_uri(¶ms, valid_1).unwrap(); + assert_eq!( + v1r.payments.get(&0).map(|p| p.amount), + Some(Zatoshis::const_from_u64(100000000)) ); } #[test] fn test_zip321_spec_invalid_examples() { + // invalid; empty string + let invalid_0 = ""; + let i0r = TransactionRequest::from_uri(invalid_0); + assert!(i0r.is_err()); + // invalid; missing `address=` let invalid_1 = "zcash:?amount=3491405.05201255&address.1=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez&amount.1=5740296.87793245"; - let i1r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_1); + let i1r = TransactionRequest::from_uri(invalid_1); assert!(i1r.is_err()); // invalid; missing `address.1=` let invalid_2 = "zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=1&amount.1=2&address.2=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez"; - let i2r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_2); + let i2r = TransactionRequest::from_uri(invalid_2); assert!(i2r.is_err()); // invalid; `address.0=` and `amount.0=` are not permitted (leading 0s). let invalid_3 = "zcash:?address.0=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez&amount.0=2"; - let i3r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_3); + let i3r = TransactionRequest::from_uri(invalid_3); assert!(i3r.is_err()); // invalid; duplicate `amount=` field let invalid_4 = "zcash:?amount=1.234&amount=2.345&address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU"; - let i4r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_4); + let i4r = TransactionRequest::from_uri(invalid_4); assert!(i4r.is_err()); // invalid; duplicate `amount.1=` field let invalid_5 = "zcash:?amount.1=1.234&amount.1=2.345&address.1=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU"; - let i5r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_5); + let i5r = TransactionRequest::from_uri(invalid_5); assert!(i5r.is_err()); //invalid; memo associated with t-addr let invalid_6 = "zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=123.456&memo=eyAia2V5IjogIlRoaXMgaXMgYSBKU09OLXN0cnVjdHVyZWQgbWVtby4iIH0&address.1=ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez&amount.1=0.789&memo.1=VGhpcyBpcyBhIHVuaWNvZGUgbWVtbyDinKjwn6aE8J-PhvCfjok"; - let i6r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_6); + let i6r = TransactionRequest::from_uri(invalid_6); assert!(i6r.is_err()); // invalid; amount component exceeds an i64 // 9223372036854775808 = i64::MAX + 1 let invalid_7 = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=9223372036854775808"; - let i7r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_7); + let i7r = TransactionRequest::from_uri(invalid_7); assert!(i7r.is_err()); // invalid; amount component wraps into a valid small positive i64 // 18446744073709551624 let invalid_7a = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=18446744073709551624"; - let i7ar = TransactionRequest::from_uri(&TEST_NETWORK, invalid_7a); + let i7ar = TransactionRequest::from_uri(invalid_7a); assert!(i7ar.is_err()); // invalid; amount component is MAX_MONEY // 21000000.00000001 let invalid_8 = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=21000000.00000001"; - let i8r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_8); + let i8r = TransactionRequest::from_uri(invalid_8); assert!(i8r.is_err()); // invalid; negative amount let invalid_9 = "zcash:ztestsapling10yy2ex5dcqkclhc7z7yrnjq2z6feyjad56ptwlfgmy77dmaqqrl9gyhprdx59qgmsnyfska2kez?amount=-1"; - let i9r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_9); + let i9r = TransactionRequest::from_uri(invalid_9); assert!(i9r.is_err()); // invalid; parameter index too large let invalid_10 = "zcash:?amount.10000=1.23&address.10000=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU"; - let i10r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_10); + let i10r = TransactionRequest::from_uri(invalid_10); assert!(i10r.is_err()); + + // invalid: bad amount format + let invalid_11 = "zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=123."; + let i11r = TransactionRequest::from_uri(invalid_11); + assert!(i11r.is_err()); } - #[cfg(all(test, feature = "test-dependencies"))] proptest! { #[test] - fn prop_zip321_roundtrip_address(addr in arb_addr()) { - let a = addr.encode(&TEST_NETWORK); - assert_eq!(RecipientAddress::decode(&TEST_NETWORK, &a), Some(addr)); + fn prop_zip321_roundtrip_address(addr in arb_address(NetworkType::Test)) { + let a = addr.encode(); + assert_eq!(ZcashAddress::try_from_encoded(&a), Ok(addr)); } #[test] - fn prop_zip321_roundtrip_address_str(a in arb_addr_str()) { - let addr = RecipientAddress::decode(&TEST_NETWORK, &a).unwrap(); - assert_eq!(addr.encode(&TEST_NETWORK), a); + fn prop_zip321_roundtrip_address_str(a in arb_addr_str(NetworkType::Test)) { + let addr = ZcashAddress::try_from_encoded(&a).unwrap(); + assert_eq!(addr.encode(), a); } #[test] - fn prop_zip321_roundtrip_amount(amt in arb_nonnegative_amount()) { - let amt_str = amount_str(amt).unwrap(); + fn prop_zip321_roundtrip_amount(amt in arb_zatoshis()) { + let amt_str = amount_str(amt); assert_eq!(amt, parse_amount(&amt_str).unwrap().1); } @@ -1013,7 +1157,7 @@ mod tests { fn prop_zip321_roundtrip_str_param( message in any::(), i in proptest::option::of(0usize..2000)) { let fragment = str_param("message", &message, i); - let (rest, iparam) = zcashparam(&TEST_NETWORK)(&fragment).unwrap(); + let (rest, iparam) = zcashparam(&fragment).unwrap(); assert_eq!(rest, ""); assert_eq!(iparam.param, Param::Message(message)); assert_eq!(iparam.payment_index, i.unwrap_or(0)); @@ -1023,28 +1167,25 @@ mod tests { fn prop_zip321_roundtrip_memo_param( memo in arb_valid_memo(), i in proptest::option::of(0usize..2000)) { let fragment = memo_param(&memo, i); - let (rest, iparam) = zcashparam(&TEST_NETWORK)(&fragment).unwrap(); + let (rest, iparam) = zcashparam(&fragment).unwrap(); assert_eq!(rest, ""); - assert_eq!(iparam.param, Param::Memo(memo)); + assert_eq!(iparam.param, Param::Memo(Box::new(memo))); assert_eq!(iparam.payment_index, i.unwrap_or(0)); } #[test] - fn prop_zip321_roundtrip_request(mut req in arb_zip321_request()) { - if let Some(req_uri) = req.to_uri(&TEST_NETWORK) { - let mut parsed = TransactionRequest::from_uri(&TEST_NETWORK, &req_uri).unwrap(); - assert!(TransactionRequest::normalize_and_eq(&TEST_NETWORK, &mut parsed, &mut req)); - } else { - panic!("Generated invalid payment request: {:?}", req); - } + fn prop_zip321_roundtrip_request(mut req in arb_zip321_request(NetworkType::Test)) { + let req_uri = req.to_uri(); + let mut parsed = TransactionRequest::from_uri(&req_uri).unwrap(); + assert!(TransactionRequest::normalize_and_eq(&mut parsed, &mut req)); } #[test] - fn prop_zip321_roundtrip_uri(uri in arb_zip321_uri()) { - let mut parsed = TransactionRequest::from_uri(&TEST_NETWORK, &uri).unwrap(); - parsed.normalize(&TEST_NETWORK); - let serialized = parsed.to_uri(&TEST_NETWORK); - assert_eq!(serialized, Some(uri)) + fn prop_zip321_roundtrip_uri(uri in arb_zip321_uri(NetworkType::Test)) { + let mut parsed = TransactionRequest::from_uri(&uri).unwrap(); + parsed.normalize(); + let serialized = parsed.to_uri(); + assert_eq!(serialized, uri) } } } diff --git a/deny.toml b/deny.toml new file mode 100644 index 0000000000..d9b2c8f95a --- /dev/null +++ b/deny.toml @@ -0,0 +1,65 @@ +# Configuration file for cargo-deny + +[graph] +targets = [ + # Targets used by zcashd + { triple = "aarch64-unknown-linux-gnu" }, + { triple = "x86_64-apple-darwin" }, + { triple = "x86_64-pc-windows-gnu" }, + { triple = "x86_64-unknown-freebsd" }, + { triple = "x86_64-unknown-linux-gnu" }, + # Targets used by zcash-android-wallet-sdk + { triple = "aarch64-linux-android" }, + { triple = "armv7-linux-androideabi" }, + { triple = "i686-linux-android" }, + { triple = "x86_64-linux-android" }, + # Targets used by zcash-swift-wallet-sdk + { triple = "aarch64-apple-darwin" }, + { triple = "aarch64-apple-ios" }, + { triple = "aarch64-apple-ios-sim" }, + { triple = "x86_64-apple-darwin" }, + { triple = "x86_64-apple-ios" }, +] +all-features = true +exclude-dev = true + +[licenses] +version = 2 +allow = [ + "Apache-2.0", + "MIT", +] +exceptions = [ + { name = "arrayref", allow = ["BSD-2-Clause"] }, + { name = "async_executors", allow = ["Unlicense"] }, + { name = "bounded-vec-deque", allow = ["BSD-3-Clause"] }, + { name = "coarsetime", allow = ["ISC"] }, + { name = "curve25519-dalek", allow = ["BSD-3-Clause"] }, + { name = "ed25519-dalek", allow = ["BSD-3-Clause"] }, + { name = "inotify", allow = ["ISC"] }, + { name = "inotify-sys", allow = ["ISC"] }, + { name = "matchit", allow = ["BSD-3-Clause"] }, + { name = "minreq", allow = ["ISC"] }, + { name = "notify", allow = ["CC0-1.0"] }, + { name = "option-ext", allow = ["MPL-2.0"] }, + { name = "priority-queue", allow = ["MPL-2.0"] }, + { name = "ring", allow = ["LicenseRef-ring"] }, + { name = "rustls-webpki", allow = ["ISC"] }, + { name = "secp256k1", allow = ["CC0-1.0"] }, + { name = "secp256k1-sys", allow = ["CC0-1.0"] }, + { name = "simple_asn1", allow = ["ISC"] }, + { name = "slotmap", allow = ["Zlib"] }, + { name = "subtle", allow = ["BSD-3-Clause"] }, + { name = "tinystr", allow = ["Unicode-3.0"] }, + { name = "unicode-ident", allow = ["Unicode-DFS-2016"] }, + { name = "untrusted", allow = ["ISC"] }, + { name = "webpki-roots", allow = ["MPL-2.0"] }, + { name = "x25519-dalek", allow = ["BSD-3-Clause"] }, +] + +[[licenses.clarify]] +name = "ring" +expression = "LicenseRef-ring" +license-files = [ + { path = "LICENSE", hash = 0xbd0eed23 }, +] diff --git a/devtools/Cargo.toml b/devtools/Cargo.toml new file mode 100644 index 0000000000..417404ed67 --- /dev/null +++ b/devtools/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "devtools" +version = "0.0.0" +edition.workspace = true +license.workspace = true +publish = false + +[dependencies] +# General tool dependencies +gumdrop = "0.8" + +# General Zcash dependencies +bech32.workspace = true +bip0039 = "0.11" +blake2b_simd.workspace = true +equihash.workspace = true +group.workspace = true +sha2.workspace = true +zcash_address.workspace = true +zcash_client_backend ={ workspace = true, features = ["lightwalletd-tonic-transport"] } +zcash_encoding.workspace = true +zcash_keys = { workspace = true, features = ["unstable"] } +zcash_note_encryption.workspace = true +zcash_primitives = { workspace = true, features = ["transparent-inputs"] } +zcash_proofs = { workspace = true, features = ["directories"] } +zcash_protocol = {workspace = true, features = ["local-consensus"] } + +# Transparent +secp256k1.workspace = true + +# Sprout +ed25519-zebra = "4" + +# Sapling +bellman.workspace = true +jubjub.workspace = true +sapling.workspace = true + +# Orchard +orchard.workspace = true + +# zcash-inspect tool +anyhow = "1" +hex.workspace = true +lazy_static.workspace = true +secrecy.workspace = true +serde.workspace = true +serde_json.workspace = true +tonic = { workspace = true, features = ["gzip", "tls-webpki-roots"] } +tokio = { workspace = true, features = ["macros", "rt-multi-thread"] } +uint = "0.9" + +[[bin]] +name = "zcash-inspect" +path = "src/bin/inspect/main.rs" diff --git a/devtools/src/bin/inspect/address.rs b/devtools/src/bin/inspect/address.rs new file mode 100644 index 0000000000..1c5aca0333 --- /dev/null +++ b/devtools/src/bin/inspect/address.rs @@ -0,0 +1,164 @@ +use zcash_address::{ + unified::{self, Container, Encoding}, + ConversionError, Network, ToAddress, ZcashAddress, +}; + +#[allow(dead_code)] +enum AddressKind { + Sprout([u8; 64]), + Sapling([u8; 43]), + Unified(unified::Address), + P2pkh([u8; 20]), + P2sh([u8; 20]), + Tex([u8; 20]), +} + +struct Address { + net: Network, + kind: AddressKind, +} + +impl zcash_address::TryFromAddress for Address { + type Error = (); + + fn try_from_sprout(net: Network, data: [u8; 64]) -> Result> { + Ok(Address { + net, + kind: AddressKind::Sprout(data), + }) + } + + fn try_from_sapling( + net: Network, + data: [u8; 43], + ) -> Result> { + Ok(Address { + net, + kind: AddressKind::Sapling(data), + }) + } + + fn try_from_unified( + net: Network, + data: unified::Address, + ) -> Result> { + Ok(Address { + net, + kind: AddressKind::Unified(data), + }) + } + + fn try_from_transparent_p2pkh( + net: Network, + data: [u8; 20], + ) -> Result> { + Ok(Address { + net, + kind: AddressKind::P2pkh(data), + }) + } + + fn try_from_transparent_p2sh( + net: Network, + data: [u8; 20], + ) -> Result> { + Ok(Address { + net, + kind: AddressKind::P2sh(data), + }) + } + + fn try_from_tex(net: Network, data: [u8; 20]) -> Result> { + Ok(Address { + net, + kind: AddressKind::Tex(data), + }) + } +} + +pub(crate) fn inspect(addr: ZcashAddress) { + eprintln!("Zcash address"); + + match addr.convert::
() { + // TODO: Check for valid internals once we have migrated to a newer zcash_address + // version with custom errors. + Err(_) => unreachable!(), + Ok(addr) => { + eprintln!( + " - Network: {}", + match addr.net { + Network::Main => "main", + Network::Test => "testnet", + Network::Regtest => "regtest", + } + ); + eprintln!( + " - Kind: {}", + match addr.kind { + AddressKind::Sprout(_) => "Sprout", + AddressKind::Sapling(_) => "Sapling", + AddressKind::Unified(_) => "Unified Address", + AddressKind::P2pkh(_) => "Transparent P2PKH", + AddressKind::P2sh(_) => "Transparent P2SH", + AddressKind::Tex(_) => "TEX (ZIP 320)", + } + ); + + match addr.kind { + AddressKind::Unified(ua) => { + eprintln!(" - Receivers:"); + for receiver in ua.items() { + match receiver { + unified::Receiver::Orchard(data) => { + eprintln!( + " - Orchard ({})", + unified::Address::try_from_items(vec![ + unified::Receiver::Orchard(data) + ]) + .unwrap() + .encode(&addr.net) + ); + } + unified::Receiver::Sapling(data) => { + eprintln!( + " - Sapling ({})", + ZcashAddress::from_sapling(addr.net, data) + ); + } + unified::Receiver::P2pkh(data) => { + eprintln!( + " - Transparent P2PKH ({})", + ZcashAddress::from_transparent_p2pkh(addr.net, data) + ); + } + unified::Receiver::P2sh(data) => { + eprintln!( + " - Transparent P2SH ({})", + ZcashAddress::from_transparent_p2sh(addr.net, data) + ); + } + unified::Receiver::Unknown { typecode, data } => { + eprintln!(" - Unknown"); + eprintln!(" - Typecode: {}", typecode); + eprintln!(" - Payload: {}", hex::encode(data)); + } + } + } + } + AddressKind::P2pkh(data) => { + eprintln!( + " - Corresponding TEX: {}", + ZcashAddress::from_tex(addr.net, data), + ); + } + AddressKind::Tex(data) => { + eprintln!( + " - Corresponding P2PKH: {}", + ZcashAddress::from_transparent_p2pkh(addr.net, data), + ); + } + _ => (), + } + } + } +} diff --git a/devtools/src/bin/inspect/block.rs b/devtools/src/bin/inspect/block.rs new file mode 100644 index 0000000000..f6a79b73c7 --- /dev/null +++ b/devtools/src/bin/inspect/block.rs @@ -0,0 +1,401 @@ +// To silence lints in the `uint::construct_uint!` macro. +#![allow(clippy::assign_op_pattern)] +#![allow(clippy::ptr_offset_with_cast)] + +use std::cmp; +use std::convert::{TryFrom, TryInto}; +use std::io::{self, Read}; + +use sha2::{Digest, Sha256}; +use zcash_encoding::Vector; +use zcash_primitives::{ + block::BlockHeader, + consensus::{BlockHeight, BranchId, Network, NetworkUpgrade, Parameters}, + transaction::Transaction, +}; + +use crate::{ + transaction::{extract_height_from_coinbase, is_coinbase}, + Context, ZUint256, +}; + +const MIN_BLOCK_VERSION: i32 = 4; + +uint::construct_uint! { + pub(crate) struct U256(4); +} + +impl U256 { + fn from_compact(compact: u32) -> (Self, bool, bool) { + let size = compact >> 24; + let word = compact & 0x007fffff; + let result = if size <= 3 { + U256::from(word >> (8 * (3 - size))) + } else { + U256::from(word) << (8 * (size - 3)) + }; + ( + result, + word != 0 && (compact & 0x00800000) != 0, + word != 0 + && ((size > 34) || (word > 0xff && size > 33) || (word > 0xffff && size > 32)), + ) + } +} + +pub(crate) trait BlockParams: Parameters { + fn equihash_n(&self) -> u32; + fn equihash_k(&self) -> u32; + fn pow_limit(&self) -> U256; +} + +impl BlockParams for Network { + fn equihash_n(&self) -> u32 { + match self { + Self::MainNetwork | Self::TestNetwork => 200, + } + } + + fn equihash_k(&self) -> u32 { + match self { + Self::MainNetwork | Self::TestNetwork => 9, + } + } + + fn pow_limit(&self) -> U256 { + match self { + Self::MainNetwork => U256::from_big_endian( + &hex::decode("0007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .unwrap(), + ), + Self::TestNetwork => U256::from_big_endian( + &hex::decode("07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") + .unwrap(), + ), + } + } +} + +pub(crate) fn guess_params(header: &BlockHeader) -> Option { + // If the block target falls between the testnet and mainnet powLimit, assume testnet. + let (target, is_negative, did_overflow) = U256::from_compact(header.bits); + if !(is_negative || did_overflow) + && target > Network::MainNetwork.pow_limit() + && target <= Network::TestNetwork.pow_limit() + { + return Some(Network::TestNetwork); + } + + None +} + +fn check_equihash_solution(header: &BlockHeader, params: Network) -> Result<(), equihash::Error> { + let eh_input = { + let mut eh_input = vec![]; + header.write(&mut eh_input).unwrap(); + eh_input.truncate(4 + 32 + 32 + 32 + 4 + 4); + eh_input + }; + equihash::is_valid_solution( + params.equihash_n(), + params.equihash_k(), + &eh_input, + &header.nonce, + &header.solution, + ) +} + +fn check_proof_of_work(header: &BlockHeader, params: Network) -> Result<(), &str> { + let (target, is_negative, did_overflow) = U256::from_compact(header.bits); + let hash = U256::from_little_endian(&header.hash().0); + + if is_negative { + Err("nBits is negative") + } else if target.is_zero() { + Err("target is zero") + } else if did_overflow { + Err("nBits overflowed") + } else if target > params.pow_limit() { + Err("target is larger than powLimit") + } else if hash > target { + Err("block hash larger than target") + } else { + Ok(()) + } +} + +fn derive_block_commitments_hash( + chain_history_root: [u8; 32], + auth_data_root: [u8; 32], +) -> [u8; 32] { + blake2b_simd::Params::new() + .hash_length(32) + .personal(b"ZcashBlockCommit") + .to_state() + .update(&chain_history_root) + .update(&auth_data_root) + .update(&[0; 32]) + .finalize() + .as_bytes() + .try_into() + .unwrap() +} + +pub(crate) struct Block { + header: BlockHeader, + txs: Vec, +} + +impl Block { + pub(crate) fn read(mut reader: R) -> io::Result { + let header = BlockHeader::read(&mut reader)?; + let txs = Vector::read(reader, |r| Transaction::read(r, BranchId::Sprout))?; + + Ok(Block { header, txs }) + } + + pub(crate) fn guess_params(&self) -> Option { + guess_params(&self.header) + } + + fn extract_height(&self) -> Option { + self.txs.first().and_then(extract_height_from_coinbase) + } + + /// Builds the Merkle tree for this block and returns its root. + /// + /// The returned `bool` indicates whether mutation was detected in the Merkle tree (a + /// duplication of transactions in the block leading to an identical Merkle root). + fn build_merkle_root(&self) -> ([u8; 32], bool) { + // Safe upper bound for the number of total nodes. + let mut merkle_tree = Vec::with_capacity(self.txs.len() * 2 + 16); + for tx in &self.txs { + merkle_tree.push(sha2::digest::generic_array::GenericArray::from( + *tx.txid().as_ref(), + )); + } + let mut size = self.txs.len(); + let mut j = 0; + let mut mutated = false; + while size > 1 { + let mut i = 0; + while i < size { + let i2 = cmp::min(i + 1, size - 1); + if i2 == i + 1 && i2 + 1 == size && merkle_tree[j + i] == merkle_tree[j + i2] { + // Two identical hashes at the end of the list at a particular level. + mutated = true; + } + let mut inner_hasher = Sha256::new(); + inner_hasher.update(merkle_tree[j + i]); + inner_hasher.update(merkle_tree[j + i2]); + merkle_tree.push(Sha256::digest(inner_hasher.finalize())); + i += 2; + } + j += size; + size = (size + 1) / 2; + } + ( + merkle_tree + .last() + .copied() + .map(|root| root.into()) + .unwrap_or([0; 32]), + mutated, + ) + } + + fn build_auth_data_root(&self) -> [u8; 32] { + fn next_pow2(x: u64) -> u64 { + // Fails if `x` is greater than `1u64 << 63`, but this can't occur because a + // block can't feasibly contain that many transactions. + 1u64 << (64 - x.saturating_sub(1).leading_zeros()) + } + + let perfect_size = next_pow2(self.txs.len() as u64) as usize; + assert_eq!((perfect_size & (perfect_size - 1)), 0); + let expected_size = cmp::max(perfect_size * 2, 1) - 1; // The total number of nodes. + let mut tree = Vec::with_capacity(expected_size); + + // Add the leaves to the tree. v1-v4 transactions will append empty leaves. + for tx in &self.txs { + tree.push(<[u8; 32]>::try_from(tx.auth_commitment().as_bytes()).unwrap()); + } + // Append empty leaves until we get a perfect tree. + tree.resize(perfect_size, [0; 32]); + + let mut j = 0; + let mut layer_width = perfect_size; + while layer_width > 1 { + let mut i = 0; + while i < layer_width { + tree.push( + blake2b_simd::Params::new() + .hash_length(32) + .personal(b"ZcashAuthDatHash") + .to_state() + .update(&tree[j + i]) + .update(&tree[j + i + 1]) + .finalize() + .as_bytes() + .try_into() + .unwrap(), + ); + i += 2; + } + + // Move to the next layer. + j += layer_width; + layer_width /= 2; + } + + assert_eq!(tree.len(), expected_size); + tree.last().copied().unwrap_or([0; 32]) + } +} + +pub(crate) fn inspect_header(header: &BlockHeader, context: Option) { + eprintln!("Zcash block header"); + inspect_header_inner( + header, + guess_params(header).or_else(|| context.and_then(|c| c.network())), + ); +} + +fn inspect_header_inner(header: &BlockHeader, params: Option) { + eprintln!(" - Hash: {}", header.hash()); + eprintln!(" - Version: {}", header.version); + if header.version < MIN_BLOCK_VERSION { + // zcashd: version-too-low + eprintln!("⚠️ Version too low",); + } + if let Some(params) = params { + if let Err(e) = check_equihash_solution(header, params) { + // zcashd: invalid-solution + eprintln!("⚠️ Invalid Equihash solution: {}", e); + } + if let Err(e) = check_proof_of_work(header, params) { + // zcashd: high-hash + eprintln!("⚠️ Invalid Proof-of-Work: {}", e); + } + } else { + eprintln!("🔎 To check contextual rules, add \"network\" to context (either \"main\" or \"test\")"); + } +} + +pub(crate) fn inspect(block: &Block, context: Option) { + eprintln!("Zcash block"); + let params = block + .guess_params() + .or_else(|| context.as_ref().and_then(|c| c.network())); + inspect_header_inner(&block.header, params); + + let height = match block.txs.len() { + 0 => { + // zcashd: bad-cb-missing + eprintln!("⚠️ Missing coinbase transaction"); + None + } + txs => { + eprintln!(" - {} transaction(s) including coinbase", txs); + + if !is_coinbase(&block.txs[0]) { + // zcashd: bad-cb-missing + eprintln!("⚠️ vtx[0] is not a coinbase transaction"); + None + } else { + let height = block.extract_height(); + match height { + Some(h) => eprintln!(" - Height: {}", h), + // zcashd: bad-cb-height + None => eprintln!("⚠️ No height in coinbase transaction"), + } + height + } + } + }; + + for (i, tx) in block.txs.iter().enumerate().skip(1) { + if is_coinbase(tx) { + // zcashd: bad-cb-multiple + eprintln!("⚠️ vtx[{}] is a coinbase transaction", i); + } + } + + let (merkle_root, merkle_root_mutated) = block.build_merkle_root(); + if merkle_root != block.header.merkle_root { + // zcashd: bad-txnmrklroot + eprintln!("⚠️ header.merkleroot doesn't match transaction Merkle tree root"); + eprintln!(" - merkleroot (calc): {}", ZUint256(merkle_root)); + eprintln!( + " - header.merkleroot: {}", + ZUint256(block.header.merkle_root) + ); + } + if merkle_root_mutated { + // zcashd: bad-txns-duplicate + eprintln!("⚠️ Transaction Merkle tree is malleable"); + } + + // The rest of the checks require network parameters and a block height. + let (params, height) = match (params, height) { + (Some(params), Some(height)) => (params, height), + _ => return, + }; + + if params.is_nu_active(NetworkUpgrade::Nu5, height) { + if block.txs[0].expiry_height() != height { + // zcashd: bad-cb-height + eprintln!( + "⚠️ [NU5] coinbase expiry height ({}) doesn't match coinbase scriptSig height ({})", + block.txs[0].expiry_height(), + height + ); + } + + if let Some(chain_history_root) = context.and_then(|c| c.chainhistoryroot) { + let auth_data_root = block.build_auth_data_root(); + let block_commitments_hash = + derive_block_commitments_hash(chain_history_root.0, auth_data_root); + + if block_commitments_hash != block.header.final_sapling_root { + // zcashd: bad-block-commitments-hash + eprintln!( + "⚠️ [NU5] header.blockcommitments doesn't match ZIP 244 block commitment" + ); + eprintln!(" - chainhistoryroot: {}", chain_history_root); + eprintln!(" - authdataroot: {}", ZUint256(auth_data_root)); + eprintln!( + " - blockcommitments (calc): {}", + ZUint256(block_commitments_hash) + ); + eprintln!( + " - header.blockcommitments: {}", + ZUint256(block.header.final_sapling_root) + ); + } + } else { + eprintln!("🔎 To check header.blockcommitments, add \"chainhistoryroot\" to context"); + } + } else if Some(height) == params.activation_height(NetworkUpgrade::Heartwood) { + if block.header.final_sapling_root != [0; 32] { + // zcashd: bad-heartwood-root-in-block + eprintln!("⚠️ This is the block that activates Heartwood but header.blockcommitments is not null"); + } + } else if params.is_nu_active(NetworkUpgrade::Heartwood, height) { + if let Some(chain_history_root) = context.and_then(|c| c.chainhistoryroot) { + if chain_history_root.0 != block.header.final_sapling_root { + // zcashd: bad-heartwood-root-in-block + eprintln!( + "⚠️ [Heartwood] header.blockcommitments doesn't match provided chain history root" + ); + eprintln!(" - chainhistoryroot: {}", chain_history_root); + eprintln!( + " - header.blockcommitments: {}", + ZUint256(block.header.final_sapling_root) + ); + } + } else { + eprintln!("🔎 To check header.blockcommitments, add \"chainhistoryroot\" to context"); + } + } +} diff --git a/devtools/src/bin/inspect/context.rs b/devtools/src/bin/inspect/context.rs new file mode 100644 index 0000000000..9f78cd42cb --- /dev/null +++ b/devtools/src/bin/inspect/context.rs @@ -0,0 +1,325 @@ +use std::convert::TryFrom; +use std::fmt; +use std::str::FromStr; + +use serde::{ + de::{Unexpected, Visitor}, + Deserialize, Serialize, Serializer, +}; +use zcash_primitives::{ + consensus::Network, + legacy::Script, + transaction::components::{amount::NonNegativeAmount, transparent, TxOut}, + zip32::AccountId, +}; + +#[derive(Clone, Copy, Debug)] +pub(crate) struct JsonNetwork(Network); + +struct JsonNetworkVisitor; + +impl<'de> Visitor<'de> for JsonNetworkVisitor { + type Value = JsonNetwork; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("either \"main\" or \"test\"") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match v { + "main" => Ok(JsonNetwork(Network::MainNetwork)), + "test" => Ok(JsonNetwork(Network::TestNetwork)), + _ => Err(serde::de::Error::invalid_value( + Unexpected::Str(v), + &"either \"main\" or \"test\"", + )), + } + } +} + +impl<'de> Deserialize<'de> for JsonNetwork { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(JsonNetworkVisitor) + } +} + +#[derive(Clone, Copy, Debug)] +struct JsonAccountId(AccountId); + +struct JsonAccountIdVisitor; + +impl<'de> Visitor<'de> for JsonAccountIdVisitor { + type Value = JsonAccountId; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a u31") + } + + fn visit_i64(self, v: i64) -> Result + where + E: serde::de::Error, + { + u32::try_from(v) + .map_err(|_| E::custom(format!("u32 out of range: {}", v))) + .and_then(|a| { + AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e))) + }) + .map(JsonAccountId) + } + + fn visit_i128(self, v: i128) -> Result + where + E: serde::de::Error, + { + u32::try_from(v) + .map_err(|_| E::custom(format!("u32 out of range: {}", v))) + .and_then(|a| { + AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e))) + }) + .map(JsonAccountId) + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + u32::try_from(v) + .map_err(|_| E::custom(format!("u32 out of range: {}", v))) + .and_then(|a| { + AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e))) + }) + .map(JsonAccountId) + } + + fn visit_u128(self, v: u128) -> Result + where + E: serde::de::Error, + { + u32::try_from(v) + .map_err(|_| E::custom(format!("u32 out of range: {}", v))) + .and_then(|a| { + AccountId::try_from(a).map_err(|e| E::custom(format!("AccountId invalid: {}", e))) + }) + .map(JsonAccountId) + } +} + +impl<'de> Deserialize<'de> for JsonAccountId { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_u32(JsonAccountIdVisitor) + } +} + +#[derive(Clone, Copy, Debug)] +pub(crate) struct ZUint256(pub [u8; 32]); + +struct ZUint256Visitor; + +impl<'de> Visitor<'de> for ZUint256Visitor { + type Value = ZUint256; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a hex-encoded 32-byte array") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let mut data = [0; 32]; + hex::decode_to_slice(v, &mut data).map_err(|e| match e { + hex::FromHexError::InvalidHexCharacter { c, .. } => { + serde::de::Error::invalid_value(Unexpected::Char(c), &"valid hex character") + } + hex::FromHexError::OddLength => { + serde::de::Error::invalid_length(v.len(), &"an even-length string") + } + hex::FromHexError::InvalidStringLength => { + serde::de::Error::invalid_length(v.len(), &"a 64-character string") + } + })?; + data.reverse(); + Ok(ZUint256(data)) + } +} + +impl<'de> Deserialize<'de> for ZUint256 { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(ZUint256Visitor) + } +} + +impl fmt::Display for ZUint256 { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut data = self.0; + data.reverse(); + formatter.write_str(&hex::encode(data)) + } +} + +#[derive(Clone, Debug)] +struct ZOutputValue(NonNegativeAmount); + +struct ZOutputValueVisitor; + +impl<'de> Visitor<'de> for ZOutputValueVisitor { + type Value = ZOutputValue; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a non-negative integer number of zatoshis") + } + + fn visit_u64(self, v: u64) -> Result + where + E: serde::de::Error, + { + NonNegativeAmount::from_u64(v) + .map(ZOutputValue) + .map_err(|e| match e { + zcash_protocol::value::BalanceError::Overflow => serde::de::Error::invalid_type( + Unexpected::Unsigned(v), + &"a valid zatoshi amount", + ), + zcash_protocol::value::BalanceError::Underflow => serde::de::Error::invalid_type( + Unexpected::Unsigned(v), + &"a non-negative zatoshi amount", + ), + }) + } +} + +impl<'de> Deserialize<'de> for ZOutputValue { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_u64(ZOutputValueVisitor) + } +} + +impl Serialize for ZOutputValue { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_u64(u64::from(self.0)) + } +} + +#[derive(Clone, Debug)] +struct ZScript(Script); + +struct ZScriptVisitor; + +impl<'de> Visitor<'de> for ZScriptVisitor { + type Value = ZScript; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a hex-encoded Script") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let data = hex::decode(v).map_err(|e| match e { + hex::FromHexError::InvalidHexCharacter { c, .. } => { + serde::de::Error::invalid_value(Unexpected::Char(c), &"valid hex character") + } + hex::FromHexError::OddLength => { + serde::de::Error::invalid_length(v.len(), &"an even-length string") + } + hex::FromHexError::InvalidStringLength => { + serde::de::Error::invalid_length(v.len(), &"a 64-character string") + } + })?; + Ok(ZScript(Script(data))) + } +} + +impl<'de> Deserialize<'de> for ZScript { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(ZScriptVisitor) + } +} + +impl Serialize for ZScript { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_str(&hex::encode(&self.0 .0)) + } +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub(crate) struct ZTxOut { + value: ZOutputValue, + script_pubkey: ZScript, +} + +impl From for ZTxOut { + fn from(out: TxOut) -> Self { + ZTxOut { + value: ZOutputValue(out.value), + script_pubkey: ZScript(out.script_pubkey), + } + } +} + +#[derive(Debug, Deserialize)] +pub(crate) struct Context { + network: Option, + accounts: Option>, + pub(crate) chainhistoryroot: Option, + transparentcoins: Option>, +} + +impl FromStr for Context { + type Err = serde_json::Error; + + fn from_str(s: &str) -> Result { + serde_json::from_str(s) + } +} + +impl Context { + pub(crate) fn network(&self) -> Option { + self.network.map(|n| n.0) + } + + pub(crate) fn addr_network(&self) -> Option { + self.network().map(|params| match params { + Network::MainNetwork => zcash_address::Network::Main, + Network::TestNetwork => zcash_address::Network::Test, + }) + } + + pub(crate) fn accounts(&self) -> Option> { + self.accounts + .as_ref() + .map(|accounts| accounts.iter().map(|id| id.0).collect()) + } + + pub(crate) fn transparent_coins(&self) -> Option> { + self.transparentcoins.as_ref().map(|coins| { + coins + .iter() + .cloned() + .map(|coin| transparent::TxOut { + value: coin.value.0, + script_pubkey: coin.script_pubkey.0, + }) + .collect() + }) + } +} diff --git a/devtools/src/bin/inspect/keys.rs b/devtools/src/bin/inspect/keys.rs new file mode 100644 index 0000000000..4a5ad8ac82 --- /dev/null +++ b/devtools/src/bin/inspect/keys.rs @@ -0,0 +1,250 @@ +use std::convert::TryInto; +use std::iter; + +use bech32::{FromBase32, ToBase32}; +use secrecy::Zeroize; +use zcash_address::{ + unified::{self, Encoding}, + ToAddress, ZcashAddress, +}; +use zcash_keys::keys::{UnifiedAddressRequest, UnifiedFullViewingKey}; +use zcash_primitives::{ + legacy::{ + keys::{AccountPrivKey, IncomingViewingKey}, + TransparentAddress, + }, + zip32, +}; +use zcash_protocol::{ + consensus::{Network, NetworkConstants, NetworkType}, + local_consensus::LocalNetwork, +}; + +use crate::Context; + +pub(crate) mod view; + +pub(crate) fn inspect_mnemonic(mnemonic: bip0039::Mnemonic, context: Option) { + eprintln!("Mnemonic phrase"); + eprintln!(" - Language: English"); + + if let Some(((network, addr_net), accounts)) = + context.and_then(|c| c.network().zip(c.addr_network()).zip(c.accounts())) + { + let mut seed = mnemonic.to_seed(""); + for account in accounts { + eprintln!(" - Account {}:", u32::from(account)); + + let orchard_fvk = match orchard::keys::SpendingKey::from_zip32_seed( + &seed, + network.coin_type(), + account, + ) { + Ok(sk) => Some(orchard::keys::FullViewingKey::from(&sk)), + Err(e) => { + eprintln!( + " ⚠️ No valid Orchard key for this account under this seed: {}", + e + ); + None + } + }; + + eprintln!(" - Sapling:"); + let sapling_master = sapling::zip32::ExtendedSpendingKey::master(&seed); + let sapling_extsk = sapling::zip32::ExtendedSpendingKey::from_path( + &sapling_master, + &[ + zip32::ChildIndex::hardened(32), + zip32::ChildIndex::hardened(network.coin_type()), + account.into(), + ], + ); + #[allow(deprecated)] + let sapling_extfvk = sapling_extsk.to_extended_full_viewing_key(); + let sapling_default_addr = sapling_extfvk.default_address(); + + let mut sapling_extsk_bytes = vec![]; + sapling_extsk.write(&mut sapling_extsk_bytes).unwrap(); + eprintln!( + " - ExtSK: {}", + bech32::encode( + network.hrp_sapling_extended_spending_key(), + sapling_extsk_bytes.to_base32(), + bech32::Variant::Bech32, + ) + .unwrap(), + ); + + let mut sapling_extfvk_bytes = vec![]; + sapling_extfvk.write(&mut sapling_extfvk_bytes).unwrap(); + eprintln!( + " - ExtFVK: {}", + bech32::encode( + network.hrp_sapling_extended_full_viewing_key(), + sapling_extfvk_bytes.to_base32(), + bech32::Variant::Bech32, + ) + .unwrap(), + ); + + let sapling_addr_bytes = sapling_default_addr.1.to_bytes(); + eprintln!( + " - Default address: {}", + bech32::encode( + network.hrp_sapling_payment_address(), + sapling_addr_bytes.to_base32(), + bech32::Variant::Bech32, + ) + .unwrap(), + ); + + let transparent_fvk = match AccountPrivKey::from_seed(&network, &seed, account) + .map(|sk| sk.to_account_pubkey()) + { + Ok(fvk) => { + eprintln!(" - Transparent:"); + match fvk.derive_external_ivk().map(|ivk| ivk.default_address().0) { + Ok(addr) => eprintln!( + " - Default address: {}", + match addr { + TransparentAddress::PublicKeyHash(data) => ZcashAddress::from_transparent_p2pkh(addr_net, data), + TransparentAddress::ScriptHash(_) => unreachable!(), + }.encode(), + ), + Err(e) => eprintln!( + " ⚠️ No valid transparent default address for this account under this seed: {:?}", + e + ), + } + + Some(fvk) + } + Err(e) => { + eprintln!( + " ⚠️ No valid transparent key for this account under this seed: {:?}", + e + ); + None + } + }; + + let items: Vec<_> = iter::empty() + .chain( + orchard_fvk + .map(|fvk| fvk.to_bytes()) + .map(unified::Fvk::Orchard), + ) + .chain(Some(unified::Fvk::Sapling( + sapling_extfvk_bytes[41..].try_into().unwrap(), + ))) + .chain( + transparent_fvk + .map(|fvk| fvk.serialize()[..].try_into().unwrap()) + .map(unified::Fvk::P2pkh), + ) + .collect(); + let item_names: Vec<_> = items + .iter() + .map(|item| match item { + unified::Fvk::Orchard(_) => "Orchard", + unified::Fvk::Sapling(_) => "Sapling", + unified::Fvk::P2pkh(_) => "Transparent", + unified::Fvk::Unknown { .. } => unreachable!(), + }) + .collect(); + + eprintln!(" - Unified ({}):", item_names.join(", ")); + let ufvk = unified::Ufvk::try_from_items(items).unwrap(); + eprintln!(" - UFVK: {}", ufvk.encode(&addr_net)); + } + seed.zeroize(); + } else { + eprintln!("🔎 To show account details, add \"network\" (either \"main\" or \"test\") and \"accounts\" array to context"); + } + + eprintln!(); + eprintln!( + "WARNING: This mnemonic phrase is now likely cached in your terminal's history buffer." + ); +} + +pub(crate) fn inspect_sapling_extsk( + raw: Vec, + variant: bech32::Variant, + network: NetworkType, +) { + match Vec::::from_base32(&raw) + .map_err(|_| ()) + .and_then(|data| sapling::zip32::ExtendedSpendingKey::read(&data[..]).map_err(|_| ())) + { + Err(_) => { + eprintln!("Invalid encoding that claims to be a Sapling extended spending key"); + } + Ok(extsk) => { + eprintln!("Sapling extended spending key"); + match variant { + bech32::Variant::Bech32 => (), + bech32::Variant::Bech32m => eprintln!("⚠️ Incorrectly encoded with Bech32m"), + } + + let default_addr_bytes = extsk.default_address().1.to_bytes(); + eprintln!( + "- Default address: {}", + bech32::encode( + network.hrp_sapling_payment_address(), + default_addr_bytes.to_base32(), + bech32::Variant::Bech32, + ) + .unwrap(), + ); + + #[allow(deprecated)] + if let Ok(ufvk) = UnifiedFullViewingKey::from_sapling_extended_full_viewing_key( + extsk.to_extended_full_viewing_key(), + ) { + let encoded_ufvk = match network { + NetworkType::Main => ufvk.encode(&Network::MainNetwork), + NetworkType::Test => ufvk.encode(&Network::TestNetwork), + NetworkType::Regtest => ufvk.encode(&LocalNetwork { + overwinter: None, + sapling: None, + blossom: None, + heartwood: None, + canopy: None, + nu5: None, + nu6: None, + nu7: None, + #[cfg(zcash_unstable = "zfuture")] + z_future: None, + }), + }; + eprintln!("- UFVK: {encoded_ufvk}"); + + let (default_ua, _) = ufvk + .default_address(UnifiedAddressRequest::unsafe_new(false, true, false)) + .expect("should exist"); + let encoded_ua = match network { + NetworkType::Main => default_ua.encode(&Network::MainNetwork), + NetworkType::Test => default_ua.encode(&Network::TestNetwork), + NetworkType::Regtest => default_ua.encode(&LocalNetwork { + overwinter: None, + sapling: None, + blossom: None, + heartwood: None, + canopy: None, + nu5: None, + nu6: None, + nu7: None, + #[cfg(zcash_unstable = "zfuture")] + z_future: None, + }), + }; + eprintln!(" - Default address: {encoded_ua}"); + } + } + } + + eprintln!(); + eprintln!("WARNING: This spending key is now likely cached in your terminal's history buffer."); +} diff --git a/devtools/src/bin/inspect/keys/view.rs b/devtools/src/bin/inspect/keys/view.rs new file mode 100644 index 0000000000..33d189c758 --- /dev/null +++ b/devtools/src/bin/inspect/keys/view.rs @@ -0,0 +1,165 @@ +use bech32::{FromBase32, ToBase32}; +use zcash_address::unified::{self, Container, Encoding}; +use zcash_keys::keys::{UnifiedAddressRequest, UnifiedFullViewingKey}; +use zcash_protocol::{ + consensus::{Network, NetworkConstants, NetworkType}, + local_consensus::LocalNetwork, +}; + +pub(crate) fn inspect_ufvk(ufvk: unified::Ufvk, network: NetworkType) { + eprintln!("Unified full viewing key"); + eprintln!( + " - Network: {}", + match network { + NetworkType::Main => "main", + NetworkType::Test => "testnet", + NetworkType::Regtest => "regtest", + } + ); + eprintln!(" - Items:"); + for item in ufvk.items() { + match item { + unified::Fvk::Orchard(data) => { + eprintln!( + " - Orchard ({})", + unified::Ufvk::try_from_items(vec![unified::Fvk::Orchard(data)]) + .unwrap() + .encode(&network) + ); + } + unified::Fvk::Sapling(data) => { + eprintln!( + " - Sapling ({})", + bech32::encode( + network.hrp_sapling_extended_full_viewing_key(), + data.to_base32(), + bech32::Variant::Bech32, + ) + .unwrap(), + ); + } + unified::Fvk::P2pkh(data) => { + eprintln!(" - Transparent P2PKH"); + eprintln!(" - Payload: {}", hex::encode(data)); + } + unified::Fvk::Unknown { typecode, data } => { + eprintln!(" - Unknown"); + eprintln!(" - Typecode: {}", typecode); + eprintln!(" - Payload: {}", hex::encode(data)); + } + } + } +} + +pub(crate) fn inspect_uivk(uivk: unified::Uivk, network: NetworkType) { + eprintln!("Unified incoming viewing key"); + eprintln!( + " - Network: {}", + match network { + NetworkType::Main => "main", + NetworkType::Test => "testnet", + NetworkType::Regtest => "regtest", + } + ); + eprintln!(" - Items:"); + for item in uivk.items() { + match item { + unified::Ivk::Orchard(data) => { + eprintln!( + " - Orchard ({})", + unified::Uivk::try_from_items(vec![unified::Ivk::Orchard(data)]) + .unwrap() + .encode(&network) + ); + } + unified::Ivk::Sapling(data) => { + eprintln!(" - Sapling"); + eprintln!(" - Payload: {}", hex::encode(data)); + } + unified::Ivk::P2pkh(data) => { + eprintln!(" - Transparent P2PKH"); + eprintln!(" - Payload: {}", hex::encode(data)); + } + unified::Ivk::Unknown { typecode, data } => { + eprintln!(" - Unknown"); + eprintln!(" - Typecode: {}", typecode); + eprintln!(" - Payload: {}", hex::encode(data)); + } + } + } +} + +pub(crate) fn inspect_sapling_extfvk( + raw: Vec, + variant: bech32::Variant, + network: NetworkType, +) { + match Vec::::from_base32(&raw) + .map_err(|_| ()) + .and_then(|data| sapling::zip32::ExtendedFullViewingKey::read(&data[..]).map_err(|_| ())) + { + Err(_) => { + eprintln!("Invalid encoding that claims to be a Sapling extended full viewing key"); + } + Ok(extfvk) => { + eprintln!("Sapling extended full viewing key"); + match variant { + bech32::Variant::Bech32 => (), + bech32::Variant::Bech32m => eprintln!("⚠️ Incorrectly encoded with Bech32m"), + } + + let default_addr_bytes = extfvk.default_address().1.to_bytes(); + eprintln!( + "- Default address: {}", + bech32::encode( + network.hrp_sapling_payment_address(), + default_addr_bytes.to_base32(), + bech32::Variant::Bech32, + ) + .unwrap(), + ); + + if let Ok(ufvk) = UnifiedFullViewingKey::from_sapling_extended_full_viewing_key(extfvk) + { + let encoded_ufvk = match network { + NetworkType::Main => ufvk.encode(&Network::MainNetwork), + NetworkType::Test => ufvk.encode(&Network::TestNetwork), + NetworkType::Regtest => ufvk.encode(&LocalNetwork { + overwinter: None, + sapling: None, + blossom: None, + heartwood: None, + canopy: None, + nu5: None, + nu6: None, + nu7: None, + #[cfg(zcash_unstable = "zfuture")] + z_future: None, + }), + }; + eprintln!("- Equivalent UFVK: {encoded_ufvk}"); + + let (default_ua, _) = ufvk + .default_address(UnifiedAddressRequest::unsafe_new(false, true, false)) + .expect("should exist"); + let encoded_ua = match network { + NetworkType::Main => default_ua.encode(&Network::MainNetwork), + NetworkType::Test => default_ua.encode(&Network::TestNetwork), + NetworkType::Regtest => default_ua.encode(&LocalNetwork { + overwinter: None, + sapling: None, + blossom: None, + heartwood: None, + canopy: None, + nu5: None, + nu6: None, + nu7: None, + #[cfg(zcash_unstable = "zfuture")] + z_future: None, + }), + }; + eprintln!(" - Default address: {encoded_ua}"); + } + } + } +} diff --git a/devtools/src/bin/inspect/lookup.rs b/devtools/src/bin/inspect/lookup.rs new file mode 100644 index 0000000000..8a902f378d --- /dev/null +++ b/devtools/src/bin/inspect/lookup.rs @@ -0,0 +1,85 @@ +use tonic::transport::{Channel, ClientTlsConfig}; +use zcash_client_backend::proto::service::{ + compact_tx_streamer_client::CompactTxStreamerClient, TxFilter, +}; +use zcash_primitives::transaction::Transaction; +use zcash_protocol::consensus::{BlockHeight, BranchId, Network}; + +const MAINNET: Server = Server { + host: "zec.rocks", + port: 443, +}; + +const TESTNET: Server = Server { + host: "testnet.zec.rocks", + port: 443, +}; + +struct Server { + host: &'static str, + port: u16, +} + +impl Server { + fn endpoint(&self) -> String { + format!("https://{}:{}", self.host, self.port) + } +} + +async fn connect(server: &Server) -> anyhow::Result> { + let channel = Channel::from_shared(server.endpoint())?; + + let tls = ClientTlsConfig::new() + .domain_name(server.host.to_string()) + .with_webpki_roots(); + let channel = channel.tls_config(tls)?; + + Ok(CompactTxStreamerClient::new(channel.connect().await?)) +} + +#[derive(Debug)] +pub(crate) struct Lightwalletd { + inner: CompactTxStreamerClient, + parameters: Network, +} + +impl Lightwalletd { + pub(crate) async fn mainnet() -> anyhow::Result { + Ok(Self { + inner: connect(&MAINNET).await?, + parameters: Network::MainNetwork, + }) + } + + pub(crate) async fn testnet() -> anyhow::Result { + Ok(Self { + inner: connect(&TESTNET).await?, + parameters: Network::TestNetwork, + }) + } + + pub(crate) async fn lookup_txid( + &mut self, + candidate: [u8; 32], + ) -> Option<(Transaction, Option)> { + let request = TxFilter { + hash: candidate.into(), + ..Default::default() + }; + let response = self.inner.get_transaction(request).await.ok()?.into_inner(); + + // `RawTransaction.height` has type u64 in the protobuf format, but is documented + // as using -1 for the "not mined" sentinel. Given that we only support u32 block + // heights, -1 in two's complement will fall outside that range. + let mined_height = response.height.try_into().ok(); + + Transaction::read( + &response.data[..], + mined_height + .map(|height| BranchId::for_height(&self.parameters, height)) + .unwrap_or(BranchId::Nu5), + ) + .ok() + .map(|tx| (tx, mined_height)) + } +} diff --git a/devtools/src/bin/inspect/main.rs b/devtools/src/bin/inspect/main.rs new file mode 100644 index 0000000000..03869a4f00 --- /dev/null +++ b/devtools/src/bin/inspect/main.rs @@ -0,0 +1,201 @@ +use std::env; +use std::io; +use std::io::Cursor; +use std::process; + +use gumdrop::{Options, ParsingStyle}; +use lazy_static::lazy_static; +use secrecy::Zeroize; +use tokio::runtime::Runtime; +use zcash_address::{ + unified::{self, Encoding}, + ZcashAddress, +}; +use zcash_primitives::{block::BlockHeader, consensus::BranchId, transaction::Transaction}; +use zcash_proofs::{default_params_folder, load_parameters, ZcashParameters}; +use zcash_protocol::consensus::NetworkType; + +mod context; +use context::{Context, ZUint256}; +use zcash_protocol::constants; + +mod address; +mod block; +mod keys; +mod lookup; +mod transaction; + +lazy_static! { + static ref GROTH16_PARAMS: ZcashParameters = { + let folder = default_params_folder().unwrap(); + load_parameters( + &folder.join("sapling-spend.params"), + &folder.join("sapling-output.params"), + Some(&folder.join("sprout-groth16.params")), + ) + }; + static ref ORCHARD_VK: orchard::circuit::VerifyingKey = orchard::circuit::VerifyingKey::build(); +} + +#[derive(Debug, Options)] +struct CliOptions { + #[options(help = "Print this help output")] + help: bool, + + #[options(help = "Query information from the chain to help determine what the data is")] + lookup: bool, + + #[options(free, required, help = "String or hex-encoded bytes to inspect")] + data: String, + + #[options( + free, + help = "JSON object with keys corresponding to requested context information" + )] + context: Option, +} + +fn main() { + let args = env::args().collect::>(); + let mut opts = + CliOptions::parse_args(&args[1..], ParsingStyle::default()).unwrap_or_else(|e| { + eprintln!("{}: {}", args[0], e); + process::exit(2); + }); + + if opts.help_requested() { + println!("Usage: {} data [context]", args[0]); + println!(); + println!("{}", CliOptions::usage()); + return; + } + + if let Ok(mnemonic) = bip0039::Mnemonic::from_phrase(&opts.data) { + opts.data.zeroize(); + keys::inspect_mnemonic(mnemonic, opts.context); + } else if let Ok(bytes) = hex::decode(&opts.data) { + inspect_bytes(bytes, opts.context, opts.lookup); + } else if let Ok(addr) = ZcashAddress::try_from_encoded(&opts.data) { + address::inspect(addr); + } else if let Ok((network, uivk)) = unified::Uivk::decode(&opts.data) { + keys::view::inspect_uivk(uivk, network); + } else if let Ok((network, ufvk)) = unified::Ufvk::decode(&opts.data) { + keys::view::inspect_ufvk(ufvk, network); + } else if let Ok((hrp, data, variant)) = bech32::decode(&opts.data) { + match hrp.as_str() { + constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => { + keys::view::inspect_sapling_extfvk(data, variant, NetworkType::Main); + } + constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => { + keys::view::inspect_sapling_extfvk(data, variant, NetworkType::Test); + } + constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY => { + keys::view::inspect_sapling_extfvk(data, variant, NetworkType::Regtest); + } + constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY => { + keys::inspect_sapling_extsk(data, variant, NetworkType::Main); + } + constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY => { + keys::inspect_sapling_extsk(data, variant, NetworkType::Test); + } + constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY => { + keys::inspect_sapling_extsk(data, variant, NetworkType::Regtest); + } + _ => { + // Unknown data format. + eprintln!("String does not match known Zcash data formats."); + process::exit(2); + } + } + } else { + // Unknown data format. + eprintln!("String does not match known Zcash data formats."); + process::exit(2); + } +} + +/// Ensures that the given reader completely consumes the given bytes. +fn complete(bytes: &[u8], f: F) -> Option +where + F: FnOnce(&mut Cursor<&[u8]>) -> io::Result, +{ + let mut cursor = Cursor::new(bytes); + let res = f(&mut cursor); + res.ok().and_then(|t| { + if cursor.position() >= bytes.len() as u64 { + Some(t) + } else { + None + } + }) +} + +fn inspect_bytes(bytes: Vec, context: Option, lookup: bool) { + if let Some(block) = complete(&bytes, |r| block::Block::read(r)) { + block::inspect(&block, context); + } else if let Some(header) = complete(&bytes, |r| BlockHeader::read(r)) { + block::inspect_header(&header, context); + } else if let Some(tx) = complete(&bytes, |r| Transaction::read(r, BranchId::Nu5)) { + // TODO: Take the branch ID used above from the context if present. + // https://github.com/zcash/zcash/issues/6831 + transaction::inspect(tx, context, None); + } else { + // It's not a known variable-length format. check fixed-length data formats. + match bytes.len() { + 32 => inspect_possible_hash(bytes.try_into().unwrap(), context, lookup), + 64 => { + // Could be a signature + eprintln!("This is most likely a signature."); + } + _ => { + eprintln!("Binary data does not match known Zcash data formats."); + process::exit(2); + } + } + } +} + +fn inspect_possible_hash(bytes: [u8; 32], context: Option, lookup: bool) { + let maybe_mainnet_block_hash = bytes.iter().take(4).all(|c| c == &0); + + if lookup { + // Block hashes and txids are byte-reversed; we didn't do this when parsing the + // original hex because other hex byte encodings are not byte-reversed. + let mut candidate = bytes; + candidate.reverse(); + + let rt = Runtime::new().unwrap(); + let found = rt.block_on(async { + match lookup::Lightwalletd::mainnet().await { + Err(e) => eprintln!("Error: Failed to connect to mainnet lightwalletd: {:?}", e), + Ok(mut mainnet) => { + if let Some((tx, mined_height)) = mainnet.lookup_txid(candidate).await { + transaction::inspect(tx, context, mined_height); + return true; + } + } + }; + + match lookup::Lightwalletd::testnet().await { + Err(e) => eprintln!("Error: Failed to connect to testnet lightwalletd: {:?}", e), + Ok(mut testnet) => { + if let Some((tx, mined_height)) = testnet.lookup_txid(candidate).await { + transaction::inspect(tx, context, mined_height); + return true; + } + } + }; + + false + }); + + if found { + return; + } + } + + eprintln!("This is most likely a hash of some sort, or maybe a commitment or nullifier."); + if maybe_mainnet_block_hash { + eprintln!("- It could be a mainnet block hash."); + } +} diff --git a/devtools/src/bin/inspect/transaction.rs b/devtools/src/bin/inspect/transaction.rs new file mode 100644 index 0000000000..2e7f0b56d6 --- /dev/null +++ b/devtools/src/bin/inspect/transaction.rs @@ -0,0 +1,573 @@ +use std::{ + collections::HashMap, + convert::{TryFrom, TryInto}, +}; + +use bellman::groth16; +use group::GroupEncoding; +use orchard::note_encryption::OrchardDomain; +use sapling::{note_encryption::SaplingDomain, SaplingVerificationContext}; +use secp256k1::{Secp256k1, VerifyOnly}; +use zcash_address::{ + unified::{self, Encoding}, + ToAddress, ZcashAddress, +}; +use zcash_note_encryption::try_output_recovery_with_ovk; +#[allow(deprecated)] +use zcash_primitives::{ + consensus::BlockHeight, + legacy::{keys::pubkey_to_address, Script, TransparentAddress}, + memo::{Memo, MemoBytes}, + transaction::{ + components::{amount::NonNegativeAmount, sapling as sapling_serialization, transparent}, + sighash::{signature_hash, SignableInput, TransparentAuthorizingContext}, + txid::TxIdDigester, + Authorization, Transaction, TransactionData, TxId, TxVersion, + }, +}; + +use crate::{ + context::{Context, ZTxOut}, + GROTH16_PARAMS, ORCHARD_VK, +}; + +#[cfg(zcash_unstable = "tze")] +use zcash_primitives::transaction::components::tze; + +pub fn is_coinbase(tx: &Transaction) -> bool { + tx.transparent_bundle() + .map(|b| b.is_coinbase()) + .unwrap_or(false) +} + +pub fn extract_height_from_coinbase(tx: &Transaction) -> Option { + const OP_0: u8 = 0x00; + const OP_1NEGATE: u8 = 0x4f; + const OP_1: u8 = 0x51; + const OP_16: u8 = 0x60; + + tx.transparent_bundle() + .and_then(|bundle| bundle.vin.first()) + .and_then(|input| match input.script_sig.0.first().copied() { + // {0, -1} will never occur as the first byte of a coinbase scriptSig. + Some(OP_0 | OP_1NEGATE) => None, + // Blocks 1 to 16. + Some(h @ OP_1..=OP_16) => Some(BlockHeight::from_u32((h - OP_1 + 1).into())), + // All other heights use CScriptNum encoding, which will never be longer + // than 5 bytes for Zcash heights. These have the format + // `[len(encoding)] || encoding`. + Some(h @ 1..=5) => { + let rest = &input.script_sig.0[1..]; + let encoding_len = h as usize; + if rest.len() < encoding_len { + None + } else { + // Parse the encoding. + let encoding = &rest[..encoding_len]; + if encoding.last().unwrap() & 0x80 != 0 { + // Height is never negative. + None + } else { + let mut height: u64 = 0; + for (i, b) in encoding.iter().enumerate() { + height |= (*b as u64) << (8 * i); + } + height.try_into().ok() + } + } + } + // Anything else is an invalid height encoding. + _ => None, + }) +} + +fn render_value(value: u64) -> String { + format!( + "{} zatoshis ({} ZEC)", + value, + (value as f64) / 1_0000_0000f64 + ) +} + +fn render_memo(memo_bytes: MemoBytes) -> String { + match Memo::try_from(memo_bytes) { + Ok(Memo::Empty) => "No memo".to_string(), + Ok(Memo::Text(memo)) => format!("Text memo: '{}'", String::from(memo)), + Ok(memo) => format!("{:?}", memo), + Err(e) => format!("Invalid memo: {}", e), + } +} + +#[derive(Clone, Debug)] +pub(crate) struct TransparentAuth { + all_prev_outputs: Vec, +} + +impl transparent::Authorization for TransparentAuth { + type ScriptSig = Script; +} + +impl TransparentAuthorizingContext for TransparentAuth { + fn input_amounts(&self) -> Vec { + self.all_prev_outputs + .iter() + .map(|prevout| prevout.value) + .collect() + } + + fn input_scriptpubkeys(&self) -> Vec