diff --git a/.github/workflows/ci-failure-issues.yml b/.github/workflows/ci-failure-issues.yml new file mode 100644 index 00000000..d3c8312c --- /dev/null +++ b/.github/workflows/ci-failure-issues.yml @@ -0,0 +1,92 @@ +name: CI Failure Issues + +on: + workflow_run: # zizmor: ignore[dangerous-triggers] + workflows: + - Fuzz + types: + - completed + branches: + - master + +permissions: + issues: write + +concurrency: + group: ci-failure-issues-${{ github.event.workflow_run.name }} + cancel-in-progress: false + +jobs: + sync-issue: + if: ${{ contains(fromJson('["failure","success"]'), github.event.workflow_run.conclusion) }} + runs-on: ubuntu-24.04 + + steps: + - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + with: + script: | + const run = context.payload.workflow_run; + const workflow = run.name; + const marker = ``; + const workflowFiles = { + "Fuzz": "cron-daily-fuzz.yml", + }; + + const formatTs = iso => { + const d = new Date(iso); + const pad = n => String(n).padStart(2, "0"); + return `${d.getUTCFullYear()}-${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} at ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())} UTC`; + }; + const shortSha = sha => (sha || "unknown").slice(0, 7); + const commitUrl = sha => `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/commit/${sha}`; + const workflowUrl = workflowFiles[workflow] + ? `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/workflows/${workflowFiles[workflow]}` + : `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions?query=${encodeURIComponent(`workflow:"${workflow}"`)}`; + + function titleFor() { + return `The ${workflow} workflow is failing`; + } + + function bodyForFailure() { + return [ + marker, + "", + `The [${workflow} workflow](${workflowUrl}) started failing on ${formatTs(run.created_at)}: [${workflow} #${run.run_number}](${run.html_url}) - [\`${shortSha(run.head_sha)}\`](${commitUrl(run.head_sha)})`, + ].join("\n"); + } + + const issues = await github.paginate(github.rest.issues.listForRepo, { + owner: context.repo.owner, + repo: context.repo.repo, + state: "open", + per_page: 100, + }); + + const existing = issues.find(issue => + !issue.pull_request && issue.body && issue.body.includes(marker) + ); + + if (run.conclusion === "failure" && !existing) { + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: titleFor(), + body: bodyForFailure(), + }); + } + + if (run.conclusion === "success" && existing) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existing.number, + body: `The [${workflow} workflow](${workflowUrl}) successfully ran again on ${formatTs(run.created_at)}: [${workflow} #${run.run_number}](${run.html_url}).`, + }); + + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: existing.number, + state: "closed", + }); + } diff --git a/.github/workflows/cron-daily-fuzz.yml b/.github/workflows/cron-daily-fuzz.yml new file mode 100644 index 00000000..7f3af38e --- /dev/null +++ b/.github/workflows/cron-daily-fuzz.yml @@ -0,0 +1,72 @@ +###### +## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-files.sh. +## Edit that script instead and re-run it. +###### +name: Fuzz +on: + schedule: + # 5am every day UTC, this correlates to: + # - 10pm PDT + # - 6am CET + # - 4pm AEDT + - cron: '00 05 * * *' +permissions: {} + +jobs: + fuzz: + if: ${{ !github.event.act }} + runs-on: ubuntu-24.04 + permissions: + contents: read + strategy: + fail-fast: false + matrix: + fuzz_target: [ + deserialize_block, + deserialize_output, + deserialize_pset, + deserialize_transaction, + ] + steps: + - name: Install test dependencies + run: sudo apt-get update -y && sudo apt-get install -y binutils-dev libunwind8-dev libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc libiberty-dev + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + id: cache-fuzz + with: + path: | + ~/.cargo/bin + fuzz/target + target + key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} + - uses: dtolnay/rust-toolchain@5d458579430fc14a04a08a1e7d3694f545e91ce6 # stable + with: + toolchain: '1.74.0' + - name: fuzz + run: | + echo "Using RUSTFLAGS $RUSTFLAGS" + cd fuzz && ./fuzz.sh "${{ matrix.fuzz_target }}" + - run: echo "${{ matrix.fuzz_target }}" >executed_${{ matrix.fuzz_target }} + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: executed_${{ matrix.fuzz_target }} + path: executed_${{ matrix.fuzz_target }} + + verify-execution: + if: ${{ !github.event.act }} + needs: fuzz + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + - run: cargo install --locked --version 0.12.0 cargo-fuzz + - name: Display structure of downloaded files + run: ls -R + - run: find executed_* -type f -exec cat {} + | sort > executed + - run: cargo fuzz list | sort | diff - executed diff --git a/.github/workflows/cron-weekly-update-nightly.yml b/.github/workflows/cron-weekly-update-nightly.yml new file mode 100644 index 00000000..c85c80b1 --- /dev/null +++ b/.github/workflows/cron-weekly-update-nightly.yml @@ -0,0 +1,35 @@ +name: Update Nightly rustc +on: + schedule: + - cron: "5 0 * * 6" # Saturday at 00:05 + workflow_dispatch: # allows manual triggering +permissions: {} +jobs: + format: + name: Update nightly rustc + runs-on: ubuntu-24.04 + permissions: + contents: write + id-token: write + pull-requests: write + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - name: Install cargo-rbmt + run: cargo install --git https://github.com/rust-bitcoin/rust-bitcoin-maintainer-tools.git --rev $(cat rbmt-version) cargo-rbmt + - name: Update nightly toolchain in Cargo.toml + run: | + eval "$(cargo rbmt toolchains --update-nightly)" + echo "nightly_version=$RBMT_NIGHTLY" >> $GITHUB_ENV + - name: Create Pull Request + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.APOELSTRA_CREATE_PR_TOKEN }} + author: Update Nightly Rustc Bot + committer: Update Nightly Rustc Bot + title: Automated daily update to rustc (to ${{ env.nightly_version }}) + body: | + Automated update to Cargo.toml workspace metadata by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action + commit-message: Automated update to rustc ${{ env.nightly_version }} + branch: create-pull-request/daily-nightly-update diff --git a/.github/workflows/cron-weekly-update-stable.yml b/.github/workflows/cron-weekly-update-stable.yml new file mode 100644 index 00000000..3ffd4e1d --- /dev/null +++ b/.github/workflows/cron-weekly-update-stable.yml @@ -0,0 +1,34 @@ +name: Update Stable rustc +on: + schedule: + - cron: "0 0 * * 5" # runs every Friday at 00:00 (generally rust releases on Thursday) + workflow_dispatch: # allows manual triggering +permissions: {} +jobs: + format: + name: Update stable rustc + runs-on: ubuntu-24.04 + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - name: Install cargo-rbmt + run: cargo install --git https://github.com/rust-bitcoin/rust-bitcoin-maintainer-tools.git --rev $(cat rbmt-version) cargo-rbmt + - name: Update stable toolchain in Cargo.toml + run: | + eval "$(cargo rbmt toolchains --update-stable)" + echo "stable_version=$RBMT_STABLE" >> $GITHUB_ENV + - name: Create Pull Request + uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 + with: + token: ${{ secrets.APOELSTRA_CREATE_PR_TOKEN }} + author: Update Stable Rustc Bot + committer: Update Stable Rustc Bot + title: Automated weekly update to rustc stable (to ${{ env.stable_version }}) + body: | + Automated update to Cargo.toml workspace metadata by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action + commit-message: Automated update to rustc stable-${{ env.stable_version }} + branch: create-pull-request/weekly-stable-update diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index f8c56286..4dc44501 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,41 +1,130 @@ on: [push, pull_request] -name: Continuous integration +name: Continuous Integration + +permissions: + contents: read jobs: - Tests: - name: Tests - runs-on: ubuntu-latest + Test: # 6 jobs: 3 toolchains × 2 lock files. + name: Test - ${{ matrix.toolchain }} toolchain (${{ matrix.dep }}) + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: - include: - - rust: 1.58.0 - env: - DO_FUZZ: true - - rust: stable - env: - DO_INTEGRATION: true - - rust: beta - env: - DUMMY: true - - rust: nightly - env: - DUMMY: true - - rust: 1.41.1 - env: - PIN_VERSIONS: true - steps: - - name: Install test dependencies - run: sudo apt-get install -y binutils-dev libunwind8-dev + dep: [recent] + toolchain: [stable, nightly, msrv] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + - uses: rust-bitcoin/rust-bitcoin-maintainer-tools/.github/actions/setup-rbmt@af3c2868415b17eedc808da6d0589e10b7482660 # v0.1.0 + - uses: Swatinem/rust-cache@v2 + - name: Run ${{ matrix.toolchain }} tests + run: | + export ELEMENTSD_EXE="$PWD/elementsd-tests/bin/elementsd" + if [ "${{ matrix.toolchain }}" = "msrv" ]; then + MSRV="$(cargo rbmt toolchains --msrv)" + cp "Cargo-${{ matrix.dep }}.lock" Cargo.lock + cargo +"$MSRV" test --locked + else + cargo rbmt --lock-file ${{ matrix.dep }} test --toolchain ${{ matrix.toolchain }} + fi + + Lint: + name: Lint - nightly toolchain + runs-on: ubuntu-24.04 + steps: + - name: Checkout Crate + uses: actions/checkout@v4 + - uses: rust-bitcoin/rust-bitcoin-maintainer-tools/.github/actions/setup-rbmt@af3c2868415b17eedc808da6d0589e10b7482660 # v0.1.0 + - uses: Swatinem/rust-cache@v2 + - name: Run lints + run: cargo rbmt --lock-file recent lint + + Docs: + name: Docs - stable toolchain + runs-on: ubuntu-24.04 + steps: + - name: Checkout Crate + uses: actions/checkout@v4 + - uses: rust-bitcoin/rust-bitcoin-maintainer-tools/.github/actions/setup-rbmt@af3c2868415b17eedc808da6d0589e10b7482660 # v0.1.0 + - uses: Swatinem/rust-cache@v2 + - name: Run doc tests + run: cargo rbmt --lock-file recent docs + + Docsrs: + name: Docs - nightly toolchain + runs-on: ubuntu-24.04 + steps: + - name: Checkout Crate + uses: actions/checkout@v4 + - uses: rust-bitcoin/rust-bitcoin-maintainer-tools/.github/actions/setup-rbmt@af3c2868415b17eedc808da6d0589e10b7482660 # v0.1.0 + - uses: Swatinem/rust-cache@v2 + - name: Run docsrs tests + run: cargo rbmt --lock-file recent docsrs + +# Format: # 1 job, run cargo fmt directly. +# name: Format - nightly toolchain +# runs-on: ubuntu-24.04 +# steps: +# - name: Checkout Crate +# uses: actions/checkout@v4 +# - uses: rust-bitcoin/rust-bitcoin-maintainer-tools/.github/actions/setup-rbmt@af3c2868415b17eedc808da6d0589e10b7482660 # v0.1.0 +# - name: Check formatting +# run: cargo rbmt --lock-file recent fmt --check + + Bench: + name: Bench - nightly toolchain + runs-on: ubuntu-24.04 + steps: + - name: Checkout Crate + uses: actions/checkout@v4 + - uses: rust-bitcoin/rust-bitcoin-maintainer-tools/.github/actions/setup-rbmt@af3c2868415b17eedc808da6d0589e10b7482660 # v0.1.0 + - uses: Swatinem/rust-cache@v2 + - name: Run benches + run: cargo rbmt --lock-file recent bench + + Arch32bit: + name: Test 32-bit version + runs-on: ubuntu-24.04 + steps: + - name: Checkout Crate + uses: actions/checkout@v4 + - name: Checkout Toolchain + uses: dtolnay/rust-toolchain@stable + - name: Add architecture i386 + run: sudo dpkg --add-architecture i386 + - name: Install i686 gcc + run: sudo apt-get update -y && sudo apt-get install -y gcc-multilib + - name: Install target + run: rustup target add i686-unknown-linux-gnu + - name: Run tests on i686 + run: cargo test --target i686-unknown-linux-gnu + + Cross: + name: Cross test + runs-on: ubuntu-24.04 + steps: + - name: Checkout Crate + uses: actions/checkout@v4 + - name: Checkout Toolchain + uses: dtolnay/rust-toolchain@stable + - name: Install target + run: rustup target add s390x-unknown-linux-gnu + - name: install cross + run: cargo install cross --locked + - name: run cross test + run: cross test --target s390x-unknown-linux-gnu + + Wasm: + name: Check WASM + runs-on: ubuntu-latest + strategy: + fail-fast: false + steps: - name: Checkout Crate - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Checkout Toolchain - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: ${{ matrix.rust }} - override: true - - name: Running test script - env: ${{ matrix.env }} - run: ./contrib/test.sh + uses: dtolnay/rust-toolchain@stable + - run: rustup target add wasm32-unknown-unknown + - run: cargo check --target wasm32-unknown-unknown diff --git a/.gitignore b/.gitignore index 2e47091b..42dea11a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ Cargo.lock *~ #fuzz -fuzz/hfuzz_target -fuzz/hfuzz_workspace +fuzz/corpus +fuzz/artifacts +fuzz/*.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 365e75c4..ff4f1a45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,117 @@ +# 0.27.0 - 2026-07-28 + +This release updates several dependencies. It attempts to minimize API breakage beyond updating +the dependencies. It should be considered a "transitional release" as we modernize the crate's API +over the next several releases. In particular, this release introduces a new encoding paradigm but +does not remove the old one, so depending on this may help users transition. + +Dependency updates: + +* **Bump MRSV to 1.74.0** + [#269](https://github.com/ElementsProject/rust-elements/pull/269) + [#265](https://github.com/ElementsProject/rust-elements/pull/265) +* Update `bitcoin_hashes` dependency to newly-stable 1.0; improve array/slice APIs +* Replace custom hex parsing with newly-stable `hex-conservative` 1.0 + [#261](https://github.com/ElementsProject/rust-elements/pull/261) + [#272](https://github.com/ElementsProject/rust-elements/pull/272) +* Introduce newly-stable `bitcoin-consensus-encoding` 1.0 crate and implement its `Encode` and + `Decode` traits on `Transaction` and subtypes. + [#288](https://github.com/ElementsProject/rust-elements/pull/288) + + +* Remove `TxOut::MAX_MONEY` whose use is likely incorrect on a multi-asset chain + [#261](https://github.com/ElementsProject/rust-elements/pull/261) +* pset: fix pegin/issuance flag handling in extract_tx + [#278](https://github.com/ElementsProject/rust-elements/pull/278) + [#279](https://github.com/ElementsProject/rust-elements/pull/279) + [#282](https://github.com/ElementsProject/rust-elements/pull/282) +* Implement calculation of AssetId and genesis blocks + [#276](https://github.com/ElementsProject/rust-elements/pull/276) +* Improve type safety for objects related to confidential transactions and asset issuance + [#286](https://github.com/ElementsProject/rust-elements/pull/286) + [#289](https://github.com/ElementsProject/rust-elements/pull/289) + +# 0.26.2 - 2026-06-15 + +* Fix multiple panics related to slicing and blinding data validation + +# 0.26.1 - 2025-08-28 + +* [#250](https://github.com/ElementsProject/rust-elements/pull/250) API cleanups + * implement `Encodable` and `Decodable` for `Vec` whenever `T` is `Encodable`/`Decodable` and `'static` + * add missing export of error sub-type `pset::PsetHash` + +# 0.26.0 - 2025-08-22 + +* [#249](https://github.com/ElementsProject/rust-elements/pull/249) docs: fix changelog links +* [#243](https://github.com/ElementsProject/rust-elements/pull/243) pset: blind: return ephemeral private key +* [#242](https://github.com/ElementsProject/rust-elements/pull/242) Edoc: fix CheckHrpstring::new reference +* [#236](https://github.com/ElementsProject/rust-elements/pull/236) chore: fix some minor issues in comments +* [#230](https://github.com/ElementsProject/rust-elements/pull/230) ci: add lockfile and use rust-bitcoin-maintainer-tools CI setup +* [#228](https://github.com/ElementsProject/rust-elements/pull/228) Enable a ton of pedantic clippy lints + +# 0.25.2 - 2025-04-18 + +* [#226](https://github.com/ElementsProject/rust-elements/pull/226) elip102: rename from elip101 +* [#225](https://github.com/ElementsProject/rust-elements/pull/225) Make AssetId::from_inner a const function +* [#224](https://github.com/ElementsProject/rust-elements/pull/224) pset: input: insert non-pset proprietary keys +* [#223](https://github.com/ElementsProject/rust-elements/pull/223) clippy: fix for new rust stable +* [#195](https://github.com/ElementsProject/rust-elements/pull/195) Fix WASM build and add a job in CI +* [#222](https://github.com/ElementsProject/rust-elements/pull/222) elementsd-tests: blind asset issuance based on node version +* [#220](https://github.com/ElementsProject/rust-elements/pull/220) tx: discountct: add missing testcase +* [#221](https://github.com/ElementsProject/rust-elements/pull/221) ci: fixes for rust stable clippy, and rust 1.56.1 compilation + +# 0.25.1 - 2024-10-24 + +* [#218](https://github.com/ElementsProject/rust-elements/pull/218) discount: fix weight calculation + +# 0.25.0 - 2024-09-23 + +* [#216](https://github.com/ElementsProject/rust-elements/pull/216) add Address::is_liquid +* [#215](https://github.com/ElementsProject/rust-elements/pull/215) docs: add a bunch of paragraph breaks. +* [#213](https://github.com/ElementsProject/rust-elements/pull/213) ELIP-0101: rename from LiquiDEX +* [#212](https://github.com/ElementsProject/rust-elements/pull/212) Stop implementing elements::Encodable with bitcoin::Encodable +* [#210](https://github.com/ElementsProject/rust-elements/pull/210) Address err refactor +* [#209](https://github.com/ElementsProject/rust-elements/pull/209) upgrade to bitcoin 0.32 +* [#207](https://github.com/ElementsProject/rust-elements/pull/207) Add elip_liquidex module +* [#206](https://github.com/ElementsProject/rust-elements/pull/206) pset: elip100: add and get token metadata +* [#204](https://github.com/ElementsProject/rust-elements/pull/204) tx: add discount_weight and discount_vsize +* [#203](https://github.com/ElementsProject/rust-elements/pull/203) transaction: range-check pegin data when parsing +* [#201](https://github.com/ElementsProject/rust-elements/pull/201) pset: add optional asset blinding factor to input and output +* [#200](https://github.com/ElementsProject/rust-elements/pull/200) pset: input: add blinded issuance flag +* [#199](https://github.com/ElementsProject/rust-elements/pull/199) pset: input: add explicit amount and asset, and their proofs + +# 0.24.1 - 2024-01-30 + +* [#196](https://github.com/ElementsProject/rust-elements/pull/196) Add constructor to `FullParams` + +# 0.24.0 - 2024-01-12 + +* [#188](https://github.com/ElementsProject/rust-elements/pull/188) Update rust-bitcoin to 0.31.0, and associated dependencies +* [#186](https://github.com/ElementsProject/rust-elements/pull/186) Updated doc for impl Value blind method - returns blinded value* +* [#185](https://github.com/ElementsProject/rust-elements/pull/185) Exposed RangeProofMessage publically +* [#183](https://github.com/ElementsProject/rust-elements/pull/183) elip100: add missing AssetMetadata::new method +* [#182](https://github.com/ElementsProject/rust-elements/pull/182) ELIP-0100 implementation +* [#178](https://github.com/ElementsProject/rust-elements/pull/178) pset: fix remove_output +* [#177](https://github.com/ElementsProject/rust-elements/pull/177) rename pset::str::Error to ParseError and expose it +* [#176](https://github.com/ElementsProject/rust-elements/pull/176) Remove slip77 +* [#175](https://github.com/ElementsProject/rust-elements/pull/175) Add to and from base64 string to pset +* [#173](https://github.com/ElementsProject/rust-elements/pull/173) Fix examples +* [#171](https://github.com/ElementsProject/rust-elements/pull/171) Create explicit empty and null values for some types + +# 0.23.0 - 2023-06-18 + +* [#167](https://github.com/ElementsProject/rust-elements/pull/167) Implement Ord for Transaction +* [#168](https://github.com/ElementsProject/rust-elements/pull/168) add Height::ZERO associated constant +* [#168](https://github.com/ElementsProject/rust-elements/pull/169) rename all Sighash types downcasing the middle "h", for example: SigHash -> Sighash + +# 0.22.0 - 2023-06-08 + +* [#159](https://github.com/ElementsProject/rust-elements/pull/159) Update `TapTweak`, and `schnorr` module generally, to match rust-bitcoin +* [#160](https://github.com/ElementsProject/rust-elements/pull/160) Make `Prevouts` generic over type of `TxOut` +* [#161](https://github.com/ElementsProject/rust-elements/pull/161) Add `Transaction::vsize` method +* [#157](https://github.com/ElementsProject/rust-elements/pull/157) dynafed: extract `FullParams` from `Params` +* [#166](https://github.com/ElementsProject/rust-elements/pull/166) **Update bitcoin dependency to 0.30.0 and secp256k1-zkp dependency to 0.9.1** # 0.21.1 - 2022-10-21 @@ -22,7 +136,7 @@ - `Block`, `BlockHeader`, `PeginData`, `PegoutData` loose the Default impl - update rust-bitcoin to 0.29.1 - update secp256k1-zkp to 0.7.0 -- update bitcoin_hases to 0.11.0 +- update bitcoin_hashes to 0.11.0 # 0.19.2 - 2022-06-16 diff --git a/Cargo-recent.lock b/Cargo-recent.lock new file mode 100644 index 00000000..1211fd24 --- /dev/null +++ b/Cargo-recent.lock @@ -0,0 +1,892 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "base58ck" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "365c0acd5b2e8dd0111a46c4faea83fb3cfb6e39a49a7c73a06e090db7b2eff0" +dependencies = [ + "bitcoin_hashes 0.14.101", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bech32" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin" +version = "0.32.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0ce8bd5baaa0d303a19915a6d93afed161f528654e42da2a7a97d05c59499a" +dependencies = [ + "base58ck", + "base64 0.21.7", + "bech32", + "bitcoin-consensus-encoding", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes 0.14.101", + "hex-conservative 0.2.2", + "hex_lit", + "secp256k1", + "serde", +] + +[[package]] +name = "bitcoin-consensus-encoding" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207311705279250ba465076a1bac4b1ac982855fff73fc5f67e22158ac58cdc9" +dependencies = [ + "bitcoin-internals", + "hex-conservative 1.1.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d573f4cf32996a8dce612e4348cece65a241f1882ed594047c9ba348e8869fa5" + +[[package]] +name = "bitcoin-io" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb5de036369d1ac59d3c1819ebc4d850f89466f5401c571a285b6ed564a4cb78" +dependencies = [ + "bitcoin-consensus-encoding", +] + +[[package]] +name = "bitcoin-private" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73290177011694f38ec25e165d0387ab7ea749a4b81cd4c80dae5988229f7a57" + +[[package]] +name = "bitcoin-units" +version = "0.1.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb95693f371d089a4b5b6fc41c6f3ea6e01ee8c15388335dfac8ea685173b51" +dependencies = [ + "bitcoin-consensus-encoding", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca4c7abb40c8817d77403c880988cfd484f23ab2365726afb2f798363e2c4a2" +dependencies = [ + "bitcoin-io", + "hex-conservative 0.2.2", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a67800fcf7f3ca52f7796d4466948a424326b225950c79d1e76d0458760bb25d" +dependencies = [ + "bitcoin-consensus-encoding", + "bitcoin-internals", + "hex-conservative 1.1.0", + "serde", +] + +[[package]] +name = "bitcoincore-rpc" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedd23ae0fd321affb4bbbc36126c6f49a32818dc6b979395d24da8c9d4e80ee" +dependencies = [ + "bitcoincore-rpc-json", + "jsonrpc", + "log", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoincore-rpc-json" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a" +dependencies = [ + "bitcoin", + "serde", + "serde_json", +] + +[[package]] +name = "bitcoind" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce6620b7c942dbe28cc49c21d95e792feb9ffd95a093205e7875ccfa69c2925" +dependencies = [ + "anyhow", + "bitcoincore-rpc", + "log", + "tempfile", + "which", +] + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elements" +version = "0.27.0" +dependencies = [ + "bech32", + "bincode", + "bitcoin", + "bitcoin-consensus-encoding", + "bitcoin-internals", + "bitcoin_hashes 1.1.0", + "getrandom 0.2.16", + "hex-conservative 1.1.0", + "rand", + "rand_chacha", + "secp256k1-zkp", + "serde", + "serde_cbor", + "serde_json", + "serde_test", +] + +[[package]] +name = "elements-fuzz" +version = "0.0.1" +dependencies = [ + "elements", + "libfuzzer-sys", +] + +[[package]] +name = "elementsd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46bf79f591aad9ce61b72839ba640a8721c8293c2d706b989f9f14fc205efda" +dependencies = [ + "bitcoind", +] + +[[package]] +name = "elementsd-tests" +version = "0.1.0" +dependencies = [ + "bitcoin", + "elements", + "elementsd", + "rand", +] + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex-conservative" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7289f6b628ce69fb1a371d0fdcf8ff38cd93ec00e3010eb055d1e044998c8d1" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonrpc" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf" +dependencies = [ + "base64 0.13.1", + "minreq", + "serde", + "serde_json", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libfuzzer-sys" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d" +dependencies = [ + "arbitrary", + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "minreq" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84885312a86831bff4a3cb04a1e54a3f698407e3274c83249313f194d3e0b678" +dependencies = [ + "log", + "serde", + "serde_json", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[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 0.2.16", +] + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "secp256k1" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" +dependencies = [ + "bitcoin_hashes 0.14.101", + "rand", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "secp256k1-zkp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a44aed3002b5ae975f8624c5df3a949cfbf00479e18778b6058fcd213b76e3" +dependencies = [ + "bitcoin-private", + "rand", + "secp256k1", + "secp256k1-zkp-sys", + "serde", +] + +[[package]] +name = "secp256k1-zkp-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57f08b2d0b143a22e07f798ae4f0ab20d5590d7c68e0d090f2088a48a21d1654" +dependencies = [ + "cc", + "secp256k1-sys", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_cbor" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ad7872ff6e6c2a9221f4c1abe681e7eefc56ca5b3e87196afbfc717d141dc8" +dependencies = [ + "byteorder", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_test" +version = "1.0.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +dependencies = [ + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom 0.3.3", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + +[[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-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[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 0.52.6", + "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-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[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_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 909f550d..8abc3df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "elements" -version = "0.21.1" +version = "0.27.0" authors = ["Andrew Poelstra "] description = "Library with support for de/serialization, parsing and executing on data structures and network messages related to Elements" license = "CC0-1.0" @@ -8,47 +8,49 @@ homepage = "https://github.com/ElementsProject/rust-elements/" repository = "https://github.com/ElementsProject/rust-elements/" documentation = "https://docs.rs/elements/" edition = "2018" +rust-version = "1.74.0" + +[workspace.metadata.rbmt.toolchains] +nightly = "nightly-2026-07-23" +stable = "1.97.1" [features] -default = [ "json-contract" ] -integration = [ "elementsd" ] +default = ["json-contract"] -json-contract = [ "serde_json" ] +json-contract = ["serde_json"] "serde" = [ + "dep:serde", + "bitcoin/serde", "bitcoin/serde", - "secp256k1-zkp/use-serde", - "actual-serde" + "hashes/serde", + "secp256k1-zkp/serde", ] -"fuzztarget" = [] +base64 = ["bitcoin/base64"] [dependencies] -bitcoin = "0.29.1" -secp256k1-zkp = { version = "0.7.0", features = [ "global-context", "bitcoin_hashes" ] } -slip21 = "0.2.0" - -# While this dependency is included in bitcoin, we need this to use the macros. -# We should probably try keep this one in sync with the bitcoin version, -# to avoid requiring two version of bitcoin_hashes. -bitcoin_hashes = "0.11.0" +bech32 = "0.11.0" +bitcoin = { version = "0.32.102", default-features = false, features = [ "encoding", "std" ] } +encoding = { package = "bitcoin-consensus-encoding", version = "1.1.0", default-features = false, features = [ "hex", "std" ] } +hashes = { package = "bitcoin_hashes", version = "1.1", features = [ "hex" ] } +internals = { package = "bitcoin-internals", version = "0.6" } +secp256k1-zkp = { version = "0.11.0", features = ["global-context", "hashes"] } # Used for ContractHash::from_json_contract. serde_json = { version = "1.0", optional = true } +serde = { version = "1.0.103", features = [ "derive" ], optional = true } +hex = { package = "hex-conservative", version = "1.1.0" } -actual-serde = { package="serde", version = "1.0", features=["derive"], optional = true } -# This should be an optional dev-dependency (only needed for integration tests), -# but dev-dependency cannot be optional, and without optionality older toolchain try to compile it and fails -elementsd = {version = "0.6.0", features=["0_21_0","bitcoind_22_0"], optional = true } +[target.wasm32-unknown-unknown.dev-dependencies] +getrandom = { version = "0.2", features = ["js"] } [dev-dependencies] rand = "0.8" rand_chacha = "0.3" -serde_test = "1.0" +serde_test = "1.0.19" serde_json = "1.0" -serde_cbor = "0.8" # older than latest version to support 1.41.1 -ryu = "<1.0.5" +serde_cbor = "0.8" # older than latest version to support 1.41.1 bincode = "1.3" -base64 = "0.13.0" [[example]] name = "pset_blind_coinjoin" @@ -58,3 +60,140 @@ name = "raw_blind" [[example]] name = "tx" + +[workspace] +members = ["elementsd-tests", "fuzz"] + +[lints.clippy] +# Exclude lints we don't think are valuable. +needless_question_mark = "allow" # https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 +manual_range_contains = "allow" # More readable than clippy's format. +uninlined_format_args = "allow" # This is a subjective style choice. +float_cmp = "allow" # Bitcoin floats are typically limited to 8 decimal places and we want them exact. +match_bool = "allow" # Adds extra indentation and LOC. +match_same_arms = "allow" # Collapses things that are conceptually unrelated to each other. +must_use_candidate = "allow" # Useful for audit but many false positives. +similar_names = "allow" # Too many (subjectively) false positives. +struct_field_names = "allow" # Dumb +# Exhaustive list of pedantic clippy lints +assigning_clones = "warn" +bool_to_int_with_if = "warn" +borrow_as_ptr = "warn" +case_sensitive_file_extension_comparisons = "warn" +cast_lossless = "warn" +cast_possible_truncation = "allow" # All casts should include a code comment (except test code). +cast_possible_wrap = "allow" # Same as above re code comment. +cast_precision_loss = "warn" +cast_ptr_alignment = "warn" +cast_sign_loss = "allow" # All casts should include a code comment (except in test code). +checked_conversions = "warn" +cloned_instead_of_copied = "warn" +copy_iterator = "warn" +default_trait_access = "warn" +doc_link_with_quotes = "warn" +doc_markdown = "warn" +empty_enums = "warn" +enum_glob_use = "warn" +expl_impl_clone_on_copy = "warn" +explicit_deref_methods = "warn" +explicit_into_iter_loop = "warn" +explicit_iter_loop = "warn" +filter_map_next = "warn" +flat_map_option = "warn" +fn_params_excessive_bools = "warn" +if_not_else = "warn" +ignored_unit_patterns = "warn" +implicit_clone = "warn" +implicit_hasher = "warn" +inconsistent_struct_constructor = "warn" +index_refutable_slice = "warn" +inefficient_to_string = "warn" +inline_always = "warn" +into_iter_without_iter = "warn" +invalid_upcast_comparisons = "warn" +items_after_statements = "warn" +iter_filter_is_ok = "warn" +iter_filter_is_some = "warn" +iter_not_returning_iterator = "warn" +iter_without_into_iter = "warn" +large_digit_groups = "warn" +large_futures = "warn" +large_stack_arrays = "warn" +large_types_passed_by_value = "warn" +linkedlist = "warn" +macro_use_imports = "warn" +manual_assert = "warn" +manual_instant_elapsed = "warn" +manual_is_power_of_two = "warn" +manual_is_variant_and = "warn" +manual_let_else = "warn" +manual_ok_or = "warn" +manual_string_new = "warn" +many_single_char_names = "warn" +map_unwrap_or = "warn" +match_wildcard_for_single_variants = "warn" +maybe_infinite_iter = "warn" +mismatching_type_param_order = "warn" +missing_errors_doc = "allow" # FIXME this triggers 184 times; we should fix most +missing_fields_in_debug = "warn" +missing_panics_doc = "allow" # FIXME this one has 40 triggers +mut_mut = "warn" +naive_bytecount = "warn" +needless_bitwise_bool = "warn" +needless_continue = "warn" +needless_for_each = "warn" +needless_pass_by_value = "warn" +needless_raw_string_hashes = "warn" +no_effect_underscore_binding = "warn" +no_mangle_with_rust_abi = "warn" +option_as_ref_cloned = "warn" +option_option = "warn" +ptr_as_ptr = "warn" +ptr_cast_constness = "warn" +pub_underscore_fields = "warn" +range_minus_one = "warn" +range_plus_one = "warn" +redundant_closure_for_method_calls = "warn" +redundant_else = "warn" +ref_as_ptr = "warn" +ref_binding_to_reference = "warn" +ref_option = "warn" +ref_option_ref = "warn" +return_self_not_must_use = "warn" +same_functions_in_if_condition = "warn" +semicolon_if_nothing_returned = "warn" +should_panic_without_expect = "warn" +single_char_pattern = "warn" +single_match_else = "warn" +stable_sort_primitive = "warn" +str_split_at_newline = "warn" +string_add_assign = "warn" +struct_excessive_bools = "warn" +too_many_lines = "allow" # FIXME 14 triggers for this lint; probably most should be fixed +transmute_ptr_to_ptr = "warn" +trivially_copy_pass_by_ref = "warn" +unchecked_time_subtraction = "warn" +unicode_not_nfc = "warn" +unnecessary_box_returns = "warn" +unnecessary_join = "warn" +unnecessary_literal_bound = "warn" +unnecessary_wraps = "warn" +unnested_or_patterns = "warn" +unreadable_literal = "warn" +unsafe_derive_deserialize = "warn" +unused_async = "warn" +unused_self = "warn" +used_underscore_binding = "warn" +used_underscore_items = "warn" +verbose_bit_mask = "warn" +wildcard_imports = "warn" +zero_sized_map_values = "warn" + +[package.metadata.rbmt.lint] +allowed_duplicates = [ + "bitcoin_hashes", + "hex-conservative", +] + +[package.metadata.rbmt.prerelease] +enabled = true diff --git a/README.md b/README.md index f53f3538..e1c8aaf3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ structures and network messages related to Elements [Documentation](https://docs.rs/elements/) - ## Minimum Supported Rust Version (MSRV) -This library should always compile with any combination of features on **Rust 1.41.1**. +This library should always compile with any combination of features on **Rust 1.74.0**. diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 00000000..c4931941 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,2 @@ +avoid-breaking-exported-api = true +large-error-threshold = 192 # default 128 complains about two public keys in RangeProofMessageError diff --git a/contrib/crates.sh b/contrib/crates.sh new file mode 100755 index 00000000..d3cb8b40 --- /dev/null +++ b/contrib/crates.sh @@ -0,0 +1,9 @@ +# Sourced by `rust-bitcoin-maintainer-tools/ci/run_task.sh`. +# +# No shebang, this file should not be executed. +# shellcheck disable=SC2148 +# +# disable verify unused vars, despite the fact that they are used when sourced +# shellcheck disable=SC2034 + +CRATES=(. elementsd-tests) \ No newline at end of file diff --git a/contrib/extra_tests.sh b/contrib/extra_tests.sh new file mode 100755 index 00000000..404b7505 --- /dev/null +++ b/contrib/extra_tests.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash + +set -ex + +REPO_DIR=$(git rev-parse --show-toplevel) + +# Make all cargo invocations verbose. +export CARGO_TERM_VERBOSE=true + +# Set to false to turn off verbose output. +flag_verbose=true + +main() { + source_test_vars # Get feature list. + + BITCOIND_EXE_DEFAULT="$(git rev-parse --show-toplevel)/elementsd-tests/bin/bitcoind" + ELEMENTSD_EXE_DEFAULT="$(git rev-parse --show-toplevel)/elementsd-tests/bin/elementsd" + + cd elementsd-tests + BITCOIND_EXE=${BITCOIND_EXE:=${BITCOIND_EXE_DEFAULT}} \ + ELEMENTSD_EXE=${ELEMENTSD_EXE:=${ELEMENTSD_EXE_DEFAULT}} \ + cargo --locked test + cd .. +} + +# ShellCheck can't follow non-constant source, `test_vars_script` is correct. +# shellcheck disable=SC1090 +source_test_vars() { + local test_vars_script="$REPO_DIR/contrib/test_vars.sh" + + verbose_say "Sourcing $test_vars_script" + + if [ -e "$test_vars_script" ]; then + # Set crate specific variables. + . "$test_vars_script" + else + err "Missing $test_vars_script" + fi +} + +say() { + echo "extra_tests: $1" +} + +verbose_say() { + if [ "$flag_verbose" = true ]; then + say "$1" + fi +} + +err() { + echo "$1" >&2 + exit 1 +} + +# +# Main script +# +main "$@" +exit 0 diff --git a/contrib/test.sh b/contrib/test.sh deleted file mode 100755 index d13bc25c..00000000 --- a/contrib/test.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -ex - -FEATURES="serde" - -# Use toolchain if explicitly specified -if [ -n "$TOOLCHAIN" ] -then - alias cargo="cargo +$TOOLCHAIN" -fi - -# Test without any features first -cargo test --verbose --no-default-features -# Then test with the default features -cargo test --verbose - -# Also build and run each example to catch regressions -cargo build --examples -# run all examples -run-parts ./target/debug/examples - -# Test each feature -for feature in ${FEATURES} -do - cargo test --verbose --features="$feature" -done - -# Fuzz if told to -if [ "$DO_FUZZ" = true ] -then - ( - cd fuzz - cargo test --verbose - ./travis-fuzz.sh - ) -fi - -# Do integration test if told to -if [ "$DO_INTEGRATION" = true ] -then - ( - cargo test --features integration - ) -fi diff --git a/contrib/test_vars.sh b/contrib/test_vars.sh new file mode 100644 index 00000000..d4c49ec8 --- /dev/null +++ b/contrib/test_vars.sh @@ -0,0 +1,14 @@ +# No shebang, this file should not be executed. +# shellcheck disable=SC2148 +# +# disable verify unused vars, despite the fact that they are used when sourced +# shellcheck disable=SC2034 + +# Test all these features with "std" enabled. +FEATURES_WITH_STD="" + +# Test all these features without "std" enabled. +FEATURES_WITHOUT_STD="json-contract serde base64" + +# Run these examples. +EXAMPLES="$(cargo metadata --no-deps --format-version 1 |jq -r '.packages | .[] | .targets | .[] | select(.kind == ["example"]) | .name + ":"')" diff --git a/elementsd-tests/Cargo.toml b/elementsd-tests/Cargo.toml new file mode 100644 index 00000000..d08aa099 --- /dev/null +++ b/elementsd-tests/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "elementsd-tests" +version = "0.1.0" +authors = ["Andrew Poelstra "] +edition = "2018" +rust-version = "1.74.0" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitcoin = "0.32.2" +elements = {path = "../", features = ["base64"]} +elementsd = "0.11.0" +rand = "0.8" + +[package.metadata.rbmt.lint] +# FIXME the bulk of these are because elementsd/bitcoind is much older than rust-bitcoin. +allowed_duplicates = [ + "bitcoin_hashes", + "bitcoin-internals", + "hex-conservative", + "base64", + "getrandom", + "wasi", + "windows-sys", + "windows-targets", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_aarch64_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_i686_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", + "windows_x86_64_gnu", +] diff --git a/elementsd-tests/bin/bitcoind b/elementsd-tests/bin/bitcoind new file mode 100755 index 00000000..39d71939 Binary files /dev/null and b/elementsd-tests/bin/bitcoind differ diff --git a/elementsd-tests/bin/elementsd b/elementsd-tests/bin/elementsd new file mode 100755 index 00000000..0776b02b Binary files /dev/null and b/elementsd-tests/bin/elementsd differ diff --git a/elementsd-tests/src/lib.rs b/elementsd-tests/src/lib.rs new file mode 100644 index 00000000..8806e583 --- /dev/null +++ b/elementsd-tests/src/lib.rs @@ -0,0 +1,165 @@ +#[cfg(test)] +mod pset; +#[cfg(test)] +mod taproot; + +use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; +use elementsd::bitcoincore_rpc::RpcApi; +#[cfg(test)] +use elementsd::bitcoind::{self, BitcoinD}; +use elementsd::ElementsD; +use std::str::FromStr; + +#[allow(dead_code)] +trait Call { + fn call(&self, cmd: &str, args: &[Value]) -> Value; + fn decode_psbt(&self, psbt: &str) -> Option; + fn get_new_address(&self) -> String; + fn get_pegin_address(&self) -> (String, String); + fn wallet_create_funded_psbt(&self, address: &str) -> String; + fn expected_next(&self, psbt: &str) -> String; + fn wallet_process_psbt(&self, psbt: &str) -> String; + fn finalize_psbt(&self, psbt: &str) -> String; + fn test_mempool_accept(&self, hex: &str) -> bool; + fn get_first_prevout(&self) -> elements::OutPoint; + fn generate(&self, blocks: u32); + fn get_balances(&self) -> Value; + + fn send_to_address(&self, addr: &str, amt: &str) -> String; + fn get_transaction(&self, txid: &str) -> String; + fn get_block_hash(&self, id: u32) -> String; + fn send_raw_transaction(&self, hex: &str) -> String; +} + +impl Call for ElementsD { + fn call(&self, cmd: &str, args: &[Value]) -> Value { + match self.client().call::(cmd, args) { + Ok(v) => v, + Err(e) => panic!("error {} while calling {} with {:?}", e, cmd, args), + } + } + + fn decode_psbt(&self, psbt: &str) -> Option { + self.client() + .call::("decodepsbt", &[psbt.into()]) + .ok() + } + + fn get_new_address(&self) -> String { + self.call("getnewaddress", &[]) + .as_str() + .unwrap() + .to_string() + } + + fn get_pegin_address(&self) -> (String, String) { + let value = self.call("getpeginaddress", &[]); + let mainchain_address = value.get("mainchain_address").unwrap(); + let mainchain_address = mainchain_address.as_str().unwrap().to_string(); + let claim_script = value.get("claim_script").unwrap(); + let claim_script = claim_script.as_str().unwrap().to_string(); + (mainchain_address, claim_script) + } + + fn wallet_create_funded_psbt(&self, address: &str) -> String { + let value = self.call( + "walletcreatefundedpsbt", + &[json!([]), json!([{address.to_string(): "1"}])], + ); + value.get("psbt").unwrap().as_str().unwrap().to_string() + } + + fn expected_next(&self, base64: &str) -> String { + let value = self.call("analyzepsbt", &[base64.into()]); + value.get("next").unwrap().as_str().unwrap().to_string() + } + + fn wallet_process_psbt(&self, base64: &str) -> String { + let value = self.call("walletprocesspsbt", &[base64.into()]); + value.get("psbt").unwrap().as_str().unwrap().to_string() + } + + fn finalize_psbt(&self, base64: &str) -> String { + let value = self.call("finalizepsbt", &[base64.into()]); + value.get("hex").unwrap().as_str().unwrap().to_string() + } + + fn test_mempool_accept(&self, hex: &str) -> bool { + let result = self.call("testmempoolaccept", &[json!([hex])]); + let allowed = result.get(0).unwrap().get("allowed"); + allowed.unwrap().as_bool().unwrap() + } + + fn get_first_prevout(&self) -> elements::OutPoint { + let value = self.call("listunspent", &[]); + let first = value.get(0).unwrap(); + let txid = first.get("txid").unwrap().as_str().unwrap(); + let vout = first.get("vout").unwrap().as_u64().unwrap(); + + elements::OutPoint::new(elements::Txid::from_str(txid).unwrap(), vout as u32) + } + + fn generate(&self, blocks: u32) { + let address = self.get_new_address(); + let _value = self.call("generatetoaddress", &[blocks.into(), address.into()]); + } + + fn get_balances(&self) -> Value { + self.call("getbalances", &[]) + } + + fn get_transaction(&self, txid: &str) -> String { + self.call("gettransaction", &[txid.into()])["hex"] + .as_str() + .unwrap() + .to_string() + } + + fn send_to_address(&self, addr: &str, amt: &str) -> String { + self.call("sendtoaddress", &[addr.into(), amt.into()]) + .as_str() + .unwrap() + .to_string() + } + + fn send_raw_transaction(&self, tx: &str) -> String { + self.call("sendrawtransaction", &[tx.into()]) + .as_str() + .unwrap() + .to_string() + } + + fn get_block_hash(&self, id: u32) -> String { + self.call("getblockhash", &[id.into()]) + .as_str() + .unwrap() + .to_string() + } +} + +#[cfg(test)] +fn setup(validate_pegin: bool) -> (ElementsD, Option) { + let mut bitcoind = None; + if validate_pegin { + let bitcoind_exe = bitcoind::exe_path().unwrap(); + let bitcoind_conf = bitcoind::Conf::default(); + bitcoind = Some(bitcoind::BitcoinD::with_conf(&bitcoind_exe, &bitcoind_conf).unwrap()); + } + + let conf = elementsd::Conf::new(bitcoind.as_ref()); + + let elementsd = ElementsD::with_conf(elementsd::exe_path().unwrap(), &conf).unwrap(); + + let create = elementsd.call("createwallet", &["wallet".into()]); + assert_eq!(create.get("name").unwrap(), "wallet"); + + let rescan = elementsd.call("rescanblockchain", &[]); + assert_eq!(rescan.get("stop_height").unwrap(), 0); + + let balances = elementsd.call("getbalances", &[]); + let mine = balances.get("mine").unwrap(); + let trusted = mine.get("trusted").unwrap(); + assert_eq!(trusted.get("bitcoin").unwrap(), 21.0); + + (elementsd, bitcoind) +} diff --git a/elementsd-tests/src/pset.rs b/elementsd-tests/src/pset.rs new file mode 100644 index 00000000..7b907278 --- /dev/null +++ b/elementsd-tests/src/pset.rs @@ -0,0 +1,164 @@ + +extern crate elements; + +extern crate elementsd; +extern crate rand; + +use crate::{setup, Call}; + +use bitcoin::{self, Address, Amount}; +use elements::encode::serialize; +use elements::hex::DisplayHex as _; +use elements::pset::PartiallySignedTransaction; +use elements::{AssetId, ContractHash}; +use elementsd::bitcoincore_rpc::jsonrpc::serde_json::json; +use elementsd::bitcoincore_rpc::RpcApi; +use elementsd::ElementsD; +use rand::distributions::{Distribution, Uniform}; +use std::str::FromStr; + +#[test] +fn tx_unblinded() { + let (elementsd, _bitcoind) = setup(false); + + let address = elementsd.get_new_address(); + let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); + assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); + psbt_rtt(&elementsd, &psbt_base64); +} + +#[test] +fn tx_blinded() { + let (elementsd, _bitcoind) = setup(false); + + let address = elementsd.get_new_address(); + let psbt_base64 = elementsd.wallet_create_funded_psbt(&address); + assert_eq!(elementsd.expected_next(&psbt_base64), "blinder"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); + psbt_rtt(&elementsd, &psbt_base64); + + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); +} + +#[test] +fn tx_issuance() { + let (elementsd, _bitcoind) = setup(false); + + // Divide out minor and patch version + let is_21 = elementsd.client().version().expect("obtain version") / 10000 == 21; + + let address_asset = elementsd.get_new_address(); + let address_reissuance = elementsd.get_new_address(); + let address_lbtc = elementsd.get_new_address(); + let prevout = elementsd.get_first_prevout(); + + let contract_hash = ContractHash::from_byte_array([0u8; 32]); + let entropy = AssetId::generate_asset_entropy(prevout, contract_hash); + let asset_id = AssetId::from_entropy(entropy); + let reissuance_id = AssetId::reissuance_token_from_entropy(entropy, is_21); + + let value = elementsd.call( + "createpsbt", + &[ + json!([{ "txid": prevout.txid.to_string(), "vout": prevout.vout, "issuance_amount": 1000, "issuance_tokens": 1, "blind_reissuance": is_21}]), + json!([ + {address_asset: "1000", "asset": asset_id.to_string(), "blinder_index": 0}, + {address_reissuance: "1", "asset": reissuance_id.to_string(), "blinder_index": 0}, + {address_lbtc: "20.9", "blinder_index": 0}, + {"fee": "0.1" } + ]), + 0.into(), + false.into(), + ], + ); + let psbt_base64 = value.as_str().unwrap().to_string(); + + assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "finalizer"); + psbt_rtt(&elementsd, &psbt_base64); + + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); +} + +#[test] +#[ignore] // TODO this fails because elements decodepsbt is not printing TxOut::asset (PSET_IN_WITNESS_UTXO) +fn tx_pegin() { + let (elementsd, bitcoind) = setup(true); + let bitcoind = bitcoind.unwrap(); + let btc_addr = bitcoind.client.get_new_address(None, None).unwrap() + .assume_checked(); + let address_lbtc = elementsd.get_new_address(); + bitcoind.client.generate_to_address(101, &btc_addr).unwrap(); + let (pegin_address, claim_script) = elementsd.get_pegin_address(); + let address = Address::from_str(&pegin_address).unwrap() + .assume_checked(); + let amount = Amount::from_sat(100_000_000); + let txid = bitcoind + .client + .send_to_address(&address, amount, None, None, None, None, None, None) + .unwrap(); + let tx = bitcoind.client.get_raw_transaction(&txid, None).unwrap(); + let tx_bytes = bitcoin::consensus::serialize(&tx); + let vout = tx + .output + .iter() + .position(|o| { + let addr = Address::from_script(&o.script_pubkey, bitcoin::Network::Regtest); + addr.unwrap().to_string() == pegin_address + }) + .unwrap(); + + bitcoind.client.generate_to_address(101, &btc_addr).unwrap(); + let proof = bitcoind.client.get_tx_out_proof(&[txid], None).unwrap(); + elementsd.generate(2); + let inputs = json!([ {"txid":txid, "vout": vout,"pegin_bitcoin_tx": tx_bytes.to_lower_hex_string(), "pegin_txout_proof": proof.to_lower_hex_string(), "pegin_claim_script": claim_script } ]); + let outputs = json!([ + {address_lbtc: "0.9", "blinder_index": 0}, + {"fee": "0.1" } + ]); + let value = elementsd.call("createpsbt", &[inputs, outputs, 0.into(), false.into()]); + let psbt_base64 = value.as_str().unwrap().to_string(); + assert_eq!(elementsd.expected_next(&psbt_base64), "updater"); + let psbt_base64 = elementsd.wallet_process_psbt(&psbt_base64); + assert_eq!(elementsd.expected_next(&psbt_base64), "extractor"); + + psbt_rtt(&elementsd, &psbt_base64); + + let tx_hex = elementsd.finalize_psbt(&rtt(&psbt_base64)); + assert!(elementsd.test_mempool_accept(&tx_hex)); +} + +fn rtt(base64: &str) -> String { + let pset: PartiallySignedTransaction = base64.parse().unwrap(); + pset.to_string() +} + +fn psbt_rtt(elementsd: &ElementsD, base64: &str) { + use bitcoin::base64::prelude::{Engine as _, BASE64_STANDARD}; + let a = elementsd.decode_psbt(base64).unwrap(); + + let b_psbt: PartiallySignedTransaction = base64.parse().unwrap(); + let mut b_bytes = serialize(&b_psbt); + let b_base64 = BASE64_STANDARD.encode(&b_bytes); + let b = elementsd.decode_psbt(&b_base64).unwrap(); + + assert_eq!(a, b); + + let mut rng = rand::thread_rng(); + let die = Uniform::from(0..b_bytes.len()); + for _ in 0..1_000 { + let i = die.sample(&mut rng); + // ensuring decode prints all data inside psbt, randomly changing a byte, + // if the results is still decodable it should not be equal to initial value + b_bytes[i] = b_bytes[i].wrapping_add(1); + let base64 = BASE64_STANDARD.encode(&b_bytes); + if let Some(decoded) = elementsd.decode_psbt(&base64) { + assert_ne!(a, decoded, "{} with changed byte {}", b_bytes.as_hex(), i); + } + b_bytes[i] = b_bytes[i].wrapping_sub(1); + } +} diff --git a/tests/taproot.rs b/elementsd-tests/src/taproot.rs similarity index 60% rename from tests/taproot.rs rename to elementsd-tests/src/taproot.rs index be9353e5..364c7230 100644 --- a/tests/taproot.rs +++ b/elementsd-tests/src/taproot.rs @@ -1,54 +1,37 @@ -#![cfg(all(test, feature = "integration"))] - extern crate elements; -extern crate bitcoin; -#[cfg(feature = "integration")] extern crate elementsd; extern crate rand; -use bitcoin::{Amount, XOnlyPublicKey, KeyPair}; -use elements::bitcoin::hashes::hex::FromHex; +use crate::{Call, setup}; + +use bitcoin::key::{XOnlyPublicKey, Keypair}; +use bitcoin::Amount; +use elements::hex; use elements::confidential::{AssetBlindingFactor, ValueBlindingFactor}; use elements::encode::{deserialize, serialize_hex}; -use elements::hashes::Hash; use elements::script::Builder; use elements::secp256k1_zkp; -use elements::sighash::{self, SigHashCache}; +use elements::sighash::{self, SighashCache}; use elements::taproot::{LeafVersion, TapTweakHash, TaprootBuilder, TaprootSpendInfo, TapLeafHash}; use elements::OutPoint; use elements::{ - confidential, opcodes, AssetIssuance, BlockHash, PackedLockTime, SchnorrSig, SchnorrSigHashType, Script, + confidential, opcodes, AssetIssuance, BlockHash, LockTime, SchnorrSig, SchnorrSighashType, Script, Sequence, TxInWitness, TxOut, Txid, }; use elements::{AddressParams, Transaction, TxIn, TxOutSecrets}; -use elementsd::bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; -use elementsd::bitcoincore_rpc::RpcApi; -use elementsd::bitcoind::BitcoinD; -use elementsd::{bitcoind, ElementsD}; +use elementsd::ElementsD; use rand::{rngs, thread_rng}; use secp256k1_zkp::Secp256k1; use std::str::FromStr; static PARAMS: AddressParams = AddressParams::ELEMENTS; -trait Call { - fn call(&self, cmd: &str, args: &[Value]) -> Value; - fn decode_psbt(&self, psbt: &str) -> Option; - fn get_new_address(&self) -> String; - fn send_to_address(&self, addr: &str, amt: &str) -> String; - fn get_transaction(&self, txid: &str) -> String; - fn get_block_hash(&self, id: u32) -> String; - fn test_mempool_accept(&self, hex: &str) -> bool; - fn send_raw_transaction(&self, hex: &str) -> String; - fn generate(&self, blocks: u32); -} - fn gen_keypair( secp: &secp256k1_zkp::Secp256k1, rng: &mut rngs::ThreadRng, -) -> (XOnlyPublicKey, KeyPair) { - let keypair = KeyPair::new(secp, rng); +) -> (XOnlyPublicKey, Keypair) { + let keypair = Keypair::new(secp, rng); let (pk, _) = XOnlyPublicKey::from_keypair(&keypair); (pk, keypair) } @@ -58,10 +41,10 @@ fn gen_keypair( struct TapTxOutData { _blind_sk: Option, _blind_pk: Option, - leaf1_keypair: KeyPair, + leaf1_keypair: Keypair, _leaf1_pk: XOnlyPublicKey, leaf1_script: Script, - internal_keypair: KeyPair, + internal_keypair: Keypair, internal_pk: XOnlyPublicKey, spend_info: TaprootSpendInfo, utxo: TxOut, @@ -85,7 +68,7 @@ fn funded_tap_txout( let (blind_sk, blind_pk) = if blind { let sk = secp256k1_zkp::SecretKey::new(&mut thread_rng()); - let pk = secp256k1_zkp::PublicKey::from_secret_key(&secp, &sk); + let pk = secp256k1_zkp::PublicKey::from_secret_key(secp, &sk); (Some(sk), Some(pk)) } else { (None, None) @@ -117,7 +100,7 @@ fn funded_tap_txout( elementsd.generate(1); let tx_hex = elementsd.get_transaction(&txid_hex); - let tx = deserialize::(&Vec::::from_hex(&tx_hex).unwrap()).unwrap(); + let tx = deserialize::(&hex::decode_to_vec(&tx_hex).unwrap()).unwrap(); let mut outpoint: Option = None; for (i, out) in tx.output.iter().enumerate() { @@ -160,17 +143,17 @@ fn taproot_spend_test( elementsd: &ElementsD, secp: &Secp256k1, genesis_hash: BlockHash, - sighash_ty: SchnorrSigHashType, + sighash_ty: SchnorrSighashType, blind_prevout: bool, blind_tx: bool, key_spend: bool, ) { - let test_data = funded_tap_txout(&elementsd, &secp, blind_prevout); + let test_data = funded_tap_txout(elementsd, secp, blind_prevout); // create a new spend that spends the above output let mut tx = Transaction { version: 2, - lock_time: PackedLockTime::ZERO, + lock_time: LockTime::ZERO, input: vec![], output: vec![], }; @@ -198,18 +181,18 @@ fn taproot_spend_test( if blind_tx { // set the nNonce as some confidential key to mark the output for blinding let sk = secp256k1_zkp::SecretKey::new(&mut thread_rng()); - let pk = secp256k1_zkp::PublicKey::from_secret_key(&secp, &sk); + let pk = secp256k1_zkp::PublicKey::from_secret_key(secp, &sk); tx.output[0].nonce = confidential::Nonce::Confidential(pk); tx.blind( &mut thread_rng(), - &secp, + secp, &[test_data.txout_secrets], false ) .unwrap(); } - let mut cache = SigHashCache::new(&tx); + let mut cache = SighashCache::new(&tx); if key_spend { // test key spend @@ -229,18 +212,18 @@ fn taproot_spend_test( test_data.internal_pk, test_data.spend_info.merkle_root(), ); - let tweak = secp256k1_zkp::Scalar::from_be_bytes(tweak.into_inner()).expect("hash value greater than curve order"); + let tweak = secp256k1_zkp::Scalar::from_be_bytes(tweak.to_byte_array()).expect("hash value greater than curve order"); let sig = secp.sign_schnorr( - &secp256k1_zkp::Message::from_slice(&sighash_msg[..]).unwrap(), - &output_keypair.add_xonly_tweak(&secp, &tweak).unwrap(), + &secp256k1_zkp::Message::from_digest(sighash_msg.to_byte_array()), + &output_keypair.add_xonly_tweak(secp, &tweak).unwrap(), ); let schnorr_sig = SchnorrSig { - sig: sig, + sig, hash_ty: sighash_ty, }; - tx.input[0].witness.script_witness = vec![schnorr_sig.to_vec()]; + tx.input[0].witness.script_witness.push(schnorr_sig.to_vec()); } else { // script spend // try spending using leaf1 @@ -255,7 +238,7 @@ fn taproot_spend_test( .unwrap(); let sig = secp.sign_schnorr( - &secp256k1_zkp::Message::from_slice(&sighash_msg[..]).unwrap(), + &secp256k1_zkp::Message::from_digest(sighash_msg.to_byte_array()), &test_data.leaf1_keypair, ); @@ -263,15 +246,13 @@ fn taproot_spend_test( let ctrl_block = test_data.spend_info.control_block(&script_ver).unwrap(); let schnorr_sig = SchnorrSig { - sig: sig, + sig, hash_ty: sighash_ty, }; - tx.input[0].witness.script_witness = vec![ - schnorr_sig.to_vec(), // witness - script_ver.0.into_bytes(), // leaf script - ctrl_block.serialize(), // control block - ]; + tx.input[0].witness.script_witness.push(schnorr_sig.to_vec()); // witness + tx.input[0].witness.script_witness.push(script_ver.0.into_bytes()); // leaf script + tx.input[0].witness.script_witness.push(ctrl_block.serialize()); // control block } let tx_hex = serialize_hex(&tx); @@ -290,26 +271,26 @@ fn taproot_tests() { let genesis_hash = BlockHash::from_str(&genesis_hash_str).unwrap(); let sighash_tys = [ - SchnorrSigHashType::Default, - SchnorrSigHashType::Single, - SchnorrSigHashType::SinglePlusAnyoneCanPay, - SchnorrSigHashType::None, - SchnorrSigHashType::NonePlusAnyoneCanPay, - SchnorrSigHashType::All, - SchnorrSigHashType::AllPlusAnyoneCanPay, + SchnorrSighashType::Default, + SchnorrSighashType::Single, + SchnorrSighashType::SinglePlusAnyoneCanPay, + SchnorrSighashType::None, + SchnorrSighashType::NonePlusAnyoneCanPay, + SchnorrSighashType::All, + SchnorrSighashType::AllPlusAnyoneCanPay, ]; - for conf_prevout in [true, false] { + for &conf_prevout in &[true, false] { // whether the input is blinded - for blind in [true, false] { + for &blind in &[true, false] { // blind the current tx if !blind && conf_prevout { // trying to spend a confidential txout to all explicit transactions // This is not possible to do because we need to balance the blinding factors continue; } - for script_spend in [true, false] { - for sighash_ty in sighash_tys { + for &script_spend in &[true, false] { + for &sighash_ty in &sighash_tys { taproot_spend_test( &elementsd, &secp, @@ -325,86 +306,3 @@ fn taproot_tests() { } } -impl Call for ElementsD { - fn call(&self, cmd: &str, args: &[Value]) -> Value { - self.client().call::(cmd, args).unwrap() - } - - fn decode_psbt(&self, psbt: &str) -> Option { - self.client() - .call::("decodepsbt", &[psbt.into()]) - .ok() - } - - fn get_new_address(&self) -> String { - self.call("getnewaddress", &[]) - .as_str() - .unwrap() - .to_string() - } - - fn get_transaction(&self, txid: &str) -> String { - self.call("gettransaction", &[txid.into()])["hex"] - .as_str() - .unwrap() - .to_string() - } - - fn send_to_address(&self, addr: &str, amt: &str) -> String { - self.call("sendtoaddress", &[addr.into(), amt.into()]) - .as_str() - .unwrap() - .to_string() - } - - fn send_raw_transaction(&self, tx: &str) -> String { - self.call("sendrawtransaction", &[tx.into()]) - .as_str() - .unwrap() - .to_string() - } - - fn get_block_hash(&self, id: u32) -> String { - self.call("getblockhash", &[id.into()]) - .as_str() - .unwrap() - .to_string() - } - - fn generate(&self, blocks: u32) { - let address = self.get_new_address(); - let _value = self.call("generatetoaddress", &[blocks.into(), address.into()]); - } - - fn test_mempool_accept(&self, hex: &str) -> bool { - let result = self.call("testmempoolaccept", &[json!([hex])]); - let allowed = result.get(0).unwrap().get("allowed"); - allowed.unwrap().as_bool().unwrap() - } -} - -fn setup(validate_pegin: bool) -> (ElementsD, Option) { - let mut bitcoind = None; - if validate_pegin { - let bitcoind_exe = bitcoind::exe_path().unwrap(); - let bitcoind_conf = bitcoind::Conf::default(); - bitcoind = Some(bitcoind::BitcoinD::with_conf(&bitcoind_exe, &bitcoind_conf).unwrap()); - } - - let conf = elementsd::Conf::new(bitcoind.as_ref()); - - let elementsd = ElementsD::with_conf(elementsd::exe_path().unwrap(), &conf).unwrap(); - - let create = elementsd.call("createwallet", &["wallet".into()]); - assert_eq!(create.get("name").unwrap(), "wallet"); - - let rescan = elementsd.call("rescanblockchain", &[]); - assert_eq!(rescan.get("stop_height").unwrap(), 0); - - let balances = elementsd.call("getbalances", &[]); - let mine = balances.get("mine").unwrap(); - let trusted = mine.get("trusted").unwrap(); - assert_eq!(trusted.get("bitcoin").unwrap(), 21.0); - - (elementsd, bitcoind) -} diff --git a/examples/pset_blind_coinjoin.rs b/examples/pset_blind_coinjoin.rs index 6f900de2..0f9931e6 100644 --- a/examples/pset_blind_coinjoin.rs +++ b/examples/pset_blind_coinjoin.rs @@ -1,13 +1,14 @@ //! PSET coinjoin example -//! 1. Person `A` create a transcation with 1 input and 3 outputs(1 fee output) -//! 2. Person `B` takes the transcation from A and adds one input and two outputs +//! 1. Person `A` create a transaction with 1 input and 3 outputs(1 fee output) +//! 2. Person `B` takes the transaction from A and adds one input and two outputs //! which transact another confidential asset //! 3. Person `B` blinds it's own outputs and gives the pset back to A -//! 4. B completly blinds the transaction +//! 4. B completely blinds the transaction //! 5. B signs the blinded Transaction and sends it back to A //! 6. A signs it's input //! 7. A finalizes the pset //! 8. A extracts and broadcasts the transaction +//! //! During the entire interaction, the output blinding factors for A and B are not //! shared with each other. extern crate bitcoin; @@ -17,20 +18,21 @@ extern crate serde_json; use std::{collections::HashMap, str::FromStr}; +use bitcoin::PublicKey; use elements::confidential::{AssetBlindingFactor, ValueBlindingFactor}; use elements::{ - bitcoin::PublicKey, pset::PartiallySignedTransaction as Pset, OutPoint, + pset::PartiallySignedTransaction as Pset, OutPoint, Script, TxOutSecrets, TxOutWitness, Txid, WScriptHash, }; use elements::{pset, secp256k1_zkp}; use elements::encode::{deserialize, serialize_hex}; -use elements::hashes::hex::FromHex; use elements::{confidential, AssetId, TxOut}; +use elements::hex; use rand::SeedableRng; // Assume txouts are simple pay to wpkh -// and keep the secrets correponding to +// and keep the secrets corresponding to // confidential txouts #[derive(Debug, Clone)] struct Secrets { @@ -39,7 +41,7 @@ struct Secrets { } fn deser_pset(psbt_hex: &str) -> Pset { - deserialize::(&Vec::::from_hex(psbt_hex).unwrap()).unwrap() + deserialize::(&hex::decode_to_vec(psbt_hex).unwrap()).unwrap() } fn parse_txout(txout_info: &str) -> (TxOut, Secrets, pset::Input) { @@ -48,38 +50,38 @@ fn parse_txout(txout_info: &str) -> (TxOut, Secrets, pset::Input) { let txout = TxOut { asset: deserialize::( - &Vec::::from_hex(&v["assetcommitment"].as_str().unwrap()).unwrap(), + &hex::decode_to_vec(v["assetcommitment"].as_str().unwrap()).unwrap(), ) .unwrap(), value: deserialize::( - &Vec::::from_hex(&v["amountcommitment"].as_str().unwrap()).unwrap(), + &hex::decode_to_vec(v["amountcommitment"].as_str().unwrap()).unwrap(), ) .unwrap(), nonce: deserialize::( - &Vec::::from_hex(&v["commitmentnonce"].as_str().unwrap()).unwrap(), + &hex::decode_to_vec(v["commitmentnonce"].as_str().unwrap()).unwrap(), ) .unwrap(), - script_pubkey: Script::from_hex(&v["scriptPubKey"].as_str().unwrap()).unwrap(), + script_pubkey: Script::from_hex_no_prefix(v["scriptPubKey"].as_str().unwrap()).unwrap(), witness: TxOutWitness::default(), }; let txoutsecrets = Secrets { - _sk: bitcoin::PrivateKey::from_wif(&v["skwif"].as_str().unwrap()).unwrap(), + _sk: bitcoin::PrivateKey::from_wif(v["skwif"].as_str().unwrap()).unwrap(), sec: TxOutSecrets { - asset_bf: AssetBlindingFactor::from_str(&v["assetblinder"].as_str().unwrap()).unwrap(), - value_bf: ValueBlindingFactor::from_str(&v["amountblinder"].as_str().unwrap()).unwrap(), + asset_bf: AssetBlindingFactor::from_str(v["assetblinder"].as_str().unwrap()).unwrap(), + value_bf: ValueBlindingFactor::from_str(v["amountblinder"].as_str().unwrap()).unwrap(), value: bitcoin::Amount::from_str_in( - &v["amount"].as_str().unwrap(), + v["amount"].as_str().unwrap(), bitcoin::Denomination::Bitcoin, ) .unwrap() .to_sat(), - asset: AssetId::from_hex(&v["asset"].as_str().unwrap()).unwrap(), + asset: AssetId::from_str(v["asset"].as_str().unwrap()).unwrap(), }, }; let inp = pset::Input::from_prevout(OutPoint::new( - Txid::from_str(&v["txid"].as_str().unwrap()).unwrap(), + Txid::from_str(v["txid"].as_str().unwrap()).unwrap(), v["vout"].as_u64().unwrap() as u32, )); diff --git a/examples/raw_blind.rs b/examples/raw_blind.rs index 74a60643..18a0e974 100644 --- a/examples/raw_blind.rs +++ b/examples/raw_blind.rs @@ -5,16 +5,17 @@ extern crate serde_json; use std::{collections::HashMap, str::FromStr}; +use bitcoin::PublicKey; use elements::confidential::{AssetBlindingFactor, ValueBlindingFactor}; use elements::{ - bitcoin::PublicKey, pset::PartiallySignedTransaction as Pset, Address, AddressParams, OutPoint, + pset::PartiallySignedTransaction as Pset, Address, AddressParams, OutPoint, Script, TxOutSecrets, TxOutWitness, Txid, WScriptHash, }; use elements::{pset, secp256k1_zkp, SurjectionInput}; use elements::encode::{deserialize, serialize_hex}; -use elements::hashes::hex::FromHex; use elements::{confidential, AssetId, TxOut}; +use elements::hex; use rand::SeedableRng; /// Pset example workflow: @@ -26,7 +27,7 @@ use rand::SeedableRng; static PARAMS: AddressParams = AddressParams::ELEMENTS; // Assume txouts are simple pay to wpkh -// and keep the secrets correponding to +// and keep the secrets corresponding to // confidential txouts #[derive(Debug, Clone)] struct Secrets { @@ -35,7 +36,7 @@ struct Secrets { } fn deser_pset(psbt_hex: &str) -> Pset { - deserialize::(&Vec::::from_hex(psbt_hex).unwrap()).unwrap() + deserialize::(&hex::decode_to_vec(psbt_hex).unwrap()).unwrap() } fn parse_txout(txout_info: &str) -> (TxOut, Secrets, pset::Input) { @@ -44,38 +45,38 @@ fn parse_txout(txout_info: &str) -> (TxOut, Secrets, pset::Input) { let txout = TxOut { asset: deserialize::( - &Vec::::from_hex(&v["assetcommitment"].as_str().unwrap()).unwrap(), + &hex::decode_to_vec(v["assetcommitment"].as_str().unwrap()).unwrap(), ) .unwrap(), value: deserialize::( - &Vec::::from_hex(&v["amountcommitment"].as_str().unwrap()).unwrap(), + &hex::decode_to_vec(v["amountcommitment"].as_str().unwrap()).unwrap(), ) .unwrap(), nonce: deserialize::( - &Vec::::from_hex(&v["commitmentnonce"].as_str().unwrap()).unwrap(), + &hex::decode_to_vec(v["commitmentnonce"].as_str().unwrap()).unwrap(), ) .unwrap(), - script_pubkey: Script::from_hex(&v["scriptPubKey"].as_str().unwrap()).unwrap(), + script_pubkey: Script::from_hex_no_prefix(v["scriptPubKey"].as_str().unwrap()).unwrap(), witness: TxOutWitness::default(), }; let txoutsecrets = Secrets { - _sk: bitcoin::PrivateKey::from_wif(&v["skwif"].as_str().unwrap()).unwrap(), + _sk: bitcoin::PrivateKey::from_wif(v["skwif"].as_str().unwrap()).unwrap(), sec: TxOutSecrets { - asset_bf: AssetBlindingFactor::from_str(&v["assetblinder"].as_str().unwrap()).unwrap(), - value_bf: ValueBlindingFactor::from_str(&v["amountblinder"].as_str().unwrap()).unwrap(), + asset_bf: AssetBlindingFactor::from_str(v["assetblinder"].as_str().unwrap()).unwrap(), + value_bf: ValueBlindingFactor::from_str(v["amountblinder"].as_str().unwrap()).unwrap(), value: bitcoin::Amount::from_str_in( - &v["amount"].as_str().unwrap(), + v["amount"].as_str().unwrap(), bitcoin::Denomination::Bitcoin, ) .unwrap() .to_sat(), - asset: AssetId::from_hex(&v["asset"].as_str().unwrap()).unwrap(), + asset: AssetId::from_str(v["asset"].as_str().unwrap()).unwrap(), }, }; let inp = pset::Input::from_prevout(OutPoint::new( - Txid::from_str(&v["txid"].as_str().unwrap()).unwrap(), + Txid::from_str(v["txid"].as_str().unwrap()).unwrap(), v["vout"].as_u64().unwrap() as u32, )); @@ -183,7 +184,7 @@ fn main() { &mut rng, &secp, dest_amt, - Address::p2wsh( + &Address::p2wsh( &Script::new_v0_wsh(&dest_wsh), Some(dest_blind_pk.inner), &PARAMS, @@ -205,7 +206,7 @@ fn main() { &mut rng, &secp, change_amt, - Address::p2wsh( + &Address::p2wsh( &Script::new_v0_wsh(&change_wsh), Some(change_blind_pk.inner), &PARAMS, @@ -279,14 +280,14 @@ fn main() { tx.verify_tx_amt_proofs(&secp, &[btc_txout, asset_txout]) .unwrap(); - let inp0_sig = Vec::::from_hex("3044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801").unwrap(); - let inp0_pk = bitcoin::PublicKey::from_str( + let inp0_sig = hex::hex!("3044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801").to_vec(); + let inp0_pk = PublicKey::from_str( "0334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe7", ) .unwrap(); - let inp1_sig = Vec::::from_hex("3044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa01").unwrap(); - let inp1_pk = bitcoin::PublicKey::from_str( + let inp1_sig = hex::hex!("3044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa01").to_vec(); + let inp1_pk = PublicKey::from_str( "03df8f51c053ba0dfb443cce9793b6dc3339ffb0ce97af4792dade3aae1eb890f6", ) .unwrap(); @@ -304,15 +305,13 @@ fn main() { // Finalize(TODO in miniscript) pset.inputs_mut()[0].partial_sigs.clear(); - pset.inputs_mut()[0].final_script_witness = Some(vec![ - inp0_sig, - inp0_pk.to_bytes(), - ]); + let wit = pset.inputs_mut()[0].final_script_witness.insert(elements::Witness::new()); + wit.push(inp0_sig); + wit.push(inp0_pk.to_bytes()); pset.inputs_mut()[1].partial_sigs.clear(); - pset.inputs_mut()[1].final_script_witness = Some(vec![ - inp1_sig, - inp1_pk.to_bytes(), - ]); + let wit = pset.inputs_mut()[1].final_script_witness.insert(elements::Witness::new()); + wit.push(inp1_sig); + wit.push(inp1_pk.to_bytes()); assert_eq!(pset, deser_pset(&tests["finalized"])); // Extracted tx diff --git a/examples/tx.rs b/examples/tx.rs index 0334c5eb..6e889809 100644 --- a/examples/tx.rs +++ b/examples/tx.rs @@ -3,19 +3,20 @@ extern crate bitcoin; extern crate rand; +use std::str::FromStr; + use elements::{Transaction, TxOutWitness}; -use elements::{secp256k1_zkp}; use elements::{AssetId, confidential, TxOut}; -use elements::hashes::hex::FromHex; use elements::encode::deserialize; +use elements::hex; fn main() { let secp = secp256k1_zkp::Secp256k1::new(); - let tx: Transaction = deserialize(&Vec::::from_hex("0200000001015f2d99582ea16cc9451e8f9e19bdfedf43379ad0ad1fafb8ff66d22359017f020000000000fdffffff030a49492a5168e8cd96aa97f60f5b294cec1ca476ba2f8c1b4c87171f274422e99d08b3b32e88a1df9b226722afe3ad85304ac33f011199fd5cc84fa3c952fd4327a90325221684f36be833d1641c5ede4e72ee065ff8e5151604e123116de12e4e44d4160014d567c426a83a1e920a93d3b5cdc0362f80c5a96c0b4c3cf42cf177683e412511dbe585bff4021abab3e3d0a3b7de0e6c9109b0366609bec8fa07871b09ee66577ade9d47bf71189f94dcd31b313c014fb9baae678554035708296db5b49bc05e8e011b0be421f75438b839c6be86f3f9904c64d4515dbd160014a9cef783b24f4df35aaf6331427713acaa4a5d9d01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000000f60000650000000000000043010001c57cafd385e2d848774a87f19409fd61500c10841b5bde34c89b928d81b9a76ffc5c999184465faf5c91377b1edb2d6aaa0168805bed16bff7a065bdbdd60935fd4e1060330000000000000001efe38f01b2cd1dba9db6a3478dae64e5a4210307895791c9bd31d4d4dddda68a10e9a660a93151c6a41bfc9711e07c7a338a135f0dbfe4b3254c43e6a4ae7c0caf0a61ed7263d68c6df68f62c980ba8bcdb7d20d3ebaa924423c6df9658ed73c11e672a9b20adb560fb8de9acdcee91324d0f86242163f17e11352cc336ece4854444fccdad0aab7f8bdc7d50bd248e606ffe612cd040c4837a5b659e34d221cdda13598f12903b3930f4acbba8a7596f4d91635e94aa8857f6c6ccb8b19595fe25a81c0ab9b324333f03309ef797418bc971bc40bf7a1eec55e08785b7dcfdb108a3210eb9002fc77c3a69e368a3ad4b490cdb77b1f82af9e93a665f52b8b20be24c59e7b49535d2708f5f17fb985370d6f636c9178ae382b82f8b77ba287a22dcd057bc465a7d49944e847a480c20cd4ce9c9149a568b221a32c9ec7068da8fa149562a347dcacca61f2dc75eb477c5bed669cdf28b907f827d7f6ba7f22100937a5595556fafd43664a54aecf3d502bbf57458bc74b77ddc7880fad23f4f384fdd965bbb2d3c339864a494b3e8a6cf00e5bb80134e9c815dfbd1bed4dd4c886f76ff50992bd493e58269536548d53876a952c002f58fff90582b8eb1629e885e39b529fdd73b188b2d492674aa56bc8bb4f2d983f86d7e484955e09c2cd42bf2f5c1091a2f675700fc689552e580cf074826e12970c8e1023b9e50c9c4236a904e77b7c9733cb47be4467bfbfec59c2b82be8d5d00383bb4e8ba11691f0fc4853c953979c2fd213abf3fdf523b5258002ea89a0de0db9799d78860580b2fd81e7ceecc0555476fd300f632d12c877f104cc8aac7a1f4c71cba1da9e4d22aafa5bbc4666d5942960399916037b106ff5d527c322eed885429a81fd2ee81edf8b7caf8c2ddc40536f6f800b2d6275dd5df471d7d3aab5285bff6250129661a43ac32ba10d442847365ba7e955d646bf0ae2df07d264a49731493bf14a90199a7f72d37b241b78db1bf8af6d1bbb04064b6bf86ce7c7b543fddaf18d890c57b3fef59d2cc098b174d229d1361b7dfe5b669166b4b3f91e170983fb60c05094bbcc4ea682da6979ab72de6a281f82ab3bdd1f0e9431b70267c101979c7972c329be5694e030f784211a236c49bb38ca77f4389b591a1e2a5e49aaa556ea68b17f45591e4bd38d467356db029ba496b334f22945926ea959f3a07f657f8bcf086c36caaac97cc651a41dc2b53ce9b82a6cf0c126c1fba7f2ad35cdae353994de1456e9539ecf10b5c4eec9ad1ed5e05152dbcfa448c016d3af78794b68322a2b1ff2d1d81b4d0208eb7065c7791cd0f4046505db5a99c0448a3b8fb27bf9ba085d5a4b1b5206e876f6242bd1da01803321a4bcf71676a949996d2b46ccce6f1f1308fbc775051c5e841f243da64b4a91a27a7192d7579a21dabded2798c3b1e99bbd2aca6b7d1f4a3028e0c018aca143bdcd842bbaa8e4ef9d70f12bd7af3f15a6fc0ce958c134fa749b2dcb25a518535239f9dbebc1034f1fd7e74c3808cf53094da2370e518660b73503a787cd0fc87f02ede9a90d0364e07fccaa9238be9ef472b7abf2378676d75cee2d85e640885e28eb5ff0c730944835122065898df69fc37444da4825a4f2524053ba834c277dce029b9a145c7382d94d64df190a4fb1cecc057413643a56f6958f87511a5bb0dccb66cf7a52b6815a6251d9b2def2a532289ab9d5dc50be8d9d399ab858e0495c2c6d487551805ea8a70b70044816ea7f8e51390ab7a23c4a58ceab9a508fff0a66b4d1ada652689a77670572e5c0e0af3b3d9ebd14f507dc1e2e61c22e1ddbcf16ecb760e41343e851240df95e99eb33c8fe84d7509f6abd8a3a42e98dbbc59299d618706ae42c8edba2e1dd08714c446fe12720dc394e80b868c5f1bd8caecea8547410977247ac554d3b6054f6c866f08f692f873a0cc41c801486cc81ef3eaed06cceeaf9706d6b8da55beaeb7ae69151de494864e30e74cc92c8aabdf80e7c589a1e0e1862333810bb3195817ebe8006b7ccc60670ba8ff76f6b2ae767ff0bee689e5c417d1f8cd09a690e8e841a3cfb1f071f6282e3adce2770d1bca0fc02d2cf7c0cabe11b154c1186985a1bbc7a96c6ed5149a0ea6986498fd588bbbffccc453dca3d391d29946cab5832cfd70304a774e509099ba244dc7c044acfad786dbff6f5060ec87b8e5d722d1773a37d6cd363226084848756bf36c81edb4ac676e2abe9f013d12d697cdababfbd1cbbcc3ca7a5efd49d571579c69c0f16498e129acc6aae9c7b95b583683ca10e3490e031a34b0f01db351a151a8d57b78617c6d2501a1e36d6c35cf9f361c5d0becaa991855b4bcb58d2bdd3270042a686072ce551f3cc2dd62fc799b4f71146cc6ccad33cdf4e6ca11992a5e2f824581c7af09da4d4a83b486173025c0e43b4baa38391f76736c99aca3b370d5a80689f1a21790b012096b97a81df2ae2dd2f944630a6654051a4d02927ef414400e8f0de0088387fa2791046a2bf4447256eff6b5da0ceaba87c20f498dfdfb773aa22901bf2d7a5b622ebe2f7a7a8a6ec5a0eb80361c3cfe91e4939d57d32b0c2f678cfd828c0507e75ed4e1afb09a61be08366832d7c2bdd4b1f882e72bfc62fb33a5daec270fc6e30ae8922f81a3afbc5c5e1899ea1485b2f7379452b803cd47c2eb355261e323f07aa5917df219100ebd0e83b4e48530f459961f5b2dfb0f4bcd6fdbfdec88cb7327ffcf4ba88646c332ddb553e55b61ae2ab9ea7dde27a8ac1ce4bb179933c5b1abd1859a9b687d8fbdf34851abf04ea102952b9024b70b1d9ffd83801dc3b733a3f8b33bd7aa3fdb1a8ddc743f3022086ceae8f0f43398f8bf069ff46b24d32deaaaef3d4cb93f150053da8b4aa50d4c4b9415004eb34b98459e9664e8370dfd9c6aa447284a3d350b09f610fa4ac4662f792cbb5478f11637da553a3a52638d7f362281acf3a8901f7ce2dd6cbce21a40a94577608d1cb068ec62717f6927a79a8c768043f16af90e97ad3a466b6115a76137456736da941d51e13e53be95522e01cfb06ee8a102518ff1bf4af8856322463fd5e920c2a1ea2a7fd403a792c0c94121b0c2a1535440ddfc96a5b2ca44ea073a192e9a427ceb4665faf26b1764c6321d592b3157d8153a2cedb5175eaea0cfead8b137f9cda7335c4d642e71bfe5cae223684b1cc83dfcc2ece5505f701be1a3cceda62e78c853b01aa6232c9bc4f7926bbc75b30fa921a3c663bfcf7236b007bcd1edb7d6f8e804e6fbab804fbcd864daaff334698af6a8cc806fc83534e0e95bd5e30e56cee45cde30e0cef551625ba7ce2f4c679d939969e43856d092cf4bad04ec84569d3a46238f8238e88ec3eec0bace677e85d0f0c3ead4bbf8f52ff217362f18fa5e6898d5f3b62e833fb45b9f8b079dd4ac5f17d6f8856ee4787c6639643c4e5772c04bcf66fe3dbbd286afd51d6cdbcac9d786b3806ec618ca1ad4763e2e18955e43c778d22b7fb9acd76ac39864ac2c2728057e46a30485371f5be8e67b400432e71d833ff8304d77de0869d80fbe4d40abf02f3dc3ca4d2135c8b0fecae2da40a8c2149611231c059859b3e5304338a70f5e3303723b5b76c0386f5fe566cf95c78cb2b14d4ba675f3ab7d2db9431c753808b3b123509245d585fab9e0e7f9f1a302739745698953be4868107b8432c96cc082a2e93044a4ed65b4775b06f90208f8589506a702615878a6188b890adb69da7b2495cb55f65b256c8b2babf85f16d94e824cd3760eac63688e341e9a3f5f7ebfbe56d9dce37b90e2fea0bec87e5dde7b8d3a048368eab5ae2cdd7df69341f5213c780ea79598e96f407bd35ad8ae653bca954cc565dc5f9a5e28a821b5cc3ce981457b11eecf8c6ca7e65515a1c8bce3ac2e7303af1ad0e9b1a00983a03fbb0cfe52d85f5beb8bebd969f7ce2c5e57160873beba3a06cda0d99a1b3a887b443d82a6a14a33a69ee64059c65e41b01f6825cdbf73089f3977f02b6008879265720693afd1aeecfb6c050f9f38b75681982b0ae91164ff83cde04410274b2450cb6fcceef45365b2473aeabc075cf4108d94a584aed4fb48afe9c90add923883e27c6c202ca4bf323e8756127d628e09d13df0449006e7d14a73125673ed8a8661a19749e5a8a01d768f500f6fbf1af08a73b7ca99117aca1d521d46e16ba12d953cfc839b17702c6f4a333e6dba3cfe450ed330e48974e751d88fa76d73036b8aeb434375c2f1b65f1a1505ba194eb4f2238d2f4ffa39e9035143de33bbc710f473d6a37e92c7d341e010f2bb545d31b50debbffda7c327be59b9469e240e2b37ca93db3545644d619c2f3173f8f5d646b545c0bf86008933308e8bfc4e1c410d901f2b1e79c74594258113212a737b286a9ac3108ed059661680f11a9d0c8994fa1188ae27339d14595733009eff14607bbb77f407f7b08bcc9973104807a34f52c4ce675c8894b3e43be483713da3440393f6079670dccc7bf4f54d81638bc411c49ec794611bff52742aafc059ee5702e91cc748da13969a1476c904d3fc261ccb022a7409f8f1ae8fbd7c2a8506f3bb2ad84ff55c93998ba367531f1c3b411ac58549a31bf205c102c67351956eab23285cba63601fbfae5498bcdc10cc173937b395e437fcb79b16482b13d824ef597144a73b8f64cf853d9b49cc93c93ff45bf63cca325ad928ceaffd59e5e8762202cdd9f95def9e4ba2d92dbc4d13d25cbc1c7aa482b4ff08e2c92e66acc9ba1475f8ec56c1dfb80af495eedcc03ea0336f18cc76ea057f815e5d2a3ca88fbc293d014862c4b505fc5e7d72876d434029b9c003d97401f604eda90dbcd76350d80529496f55ad8003fbb85f7752df6ceaf9d9ffc3f60fc0bde381fca70306b17837a81e2429c85027a1b377682c0b554a8a78252636384f5559a77573b0070c3baa520d716263b385a0defd90c2384b7ebc564a030c33cd6997b472b43942e063e44817f28f5ca7d5a1ff48d80ab6703600a7dd11395a3f64d8683d47b7c9bb83723134f850b45e3038f9395faab53d6d6302cf9c1a693c86d978addd808807ae9f0dd87ea4e98129a0bd0f0813509764c213eac369692900b35063f0dcbf7bf5961d017bdde3a1ada42c24b71a8ec1f4e49257a35d575327a24ff6049444d4e125257aa7f4c20588d978eadafec231b242b158daa825e6aa5d67066c1f16c004c9e315162aa8c9bbcb254ca8e180c8a177d505b9962b2991f85cf235680df8ea3d531fa393497041736a032c68eee70bf92145b429a0ba825aab62a7e9f023b7c34c7aa4da2251415cafa0badbb046f661ad11718013378697e12eed08f02c21de96c5d0a2fee464b69e02c382f5967a3ce115f18f09e99c9216da22518f510555f44852c4d43cfd4bd4746bb955dec5629cf89c08816ea2d43585b95e5f7beba50200f7cff83d9415476daa7e99853b5fe9440db3aa610ec1f8420a212c5bd55a1f2a5cc674ea0dde9240a9010821b4e6edb312b1f480743bcccffbb8d4716317ac74a2217d6ea49de513838bbddd890a35aea5c5b47ec286389055202da360387bfb2327eb38a2ec5521680fbf7c54b20b0c4d21e2f23046939fe683d63bc60c9451d5985cb3d74ca56ec7d848037298f5acf7f3059ea5f73c5213408b458184d334bca66ddc792c674c626e6e3e2e366b2284b21cd8c134e0783e7494b5278dcc29af3b17d5609c04e1d6bf3f21670befce3fab0f7b2d9b7e6c46656bf423b500df83f2bfd0cc7502db1a3d8320235064708115f45b9ae7810ecaf73304e153662620a247d3d6d2d4251441c4dcdd14fb9b33f2f52f1b4a05fa1cd8e77c3c79178ae0ad961a9737c01a0ec073e4743010001662279b3e4efb9e9b04d9859d348dbdecaf42dcbe0664d7d035851e52630e9f537632b1b090f530aa6a9d3e001feb931eeea6006d339890fbbf481f842a96d05fd4e106033000000000000000198b771005818add879ce29234db70c78e5a9d046b424d5e2cab6e9499bd13df7432a9ee058a2454d33bc1ddef43b73de3a83eff603d0ba0686b127c6eb7f2c02c5d46b6d58a4fe22cbd3fa865bf378465d0133b2fafc47c9aaf56cde01dd065b2457911e889d4bf1f63e1575799ebeed4efa00edb19898b1b3d7e1eac163cfa25550e5ea917f0912d54aa9dd9980aa8757219e94e93325d126fd6f31fc3bb8784ff910cbaf274e46e62d42f1e25b8d78bea8498ebfd3cd52bbc41f971a50bb235d83d014e420a92969a24292aa2692021ebb7717691b1bd0bc81eb80c9ede4174e64352238ccce5efe4ff231db947cdd03b03786042cc7ca7cab2253850abf322cb98f039890a5ab690c0803e0692d2482e03a2cc56ef08f4e1cadf082764775ad8aa85bd380ee8ebedd4115b3a4513f3baa630ce21476ab129668994c7ae936f0b439be17e5a13326081d19ae36211992752e08549632cfeedd4b7effd2e07405baa70ac0cdd6accbc1c8f4b9f5b3d27e2ee3cced0254927d649a61ebec2d67ce51746911512e9c9547365ffe406d3604d67f6d8cad7dea01eebfbb32a986ea7b8b4d78d8df0a368c5da4039f53787369b1be5ed937e8608cd115a7d54c6e3819c34014e0a3b69e192e7f195cb570add2b1c583017834d20f5f2b83038859a2888344eb6ac38a2f9f1e7aa7468454351c9df871c802060525844e72cadcce258469dff954aca0403efa569212e348440b35a94f75fbf237d9508f406f6b88dcb76c46108371c969518f46c32590ce4a527500564e2504401a8b74c5edda5c2037b5e43ba1682916c5e4bedfd559b475e6b9afacd2bf4bc2861497c3154b684dd5669b80c1548114feff221b4ba6fa9630f15a348942a5ff0fc1b6b7bbaf10de7e9268b3f3059895bad350eab5e6678eb379c78b7cca0a19206fe2fb51b9543eafe174af2007badb1a86ac43ee32386150ebc1ab27123dedafa2a3b7e6e6ce4b6c4686529a157fed25c4ae5f7e44e0f63bad17c8d7c88b507280c03e952b7af36962381699150a15dae23aec75d78708bf9aab8f9197d6d3ad97d179d0d7890e16fae101fd57d49d17e090c01ac5b72d53111df514917c735926fe66665b7ac08ad1249f99e1d0fd1bd2c11f32a5edf273dac9a9d904abab6551745f71cdd2bcf5b114c31bec42b4f7078e5da4076e140f6370133b593366786bf10be1dbe9dc411bca1b7ec80587bf7735ecd3e3dbaca3855a9df3b01782ef6c009a916b597ac1b968040c0fb2054c7685ff34851a2f0153ea1f9f0aea6781234b7848148e03fb0f067d77e272ac86a59cbd54a501fa763be2c3bc8f083ad2accbb3d8b2cfb933823510b5b86b7ba22724511eebd7c497dd6b79243c5985054fd01b1328f7dcee2d6e7090bda4763e7d0aca6fa7a283a84be84ab15aa95beb1b4b5d6760f39e42ed72d69f7ab0c6740e92a14ef3490cc5d610bcdf0300a8192a2bba672c3971782ff9847e7a6d479896d87e9d5e95fc1158cf6e30d44697be519ea5d312855e3ee7205cbfc1e496cd8909a46e8e167b3d8bbb61a824340ed8cb434ebca7789ed0430d85fdb7548840e42a63e5d201bb77bb66f9c515fc7ed6d812916962adfd42f621ee50cdfed8b9593d747e094e618c94f8198707c598f1c1ad019aeb7fee68c72e66656f43b56f443014986c0d3c7446500999316ae004082843accea6fb833c893046094dd2b995d84e4cd74789956a5289d6ad4ff2a766d0e7a64d91cc7f7dc6f225fbc27ca0f2d30e6d2ba6105e5e04aa6c261ccc0a0dad14311863c6bb863858707779f6a708c5feb9c55b612452ba50c10472b18b5e0abf69f019f4b90246b2d36795b598a809a4599412623d810c94baca6c369a82dffae8c711fc2d105e92a8d7b4c244f3eecdeca19698e79f83462d6870d0b91fa926331dea367a418be40b1d7d530b1f239b77b893bc5a4525013252cf199a3c66166eeab9e7876cb47a0d6f4cc9df5c83bdc5230a345b44a4c7362e02a091fda5d7ff731d73bd7bbcf3092494eb7e873f976a2015f3631dd0faf76c0f54159dfcb6796fd5144c3e31c998ad5b6028b9182445e9e25faa8a8b5c332a19947d5cb14c2a1dfb76313d711a2eea0ca56c4b4c9edb8cfd089f03672a499e6419fca6914052f7d0176bb2e607c2fc6a3de7e58c3f63ac54f2122af3a9135d5e58b28918b9c481f9f8b94a76c5b2cb790df5e726a6b4c24dd56809d3ba683941fbe3f4334e62d37990aa4b64efa848404cb090cf98d254f0cc8ef48090a46997c64fe4b129ac84663bfc2b0a25a02bc5898a199643af41cbad18631c5db596048cb1bb89f0ea9243ec8f72ee67a6a95eb751ba7f59ded999f2875ca40082a72b62f9bdec52142562d0a48c50730a940db9613596ebbd359237fea5ad701fb2b68bd64f0fbdc2485a8102444fd321777a6e22f093e15dc23eb158035054681053c2035326135e407681a633370138fc265a7cda97825423b08f7955cff40bf4590b38e482bd388741c17a3d79a40662fb41fd760e15f4b58d540fb66b3ad698365c339d8fc6babfb7d3b039af01a840859f9ac7ede34cca3d9fa2efe8eeb7c804b02225d039aa1db5a18dfbb96445742e08e643d897e3db984bce40167997547657432c88c81005d1eba0a5c2b27bbf2fe3f92dfdd0d59a49a1432d2352e912480b8a76b7618576b6db40a57af5d5bf3f2df7bf01e30f8b823a4b714c39082afc4f2f1ec198586ae3db5d5557fb054ab069680736da45187f264ef8d9a45d4b8d0eb9a54f5f074a9c5b6fd8d82550f290e599e3a73c0eac52dd94dc918ae4b985f6863a5783d764662ce64a0d3eafe619e1ffdaa3faf4a6985dfe6a58f7a132ddf896d9a757349b0a51d10432730111c391d926b96fa605714c7dbb1719d868fb7ef6995d63d4d5619653e865dcff21bc8713e6093daab77f2acf5491a66244d9d46335da8a16f63e2d296e83f540f5e4f574f1a5a1500a20a8190b0bbf60f0b401a4a5c333edd38728866bf26ac44ebaf7f41356c8fd60f1bd60112a0d9715c11c9b271d2b7a3cede68f4a88790c47ae1b027700cc9edc1a9a63f8f63b796e9ff2773d5138bc8d1fc98a84a7624b3cd5d04d6a11d4e55e08ad2f69f1f90cbca0a1db3f53008bb85f155385de4bd312af8920b1c63489525b9e0a97bc5eb54b1ef6173af2950559e9f56b24374a61c96458184ceabc34ac24021c26ba82c3e3687a3d8a8e28ec3aa563c94ac9125ae8b0b9881e071b8c862827970028e55cf0c7821411c824a5c4da44fbdee147c86f943b718387c8232291fc844a89aab8466736296b930fe971d0346527dcc71090ae165c7a8e8edc2c72a9af00d5c3fcd52ba9253ac15132045fd311203339e782c6e54ff26e3a950f2430f74961f554593c7c435ee5a79a0a0bef3bdc347ea7faa6b3e4579adafeff126d6ded61b116d20c2fa9e5b020f71395c4d1efc8f18adae9f5326d52962b0862793fc889d984069631cae5d4295aceea308881579e1c313cd09833d67097c247def9cca36eb72a0e069dfb30ec4e14148196b08fd416de071cbac34895c13b754f354af6aeffcb6d64531e48becab49697177ebe29d550c17f22626d9d073d0c40c5151d99ecf52a51cc47d6c2c2bc2087fd94b8ab3452eb3acdd0a2e091fa3fe7baccb70e69348854504562752cb5489fd65765719108e5f6387f231e72a61f33ad1f50580519f9e6693695c767cc7f0a60a9b6927630e874f7ad8a072ad1ea326c8b65d51b0cf247d83acbc6efde4628581bda8449c5278ba69115c6d0e40531cd19783c34ff963204830cadd81e263062521ce8cd1559bcff6fcd4b331238e8620348b494fd9248ef2f364fa9f836abec7b499685ec7d03287042a8ef3d68292d27fafad43c78d3564ec4415e1549471bb4c93957be34315b81ad020ee257fe8e6f8c6b8f429257bbc91015ff376ef698d4236c7d925aabd87fb8c50c737cfd4dd37791b1c43d7d925dda0af5cef8e5828993dff41d4019e61152b20017f18723d6f8589a72ee6aa61df954b11a094dfee7f8de82b4c6e0b8378cdc20965297622ee78b665df2fc9e0a59e2416d8101aab95a72d5ec8c6bacc46b8b052454bed1b1155e323c778b76cc12c4337a0456479e99cd954ee9b7a610ef2fdd85115c8761e48a15ee0dc75a1c44a0595783ee7a3b3f0be27bd901f1118ffd7967aa98fef47180b3c7314a238223aa468b3acd5c568ea74934fdc10b06f42b18b23d7a425cb4180a5f5e76de3f54064df11ff9cee21403be789639ea2946e88ecc81bcb492adf6d7652a600ca07d614a0916c89eee9348ab1e356e153bc37434e37bc7e581bb797251198ffc955e555ee20ce6118f175166ab25e25cbc847b825f969a66085f745eb64ad7ab1e193a7dadf466c29eff0ded9a9a82323f1d0ab88134ada28d3691e98e90ceeaec3369d466e24526e4eee3255e0266318c2851476b98af959eeacafd88994d597c52afd23d22578876cadbe26026e10753aef97ed889dd436d0e9369bafc8cd50dd0b6090cc3944b81063bbada167cebcab6866fb3b87791b6dff1de3b2aab67397df70c60ebefa9389e741fd5337113a1da0d7ccf733348eb91cf40d0c323baa3f692f9b45881efb66edaf4ea7ac6d88b6a93fb7237f6a9bb43902ccdb2ea3e6a342abfaa5e36d9b685bb1a97f0a2b94a69c674935fad7b38d7702bdff95d3aca3895f3b05c70cf1754cacc5980610f09b6e2cb3e89311638fd4b271805efc7486d2c4f0cf0910598427c66627c74ae12f29b63a7ad778e0aaa38f7b1cc23189603d01891ba26c14e4e96c0f7c86e63c73d8d1098661540b4daf2424c89be4991f07ed284a8765d92c6b52c080bdf62bca1428a018352baa32571d8d36da5653a91415dfe8d0c4aa46ed6766f4c162a05ddbccdf14ca9f84e2d487199467f338e81f3f5058bcd22e96a04119d036de1ceb872ca05954e53c86b85626a44856e5685fe35d8217af87349d24650059e00ec7b80d7c0496950a1c827f443db6933a756723cd529c25d7a93e3be32666bf2d20f533d6b3e1378296cafa3b468a3926e7754781923b12d9701bad179da601a623e25f76731a98e93712415496ea84f88dffd243f7ec9a480cc22358bb6705d46be5807e3aa49019e16f86d0c35af6c6e6f7019cc38f5dee215298ae1319bcf248aed114fdb0a5b342b8d250fdbc3bd5ba21e3232a7655bf424eaed8c810b6b1912fa31e37399a56aa33a9a6a76974175ac96b67c43c1ecfeb5de2b0c7b8d4f29993def838ace907acee23a1a7fb52d53078a4d544c858ba18412f152f9596b31a15107e428c96175b5fd49dafda407a0c86bfc9ff6a44217a635bbe6c672c85156f21ec664465ca9e6b9e2e4636aaf6c7d02a9cc28907f5b84d08b1a0a216c8a8494f165a637f5d4edcd0e272390d8704840cd62846db58c4f4c118f19ae3087d9198a05f8b2b256cbe176bc9604df06bda9b7e49d7ded17a059f32b28e1ed47421f32d425004261ce060a2e6af1b7f583f9d7b6e887103df1c359f9d043fdcf91220c57c438fe37b6e606492850ae87dd6f0fc4339777202464dd8ba10e119775ec36daffcca9aebe2208249ffb7068009612a15fd4ee01fb564b567c7c31a5179f88bd1e71d4bd0771b51651ed98ea4bead8deb759c86b2d705d16f4335457b0c9d3b0e9cff91f8df33f265600bd0cc8e8128918456726dfd3cbd8b8c45b21506dab2c49975e10440a0673bee73c2d757f48ea9e4c8de2c4a6eeb55d3127c78b95c2f13b4b0f66c9ddfaf6d28ca7070a6b713d28e8ecb64d175b2a4e8151323f0f9e1769b4ca1228f7d555ed851aacfd6bb782629e6f38aae70000").unwrap()).unwrap(); - let asset_id = elements::hashes::sha256d::Hash::from_hex("b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23").expect("Valid asset id"); - let btc_asset = confidential::Asset::Explicit(AssetId::from_slice(&asset_id).unwrap()); + let tx: Transaction = deserialize(&hex::hex!("0200000001015f2d99582ea16cc9451e8f9e19bdfedf43379ad0ad1fafb8ff66d22359017f020000000000fdffffff030a49492a5168e8cd96aa97f60f5b294cec1ca476ba2f8c1b4c87171f274422e99d08b3b32e88a1df9b226722afe3ad85304ac33f011199fd5cc84fa3c952fd4327a90325221684f36be833d1641c5ede4e72ee065ff8e5151604e123116de12e4e44d4160014d567c426a83a1e920a93d3b5cdc0362f80c5a96c0b4c3cf42cf177683e412511dbe585bff4021abab3e3d0a3b7de0e6c9109b0366609bec8fa07871b09ee66577ade9d47bf71189f94dcd31b313c014fb9baae678554035708296db5b49bc05e8e011b0be421f75438b839c6be86f3f9904c64d4515dbd160014a9cef783b24f4df35aaf6331427713acaa4a5d9d01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000000f60000650000000000000043010001c57cafd385e2d848774a87f19409fd61500c10841b5bde34c89b928d81b9a76ffc5c999184465faf5c91377b1edb2d6aaa0168805bed16bff7a065bdbdd60935fd4e1060330000000000000001efe38f01b2cd1dba9db6a3478dae64e5a4210307895791c9bd31d4d4dddda68a10e9a660a93151c6a41bfc9711e07c7a338a135f0dbfe4b3254c43e6a4ae7c0caf0a61ed7263d68c6df68f62c980ba8bcdb7d20d3ebaa924423c6df9658ed73c11e672a9b20adb560fb8de9acdcee91324d0f86242163f17e11352cc336ece4854444fccdad0aab7f8bdc7d50bd248e606ffe612cd040c4837a5b659e34d221cdda13598f12903b3930f4acbba8a7596f4d91635e94aa8857f6c6ccb8b19595fe25a81c0ab9b324333f03309ef797418bc971bc40bf7a1eec55e08785b7dcfdb108a3210eb9002fc77c3a69e368a3ad4b490cdb77b1f82af9e93a665f52b8b20be24c59e7b49535d2708f5f17fb985370d6f636c9178ae382b82f8b77ba287a22dcd057bc465a7d49944e847a480c20cd4ce9c9149a568b221a32c9ec7068da8fa149562a347dcacca61f2dc75eb477c5bed669cdf28b907f827d7f6ba7f22100937a5595556fafd43664a54aecf3d502bbf57458bc74b77ddc7880fad23f4f384fdd965bbb2d3c339864a494b3e8a6cf00e5bb80134e9c815dfbd1bed4dd4c886f76ff50992bd493e58269536548d53876a952c002f58fff90582b8eb1629e885e39b529fdd73b188b2d492674aa56bc8bb4f2d983f86d7e484955e09c2cd42bf2f5c1091a2f675700fc689552e580cf074826e12970c8e1023b9e50c9c4236a904e77b7c9733cb47be4467bfbfec59c2b82be8d5d00383bb4e8ba11691f0fc4853c953979c2fd213abf3fdf523b5258002ea89a0de0db9799d78860580b2fd81e7ceecc0555476fd300f632d12c877f104cc8aac7a1f4c71cba1da9e4d22aafa5bbc4666d5942960399916037b106ff5d527c322eed885429a81fd2ee81edf8b7caf8c2ddc40536f6f800b2d6275dd5df471d7d3aab5285bff6250129661a43ac32ba10d442847365ba7e955d646bf0ae2df07d264a49731493bf14a90199a7f72d37b241b78db1bf8af6d1bbb04064b6bf86ce7c7b543fddaf18d890c57b3fef59d2cc098b174d229d1361b7dfe5b669166b4b3f91e170983fb60c05094bbcc4ea682da6979ab72de6a281f82ab3bdd1f0e9431b70267c101979c7972c329be5694e030f784211a236c49bb38ca77f4389b591a1e2a5e49aaa556ea68b17f45591e4bd38d467356db029ba496b334f22945926ea959f3a07f657f8bcf086c36caaac97cc651a41dc2b53ce9b82a6cf0c126c1fba7f2ad35cdae353994de1456e9539ecf10b5c4eec9ad1ed5e05152dbcfa448c016d3af78794b68322a2b1ff2d1d81b4d0208eb7065c7791cd0f4046505db5a99c0448a3b8fb27bf9ba085d5a4b1b5206e876f6242bd1da01803321a4bcf71676a949996d2b46ccce6f1f1308fbc775051c5e841f243da64b4a91a27a7192d7579a21dabded2798c3b1e99bbd2aca6b7d1f4a3028e0c018aca143bdcd842bbaa8e4ef9d70f12bd7af3f15a6fc0ce958c134fa749b2dcb25a518535239f9dbebc1034f1fd7e74c3808cf53094da2370e518660b73503a787cd0fc87f02ede9a90d0364e07fccaa9238be9ef472b7abf2378676d75cee2d85e640885e28eb5ff0c730944835122065898df69fc37444da4825a4f2524053ba834c277dce029b9a145c7382d94d64df190a4fb1cecc057413643a56f6958f87511a5bb0dccb66cf7a52b6815a6251d9b2def2a532289ab9d5dc50be8d9d399ab858e0495c2c6d487551805ea8a70b70044816ea7f8e51390ab7a23c4a58ceab9a508fff0a66b4d1ada652689a77670572e5c0e0af3b3d9ebd14f507dc1e2e61c22e1ddbcf16ecb760e41343e851240df95e99eb33c8fe84d7509f6abd8a3a42e98dbbc59299d618706ae42c8edba2e1dd08714c446fe12720dc394e80b868c5f1bd8caecea8547410977247ac554d3b6054f6c866f08f692f873a0cc41c801486cc81ef3eaed06cceeaf9706d6b8da55beaeb7ae69151de494864e30e74cc92c8aabdf80e7c589a1e0e1862333810bb3195817ebe8006b7ccc60670ba8ff76f6b2ae767ff0bee689e5c417d1f8cd09a690e8e841a3cfb1f071f6282e3adce2770d1bca0fc02d2cf7c0cabe11b154c1186985a1bbc7a96c6ed5149a0ea6986498fd588bbbffccc453dca3d391d29946cab5832cfd70304a774e509099ba244dc7c044acfad786dbff6f5060ec87b8e5d722d1773a37d6cd363226084848756bf36c81edb4ac676e2abe9f013d12d697cdababfbd1cbbcc3ca7a5efd49d571579c69c0f16498e129acc6aae9c7b95b583683ca10e3490e031a34b0f01db351a151a8d57b78617c6d2501a1e36d6c35cf9f361c5d0becaa991855b4bcb58d2bdd3270042a686072ce551f3cc2dd62fc799b4f71146cc6ccad33cdf4e6ca11992a5e2f824581c7af09da4d4a83b486173025c0e43b4baa38391f76736c99aca3b370d5a80689f1a21790b012096b97a81df2ae2dd2f944630a6654051a4d02927ef414400e8f0de0088387fa2791046a2bf4447256eff6b5da0ceaba87c20f498dfdfb773aa22901bf2d7a5b622ebe2f7a7a8a6ec5a0eb80361c3cfe91e4939d57d32b0c2f678cfd828c0507e75ed4e1afb09a61be08366832d7c2bdd4b1f882e72bfc62fb33a5daec270fc6e30ae8922f81a3afbc5c5e1899ea1485b2f7379452b803cd47c2eb355261e323f07aa5917df219100ebd0e83b4e48530f459961f5b2dfb0f4bcd6fdbfdec88cb7327ffcf4ba88646c332ddb553e55b61ae2ab9ea7dde27a8ac1ce4bb179933c5b1abd1859a9b687d8fbdf34851abf04ea102952b9024b70b1d9ffd83801dc3b733a3f8b33bd7aa3fdb1a8ddc743f3022086ceae8f0f43398f8bf069ff46b24d32deaaaef3d4cb93f150053da8b4aa50d4c4b9415004eb34b98459e9664e8370dfd9c6aa447284a3d350b09f610fa4ac4662f792cbb5478f11637da553a3a52638d7f362281acf3a8901f7ce2dd6cbce21a40a94577608d1cb068ec62717f6927a79a8c768043f16af90e97ad3a466b6115a76137456736da941d51e13e53be95522e01cfb06ee8a102518ff1bf4af8856322463fd5e920c2a1ea2a7fd403a792c0c94121b0c2a1535440ddfc96a5b2ca44ea073a192e9a427ceb4665faf26b1764c6321d592b3157d8153a2cedb5175eaea0cfead8b137f9cda7335c4d642e71bfe5cae223684b1cc83dfcc2ece5505f701be1a3cceda62e78c853b01aa6232c9bc4f7926bbc75b30fa921a3c663bfcf7236b007bcd1edb7d6f8e804e6fbab804fbcd864daaff334698af6a8cc806fc83534e0e95bd5e30e56cee45cde30e0cef551625ba7ce2f4c679d939969e43856d092cf4bad04ec84569d3a46238f8238e88ec3eec0bace677e85d0f0c3ead4bbf8f52ff217362f18fa5e6898d5f3b62e833fb45b9f8b079dd4ac5f17d6f8856ee4787c6639643c4e5772c04bcf66fe3dbbd286afd51d6cdbcac9d786b3806ec618ca1ad4763e2e18955e43c778d22b7fb9acd76ac39864ac2c2728057e46a30485371f5be8e67b400432e71d833ff8304d77de0869d80fbe4d40abf02f3dc3ca4d2135c8b0fecae2da40a8c2149611231c059859b3e5304338a70f5e3303723b5b76c0386f5fe566cf95c78cb2b14d4ba675f3ab7d2db9431c753808b3b123509245d585fab9e0e7f9f1a302739745698953be4868107b8432c96cc082a2e93044a4ed65b4775b06f90208f8589506a702615878a6188b890adb69da7b2495cb55f65b256c8b2babf85f16d94e824cd3760eac63688e341e9a3f5f7ebfbe56d9dce37b90e2fea0bec87e5dde7b8d3a048368eab5ae2cdd7df69341f5213c780ea79598e96f407bd35ad8ae653bca954cc565dc5f9a5e28a821b5cc3ce981457b11eecf8c6ca7e65515a1c8bce3ac2e7303af1ad0e9b1a00983a03fbb0cfe52d85f5beb8bebd969f7ce2c5e57160873beba3a06cda0d99a1b3a887b443d82a6a14a33a69ee64059c65e41b01f6825cdbf73089f3977f02b6008879265720693afd1aeecfb6c050f9f38b75681982b0ae91164ff83cde04410274b2450cb6fcceef45365b2473aeabc075cf4108d94a584aed4fb48afe9c90add923883e27c6c202ca4bf323e8756127d628e09d13df0449006e7d14a73125673ed8a8661a19749e5a8a01d768f500f6fbf1af08a73b7ca99117aca1d521d46e16ba12d953cfc839b17702c6f4a333e6dba3cfe450ed330e48974e751d88fa76d73036b8aeb434375c2f1b65f1a1505ba194eb4f2238d2f4ffa39e9035143de33bbc710f473d6a37e92c7d341e010f2bb545d31b50debbffda7c327be59b9469e240e2b37ca93db3545644d619c2f3173f8f5d646b545c0bf86008933308e8bfc4e1c410d901f2b1e79c74594258113212a737b286a9ac3108ed059661680f11a9d0c8994fa1188ae27339d14595733009eff14607bbb77f407f7b08bcc9973104807a34f52c4ce675c8894b3e43be483713da3440393f6079670dccc7bf4f54d81638bc411c49ec794611bff52742aafc059ee5702e91cc748da13969a1476c904d3fc261ccb022a7409f8f1ae8fbd7c2a8506f3bb2ad84ff55c93998ba367531f1c3b411ac58549a31bf205c102c67351956eab23285cba63601fbfae5498bcdc10cc173937b395e437fcb79b16482b13d824ef597144a73b8f64cf853d9b49cc93c93ff45bf63cca325ad928ceaffd59e5e8762202cdd9f95def9e4ba2d92dbc4d13d25cbc1c7aa482b4ff08e2c92e66acc9ba1475f8ec56c1dfb80af495eedcc03ea0336f18cc76ea057f815e5d2a3ca88fbc293d014862c4b505fc5e7d72876d434029b9c003d97401f604eda90dbcd76350d80529496f55ad8003fbb85f7752df6ceaf9d9ffc3f60fc0bde381fca70306b17837a81e2429c85027a1b377682c0b554a8a78252636384f5559a77573b0070c3baa520d716263b385a0defd90c2384b7ebc564a030c33cd6997b472b43942e063e44817f28f5ca7d5a1ff48d80ab6703600a7dd11395a3f64d8683d47b7c9bb83723134f850b45e3038f9395faab53d6d6302cf9c1a693c86d978addd808807ae9f0dd87ea4e98129a0bd0f0813509764c213eac369692900b35063f0dcbf7bf5961d017bdde3a1ada42c24b71a8ec1f4e49257a35d575327a24ff6049444d4e125257aa7f4c20588d978eadafec231b242b158daa825e6aa5d67066c1f16c004c9e315162aa8c9bbcb254ca8e180c8a177d505b9962b2991f85cf235680df8ea3d531fa393497041736a032c68eee70bf92145b429a0ba825aab62a7e9f023b7c34c7aa4da2251415cafa0badbb046f661ad11718013378697e12eed08f02c21de96c5d0a2fee464b69e02c382f5967a3ce115f18f09e99c9216da22518f510555f44852c4d43cfd4bd4746bb955dec5629cf89c08816ea2d43585b95e5f7beba50200f7cff83d9415476daa7e99853b5fe9440db3aa610ec1f8420a212c5bd55a1f2a5cc674ea0dde9240a9010821b4e6edb312b1f480743bcccffbb8d4716317ac74a2217d6ea49de513838bbddd890a35aea5c5b47ec286389055202da360387bfb2327eb38a2ec5521680fbf7c54b20b0c4d21e2f23046939fe683d63bc60c9451d5985cb3d74ca56ec7d848037298f5acf7f3059ea5f73c5213408b458184d334bca66ddc792c674c626e6e3e2e366b2284b21cd8c134e0783e7494b5278dcc29af3b17d5609c04e1d6bf3f21670befce3fab0f7b2d9b7e6c46656bf423b500df83f2bfd0cc7502db1a3d8320235064708115f45b9ae7810ecaf73304e153662620a247d3d6d2d4251441c4dcdd14fb9b33f2f52f1b4a05fa1cd8e77c3c79178ae0ad961a9737c01a0ec073e4743010001662279b3e4efb9e9b04d9859d348dbdecaf42dcbe0664d7d035851e52630e9f537632b1b090f530aa6a9d3e001feb931eeea6006d339890fbbf481f842a96d05fd4e106033000000000000000198b771005818add879ce29234db70c78e5a9d046b424d5e2cab6e9499bd13df7432a9ee058a2454d33bc1ddef43b73de3a83eff603d0ba0686b127c6eb7f2c02c5d46b6d58a4fe22cbd3fa865bf378465d0133b2fafc47c9aaf56cde01dd065b2457911e889d4bf1f63e1575799ebeed4efa00edb19898b1b3d7e1eac163cfa25550e5ea917f0912d54aa9dd9980aa8757219e94e93325d126fd6f31fc3bb8784ff910cbaf274e46e62d42f1e25b8d78bea8498ebfd3cd52bbc41f971a50bb235d83d014e420a92969a24292aa2692021ebb7717691b1bd0bc81eb80c9ede4174e64352238ccce5efe4ff231db947cdd03b03786042cc7ca7cab2253850abf322cb98f039890a5ab690c0803e0692d2482e03a2cc56ef08f4e1cadf082764775ad8aa85bd380ee8ebedd4115b3a4513f3baa630ce21476ab129668994c7ae936f0b439be17e5a13326081d19ae36211992752e08549632cfeedd4b7effd2e07405baa70ac0cdd6accbc1c8f4b9f5b3d27e2ee3cced0254927d649a61ebec2d67ce51746911512e9c9547365ffe406d3604d67f6d8cad7dea01eebfbb32a986ea7b8b4d78d8df0a368c5da4039f53787369b1be5ed937e8608cd115a7d54c6e3819c34014e0a3b69e192e7f195cb570add2b1c583017834d20f5f2b83038859a2888344eb6ac38a2f9f1e7aa7468454351c9df871c802060525844e72cadcce258469dff954aca0403efa569212e348440b35a94f75fbf237d9508f406f6b88dcb76c46108371c969518f46c32590ce4a527500564e2504401a8b74c5edda5c2037b5e43ba1682916c5e4bedfd559b475e6b9afacd2bf4bc2861497c3154b684dd5669b80c1548114feff221b4ba6fa9630f15a348942a5ff0fc1b6b7bbaf10de7e9268b3f3059895bad350eab5e6678eb379c78b7cca0a19206fe2fb51b9543eafe174af2007badb1a86ac43ee32386150ebc1ab27123dedafa2a3b7e6e6ce4b6c4686529a157fed25c4ae5f7e44e0f63bad17c8d7c88b507280c03e952b7af36962381699150a15dae23aec75d78708bf9aab8f9197d6d3ad97d179d0d7890e16fae101fd57d49d17e090c01ac5b72d53111df514917c735926fe66665b7ac08ad1249f99e1d0fd1bd2c11f32a5edf273dac9a9d904abab6551745f71cdd2bcf5b114c31bec42b4f7078e5da4076e140f6370133b593366786bf10be1dbe9dc411bca1b7ec80587bf7735ecd3e3dbaca3855a9df3b01782ef6c009a916b597ac1b968040c0fb2054c7685ff34851a2f0153ea1f9f0aea6781234b7848148e03fb0f067d77e272ac86a59cbd54a501fa763be2c3bc8f083ad2accbb3d8b2cfb933823510b5b86b7ba22724511eebd7c497dd6b79243c5985054fd01b1328f7dcee2d6e7090bda4763e7d0aca6fa7a283a84be84ab15aa95beb1b4b5d6760f39e42ed72d69f7ab0c6740e92a14ef3490cc5d610bcdf0300a8192a2bba672c3971782ff9847e7a6d479896d87e9d5e95fc1158cf6e30d44697be519ea5d312855e3ee7205cbfc1e496cd8909a46e8e167b3d8bbb61a824340ed8cb434ebca7789ed0430d85fdb7548840e42a63e5d201bb77bb66f9c515fc7ed6d812916962adfd42f621ee50cdfed8b9593d747e094e618c94f8198707c598f1c1ad019aeb7fee68c72e66656f43b56f443014986c0d3c7446500999316ae004082843accea6fb833c893046094dd2b995d84e4cd74789956a5289d6ad4ff2a766d0e7a64d91cc7f7dc6f225fbc27ca0f2d30e6d2ba6105e5e04aa6c261ccc0a0dad14311863c6bb863858707779f6a708c5feb9c55b612452ba50c10472b18b5e0abf69f019f4b90246b2d36795b598a809a4599412623d810c94baca6c369a82dffae8c711fc2d105e92a8d7b4c244f3eecdeca19698e79f83462d6870d0b91fa926331dea367a418be40b1d7d530b1f239b77b893bc5a4525013252cf199a3c66166eeab9e7876cb47a0d6f4cc9df5c83bdc5230a345b44a4c7362e02a091fda5d7ff731d73bd7bbcf3092494eb7e873f976a2015f3631dd0faf76c0f54159dfcb6796fd5144c3e31c998ad5b6028b9182445e9e25faa8a8b5c332a19947d5cb14c2a1dfb76313d711a2eea0ca56c4b4c9edb8cfd089f03672a499e6419fca6914052f7d0176bb2e607c2fc6a3de7e58c3f63ac54f2122af3a9135d5e58b28918b9c481f9f8b94a76c5b2cb790df5e726a6b4c24dd56809d3ba683941fbe3f4334e62d37990aa4b64efa848404cb090cf98d254f0cc8ef48090a46997c64fe4b129ac84663bfc2b0a25a02bc5898a199643af41cbad18631c5db596048cb1bb89f0ea9243ec8f72ee67a6a95eb751ba7f59ded999f2875ca40082a72b62f9bdec52142562d0a48c50730a940db9613596ebbd359237fea5ad701fb2b68bd64f0fbdc2485a8102444fd321777a6e22f093e15dc23eb158035054681053c2035326135e407681a633370138fc265a7cda97825423b08f7955cff40bf4590b38e482bd388741c17a3d79a40662fb41fd760e15f4b58d540fb66b3ad698365c339d8fc6babfb7d3b039af01a840859f9ac7ede34cca3d9fa2efe8eeb7c804b02225d039aa1db5a18dfbb96445742e08e643d897e3db984bce40167997547657432c88c81005d1eba0a5c2b27bbf2fe3f92dfdd0d59a49a1432d2352e912480b8a76b7618576b6db40a57af5d5bf3f2df7bf01e30f8b823a4b714c39082afc4f2f1ec198586ae3db5d5557fb054ab069680736da45187f264ef8d9a45d4b8d0eb9a54f5f074a9c5b6fd8d82550f290e599e3a73c0eac52dd94dc918ae4b985f6863a5783d764662ce64a0d3eafe619e1ffdaa3faf4a6985dfe6a58f7a132ddf896d9a757349b0a51d10432730111c391d926b96fa605714c7dbb1719d868fb7ef6995d63d4d5619653e865dcff21bc8713e6093daab77f2acf5491a66244d9d46335da8a16f63e2d296e83f540f5e4f574f1a5a1500a20a8190b0bbf60f0b401a4a5c333edd38728866bf26ac44ebaf7f41356c8fd60f1bd60112a0d9715c11c9b271d2b7a3cede68f4a88790c47ae1b027700cc9edc1a9a63f8f63b796e9ff2773d5138bc8d1fc98a84a7624b3cd5d04d6a11d4e55e08ad2f69f1f90cbca0a1db3f53008bb85f155385de4bd312af8920b1c63489525b9e0a97bc5eb54b1ef6173af2950559e9f56b24374a61c96458184ceabc34ac24021c26ba82c3e3687a3d8a8e28ec3aa563c94ac9125ae8b0b9881e071b8c862827970028e55cf0c7821411c824a5c4da44fbdee147c86f943b718387c8232291fc844a89aab8466736296b930fe971d0346527dcc71090ae165c7a8e8edc2c72a9af00d5c3fcd52ba9253ac15132045fd311203339e782c6e54ff26e3a950f2430f74961f554593c7c435ee5a79a0a0bef3bdc347ea7faa6b3e4579adafeff126d6ded61b116d20c2fa9e5b020f71395c4d1efc8f18adae9f5326d52962b0862793fc889d984069631cae5d4295aceea308881579e1c313cd09833d67097c247def9cca36eb72a0e069dfb30ec4e14148196b08fd416de071cbac34895c13b754f354af6aeffcb6d64531e48becab49697177ebe29d550c17f22626d9d073d0c40c5151d99ecf52a51cc47d6c2c2bc2087fd94b8ab3452eb3acdd0a2e091fa3fe7baccb70e69348854504562752cb5489fd65765719108e5f6387f231e72a61f33ad1f50580519f9e6693695c767cc7f0a60a9b6927630e874f7ad8a072ad1ea326c8b65d51b0cf247d83acbc6efde4628581bda8449c5278ba69115c6d0e40531cd19783c34ff963204830cadd81e263062521ce8cd1559bcff6fcd4b331238e8620348b494fd9248ef2f364fa9f836abec7b499685ec7d03287042a8ef3d68292d27fafad43c78d3564ec4415e1549471bb4c93957be34315b81ad020ee257fe8e6f8c6b8f429257bbc91015ff376ef698d4236c7d925aabd87fb8c50c737cfd4dd37791b1c43d7d925dda0af5cef8e5828993dff41d4019e61152b20017f18723d6f8589a72ee6aa61df954b11a094dfee7f8de82b4c6e0b8378cdc20965297622ee78b665df2fc9e0a59e2416d8101aab95a72d5ec8c6bacc46b8b052454bed1b1155e323c778b76cc12c4337a0456479e99cd954ee9b7a610ef2fdd85115c8761e48a15ee0dc75a1c44a0595783ee7a3b3f0be27bd901f1118ffd7967aa98fef47180b3c7314a238223aa468b3acd5c568ea74934fdc10b06f42b18b23d7a425cb4180a5f5e76de3f54064df11ff9cee21403be789639ea2946e88ecc81bcb492adf6d7652a600ca07d614a0916c89eee9348ab1e356e153bc37434e37bc7e581bb797251198ffc955e555ee20ce6118f175166ab25e25cbc847b825f969a66085f745eb64ad7ab1e193a7dadf466c29eff0ded9a9a82323f1d0ab88134ada28d3691e98e90ceeaec3369d466e24526e4eee3255e0266318c2851476b98af959eeacafd88994d597c52afd23d22578876cadbe26026e10753aef97ed889dd436d0e9369bafc8cd50dd0b6090cc3944b81063bbada167cebcab6866fb3b87791b6dff1de3b2aab67397df70c60ebefa9389e741fd5337113a1da0d7ccf733348eb91cf40d0c323baa3f692f9b45881efb66edaf4ea7ac6d88b6a93fb7237f6a9bb43902ccdb2ea3e6a342abfaa5e36d9b685bb1a97f0a2b94a69c674935fad7b38d7702bdff95d3aca3895f3b05c70cf1754cacc5980610f09b6e2cb3e89311638fd4b271805efc7486d2c4f0cf0910598427c66627c74ae12f29b63a7ad778e0aaa38f7b1cc23189603d01891ba26c14e4e96c0f7c86e63c73d8d1098661540b4daf2424c89be4991f07ed284a8765d92c6b52c080bdf62bca1428a018352baa32571d8d36da5653a91415dfe8d0c4aa46ed6766f4c162a05ddbccdf14ca9f84e2d487199467f338e81f3f5058bcd22e96a04119d036de1ceb872ca05954e53c86b85626a44856e5685fe35d8217af87349d24650059e00ec7b80d7c0496950a1c827f443db6933a756723cd529c25d7a93e3be32666bf2d20f533d6b3e1378296cafa3b468a3926e7754781923b12d9701bad179da601a623e25f76731a98e93712415496ea84f88dffd243f7ec9a480cc22358bb6705d46be5807e3aa49019e16f86d0c35af6c6e6f7019cc38f5dee215298ae1319bcf248aed114fdb0a5b342b8d250fdbc3bd5ba21e3232a7655bf424eaed8c810b6b1912fa31e37399a56aa33a9a6a76974175ac96b67c43c1ecfeb5de2b0c7b8d4f29993def838ace907acee23a1a7fb52d53078a4d544c858ba18412f152f9596b31a15107e428c96175b5fd49dafda407a0c86bfc9ff6a44217a635bbe6c672c85156f21ec664465ca9e6b9e2e4636aaf6c7d02a9cc28907f5b84d08b1a0a216c8a8494f165a637f5d4edcd0e272390d8704840cd62846db58c4f4c118f19ae3087d9198a05f8b2b256cbe176bc9604df06bda9b7e49d7ded17a059f32b28e1ed47421f32d425004261ce060a2e6af1b7f583f9d7b6e887103df1c359f9d043fdcf91220c57c438fe37b6e606492850ae87dd6f0fc4339777202464dd8ba10e119775ec36daffcca9aebe2208249ffb7068009612a15fd4ee01fb564b567c7c31a5179f88bd1e71d4bd0771b51651ed98ea4bead8deb759c86b2d705d16f4335457b0c9d3b0e9cff91f8df33f265600bd0cc8e8128918456726dfd3cbd8b8c45b21506dab2c49975e10440a0673bee73c2d757f48ea9e4c8de2c4a6eeb55d3127c78b95c2f13b4b0f66c9ddfaf6d28ca7070a6b713d28e8ecb64d175b2a4e8151323f0f9e1769b4ca1228f7d555ed851aacfd6bb782629e6f38aae70000")).unwrap(); + let asset_id = AssetId::from_str("b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23").expect("Valid asset id"); + let btc_asset = confidential::Asset::Explicit(asset_id); let btc_value = confidential::Value::Explicit(21_000_000 * 100_000_000); let spk = elements::script::Builder::new().push_int(1).into_script(); @@ -31,7 +32,7 @@ fn main() { let res = tx.verify_tx_amt_proofs(&secp, &[txout]); match res { - Ok(_) => {} + Ok(()) => {} Err(e) => { panic!("{}", e); } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 028baa32..5bbfee40 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,38 +1,69 @@ +###### +## DO NOT EDIT THIS FILE DIRECTLY. It is generated by generate-files.sh. +## Edit that script instead and re-run it. +###### [package] name = "elements-fuzz" +edition = "2021" +rust-version = "1.74.0" version = "0.0.1" -authors = ["Automatically generated"] +authors = ["Generated by fuzz/generate-files.sh"] publish = false -edition = "2018" [package.metadata] cargo-fuzz = true -[features] -afl_fuzz = ["afl"] -honggfuzz_fuzz = ["honggfuzz"] - [dependencies] -honggfuzz = { version = "0.5", optional = true, default-features = false } -afl = { version = "0.11", optional = true } -elements = { path = "..", features = ["fuzztarget", "serde"] } +elements = { path = "../", features = [ "serde" ] } +libfuzzer-sys = { version = "0.4.0" } + +[lints.rust] +unexpected_cfgs = { level = "deny", check-cfg = ['cfg(fuzzing)'] } + +[lints.clippy] +redundant_clone = "warn" +use_self = "warn" -# Prevent this from interfering with workspaces -[workspace] -members = ["."] +[package.metadata.rbmt.lint] +allowed_duplicates = [ + "bitcoin_hashes", + "bitcoin-internals", + "hex-conservative", + "getrandom", + "wasi", +] [[bin]] -name = "deserialize_transaction" -path = "fuzz_targets/deserialize_transaction.rs" +name = "decode_equiv_transaction" +path = "fuzz_targets/decode_equiv_transaction.rs" +test = false +doc = false +bench = false [[bin]] name = "deserialize_block" path = "fuzz_targets/deserialize_block.rs" +test = false +doc = false +bench = false [[bin]] name = "deserialize_output" path = "fuzz_targets/deserialize_output.rs" +test = false +doc = false +bench = false [[bin]] name = "deserialize_pset" path = "fuzz_targets/deserialize_pset.rs" +test = false +doc = false +bench = false + +[[bin]] +name = "deserialize_transaction" +path = "fuzz_targets/deserialize_transaction.rs" +test = false +doc = false +bench = false diff --git a/fuzz/README.md b/fuzz/README.md new file mode 100644 index 00000000..001f1ef0 --- /dev/null +++ b/fuzz/README.md @@ -0,0 +1,161 @@ +# Fuzzing + +`rust-bitcoin` has fuzzing harnesses setup for use with +`cargo-fuzz`. + +To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply +run + +```bash +./fuzz.sh +``` + +in this directory. + +By default, `fuzz.sh` runs each target for 100 seconds. Pass +`-max_total_time` to run for longer or shorter: + +```bash +./fuzz.sh -max_total_time=300 +``` + +## Fuzzing with weak cryptography + +You may wish to replace the hashing and signing code with broken crypto, +which will be faster and enable the fuzzer to do otherwise impossible +things such as forging signatures or finding preimages to hashes. + +Doing so may result in spurious bug reports since the broken crypto does +not respect the encoding or algebraic invariants upheld by the real crypto. We +would like to improve this, but it's a nontrivial problem -- though not +beyond the abilities of a motivated student with a few months of time. +Please let us know if you are interested in taking this on! + +Meanwhile, to use the broken crypto, simply compile (and run the fuzzing +scripts) with + +```bash +RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz" +``` + +which will replace the hashing library with broken hashes, and the +`secp256k1` library with broken cryptography. + +Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a +fuzzer can break your crypto, so can anybody. + +## Long-term fuzzing + +To see the full list of targets, the most straightforward way is to run + +```bash +cargo fuzz list +``` + +To run each of them for an hour, run + +```bash +./cycle.sh +``` +This script uses the `chrt` utility to try to reduce the priority of the +jobs. If you would like to run for longer, the most straightforward way +is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel, +you will need to implement a custom harness. + +To run a single fuzztest indefinitely, run + +```bash +cargo +nightly fuzz run "" +``` + +## Adding fuzz tests + +All fuzz tests can be found in the `fuzz_target/` directory. Adding a new +one is as simple as copying an existing one and editing the `do_test` +function to do what you want. + +If your test clearly belongs to a specific crate, please put it in that +crate's directory. Otherwise, you can put it directly in `fuzz_target/`. + +If you need to add dependencies, edit the file `generate-files.sh` to add +it to the generated `Cargo.toml`. + +Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by +running + +```bash +./generate-files.sh +``` + +Then to test your fuzztest, run + +```bash +./fuzz.sh +``` + +If it is working, you will see a rapid stream of data for many seconds +(you can hit Ctrl+C to stop it early) that looks something like this: +```text +INFO: Running with entropic power schedule (0xFF, 100). +INFO: Seed: 2953319389 +INFO: Loaded 1 modules (9121 inline 8-bit counters): 9121 [0x104132ea0, 0x104135241), +INFO: Loaded 1 PC tables (9121 PCs): 9121 [0x104135248,0x104158c58), +INFO: 0 files found in /some/path/to/rust-bitcoin/fuzz/corpus/units_arbitrary_weight +INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes +INFO: A corpus is not provided, starting from an empty corpus +#2 INITED cov: 42 ft: 42 corp: 1/1b exec/s: 0 rss: 36Mb +#411 NEW cov: 43 ft: 43 corp: 2/9b lim: 8 exec/s: 0 rss: 37Mb L: 8/8 MS: 4 ChangeBinInt-ShuffleBytes-ShuffleBytes-InsertRepeatedBytes- +#1329 NEW cov: 43 ft: 44 corp: 3/26b lim: 17 exec/s: 0 rss: 37Mb L: 17/17 MS: 3 InsertRepeatedBytes-CMP-CopyPart- DE: "\001\000\000\000"- +#1357 REDUCE cov: 43 ft: 44 corp: 3/25b lim: 17 exec/s: 0 rss: 37Mb L: 16/16 MS: 3 CopyPart-CMP-EraseBytes- DE: "\000\000\000\000\000\000\000\000"- +... +``` +If you don't see this, you should quickly see an error. + +## Reproducing Failures + +If a fuzztest fails, it will exit with a summary which looks something like +```text +... +thread '' (3001874) panicked at units/src/weight.rs:103:25: +attempt to multiply with overflow +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +==66478== ERROR: libFuzzer: deadly signal + #0 0x0001049fd3c4 in __sanitizer_print_stack_trace+0x28 (librustc-nightly_rt.asan.dylib:arm64+0x5d3c4) + #1 0x000104078b90 in fuzzer::PrintStackTrace()+0x30 (units_arbitrary_weight:arm64+0x100070b90) + #2 0x00010406d074 in fuzzer::Fuzzer::CrashCallback()+0x54 (units_arbitrary_weight:arm64+0x100065074) + #3 0x000180d26740 in _sigtramp+0x34 (libsystem_platform.dylib:arm64+0x3740) + ... +``` +This will tell you where the test failed and is followed by information about how to reproduce the crash. +It will look something like this: + +```text +... +NOTE: libFuzzer has rudimentary signal handlers. + Combine libFuzzer with AddressSanitizer or similar for better crash reports. +SUMMARY: libFuzzer: deadly signal +MS: 2 ChangeByte-CopyPart-; base unit: 25058c6b0d02cd1d71a030ad61c46b7396ddcdb9 +0x5e,0x5e,0x5e,0x5e,0x5e,0x44,0x0,0x0,0x0,0x0,0x0,0x5d,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x5e,0xa,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0x1,0xa5,0x1,0x1,0x1, +^^^^^D\000\000\000\000\000]\001\000\000\000\000\000\000^\012\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\245\001\001\001 +artifact_prefix='/some/path/to/rust-bitcoin/fuzz/artifacts/units_arbitrary_weight/'; Test unit written to /some/path/to/rust-bitcoin/fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 +Base64: Xl5eXl5EAAAAAABdAQAAAAAAAF4KAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBpQEBAQ== +──────────────────────────────────────────────────────────────────────────────── + +Failing input: + + fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 + +Output of `std::fmt::Debug`: + + [94, 94, 94, 94, 94, 68, 0, 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 0, 0, 94, 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 165, 1, 1, 1] + +Reproduce with: + + cargo fuzz run units_arbitrary_weight fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 + +Minimize test case with: + + cargo fuzz tmin units_arbitrary_weight fuzz/artifacts/units_arbitrary_weight/crash-1b454523d38a6c3f45d453dfea4099f3cb574822 + +──────────────────────────────────────────────────────────────────────────────── +``` diff --git a/fuzz/cycle.sh b/fuzz/cycle.sh new file mode 100755 index 00000000..84c1cb6a --- /dev/null +++ b/fuzz/cycle.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash + +# Continuously cycle over fuzz targets running each for 1 hour. +# It uses chrt SCHED_IDLE so that other process takes priority. +# +# For cargo-fuzz usage see https://github.com/rust-fuzz/cargo-fuzz?tab=readme-ov-file#usage + +set -euo pipefail + +REPO_DIR=$(git rev-parse --show-toplevel) +# can't find the file because of the ENV var +# shellcheck source=/dev/null +source "$REPO_DIR/fuzz/fuzz-util.sh" + +while : +do + for targetFile in $(listTargetFiles); do + targetName=$(targetFileToName "$targetFile") + echo "Fuzzing target $targetName ($targetFile)" + + # fuzz for one hour + chrt -i 0 cargo +nightly fuzz run "$targetName" -- -max_total_time=3600 + cargo +nightly fuzz cmin "$targetName" + done +done + diff --git a/fuzz/fuzz-util.sh b/fuzz/fuzz-util.sh new file mode 100755 index 00000000..5b63de9c --- /dev/null +++ b/fuzz/fuzz-util.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +# Sort order is affected by locale. See `man sort`. +# > Set LC_ALL=C to get the traditional sort order that uses native byte values. +export LC_ALL=C + +REPO_DIR=$(git rev-parse --show-toplevel) + +listTargetFiles() { + pushd "$REPO_DIR/fuzz" > /dev/null || exit 1 + find fuzz_targets/ -type f -name "*.rs" | sort + popd > /dev/null || exit 1 +} + +targetFileToName() { + echo "$1" \ + | sed 's/^fuzz_targets\///' \ + | sed 's/\.rs$//' \ + | sed 's/\//_/g' \ + | sed 's/^_//g' +} + +# Utility function to avoid CI failures on Windows +checkWindowsFiles() { + incorrectFilenames=$(find . -type f -name "*,*" -o -name "*:*" -o -name "*<*" -o -name "*>*" -o -name "*|*" -o -name "*\?*" -o -name "*\**" -o -name "*\"*" | wc -l) + if [ "$incorrectFilenames" -gt 0 ]; then + echo "Bailing early because there is a Windows-incompatible filename in the tree." + exit 2 + fi +} + +# Checks whether a fuzz case has artifacts, and dumps them in hex +checkReport() { + artifactDir="fuzz/artifacts/$1" + if [ -d "$artifactDir" ] && [ -n "$(ls -A "$artifactDir" 2>/dev/null)" ]; then + echo "Artifacts found for target: $1" + for artifact in "$artifactDir"/*; do + if [ -f "$artifact" ]; then + echo "Artifact: $(basename "$artifact")" + xxd -p -c10000 < "$artifact" + fi + done + exit 1 + fi +} diff --git a/fuzz/fuzz.sh b/fuzz/fuzz.sh new file mode 100755 index 00000000..3a4aa602 --- /dev/null +++ b/fuzz/fuzz.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# This script is used to briefly fuzz every target when no target is provided. Otherwise, it will briefly fuzz the +# provided target + +set -euox pipefail + +REPO_DIR=$(git rev-parse --show-toplevel) + +# can't find the file because of the ENV var +# shellcheck source=/dev/null +source "$REPO_DIR/fuzz/fuzz-util.sh" + +target= +max_total_time=100 + +for arg in "$@"; do + case "$arg" in + -max_total_time=*) + max_total_time="${arg#-max_total_time=}" + ;; + -*) + echo "Unknown option: $arg" + exit 2 + ;; + *) + if [ -n "$target" ]; then + echo "Unexpected argument: $arg" + exit 2 + fi + target="$arg" + ;; + esac +done + +case "$max_total_time" in + ''|*[!0-9]*) + echo "-max_total_time must be a non-negative integer number of seconds" + exit 2 + ;; +esac + +# Check that input files are correct Windows file names +checkWindowsFiles + +if [ -z "$target" ]; then + targetFiles="$(listTargetFiles)" +else + targetFiles=fuzz_targets/"$target".rs +fi + +cargo --version +rustc --version + +# Testing +cargo install --force --locked --version 0.12.0 cargo-fuzz +for targetFile in $targetFiles; do + targetName=$(targetFileToName "$targetFile") + echo "Fuzzing target $targetName ($targetFile) for $max_total_time seconds" + # cargo-fuzz will check for the corpus at fuzz/corpus/ + cargo +nightly fuzz run "$targetName" -- -max_total_time="$max_total_time" + checkReport "$targetName" +done diff --git a/fuzz/fuzz_targets/decode_equiv_transaction.rs b/fuzz/fuzz_targets/decode_equiv_transaction.rs new file mode 100644 index 00000000..38281db5 --- /dev/null +++ b/fuzz/fuzz_targets/decode_equiv_transaction.rs @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Fuzz test for equivalent decoding between the legacy and consensus-encoding decoding schemes. + +#![cfg_attr(fuzzing, no_main)] +#![cfg_attr(not(fuzzing), allow(unused))] +use libfuzzer_sys::fuzz_target; + +use elements::bitcoin::hex::DisplayHex as _; + +type Target = elements::Transaction; + +#[cfg(not(fuzzing))] +fn main() {} + +struct DisplayError<'e>(&'e dyn std::error::Error); + +impl core::fmt::Display for DisplayError<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "{}", self.0)?; + let mut e = self.0; + while let Some(source) = e.source() { + writeln!(f, "caused by {}", source)?; + e = source; + } + Ok(()) + } +} + +fn do_test(data: &[u8]) { + let old: Result = elements::encode::deserialize(data); + let new: Result = elements::encoding::decode_from_slice(data); + + match (old, new) { + (Err(_), Err(_)) => {}, + (Err(e), Ok(new)) => { + let e = DisplayError(&e); + panic!("Decodable trait failed with {e}; Decode parsed {:?}", new); + } + (Ok(old), Err(e)) => { + let e = DisplayError(&e); + panic!("Decode trait failed with {e}; Decodable parsed {:?}", old); + } + (Ok(old), Ok(new)) => { + assert_eq!( + old, new, + "Decodable (left) did not match Decode (right)", + ); + + let reser_old = elements::encode::serialize(&old); + let reser_new = elements::encoding::encode_to_vec(&new); + assert_eq!( + reser_old, reser_new, + "Encodable (left) did not match Encode (right)\nOld hex: {}\nNew hex: {}\nTransaction: {:?}", + reser_old.as_hex(), + reser_new.as_hex(), + old, + ); + } + } +} + +fuzz_target!(|data: &[u8]| { + do_test(data); +}); + +#[cfg(test)] +mod tests { + #[test] + fn duplicate_crash() { + let v = elements::hex::decode_to_vec("abcd").unwrap(); + super::do_test(&v); + } +} diff --git a/fuzz/fuzz_targets/deserialize_block.rs b/fuzz/fuzz_targets/deserialize_block.rs index 061fec7e..f93a478b 100644 --- a/fuzz/fuzz_targets/deserialize_block.rs +++ b/fuzz/fuzz_targets/deserialize_block.rs @@ -1,5 +1,10 @@ +#![cfg_attr(fuzzing, no_main)] +#![cfg_attr(not(fuzzing), allow(unused))] -extern crate elements; +use libfuzzer_sys::fuzz_target; + +#[cfg(not(fuzzing))] +fn main() {} fn do_test(data: &[u8]) { let block_result: Result = elements::encode::deserialize(data); @@ -12,25 +17,9 @@ fn do_test(data: &[u8]) { } } -#[cfg(feature = "afl")] -extern crate afl; -#[cfg(feature = "afl")] -fn main() { - afl::read_stdio_bytes(|data| { - do_test(&data); - }); -} - -#[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; -#[cfg(feature = "honggfuzz")] -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +fuzz_target!(|data: &[u8]| { + do_test(data); +}); #[cfg(test)] mod tests { @@ -39,9 +28,9 @@ mod tests { for (idx, c) in hex.as_bytes().iter().enumerate() { b <<= 4; match *c { - b'A'...b'F' => b |= c - b'A' + 10, - b'a'...b'f' => b |= c - b'a' + 10, - b'0'...b'9' => b |= c - b'0', + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', _ => panic!("Bad hex"), } if (idx & 1) == 1 { diff --git a/fuzz/fuzz_targets/deserialize_output.rs b/fuzz/fuzz_targets/deserialize_output.rs index 22bc06d9..25255340 100644 --- a/fuzz/fuzz_targets/deserialize_output.rs +++ b/fuzz/fuzz_targets/deserialize_output.rs @@ -1,5 +1,10 @@ +#![cfg_attr(fuzzing, no_main)] +#![cfg_attr(not(fuzzing), allow(unused))] -extern crate elements; +use libfuzzer_sys::fuzz_target; + +#[cfg(not(fuzzing))] +fn main() {} fn do_test(data: &[u8]) { let result: Result = elements::encode::deserialize(data); @@ -18,25 +23,9 @@ fn do_test(data: &[u8]) { } } -#[cfg(feature = "afl")] -extern crate afl; -#[cfg(feature = "afl")] -fn main() { - afl::read_stdio_bytes(|data| { - do_test(&data); - }); -} - -#[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; -#[cfg(feature = "honggfuzz")] -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +fuzz_target!(|data: &[u8]| { + do_test(data); +}); #[cfg(test)] mod tests { @@ -45,9 +34,9 @@ mod tests { for (idx, c) in hex.as_bytes().iter().enumerate() { b <<= 4; match *c { - b'A'...b'F' => b |= c - b'A' + 10, - b'a'...b'f' => b |= c - b'a' + 10, - b'0'...b'9' => b |= c - b'0', + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', _ => panic!("Bad hex"), } if (idx & 1) == 1 { diff --git a/fuzz/fuzz_targets/deserialize_pset.rs b/fuzz/fuzz_targets/deserialize_pset.rs index bf1576a5..e7a26237 100644 --- a/fuzz/fuzz_targets/deserialize_pset.rs +++ b/fuzz/fuzz_targets/deserialize_pset.rs @@ -1,5 +1,10 @@ +#![cfg_attr(fuzzing, no_main)] +#![cfg_attr(not(fuzzing), allow(unused))] -extern crate elements; +use libfuzzer_sys::fuzz_target; + +#[cfg(not(fuzzing))] +fn main() {} fn do_test(data: &[u8]) { let psbt: Result = elements::encode::deserialize(data); @@ -14,25 +19,9 @@ fn do_test(data: &[u8]) { } } -#[cfg(feature = "afl")] -extern crate afl; -#[cfg(feature = "afl")] -fn main() { - afl::read_stdio_bytes(|data| { - do_test(&data); - }); -} - -#[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; -#[cfg(feature = "honggfuzz")] -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +fuzz_target!(|data: &[u8]| { + do_test(data); +}); #[cfg(test)] mod tests { @@ -41,9 +30,9 @@ mod tests { for (idx, c) in hex.as_bytes().iter().enumerate() { b <<= 4; match *c { - b'A'...b'F' => b |= c - b'A' + 10, - b'a'...b'f' => b |= c - b'a' + 10, - b'0'...b'9' => b |= c - b'0', + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', _ => panic!("Bad hex"), } if (idx & 1) == 1 { diff --git a/fuzz/fuzz_targets/deserialize_transaction.rs b/fuzz/fuzz_targets/deserialize_transaction.rs index 25148086..288c627d 100644 --- a/fuzz/fuzz_targets/deserialize_transaction.rs +++ b/fuzz/fuzz_targets/deserialize_transaction.rs @@ -1,5 +1,10 @@ +#![cfg_attr(fuzzing, no_main)] +#![cfg_attr(not(fuzzing), allow(unused))] -extern crate elements; +use libfuzzer_sys::fuzz_target; + +#[cfg(not(fuzzing))] +fn main() {} fn do_test(data: &[u8]) { let tx_result: Result = elements::encode::deserialize(data); @@ -9,14 +14,14 @@ fn do_test(data: &[u8]) { let reser = elements::encode::serialize(&tx); assert_eq!(data, &reser[..]); let len = reser.len(); - let calculated_weight = tx.get_weight(); + let calculated_weight = tx.weight(); for input in &mut tx.input { input.witness = elements::TxInWitness::default(); } for output in &mut tx.output { output.witness = elements::TxOutWitness::default(); } - assert_eq!(tx.has_witness(), false); + assert!(!tx.has_witness()); let no_witness_len = elements::encode::serialize(&tx).len(); assert_eq!(no_witness_len * 3 + len, calculated_weight); @@ -31,25 +36,9 @@ fn do_test(data: &[u8]) { } } -#[cfg(feature = "afl")] -extern crate afl; -#[cfg(feature = "afl")] -fn main() { - afl::read_stdio_bytes(|data| { - do_test(&data); - }); -} - -#[cfg(feature = "honggfuzz")] -#[macro_use] extern crate honggfuzz; -#[cfg(feature = "honggfuzz")] -fn main() { - loop { - fuzz!(|data| { - do_test(data); - }); - } -} +fuzz_target!(|data: &[u8]| { + do_test(data); +}); #[cfg(test)] mod tests { @@ -58,9 +47,9 @@ mod tests { for (idx, c) in hex.as_bytes().iter().enumerate() { b <<= 4; match *c { - b'A'...b'F' => b |= c - b'A' + 10, - b'a'...b'f' => b |= c - b'a' + 10, - b'0'...b'9' => b |= c - b'0', + b'A'..=b'F' => b |= c - b'A' + 10, + b'a'..=b'f' => b |= c - b'a' + 10, + b'0'..=b'9' => b |= c - b'0', _ => panic!("Bad hex"), } if (idx & 1) == 1 { diff --git a/fuzz/generate-files.sh b/fuzz/generate-files.sh new file mode 100755 index 00000000..dc19fb28 --- /dev/null +++ b/fuzz/generate-files.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_DIR=$(git rev-parse --show-toplevel) + +# can't find the file because of the ENV var +# shellcheck source=/dev/null +source "$REPO_DIR/fuzz/fuzz-util.sh" + +# 1. Generate fuzz/Cargo.toml +cat > "$REPO_DIR/fuzz/Cargo.toml" <> "$REPO_DIR/fuzz/Cargo.toml" < "$REPO_DIR/.github/workflows/cron-daily-fuzz.yml" <executed_\${{ matrix.fuzz_target }} + - uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 + with: + name: executed_\${{ matrix.fuzz_target }} + path: executed_\${{ matrix.fuzz_target }} + + verify-execution: + if: \${{ !github.event.act }} + needs: fuzz + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + - uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 + - run: cargo install --locked --version 0.12.0 cargo-fuzz + - name: Display structure of downloaded files + run: ls -R + - run: find executed_* -type f -exec cat {} + | sort > executed + - run: cargo fuzz list | sort | diff - executed +EOF + diff --git a/fuzz/travis-fuzz.sh b/fuzz/travis-fuzz.sh deleted file mode 100755 index 1b099884..00000000 --- a/fuzz/travis-fuzz.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -e -cargo install --force honggfuzz --no-default-features -for TARGET in fuzz_targets/*; do - FILENAME=$(basename $TARGET) - FILE="${FILENAME%.*}" - if [ -d hfuzz_input/$FILE ]; then - HFUZZ_INPUT_ARGS="-f hfuzz_input/$FILE/input" - fi - HFUZZ_BUILD_ARGS="--features honggfuzz_fuzz" HFUZZ_RUN_ARGS="-N100000 --exit_upon_crash -v $HFUZZ_INPUT_ARGS" cargo hfuzz run $FILE - - if [ -f hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT ]; then - cat hfuzz_workspace/$FILE/HONGGFUZZ.REPORT.TXT - for CASE in hfuzz_workspace/$FILE/SIG*; do - cat $CASE | xxd -p - done - exit 1 - fi -done diff --git a/rbmt-version b/rbmt-version new file mode 100644 index 00000000..de57ca2d --- /dev/null +++ b/rbmt-version @@ -0,0 +1 @@ +6560b728ae6a81af9d92713b630ba26772fbd970 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..e9b78f17 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,84 @@ +ignore = [ + "/", + "!/src/lib.rs", + "!/src/confidential/*.rs", + "!/src/transaction/decoders.rs", + "!/src/transaction/encoders.rs", + "!/src/transaction/pegin_witness.rs" +] +hard_tabs = false +tab_spaces = 4 +newline_style = "Auto" +indent_style = "Block" + +max_width = 100 # This is the number of characters. +# `use_small_heuristics` is ignored if the granular width config values are explicitly set. +use_small_heuristics = "Max" # "Max" == All granular width settings same as `max_width`. +# # Granular width configuration settings. These are percentages of `max_width`. +# fn_call_width = 60 +# attr_fn_like_width = 70 +# struct_lit_width = 18 +# struct_variant_width = 35 +# array_width = 60 +# chain_width = 60 +# single_line_if_else_max_width = 50 + +wrap_comments = false +format_code_in_doc_comments = false +comment_width = 100 # Default 80 +normalize_comments = false +normalize_doc_attributes = false +format_strings = false +format_macro_matchers = false +format_macro_bodies = true +hex_literal_case = "Preserve" +empty_item_single_line = true +struct_lit_single_line = true +fn_single_line = true # Default false +where_single_line = false +imports_indent = "Block" +imports_layout = "Mixed" +imports_granularity = "Module" # Default "Preserve" +group_imports = "StdExternalCrate" # Default "Preserve" +reorder_imports = true +reorder_modules = true +reorder_impl_items = false +type_punctuation_density = "Wide" +space_before_colon = false +space_after_colon = true +spaces_around_ranges = false +binop_separator = "Front" +remove_nested_parens = true +combine_control_expr = true +overflow_delimited_expr = false +struct_field_align_threshold = 0 +enum_discrim_align_threshold = 0 +match_arm_blocks = false # Default true +match_arm_leading_pipes = "Never" +force_multiline_blocks = false +fn_params_layout = "Tall" +brace_style = "SameLineWhere" +control_brace_style = "AlwaysSameLine" +trailing_semicolon = true +trailing_comma = "Vertical" +match_block_trailing_comma = false +blank_lines_upper_bound = 1 +blank_lines_lower_bound = 0 +edition = "2021" +style_edition = "2021" +inline_attribute_width = 0 +format_generated_files = true +merge_derives = true +use_try_shorthand = false +use_field_init_shorthand = false +force_explicit_abi = true +condense_wildcard_suffixes = false +color = "Auto" +unstable_features = false +disable_all_formatting = false +skip_children = false +show_parse_errors = true +error_on_line_overflow = false +error_on_unformatted = false +emit_mode = "Files" +make_backup = false diff --git a/src/address.rs b/src/address.rs index d6c2c17d..55ea613c 100644 --- a/src/address.rs +++ b/src/address.rs @@ -15,27 +15,30 @@ //! # Addresses //! +use std::convert::{TryFrom as _, TryInto as _}; use std::error; use std::fmt; +use std::fmt::Write as _; use std::str::FromStr; -use bitcoin::bech32::{self, u5, FromBase32, ToBase32}; -use bitcoin::util::base58; +use bech32::{Bech32, Bech32m, ByteIterExt, Fe32, Fe32IterExt, Hrp}; +use crate::blech32::{Blech32, Blech32m}; +use bitcoin::base58; +use bitcoin::hashes::Hash as _; use bitcoin::PublicKey; -use bitcoin::hashes::Hash; +use internals::array::ArrayExt as _; +use internals::slice::SliceExt; use secp256k1_zkp; use secp256k1_zkp::Secp256k1; use secp256k1_zkp::Verification; #[cfg(feature = "serde")] use serde; -use crate::blech32; - use crate::schnorr::{TapTweak, TweakedPublicKey, UntweakedPublicKey}; -use crate::taproot::TapBranchHash; +use crate::taproot::TapNodeHash; -use crate::{PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash}; use crate::{opcodes, script}; +use crate::{PubkeyHash, ScriptHash, WScriptHash}; /// Encoding error #[derive(Debug, PartialEq)] @@ -43,9 +46,9 @@ pub enum AddressError { /// Base58 encoding error Base58(base58::Error), /// Bech32 encoding error - Bech32(bech32::Error), + Bech32(bech32::primitives::decode::SegwitHrpstringError), /// Blech32 encoding error - Blech32(bech32::Error), + Blech32(crate::blech32::decode::SegwitHrpstringError), /// Was unable to parse the address. InvalidAddress(String), /// Script version must be 0 to 16 inclusive @@ -61,6 +64,24 @@ pub enum AddressError { /// An invalid blinding pubkey was encountered. InvalidBlindingPubKey(secp256k1_zkp::UpstreamError), + + /// The length (in bytes) of the object was not correct. + InvalidLength(usize), + + /// Address version byte were not recognized. + InvalidAddressVersion(u8), +} + +impl From for AddressError { + fn from(e: bech32::primitives::decode::SegwitHrpstringError) -> Self { + AddressError::Bech32(e) + } +} + +impl From for AddressError { + fn from(e: crate::blech32::decode::SegwitHrpstringError) -> Self { + AddressError::Blech32(e) + } } impl fmt::Display for AddressError { @@ -76,10 +97,18 @@ impl fmt::Display for AddressError { write!(f, "invalid witness script version: {}", wver) } AddressError::InvalidWitnessProgramLength(ref len) => { - write!(f, "the witness program must be between 2 and 40 bytes in length, not {}", len) + write!( + f, + "the witness program must be between 2 and 40 bytes in length, not {}", + len + ) } AddressError::InvalidSegwitV0ProgramLength(ref len) => { - write!(f, "a v0 witness program must be length 20 or 32, not {}", len) + write!( + f, + "a v0 witness program must be length 20 or 32, not {}", + len + ) } AddressError::InvalidBlindingPubKey(ref e) => { write!(f, "an invalid blinding pubkey was encountered: {}", e) @@ -90,6 +119,12 @@ impl fmt::Display for AddressError { AddressError::InvalidSegwitV0Encoding => { write!(f, "v0 witness program must use b(l)ech32 not b(l)ech32m") } + AddressError::InvalidLength(len) => { + write!(f, "Address data has invalid length {}", len) + } + AddressError::InvalidAddressVersion(v) => { + write!(f, "address version {} is invalid for this type", v) + } } } } @@ -123,9 +158,9 @@ pub struct AddressParams { /// The base58 prefix for blinded addresses. pub blinded_prefix: u8, /// The bech32 HRP for unblinded segwit addresses. - pub bech_hrp: &'static str, + pub bech_hrp: Hrp, /// The bech32 HRP for blinded segwit addresses. - pub blech_hrp: &'static str, + pub blech_hrp: Hrp, } impl AddressParams { @@ -134,8 +169,8 @@ impl AddressParams { p2pkh_prefix: 57, p2sh_prefix: 39, blinded_prefix: 12, - bech_hrp: "ex", - blech_hrp: "lq", + bech_hrp: Hrp::parse_unchecked("ex"), + blech_hrp: Hrp::parse_unchecked("lq"), }; /// The default Elements network address parameters. @@ -143,8 +178,8 @@ impl AddressParams { p2pkh_prefix: 235, p2sh_prefix: 75, blinded_prefix: 4, - bech_hrp: "ert", - blech_hrp: "el", + bech_hrp: Hrp::parse_unchecked("ert"), + blech_hrp: Hrp::parse_unchecked("el"), }; /// The default liquid testnet network address parameters. @@ -152,13 +187,13 @@ impl AddressParams { p2pkh_prefix: 36, p2sh_prefix: 19, blinded_prefix: 23, - bech_hrp: "tex", - blech_hrp: "tlq", + bech_hrp: Hrp::parse_unchecked("tex"), + blech_hrp: Hrp::parse_unchecked("tlq"), }; } /// The method used to produce an address -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Payload { /// pay-to-pkhash address PubkeyHash(PubkeyHash), @@ -167,14 +202,14 @@ pub enum Payload { /// Segwit address WitnessProgram { /// The segwit version. - version: u5, + version: Fe32, /// The segwit program. program: Vec, }, } /// An Elements address. -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Clone, PartialEq, Eq, Hash)] pub struct Address { /// the network pub params: &'static AddressParams, @@ -190,6 +225,11 @@ impl Address { self.blinding_pubkey.is_some() } + /// Return if the address is for the Liquid network + pub fn is_liquid(&self) -> bool { + self.params == &AddressParams::LIQUID + } + /// Creates a pay to (compressed) public key hash address from a public key /// This is the preferred non-witness type address #[inline] @@ -198,12 +238,9 @@ impl Address { blinder: Option, params: &'static AddressParams, ) -> Address { - let mut hash_engine = PubkeyHash::engine(); - pk.write_into(&mut hash_engine).expect("engines don't error"); - Address { params, - payload: Payload::PubkeyHash(PubkeyHash::from_engine(hash_engine)), + payload: Payload::PubkeyHash(pk.pubkey_hash()), blinding_pubkey: blinder, } } @@ -218,26 +255,27 @@ impl Address { ) -> Address { Address { params, - payload: Payload::ScriptHash(ScriptHash::hash(&script[..])), + payload: Payload::ScriptHash(ScriptHash::hash_script(script)), blinding_pubkey: blinder, } } /// Create a witness pay to public key address from a public key /// This is the native segwit address type for an output redeemable with a single signature + /// + /// # Panics + /// + /// Panics if the provided public key is not compressed. pub fn p2wpkh( pk: &PublicKey, blinder: Option, params: &'static AddressParams, ) -> Address { - let mut hash_engine = WPubkeyHash::engine(); - pk.write_into(&mut hash_engine).expect("engines don't error"); - Address { params, payload: Payload::WitnessProgram { - version: u5::try_from_u8(0).expect("0<32"), - program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(), + version: Fe32::Q, + program: pk.wpubkey_hash().expect("public key must be compressed").as_byte_array().to_vec(), }, blinding_pubkey: blinder, } @@ -245,21 +283,23 @@ impl Address { /// Create a pay to script address that embeds a witness pay to public key /// This is a segwit address type that looks familiar (as p2sh) to legacy clients + /// + /// # Panics + /// + /// Panics if the provided public key is not compressed. pub fn p2shwpkh( pk: &PublicKey, blinder: Option, params: &'static AddressParams, ) -> Address { - let mut hash_engine = ScriptHash::engine(); - pk.write_into(&mut hash_engine).expect("engines don't error"); - + let pkh = pk.wpubkey_hash().expect("public key must be compressed"); let builder = script::Builder::new() .push_int(0) - .push_slice(&ScriptHash::from_engine(hash_engine)[..]); + .push_slice(pkh.as_ref()); Address { params, - payload: Payload::ScriptHash(ScriptHash::hash(builder.into_script().as_bytes())), + payload: Payload::ScriptHash(ScriptHash::hash_script(&builder.into_script())), blinding_pubkey: blinder, } } @@ -273,8 +313,8 @@ impl Address { Address { params, payload: Payload::WitnessProgram { - version: u5::try_from_u8(0).expect("0<32"), - program: WScriptHash::hash(&script[..])[..].to_vec(), + version: Fe32::Q, + program: WScriptHash::hash_script(script).as_byte_array().to_vec(), }, blinding_pubkey: blinder, } @@ -289,12 +329,12 @@ impl Address { ) -> Address { let ws = script::Builder::new() .push_int(0) - .push_slice(&WScriptHash::hash(&script[..])[..]) + .push_slice(WScriptHash::hash_script(script).as_ref()) .into_script(); Address { params, - payload: Payload::ScriptHash(ScriptHash::hash(&ws[..])), + payload: Payload::ScriptHash(ScriptHash::hash_script(&ws)), blinding_pubkey: blinder, } } @@ -303,7 +343,7 @@ impl Address { pub fn p2tr( secp: &Secp256k1, internal_key: UntweakedPublicKey, - merkle_root: Option, + merkle_root: Option, blinder: Option, params: &'static AddressParams, ) -> Address { @@ -312,7 +352,7 @@ impl Address { payload: { let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root); Payload::WitnessProgram { - version: u5::try_from_u8(1).expect("0<32"), + version: Fe32::P, program: output_key.into_inner().serialize().to_vec(), } }, @@ -331,7 +371,7 @@ impl Address { Address { params, payload: Payload::WitnessProgram { - version: u5::try_from_u8(1).expect("0<32"), + version: Fe32::P, program: output_key.into_inner().serialize().to_vec(), }, blinding_pubkey: blinder, @@ -346,22 +386,22 @@ impl Address { ) -> Option
{ Some(Address { payload: if script.is_p2pkh() { - Payload::PubkeyHash(Hash::from_slice(&script.as_bytes()[3..23]).unwrap()) + Payload::PubkeyHash(PubkeyHash::from_byte_array(script.as_bytes()[3..23].try_into().unwrap())) } else if script.is_p2sh() { - Payload::ScriptHash(Hash::from_slice(&script.as_bytes()[2..22]).unwrap()) + Payload::ScriptHash(ScriptHash::from_byte_array(script.as_bytes()[2..22].try_into().unwrap())) } else if script.is_v0_p2wpkh() { Payload::WitnessProgram { - version: u5::try_from_u8(0).expect("0<32"), + version: Fe32::Q, program: script.as_bytes()[2..22].to_vec(), } } else if script.is_v0_p2wsh() { Payload::WitnessProgram { - version: u5::try_from_u8(0).expect("0<32"), + version: Fe32::Q, program: script.as_bytes()[2..34].to_vec(), } } else if script.is_v1plus_p2witprog() { Payload::WitnessProgram { - version: u5::try_from_u8(script.as_bytes()[0] - 0x50).expect("0<32"), + version: Fe32::try_from(script.as_bytes()[0] - 0x50).expect("0<32"), program: script.as_bytes()[2..].to_vec(), } } else { @@ -378,22 +418,25 @@ impl Address { Payload::PubkeyHash(ref hash) => script::Builder::new() .push_opcode(opcodes::all::OP_DUP) .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) + .push_slice(hash.as_ref()) .push_opcode(opcodes::all::OP_EQUALVERIFY) .push_opcode(opcodes::all::OP_CHECKSIG), Payload::ScriptHash(ref hash) => script::Builder::new() .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&hash[..]) + .push_slice(hash.as_byte_array()) .push_opcode(opcodes::all::OP_EQUAL), Payload::WitnessProgram { version: witver, program: ref witprog, - } => script::Builder::new().push_int(witver.to_u8() as i64).push_slice(&witprog), + } => script::Builder::new() + .push_int(i64::from(witver.to_u8())) + .push_slice(witprog), } .into_script() } /// Convert this address to an unconfidential address. + #[must_use] pub fn to_unconfidential(&self) -> Address { Address { params: self.params, @@ -403,6 +446,7 @@ impl Address { } /// Convert this address to a confidential address with the given blinding pubkey. + #[must_use] pub fn to_confidential(&self, blinding_pubkey: secp256k1_zkp::PublicKey) -> Address { Address { params: self.params, @@ -416,113 +460,71 @@ impl Address { blinded: bool, params: &'static AddressParams, ) -> Result { - let (payload, is_bech32m) = if !blinded { - let (_, payload, variant) = bech32::decode(s).map_err(AddressError::Bech32)?; - (payload, variant == bech32::Variant::Bech32m) + let (version, data): (Fe32, Vec) = if blinded { + let hs = crate::blech32::decode::SegwitHrpstring::new(s)?; + (hs.witness_version(), hs.byte_iter().collect()) } else { - let (_, payload, variant) = blech32::decode(s).map_err(AddressError::Blech32)?; - (payload, variant == blech32::Variant::Blech32m) - }; - - if payload.is_empty() { - return Err(AddressError::InvalidAddress(s.to_owned())); - } - - // Get the script version and program (converted from 5-bit to 8-bit) - let (version, data) = { - let (v, p5) = payload.split_at(1); - let data_res = Vec::from_base32(p5); - if let Err(e) = data_res { - return Err(match blinded { - true => AddressError::Blech32(e), - false => AddressError::Bech32(e), - }); - } - (v[0], data_res.unwrap()) + let hs = bech32::primitives::decode::SegwitHrpstring::new(s)?; + (hs.witness_version(), hs.byte_iter().collect()) }; - // Generic segwit checks. - if version.to_u8() > 16 { - return Err(AddressError::InvalidWitnessVersion(version.to_u8())); - } - if data.len() < 2 || data.len() > 40 + if blinded { 33 } else { 0 } { - return Err(AddressError::InvalidWitnessProgramLength(data.len() - if blinded { 33 } else { 0 })); - } - - // Specific segwit v0 check. - if !blinded && version.to_u8() == 0 && data.len() != 20 && data.len() != 32 { - return Err(AddressError::InvalidSegwitV0ProgramLength(data.len())); - } - if blinded && version.to_u8() == 0 && data.len() != 53 && data.len() != 65 { - return Err(AddressError::InvalidSegwitV0ProgramLength(data.len() - 33)); - } - - if version.to_u8() == 0 && is_bech32m { - return Err(AddressError::InvalidSegwitV0Encoding); - } else if version.to_u8() > 0 && !is_bech32m { - return Err(AddressError::InvalidWitnessEncoding); - } - let (blinding_pubkey, program) = match blinded { - true => ( + true => { + let (pk, rest) = SliceExt::split_first_chunk::<33>(data.as_slice()) + .ok_or(AddressError::InvalidSegwitV0Encoding)?; + ( Some( - secp256k1_zkp::PublicKey::from_slice(&data[..33]) + secp256k1_zkp::PublicKey::from_slice(pk) .map_err(AddressError::InvalidBlindingPubKey)?, - ), - data[33..].to_vec(), - ), + ), + rest.to_vec(), + ) + }, false => (None, data), }; Ok(Address { params, - payload: Payload::WitnessProgram { - version, - program, - }, + payload: Payload::WitnessProgram { version, program }, blinding_pubkey, }) } // data.len() should be >= 1 when this method is called fn from_base58(data: &[u8], params: &'static AddressParams) -> Result { + let len_error = AddressError::InvalidLength(data.len()); // When unblinded, the structure is: // <1: regular prefix> <20: hash160> // When blinded, the structure is: // <1: blinding prefix> <1: regular prefix> <33: blinding pubkey> <20: hash160> - let (blinded, prefix) = match data[0] == params.blinded_prefix { - true => { - if data.len() != 55 { - return Err(base58::Error::InvalidLength(data.len()).into()); - } - (true, data[1]) - } - false => { - if data.len() != 21 { - return Err(base58::Error::InvalidLength(data.len()).into()); - } - (false, data[0]) - } + let Some((blinding_prefix, blinded_data)) = data.split_first() else { + return Err(len_error); }; - let (blinding_pubkey, payload_data) = match blinded { - true => ( - Some( - secp256k1_zkp::PublicKey::from_slice(&data[2..35]) - .map_err(AddressError::InvalidBlindingPubKey)?, - ), - &data[35..], - ), - false => (None, &data[1..]), + let (prefix, blinding_pubkey, hash) = if *blinding_prefix == params.blinded_prefix { + let Some((prefix, pubkey_and_hash)) = blinded_data.split_first() else { + return Err(len_error); + }; + + let pubkey_and_hash = <&[u8; 53]>::try_from(pubkey_and_hash).map_err(|_| len_error)?; + let (pubkey, hash) = pubkey_and_hash.split_array::<33, 20>(); + + let blinding_pubkey = secp256k1_zkp::PublicKey::from_slice(pubkey) + .map_err(AddressError::InvalidBlindingPubKey)?; + + (prefix, Some(blinding_pubkey), hash) + } else { + let hash = <&[u8; 20]>::try_from(blinded_data).map_err(|_| len_error)?; + (blinding_prefix, None, hash) }; - let payload = if prefix == params.p2pkh_prefix { - Payload::PubkeyHash(PubkeyHash::from_slice(payload_data).unwrap()) - } else if prefix == params.p2sh_prefix { - Payload::ScriptHash(ScriptHash::from_slice(payload_data).unwrap()) + let payload = if *prefix == params.p2pkh_prefix { + Payload::PubkeyHash(PubkeyHash::from_byte_array(*hash)) + } else if *prefix == params.p2sh_prefix { + Payload::ScriptHash(ScriptHash::from_byte_array(*hash)) } else { - return Err(base58::Error::InvalidAddressVersion(prefix).into()); + return Err(AddressError::InvalidAddressVersion(*prefix)); }; Ok(Address { @@ -533,7 +535,7 @@ impl Address { } /// Parse the address using the given parameters. - /// When using the built-in parameters, you can use [FromStr]. + /// When using the built-in parameters, you can use [`FromStr`]. pub fn parse_with_params( s: &str, params: &'static AddressParams, @@ -548,9 +550,9 @@ impl Address { // Base58. if s.len() > 150 { - return Err(base58::Error::InvalidLength(s.len() * 11 / 15).into()); + return Err(AddressError::InvalidLength(s.len() * 11 / 15)); } - let data = base58::from_check(s)?; + let data = base58::decode_check(s)?; Address::from_base58(&data, params) } } @@ -564,13 +566,13 @@ impl fmt::Display for Address { prefixed[0] = self.params.blinded_prefix; prefixed[1] = self.params.p2pkh_prefix; prefixed[2..35].copy_from_slice(&blinder.serialize()); - prefixed[35..].copy_from_slice(&hash[..]); - base58::check_encode_slice_to_fmt(fmt, &prefixed[..]) + prefixed[35..].copy_from_slice(hash.as_ref()); + base58::encode_check_to_fmt(fmt, &prefixed[..]) } else { let mut prefixed = [0; 21]; prefixed[0] = self.params.p2pkh_prefix; - prefixed[1..].copy_from_slice(&hash[..]); - base58::check_encode_slice_to_fmt(fmt, &prefixed[..]) + prefixed[1..].copy_from_slice(hash.as_ref()); + base58::encode_check_to_fmt(fmt, &prefixed[..]) } } Payload::ScriptHash(ref hash) => { @@ -579,13 +581,13 @@ impl fmt::Display for Address { prefixed[0] = self.params.blinded_prefix; prefixed[1] = self.params.p2sh_prefix; prefixed[2..35].copy_from_slice(&blinder.serialize()); - prefixed[35..].copy_from_slice(&hash[..]); - base58::check_encode_slice_to_fmt(fmt, &prefixed[..]) + prefixed[35..].copy_from_slice(hash.as_byte_array()); + base58::encode_check_to_fmt(fmt, &prefixed[..]) } else { let mut prefixed = [0; 21]; prefixed[0] = self.params.p2sh_prefix; - prefixed[1..].copy_from_slice(&hash[..]); - base58::check_encode_slice_to_fmt(fmt, &prefixed[..]) + prefixed[1..].copy_from_slice(hash.as_byte_array()); + base58::encode_check_to_fmt(fmt, &prefixed[..]) } } Payload::WitnessProgram { @@ -597,25 +599,53 @@ impl fmt::Display for Address { false => self.params.bech_hrp, }; + // FIXME: surely we can fix this logic to not be so repetitive. if self.is_blinded() { - let mut data = Vec::with_capacity(53); if let Some(ref blinder) = self.blinding_pubkey { - data.extend_from_slice(&blinder.serialize()); + let byte_iter = IntoIterator::into_iter(blinder.serialize()) + .chain(witprog.iter().copied()); + let fe_iter = byte_iter.bytes_to_fes(); + if witver.to_u8() == 0 { + for c in fe_iter + .with_checksum::(&hrp) + .with_witness_version(witver) + .chars() + { + fmt.write_char(c)?; + } + } else { + for c in fe_iter + .with_checksum::(&hrp) + .with_witness_version(witver) + .chars() + { + fmt.write_char(c)?; + } + } + return Ok(()); } - data.extend_from_slice(&witprog); - let mut b32_data = vec![witver]; - b32_data.extend_from_slice(&data.to_base32()); - if witver.to_u8() == 0 { - blech32::encode_to_fmt(fmt, &hrp, &b32_data, blech32::Variant::Blech32) - } else { - blech32::encode_to_fmt(fmt, &hrp, &b32_data, blech32::Variant::Blech32m) + } + + let byte_iter = witprog.iter().copied(); + let fe_iter = byte_iter.bytes_to_fes(); + if witver.to_u8() == 0 { + for c in fe_iter + .with_checksum::(&hrp) + .with_witness_version(witver) + .chars() + { + fmt.write_char(c)?; } } else { - let var = if witver.to_u8() == 0 { bech32::Variant::Bech32 } else { bech32::Variant::Bech32m }; - let mut bech32_writer = bech32::Bech32Writer::new(hrp, var, fmt)?; - bech32::WriteBase32::write_u5(&mut bech32_writer, witver)?; - bech32::ToBase32::write_base32(&witprog, &mut bech32_writer) + for c in fe_iter + .with_checksum::(&hrp) + .with_witness_version(witver) + .chars() + { + fmt.write_char(c)?; + } } + Ok(()) } } } @@ -640,15 +670,11 @@ fn find_prefix(bech32: &str) -> &str { /// Checks if both prefixes match, regardless of case. /// The first prefix can be mixed case, but the second one is expected in /// lower case. -fn match_prefix(prefix_mixed: &str, prefix_lower: &str) -> bool { - if prefix_lower.len() != prefix_mixed.len() { - false - } else { - prefix_lower - .chars() - .zip(prefix_mixed.chars()) - .all(|(char_lower, char_mixed)| char_lower == char_mixed.to_ascii_lowercase()) - } +fn match_prefix(prefix_mixed: &str, target: Hrp) -> bool { + target.len() == prefix_mixed.len() && target + .lowercase_char_iter() + .zip(prefix_mixed.chars()) + .all(|(char_lower, char_mixed)| char_lower == char_mixed.to_ascii_lowercase()) } impl FromStr for Address { @@ -663,7 +689,7 @@ impl FromStr for Address { let net_arr = [liq, ele, liq_test]; let prefix = find_prefix(s); - for net in net_arr.iter() { + for net in &net_arr { // Bech32. if match_prefix(prefix, net.bech_hrp) { return Address::from_bech32(s, false, net); @@ -675,15 +701,15 @@ impl FromStr for Address { // Base58. if s.len() > 150 { - return Err(base58::Error::InvalidLength(s.len() * 11 / 15).into()); + return Err(AddressError::InvalidLength(s.len() * 11 / 15)); } - let data = base58::from_check(s)?; + let data = base58::decode_check(s)?; if data.is_empty() { - return Err(base58::Error::InvalidLength(data.len()).into()); + return Err(AddressError::InvalidLength(data.len())); } let p = data[0]; - for net in net_arr.iter() { + for net in &net_arr { if p == net.p2pkh_prefix || p == net.p2sh_prefix || p == net.blinded_prefix { return Address::from_base58(&data, net); } @@ -749,9 +775,9 @@ impl serde::Serialize for Address { #[cfg(test)] mod test { use super::*; - use bitcoin::util::key; - use secp256k1_zkp::{PublicKey, Secp256k1}; use crate::Script; + use bitcoin::key; + use secp256k1_zkp::{PublicKey, Secp256k1}; #[cfg(feature = "serde")] use serde_json; @@ -770,11 +796,20 @@ mod test { ); #[cfg(feature = "serde")] assert_eq!( - serde_json::from_value::
(serde_json::to_value(&addr).unwrap()).ok().as_ref(), + serde_json::from_value::
(serde_json::to_value(addr).unwrap()) + .ok() + .as_ref(), Some(addr) ); } + #[test] + fn regression_188() { + // Tests that the `tlq` prefix was not accidentally changed, e.g. to `tlg` :). + let addr = Address::from_str("tlq1qq2xvpcvfup5j8zscjq05u2wxxjcyewk7979f3mmz5l7uw5pqmx6xf5xy50hsn6vhkm5euwt72x878eq6zxx2z58hd7zrsg9qn").unwrap(); + roundtrips(&addr); + } + #[test] fn exhaustive() { let blinder_hex = "0218845781f631c48f1c9709e23092067d06837f30aa0cd0544ac887fe91ddd166"; @@ -839,7 +874,12 @@ mod test { for &(a, blinded, ref params) in &addresses { let result = a.parse(); - assert!(result.is_ok(), "vector: {}, err: \"{}\"", a, result.unwrap_err()); + assert!( + result.is_ok(), + "vector: {}, err: \"{}\"", + a, + result.unwrap_err() + ); let addr: Address = result.unwrap(); assert_eq!(a, &addr.to_string(), "vector: {}", a); assert_eq!(blinded, addr.is_blinded()); @@ -857,33 +897,120 @@ mod test { let address: Result = "el1pq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsxguu9nrdg2pc".parse(); assert_eq!( address.err().unwrap().to_string(), - "v1+ witness program must use b(l)ech32m not b(l)ech32", + "blech32 error: invalid checksum", // is valid blech32, but should be blech32m ); let address: Result = "el1qq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsnnmzrstzt7de".parse(); assert_eq!( address.err().unwrap().to_string(), - "v0 witness program must use b(l)ech32 not b(l)ech32m", + "blech32 error: invalid checksum", // is valid blech32m, but should be blech32 ); - let address: Result = "ert130xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqqu2tys".parse(); + let address: Result = + "ert130xlxvlhemja6c4dqv22uapctqupfhlxm9h8z3k2e72q4k9hcz7vqqu2tys".parse(); assert_eq!( address.err().unwrap().to_string(), - "invalid witness script version: 17", + "bech32 error: invalid segwit witness version: 17 (bech32 character: '3')", ); let address: Result = "el1pq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfsqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpe9jfn0gypaj".parse(); assert_eq!( address.err().unwrap().to_string(), - "the witness program must be between 2 and 40 bytes in length, not 41", + "blech32 error: invalid witness length", ); // "invalid prefix" gives a weird error message because we do // a dumb prefix check before even attempting bech32 decoding let address: Result = "rrr1qq0umk3pez693jrrlxz9ndlkuwne93gdu9g83mhhzuyf46e3mdzfpva0w48gqgzgrklncnm0k5zeyw8my2ypfs2d9rp7meq4kg".parse(); - assert_eq!( - address.err().unwrap().to_string(), - "base58 error: invalid base58 character 0x30", - ); + assert_eq!(address.err().unwrap().to_string(), "base58 error: decode",); + } + + #[test] + fn test_fixed_addresses() { + let pk = bitcoin::PublicKey::from_str( + "0212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea", + ) + .unwrap(); + let script = Script::default(); + let secp = Secp256k1::verification_only(); + let internal_key = UntweakedPublicKey::from_str( + "93c7378d96518a75448821c4f7c8f4bae7ce60f804d03d1f0628dd5dd0f5de51", + ) + .unwrap(); + let tap_node_hash = TapNodeHash::from_byte_array([0; 32]); + + let mut expected = IntoIterator::into_iter([ + "2dszRCFv8Ub4ytKo1Q1vXXGgSx7mekNDwSJ", + "XToMocNywBYNSiXUe5xvoa2naAps9Ek1hq", + "ert1qew0l0emv7449u7hqgc8utzdzryhse79yhq2sxv", + "XZF6k8S6eoVxXMB4NpWjh2s7LjQUP7pw2R", + "ert1quwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2szaqlpq", + "ert1p8qs0qcn25l2y6yvtc5t95rr8w9pndcj64c8rkutnvkcvdp6gh02q2cqvj9", + "ert1pxrrurkg8j8pve97lffvv2y67cf7ux478h077c87qacqzhue7390sqkjp06", + "CTEkC79sYAvWNcxd8iTYnYo226FqRBbzBcMppq7L2dA8jVXJWoo1kKWB3UBLY6gBjiXf87ibs8c6mQyZ", + "AzpjUhKMLJi9y2oLt3ZdM3BP9nHdLPJfGMVxRBaRc2gDpeNqPMVpShTszJW7bX42vT2KoejYy8GtbcxH", + "el1qqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww4jul7lnkeat2teawq3s0cky6yxf0pnu2gmz9ej9kyq5yc", + "AzpjUhKMLJi9y2oLt3ZdM3BP9nHdLPJfGMVxRBaRc2gDpeNvq6SLVpBVwtakF6nmUFundyW7YjUdVkpr", + "el1qqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww4casc3pf3lquzjd0haxgn9hmjfp84eq7geymjdx2f9verdu99wz4h79u87cnxdzq", + "el1pqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww5wpq7p3x4f75f5gch3gktgxxwu2rxm394tsw8dchxedsc6r53w75cj24fq2u2ls5", + "el1pqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww5vx8c8vs0ywzejta7jjcc5f4asnacdtu0wlaas0upmsq90enaz2lhjd0k0q7qn4h", + "QFq3vvrr6Ub2KAyb3LdoCxEQvKukB6nN9i", + "GydeMhecNgrq17WMkyyTM4ETv1YubMVtLN", + "ex1qew0l0emv7449u7hqgc8utzdzryhse79ydjqgek", + "H55PJDhj6JpR5k9wViXGEX4nga8WmhXtnD", + "ex1quwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2s4sla8h", + "ex1p8qs0qcn25l2y6yvtc5t95rr8w9pndcj64c8rkutnvkcvdp6gh02qa4lw5j", + "ex1pxrrurkg8j8pve97lffvv2y67cf7ux478h077c87qacqzhue7390shmdrfd", + "VTptY6cqJbusNpL5xvo8VL38nLX9PGDjfYQfqhu9EaA7FtuidkWyQzMHY9jzZrpBcCXT437vM6V4N8kh", + "VJL64Ep3rcngP4cScRme15q9i8MCNiuqWeiG3YbtduUidVyorg7nRsgmmF714QtH3sNpWB2CqsVVciQh", + "lq1qqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww4jul7lnkeat2teawq3s0cky6yxf0pnu2gs2923tg58xcz", + "VJL64Ep3rcngP4cScRme15q9i8MCNiuqWeiG3YbtduUidVyuJR4JUzQPiqBdhzd1bgGHLVnmRUjfHc68", + "lq1qqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww4casc3pf3lquzjd0haxgn9hmjfp84eq7geymjdx2f9verdu99wz47jmkmgmr9a4s", + "lq1pqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww5wpq7p3x4f75f5gch3gktgxxwu2rxm394tsw8dchxedsc6r53w75375l4kfvf08y", + "lq1pqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww5vx8c8vs0ywzejta7jjcc5f4asnacdtu0wlaas0upmsq90enaz2l77n92erwrrz8", + "FojPFeboBgrd953mXXe72KWthjVwHWozqN", + "8vsafXgrB5bJeSidGbK5eYnjKvQ3RiB4BB", + "tex1qew0l0emv7449u7hqgc8utzdzryhse79yh5jp9a", + "92KKc3jxthYtj5ND1KrtY1d46UyeWV6XbP", + "tex1quwcvgs5clswpfxhm7nyfjmaeysn6us0yvjdexn9yjkv3k7zjhp2s5fd6kc", + "tex1p8qs0qcn25l2y6yvtc5t95rr8w9pndcj64c8rkutnvkcvdp6gh02quvdf9a", + "tex1pxrrurkg8j8pve97lffvv2y67cf7ux478h077c87qacqzhue7390skzlycz", + "vtS71VhcpFt978sha5d1L2gCzp3UL5kXacRpb3N4GTW5MwvBzz5HwxYyB8Pns4yM2dd2osmQkHSkp88u", + "vjTuLJ76nGi8PUopBVmGK8bLKPfBpaBWf6wKfn8z9Vdz6ubVhpvmMr6TK2RcqAYiujN1g1uwg8kejrM3", + "tlq1qqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww4jul7lnkeat2teawq3s0cky6yxf0pnu2gq8g2kuxfj8ft", + "vjTuLJ76nGi8PUopBVmGK8bLKPfBpaBWf6wKfn8z9Vdz6ubb9ZsHQxp5GcWFUkHTTYFUWLgWFk1DN5Fe", + "tlq1qqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww4casc3pf3lquzjd0haxgn9hmjfp84eq7geymjdx2f9verdu99wz4e6vcdfcyp5m8", + "tlq1pqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww5wpq7p3x4f75f5gch3gktgxxwu2rxm394tsw8dchxedsc6r53w75kkr3rh2tdxfn", + "tlq1pqgft7r4ytdenml0gaj67393sd3qkt3nxex0ut5dt3plhzwf6jaww5vx8c8vs0ywzejta7jjcc5f4asnacdtu0wlaas0upmsq90enaz2lekytucqf82vs", + ]); + + for params in [ + &AddressParams::ELEMENTS, + &AddressParams::LIQUID, + &AddressParams::LIQUID_TESTNET, + ] { + for blinder in [None, Some(pk.inner)] { + let addr = Address::p2pkh(&pk, blinder, params); + assert_eq!(&addr.to_string(), expected.next().unwrap()); + + let addr = Address::p2sh(&script, blinder, params); + assert_eq!(&addr.to_string(), expected.next().unwrap()); + + let addr = Address::p2wpkh(&pk, blinder, params); + assert_eq!(&addr.to_string(), expected.next().unwrap()); + + let addr = Address::p2shwpkh(&pk, blinder, params); + assert_eq!(&addr.to_string(), expected.next().unwrap()); + + let addr = Address::p2wsh(&script, blinder, params); + assert_eq!(&addr.to_string(), expected.next().unwrap()); + + let addr = Address::p2tr(&secp, internal_key, None, blinder, params); + assert_eq!(&addr.to_string(), expected.next().unwrap()); + + let addr = Address::p2tr(&secp, internal_key, Some(tap_node_hash), blinder, params); + assert_eq!(&addr.to_string(), expected.next().unwrap()); + } + } } } diff --git a/src/blech32.rs b/src/blech32.rs deleted file mode 100644 index 75f52e16..00000000 --- a/src/blech32.rs +++ /dev/null @@ -1,297 +0,0 @@ -// Rust Elements Library -// Written by -// The Elements developers -// -// To the extent possible under law, the author(s) have dedicated all -// copyright and related and neighboring rights to this software to -// the public domain worldwide. This software is distributed without -// any warranty. -// -// You should have received a copy of the CC0 Public Domain Dedication -// along with this software. -// If not, see . - -// This file is an adaptation of the bech32 crate with the following -// license notice: -// -// Copyright (c) 2017 Clark Moody -// -// 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. - -//! # Blech32 -//! -//! A variation of the bech32 encoding for blinded Elements addresses. - -// Original documentation is left untouched, so it corresponds to bech32. - -use std::fmt; - -use bitcoin::bech32::{u5, Error}; - -/// Used for encode/decode operations for the two variants of Blech32 -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub enum Variant { - /// The original Blech32 - Blech32, - /// The improved Blech32m - Blech32m, -} - -const BLECH32_CONST: u64 = 1; -const BLECH32M_CONST: u64 = 0x455972a3350f7a1; - -impl Variant { - // Produce the variant based on the remainder of the polymod operation - fn from_remainder(c: u64) -> Option { - match c { - BLECH32_CONST => Some(Variant::Blech32), - BLECH32M_CONST => Some(Variant::Blech32m), - _ => None, - } - } - - fn constant(self) -> u64 { - match self { - Variant::Blech32 => BLECH32_CONST, - Variant::Blech32m => BLECH32M_CONST, - } - } -} - -/// Encode a bech32 payload to an [fmt::Formatter]. -pub fn encode_to_fmt>( - fmt: &mut dyn fmt::Write, - hrp: &str, - data: T, - variant: Variant, -) -> fmt::Result { - let hrp_bytes: &[u8] = hrp.as_bytes(); - let checksum = create_checksum(hrp_bytes, data.as_ref(), variant); - let data_part = data.as_ref().iter().chain(checksum.iter()); - - write!( - fmt, - "{}{}{}", - hrp, - SEP, - data_part - .map(|p| CHARSET[*p.as_ref() as usize]) - .collect::() - ) -} - -/// Decode a bech32 string into the raw HRP and the data bytes. -/// The HRP is returned as it was found in the original string, -/// so it can be either lower or upper case. -pub fn decode(s: &str) -> Result<(&str, Vec, Variant), Error> { - // Ensure overall length is within bounds - let len: usize = s.len(); - // ELEMENTS: 8->14 - if len < 14 { - return Err(Error::InvalidLength); - } - - // Split at separator and check for two pieces - let (raw_hrp, raw_data) = match s.rfind('1') { - None => return Err(Error::MissingSeparator), - Some(sep) => { - let (hrp, data) = s.split_at(sep); - (hrp, &data[1..]) - } - }; - // ELEMENTS: 6->12 - if raw_hrp.is_empty() || raw_data.len() < 12 || raw_hrp.len() > 83 { - return Err(Error::InvalidLength); - } - - let mut has_lower: bool = false; - let mut has_upper: bool = false; - let mut hrp_bytes: Vec = Vec::new(); - for b in raw_hrp.bytes() { - // Valid subset of ASCII - if b < 33 || b > 126 { - return Err(Error::InvalidChar(b as char)); - } - let mut c = b; - // Lowercase - if b >= b'a' && b <= b'z' { - has_lower = true; - } - // Uppercase - if b >= b'A' && b <= b'Z' { - has_upper = true; - // Convert to lowercase - c = b + (b'a' - b'A'); - } - hrp_bytes.push(c); - } - - // Check data payload - let mut data = raw_data - .chars() - .map(|c| { - // Only check if c is in the ASCII range, all invalid ASCII characters have the value -1 - // in CHARSET_REV (which covers the whole ASCII range) and will be filtered out later. - if !c.is_ascii() { - return Err(Error::InvalidChar(c)); - } - - if c.is_lowercase() { - has_lower = true; - } else if c.is_uppercase() { - has_upper = true; - } - - // c should be <128 since it is in the ASCII range, CHARSET_REV.len() == 128 - let num_value = CHARSET_REV[c as usize]; - - if num_value > 31 || num_value < 0 { - return Err(Error::InvalidChar(c)); - } - - Ok(u5::try_from_u8(num_value as u8).expect("range checked above, num_value <= 31")) - }) - .collect::, Error>>()?; - - // Ensure no mixed case - if has_lower && has_upper { - return Err(Error::MixedCase); - } - - // Ensure checksum - match verify_checksum(&raw_hrp.as_bytes(), &data) { - Some(variant) => { - // Remove checksum from data payload - let dbl: usize = data.len(); - data.truncate(dbl - 12); - - Ok((raw_hrp, data, variant)) - } - None => Err(Error::InvalidChecksum), - } -} - -fn create_checksum(hrp: &[u8], data: &[u5], variant: Variant) -> Vec { - let mut values: Vec = hrp_expand(hrp); - values.extend_from_slice(data); - // Pad with 12 zeros - values.extend_from_slice(&[u5::try_from_u8(0).unwrap(); 12]); // ELEMENTS: 6->12 - let plm: u64 = polymod(&values) ^ variant.constant(); - let mut checksum: Vec = Vec::new(); - // ELEMENTS: 6->12 - for p in 0..12 { - checksum.push(u5::try_from_u8(((plm >> (5 * (11 - p))) & 0x1f) as u8).unwrap()); // ELEMENTS: 5->11 - } - checksum -} - -fn verify_checksum(hrp: &[u8], data: &[u5]) -> Option { - let mut exp = hrp_expand(hrp); - exp.extend_from_slice(data); - Variant::from_remainder(polymod(&exp)) -} - -fn hrp_expand(hrp: &[u8]) -> Vec { - let mut v: Vec = Vec::new(); - for b in hrp { - v.push(u5::try_from_u8(*b >> 5).expect("can't be out of range, max. 7")); - } - v.push(u5::try_from_u8(0).unwrap()); - for b in hrp { - v.push(u5::try_from_u8(*b & 0x1f).expect("can't be out of range, max. 31")); - } - v -} - -fn polymod(values: &[u5]) -> u64 { - let mut chk: u64 = 1; - let mut b: u8; - for v in values { - b = (chk >> 55) as u8; // ELEMENTS: 25->55 - chk = (chk & 0x7fffffffffffff) << 5 ^ (u64::from(*v.as_ref())); // ELEMENTS 0x1ffffff->0x7fffffffffffff - for (i, coef) in GEN.iter().enumerate() { - if (b >> i) & 1 == 1 { - chk ^= coef - } - } - } - chk -} - -/// Human-readable part and data part separator -const SEP: char = '1'; - -/// Encoding character set. Maps data value -> char -const CHARSET: [char; 32] = [ - 'q', 'p', 'z', 'r', 'y', '9', 'x', '8', 'g', 'f', '2', 't', 'v', 'd', 'w', '0', 's', '3', 'j', - 'n', '5', '4', 'k', 'h', 'c', 'e', '6', 'm', 'u', 'a', '7', 'l', -]; - -// Reverse character set. Maps ASCII byte -> CHARSET index on [0,31] -const CHARSET_REV: [i8; 128] = [ - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23, - -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, - -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, - -1, -1, -1, -1, -]; - -/// Generator coefficients -const GEN: [u64; 5] = [ - // ELEMENTS - 0x7d52fba40bd886, - 0x5e8dbf1a03950c, - 0x1c3a3c74072a18, - 0x385d72fa0e5139, - 0x7093e5a608865b, -]; - -#[cfg(test)] -mod test { - use super::*; - - use bitcoin::bech32::ToBase32; - use rand; - - #[test] - fn test_polymod_sanity() { - let data: [u8; 32] = rand::random(); - - let data1 = data.to_vec(); - let data1_b32 = data1.to_base32(); - let polymod1 = polymod(&data1_b32); - - let data2 = data.to_vec(); - let mut data2_b32 = data2.to_base32(); - data2_b32.extend(vec![u5::try_from_u8(0).unwrap(); 1023]); - let polymod2 = polymod(&data2_b32); - assert_eq!(polymod1, polymod2); - } - - #[test] - fn test_checksum() { - let data = vec![7,2,3,4,5,6,7,8,9,234,123,213,16]; - let cs = create_checksum(b"lq", &data.to_base32(), Variant::Blech32); - let expected_cs = vec![22,13,13,5,4,4,23,7,28,21,30,12]; - for i in 0..expected_cs.len() { - assert_eq!(expected_cs[i], *cs[i].as_ref()); - } - } -} diff --git a/src/blech32/decode.rs b/src/blech32/decode.rs new file mode 100644 index 00000000..8a504bbb --- /dev/null +++ b/src/blech32/decode.rs @@ -0,0 +1,900 @@ +// +// This file is essentially a copy of src/primitives/decode.rs from the bech32 +// crate. It is not public-domain licensed. It is MIT-licensed. The changes are: +// * Imports changed to use the public bech32 crate +// * Bech32 changed to Blech32 by search-and-replace +// * Fe32::from_char_unchecked and .0 were changed to use public API +// * CheckedHrpstring::validate_witness_program_length replaced to use Elements limits +// * `std` feature gates were removed +// * a couple tests with fixed vectors were disabled since we replaced the checksum +// * doccomment examples with fixed vectors were removed +// + +// Copyright (c) 2023 Tobin Harding and Andrew Poelstra +// +// 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. + +//! Decoding of bech32 encoded strings as specified by [BIP-173] and [BIP-350]. +//! +//! You should only need to use this module directly if you want control over exactly what is +//! checked and when it is checked (correct bech32 characters, valid checksum, valid checksum for +//! specific checksum algorithm, etc). If you are parsing/validating modern (post BIP-350) bitcoin +//! segwit addresses consider using the higher crate level API. +//! +//! If you do find yourself using this module directly then consider using the most general type +//! that serves your purposes, each type can be created by parsing an address string to `new`. You +//! likely do not want to arbitrarily transition from one type to the next even though possible. And +//! be prepared to spend some time with the bips - you have been warned :) +//! +//! # Details +//! +//! A Blech32 string is at most 90 characters long and consists of: +//! +//! - The human-readable part, which is intended to convey the type of data, or anything else that +//! is relevant to the reader. This part MUST contain 1 to 83 US-ASCII characters. +//! - The separator, which is always "1". +//! - The data part, which is at least 6 characters long and only consists of alphanumeric +//! characters excluding "1", "b", "i", and "o". +//! +//! The types in this module heavily lean on the wording in BIP-173: *We first +//! describe the general checksummed base32 format called Blech32 and then define Segregated Witness +//! addresses using it.* +//! +//! - `UncheckedHrpstring`: Parses the general checksummed base32 format and provides checksum validation. +//! - `CheckedHrpstring`: Provides access to the data encoded by a general checksummed base32 string and segwit checks. +//! - `SegwitHrpstring`: Provides access to the data encoded by a segwit address. +//! +//! [BIP-173]: +//! [BIP-350]: + +use core::{fmt, iter, slice, str}; + +use crate::error::write_err; +use bech32::primitives::checksum::{self, Checksum}; +use bech32::primitives::gf32::Fe32; +use bech32::primitives::hrp::{self, Hrp}; +use bech32::primitives::iter::{Fe32IterExt, FesToBytes}; +use bech32::primitives::segwit::{WitnessLengthError, VERSION_0}; +use super::{Blech32, Blech32m}; + +/// Separator between the hrp and payload (as defined by BIP-173). +const SEP: char = '1'; + +/// An HRP string that has been parsed but not yet had the checksum checked. +/// +/// Parsing an HRP string only checks validity of the characters, it does not validate the +/// checksum in any way. +/// +/// Unless you are attempting to validate a string with multiple checksums then you likely do not +/// want to use this type directly, instead use [`CheckedHrpstring::new`]. +#[derive(Debug)] +pub struct UncheckedHrpstring<'s> { + /// The human-readable part, guaranteed to be lowercase ASCII characters. + hrp: Hrp, + /// This is ASCII byte values of the parsed string, guaranteed to be valid bech32 characters. + /// + /// Contains the checksum if one was present in the parsed string. + data: &'s [u8], +} + +impl<'s> UncheckedHrpstring<'s> { + /// Parses an bech32 encode string and constructs a [`UncheckedHrpstring`] object. + /// + /// Checks for valid ASCII values, does not validate the checksum. + #[inline] + pub fn new(s: &'s str) -> Result { + let sep_pos = check_characters(s)?; + let (hrp, data) = s.split_at(sep_pos); + + let ret = UncheckedHrpstring { + hrp: Hrp::parse(hrp)?, + data: &data.as_bytes()[1..], // Skip the separator. + }; + + Ok(ret) + } + + /// Returns the human-readable part. + #[inline] + pub fn hrp(&self) -> Hrp { self.hrp } + + /// Validates that data has a valid checksum for the `Ck` algorithm and returns a [`CheckedHrpstring`]. + #[inline] + pub fn validate_and_remove_checksum( + self, + ) -> Result, ChecksumError> { + self.validate_checksum::()?; + Ok(self.remove_checksum::()) + } + + /// Validates that data has a valid checksum for the `Ck` algorithm (this may mean an empty + /// checksum if `NoChecksum` is used). + /// + /// This is useful if you do not know which checksum algorithm was used and wish to validate + /// against multiple algorithms consecutively. If this function returns `true` then call + /// `remove_checksum` to get a [`CheckedHrpstring`]. + #[inline] + pub fn has_valid_checksum(&self) -> bool { + self.validate_checksum::().is_ok() + } + + /// Validates that data has a valid checksum for the `Ck` algorithm (this may mean an empty + /// checksum if `NoChecksum` is used). + #[inline] + pub fn validate_checksum(&self) -> Result<(), ChecksumError> { + use ChecksumError as E; + + if Ck::CHECKSUM_LENGTH == 0 { + // Called with NoChecksum + return Ok(()); + } + + if self.data.len() < Ck::CHECKSUM_LENGTH { + return Err(E::InvalidChecksumLength); + } + + let mut checksum_eng = checksum::Engine::::new(); + checksum_eng.input_hrp(self.hrp()); + + // Unwrap ok since we checked all characters in our constructor. + for fe in self.data.iter().map(|&b| Fe32::from_char(b.into()).unwrap()) { + checksum_eng.input_fe(fe); + } + + if checksum_eng.residue() != &Ck::TARGET_RESIDUE { + return Err(E::InvalidChecksum); + } + + Ok(()) + } + + /// Removes the checksum for the `Ck` algorithm and returns an [`CheckedHrpstring`]. + /// + /// Data must be valid (ie, first call `has_valid_checksum` or `validate_checksum()`). This + /// function is typically paired with `has_valid_checksum` when validating against multiple + /// checksum algorithms consecutively. + /// + /// # Panics + /// + /// May panic if data is not valid. + #[inline] + pub fn remove_checksum(self) -> CheckedHrpstring<'s> { + let data_len = self.data.len() - Ck::CHECKSUM_LENGTH; + + CheckedHrpstring { hrp: self.hrp(), data: &self.data[..data_len] } + } +} + +/// An HRP string that has been parsed and had the checksum validated. +/// +/// This type does not treat the first byte of the data in any special way i.e., as the witness +/// version byte. If you are parsing Bitcoin segwit addresses you likely want to use [`SegwitHrpstring`]. +/// +/// > We first describe the general checksummed base32 format called Blech32 and then +/// > define Segregated Witness addresses using it. +/// +/// This type abstracts over the general checksummed base32 format called Blech32. +#[derive(Debug)] +pub struct CheckedHrpstring<'s> { + /// The human-readable part, guaranteed to be lowercase ASCII characters. + hrp: Hrp, + /// This is ASCII byte values of the parsed string, guaranteed to be valid bech32 characters, + /// with the checksum removed. + data: &'s [u8], +} + +impl<'s> CheckedHrpstring<'s> { + /// Parses and validates an HRP string, without treating the first data character specially. + /// + /// If you are validating the checksum multiple times consider using [`UncheckedHrpstring`]. + /// + /// This is equivalent to `UncheckedHrpstring::new().validate_and_remove_checksum::()`. + #[inline] + pub fn new(s: &'s str) -> Result { + let unchecked = UncheckedHrpstring::new(s)?; + let checked = unchecked.validate_and_remove_checksum::()?; + Ok(checked) + } + + /// Returns the human-readable part. + #[inline] + pub fn hrp(&self) -> Hrp { self.hrp } + + /// Returns an iterator that yields the data part of the parsed bech32 encoded string. + /// + /// Converts the ASCII bytes representing field elements to the respective field elements, then + /// converts the stream of field elements to a stream of bytes. + #[inline] + pub fn byte_iter(&self) -> ByteIter<'_> { + ByteIter { iter: AsciiToFe32Iter { iter: self.data.iter().copied() }.fes_to_bytes() } + } + + /// Converts this type to a [`SegwitHrpstring`] after validating the witness and HRP. + #[inline] + pub fn validate_segwit(mut self) -> Result, SegwitHrpstringError> { + if self.data.is_empty() { + return Err(SegwitHrpstringError::MissingWitnessVersion); + } + // Unwrap ok since check_characters checked the bech32-ness of this char. + let witness_version = Fe32::from_char(self.data[0].into()).unwrap(); + self.data = &self.data[1..]; // Remove the witness version byte from data. + + self.validate_padding()?; + self.validate_witness_program_length(witness_version)?; + + Ok(SegwitHrpstring { hrp: self.hrp(), witness_version, data: self.data }) + } + + /// Validates the segwit padding rules. + /// + /// Must be called after the witness version byte is removed from the data. + /// + /// From BIP-173: + /// > Re-arrange those bits into groups of 8 bits. Any incomplete group at the + /// > end MUST be 4 bits or less, MUST be all zeroes, and is discarded. + fn validate_padding(&self) -> Result<(), PaddingError> { + if self.data.is_empty() { + return Ok(()); // Empty data implies correct padding. + } + + let fe_iter = AsciiToFe32Iter { iter: self.data.iter().copied() }; + let padding_len = fe_iter.len() * 5 % 8; + + if padding_len > 4 { + return Err(PaddingError::TooMuch); + } + + let last_fe = fe_iter.last().expect("checked above"); + let last_byte = last_fe.to_u8(); + + let padding_contains_non_zero_bits = match padding_len { + 0 => false, + 1 => last_byte & 0b0001 > 0, + 2 => last_byte & 0b0011 > 0, + 3 => last_byte & 0b0111 > 0, + 4 => last_byte & 0b1111 > 0, + _ => unreachable!("checked above"), + }; + if padding_contains_non_zero_bits { + Err(PaddingError::NonZero) + } else { + Ok(()) + } + } + + /// Validates the segwit witness length rules. + /// + /// Must be called after the witness version byte is removed from the data. + fn validate_witness_program_length( + &self, + witness_version: Fe32, + ) -> Result<(), WitnessLengthError> { + let len = self.byte_iter().len(); + if len < 2 { + Err(WitnessLengthError::TooShort) + } else if len > 40 + 33 { + Err(WitnessLengthError::TooLong) + } else if witness_version == Fe32::Q && len != 53 && len != 65 { + Err(WitnessLengthError::InvalidSegwitV0) + } else { + Ok(()) + } + } +} + +/// An HRP string that has been parsed, had the checksum validated, had the witness version +/// validated, had the witness data length checked, and the had witness version and checksum +/// removed. +/// +#[derive(Debug)] +pub struct SegwitHrpstring<'s> { + /// The human-readable part, valid for segwit addresses. + hrp: Hrp, + /// The first byte of the parsed data. + witness_version: Fe32, + /// This is ASCII byte values of the parsed string, guaranteed to be valid bech32 characters, + /// with the witness version and checksum removed. + data: &'s [u8], +} + +impl<'s> SegwitHrpstring<'s> { + /// Parses an HRP string, treating the first data character as a witness version. + /// + /// The version byte does not appear in the extracted binary data, but is covered by the + /// checksum. It can be accessed with [`Self::witness_version`]. + /// + /// NOTE: We do not enforce any restrictions on the HRP, use [`SegwitHrpstring::has_valid_hrp`] + /// to get strict BIP conformance (also [`Hrp::is_valid_on_mainnet`] and friends). + #[inline] + pub fn new(s: &'s str) -> Result { + let unchecked = UncheckedHrpstring::new(s)?; + + if unchecked.data.is_empty() { + return Err(SegwitHrpstringError::MissingWitnessVersion); + } + + // Unwrap ok since check_characters (in `Self::new`) checked the bech32-ness of this char. + let witness_version = Fe32::from_char(unchecked.data[0].into()).unwrap(); + if witness_version.to_u8() > 16 { + return Err(SegwitHrpstringError::InvalidWitnessVersion(witness_version)); + } + + let checked: CheckedHrpstring<'s> = match witness_version { + VERSION_0 => unchecked.validate_and_remove_checksum::()?, + _ => unchecked.validate_and_remove_checksum::()?, + }; + + checked.validate_segwit() + } + + /// Parses an HRP string, treating the first data character as a witness version. + /// + /// ## WARNING + /// + /// You almost certainly do not want to use this function. + /// + /// It is provided for backwards comparability to parse addresses that have an non-zero witness + /// version because [BIP-173] explicitly allows using the bech32 checksum with any witness + /// version however [BIP-350] specifies all witness version > 0 now MUST use bech32m. + /// + /// [BIP-173]: https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki + /// [BIP-350]: https://github.com/bitcoin/bips/blob/master/bip-0350.mediawiki + #[inline] + pub fn new_bech32(s: &'s str) -> Result { + let unchecked = UncheckedHrpstring::new(s)?; + + // Unwrap ok since check_characters (in `Self::new`) checked the bech32-ness of this char. + let witness_version = Fe32::from_char(unchecked.data[0].into()).unwrap(); + if witness_version.to_u8() > 16 { + return Err(SegwitHrpstringError::InvalidWitnessVersion(witness_version)); + } + + let checked = unchecked.validate_and_remove_checksum::()?; + checked.validate_segwit() + } + + /// Returns `true` if the HRP is "bc" or "tb". + /// + /// BIP-173 requires that the HRP is "bc" or "tb" but software in the Bitcoin ecosystem uses + /// other HRPs, specifically "bcrt" for regtest addresses. We provide this function in order to + /// be BIP-173 compliant but their are no restrictions on the HRP of [`SegwitHrpstring`]. + #[inline] + pub fn has_valid_hrp(&self) -> bool { self.hrp().is_valid_segwit() } + + /// Returns the human-readable part. + #[inline] + pub fn hrp(&self) -> Hrp { self.hrp } + + /// Returns the witness version. + #[inline] + pub fn witness_version(&self) -> Fe32 { self.witness_version } + + /// Returns an iterator that yields the data part, excluding the witness version, of the parsed + /// bech32 encoded string. + /// + /// Converts the ASCII bytes representing field elements to the respective field elements, then + /// converts the stream of field elements to a stream of bytes. + /// + /// Use `self.witness_version()` to get the witness version. + #[inline] + pub fn byte_iter(&self) -> ByteIter<'_> { + ByteIter { iter: AsciiToFe32Iter { iter: self.data.iter().copied() }.fes_to_bytes() } + } +} + +/// Checks whether a given HRP string has data characters in the bech32 alphabet (incl. checksum +/// characters), and that the whole string has consistent casing (hrp, data, and checksum). +/// +/// # Returns +/// +/// The byte-index into the string where the '1' separator occurs, or an error if it does not. +fn check_characters(s: &str) -> Result { + use CharError as E; + + let mut has_upper = false; + let mut has_lower = false; + let mut req_bech32 = true; + let mut sep_pos = None; + for (n, ch) in s.char_indices().rev() { + if ch == SEP && sep_pos.is_none() { + req_bech32 = false; + sep_pos = Some(n); + } + if req_bech32 { + Fe32::from_char(ch).map_err(|_| E::InvalidChar(ch))?; + } + if ch.is_ascii_uppercase() { + has_upper = true; + } else if ch.is_ascii_lowercase() { + has_lower = true; + } + } + if has_upper && has_lower { + Err(E::MixedCase) + } else if let Some(pos) = sep_pos { + Ok(pos) + } else { + Err(E::MissingSeparator) + } +} + +/// An iterator over a parsed HRP string data as bytes. +pub struct ByteIter<'s> { + iter: FesToBytes>>>, +} + +impl Iterator for ByteIter<'_> { + type Item = u8; + #[inline] + fn next(&mut self) -> Option { self.iter.next() } + #[inline] + fn size_hint(&self) -> (usize, Option) { self.iter.size_hint() } +} + +impl ExactSizeIterator for ByteIter<'_> { + #[inline] + fn len(&self) -> usize { self.iter.len() } +} + +/// Helper iterator adaptor that maps an iterator of valid bech32 character ASCII bytes to an +/// iterator of field elements. +/// +/// # Panics +/// +/// If any `u8` in the input iterator is out of range for an [`Fe32`]. Should only be used on data +/// that has already been checked for validity (eg, by using `check_characters`). +struct AsciiToFe32Iter> { + iter: I, +} + +impl Iterator for AsciiToFe32Iter +where + I: Iterator, +{ + type Item = Fe32; + #[inline] + fn next(&mut self) -> Option { self.iter.next().map(|ch| Fe32::from_char(ch.into()).unwrap()) } + #[inline] + fn size_hint(&self) -> (usize, Option) { + // Each ASCII character is an fe32 so iterators are the same size. + self.iter.size_hint() + } +} + +impl ExactSizeIterator for AsciiToFe32Iter +where + I: Iterator + ExactSizeIterator, +{ + #[inline] + fn len(&self) -> usize { self.iter.len() } +} + +/// An error while constructing a [`SegwitHrpstring`] type. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum SegwitHrpstringError { + /// Error while parsing the encoded address string. + Unchecked(UncheckedHrpstringError), + /// The witness version byte is missing. + MissingWitnessVersion, + /// Invalid witness version (must be 0-16 inclusive). + InvalidWitnessVersion(Fe32), + /// Invalid padding on the witness data. + Padding(PaddingError), + /// Invalid witness length. + WitnessLength(WitnessLengthError), + /// Invalid checksum. + Checksum(ChecksumError), +} + +impl fmt::Display for SegwitHrpstringError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Unchecked(ref e) => write_err!(f, "parsing unchecked hrpstring failed"; e), + Self::MissingWitnessVersion => write!(f, "the witness version byte is missing"), + Self::InvalidWitnessVersion(fe) => write!(f, "invalid segwit witness version: {}", fe.to_u8()), + Self::Padding(ref e) => write_err!(f, "invalid padding on the witness data"; e), + Self::WitnessLength(ref e) => write_err!(f, "invalid witness length"; e), + Self::Checksum(ref e) => write_err!(f, "invalid checksum"; e), + } + } +} + +impl std::error::Error for SegwitHrpstringError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::Unchecked(ref e) => Some(e), + Self::Padding(ref e) => Some(e), + Self::WitnessLength(ref e) => Some(e), + Self::Checksum(ref e) => Some(e), + Self::MissingWitnessVersion | Self::InvalidWitnessVersion(_) => None, + } + } +} + +impl From for SegwitHrpstringError { + #[inline] + fn from(e: UncheckedHrpstringError) -> Self { Self::Unchecked(e) } +} + +impl From for SegwitHrpstringError { + #[inline] + fn from(e: WitnessLengthError) -> Self { Self::WitnessLength(e) } +} + +impl From for SegwitHrpstringError { + #[inline] + fn from(e: PaddingError) -> Self { Self::Padding(e) } +} + +impl From for SegwitHrpstringError { + #[inline] + fn from(e: ChecksumError) -> Self { Self::Checksum(e) } +} + +/// An error while constructing a [`CheckedHrpstring`] type. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum CheckedHrpstringError { + /// Error while parsing the encoded address string. + Parse(UncheckedHrpstringError), + /// Invalid checksum. + Checksum(ChecksumError), +} + +impl fmt::Display for CheckedHrpstringError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Parse(ref e) => write_err!(f, "parse failed"; e), + Self::Checksum(ref e) => write_err!(f, "invalid checksum"; e), + } + } +} + +impl std::error::Error for CheckedHrpstringError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::Parse(ref e) => Some(e), + Self::Checksum(ref e) => Some(e), + } + } +} + +impl From for CheckedHrpstringError { + #[inline] + fn from(e: UncheckedHrpstringError) -> Self { Self::Parse(e) } +} + +impl From for CheckedHrpstringError { + #[inline] + fn from(e: ChecksumError) -> Self { Self::Checksum(e) } +} + +/// Errors when parsing a bech32 encoded string. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum UncheckedHrpstringError { + /// An error with the characters of the input string. + Char(CharError), + /// The human-readable part is invalid. + Hrp(hrp::Error), +} + +impl fmt::Display for UncheckedHrpstringError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Char(ref e) => write_err!(f, "character error"; e), + Self::Hrp(ref e) => write_err!(f, "invalid human-readable part"; e), + } + } +} + +impl std::error::Error for UncheckedHrpstringError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::Char(ref e) => Some(e), + Self::Hrp(ref e) => Some(e), + } + } +} + +impl From for UncheckedHrpstringError { + #[inline] + fn from(e: CharError) -> Self { Self::Char(e) } +} + +impl From for UncheckedHrpstringError { + #[inline] + fn from(e: hrp::Error) -> Self { Self::Hrp(e) } +} + +/// Character errors in a bech32 encoded string. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum CharError { + /// String does not contain the separator character. + MissingSeparator, + /// No characters after the separator. + NothingAfterSeparator, + /// The checksum does not match the rest of the data. + InvalidChecksum, + /// The checksum is not a valid length. + InvalidChecksumLength, + /// Some part of the string contains an invalid character. + InvalidChar(char), + /// The whole string must be of one case. + MixedCase, +} + +impl fmt::Display for CharError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::MissingSeparator => write!(f, "missing human-readable separator, \"{}\"", SEP), + Self::NothingAfterSeparator => write!(f, "invalid data - no characters after the separator"), + Self::InvalidChecksum => write!(f, "invalid checksum"), + Self::InvalidChecksumLength => write!(f, "the checksum is not a valid length"), + Self::InvalidChar(n) => write!(f, "invalid character (code={})", n), + Self::MixedCase => write!(f, "mixed-case strings not allowed"), + } + } +} + +impl std::error::Error for CharError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::MissingSeparator + | Self::NothingAfterSeparator + | Self::InvalidChecksum + | Self::InvalidChecksumLength + | Self::InvalidChar(_) + | Self::MixedCase => None, + } + } +} + +/// Errors in the checksum of a bech32 encoded string. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum ChecksumError { + /// The checksum does not match the rest of the data. + InvalidChecksum, + /// The checksum is not a valid length. + InvalidChecksumLength, +} + +impl fmt::Display for ChecksumError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::InvalidChecksum => write!(f, "invalid checksum"), + Self::InvalidChecksumLength => write!(f, "the checksum is not a valid length"), + } + } +} + +impl std::error::Error for ChecksumError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::InvalidChecksum | Self::InvalidChecksumLength => None, + } + } +} + +/// Error validating the padding bits on the witness data. +#[derive(Debug, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum PaddingError { + /// The data payload has too many bits of padding. + TooMuch, + /// The data payload is padded with non-zero bits. + NonZero, +} + +impl fmt::Display for PaddingError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::TooMuch => write!(f, "the data payload has too many bits of padding"), + Self::NonZero => write!(f, "the data payload is padded with non-zero bits"), + } + } +} + +impl std::error::Error for PaddingError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::TooMuch | Self::NonZero => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bip_173_invalid_parsing_fails() { + use UncheckedHrpstringError as E; + + let invalid: Vec<(&str, UncheckedHrpstringError)> = vec!( + ("\u{20}1nwldj5", + // TODO: Rust >= 1.59.0 use Hrp(hrp::Error::InvalidAsciiByte('\u{20}'.try_into().unwrap()))), + E::Hrp(hrp::Error::InvalidAsciiByte(32))), + ("\u{7F}1axkwrx", + E::Hrp(hrp::Error::InvalidAsciiByte(127))), + ("\u{80}1eym55h", + E::Hrp(hrp::Error::NonAsciiChar('\u{80}'))), + ("an84characterslonghumanreadablepartthatcontainsthetheexcludedcharactersbioandnumber11d6pts4", + E::Hrp(hrp::Error::TooLong(84))), + ("pzry9x0s0muk", + E::Char(CharError::MissingSeparator)), + ("1pzry9x0s0muk", + E::Hrp(hrp::Error::Empty)), + ("x1b4n0q5v", + E::Char(CharError::InvalidChar('b'))), + // "li1dgmt3" in separate test because error is a checksum error. + ("de1lg7wt\u{ff}", + E::Char(CharError::InvalidChar('\u{ff}'))), + // "A1G7SGD8" in separate test because error is a checksum error. + ("10a06t8", + E::Hrp(hrp::Error::Empty)), + ("1qzzfhee", + E::Hrp(hrp::Error::Empty)), + ); + + for (s, want) in invalid { + let got = UncheckedHrpstring::new(s).unwrap_err(); + assert_eq!(got, want); + } + } + + /* + #[test] + fn bip_173_invalid_parsing_fails_invalid_checksum() { + use ChecksumError as E; + + let err = UncheckedHrpstring::new("li1dgmt3") + .expect("string parses correctly") + .validate_checksum::() + .unwrap_err(); + assert_eq!(err, E::InvalidChecksumLength); + + let err = UncheckedHrpstring::new("A1G7SGD8") + .expect("string parses correctly") + .validate_checksum::() + .unwrap_err(); + assert_eq!(err, E::InvalidChecksum); + } + */ + + #[test] + fn bip_350_invalid_parsing_fails() { + use UncheckedHrpstringError as E; + + let invalid: Vec<(&str, UncheckedHrpstringError)> = vec!( + ("\u{20}1xj0phk", + // TODO: Rust >= 1.59.0 use Hrp(hrp::Error::InvalidAsciiByte('\u{20}'.try_into().unwrap()))), + E::Hrp(hrp::Error::InvalidAsciiByte(32))), + ("\u{7F}1g6xzxy", + E::Hrp(hrp::Error::InvalidAsciiByte(127))), + ("\u{80}1g6xzxy", + E::Hrp(hrp::Error::NonAsciiChar('\u{80}'))), + ("an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", + E::Hrp(hrp::Error::TooLong(84))), + ("qyrz8wqd2c9m", + E::Char(CharError::MissingSeparator)), + ("1qyrz8wqd2c9m", + E::Hrp(hrp::Error::Empty)), + ("y1b0jsk6g", + E::Char(CharError::InvalidChar('b'))), + ("lt1igcx5c0", + E::Char(CharError::InvalidChar('i'))), + // "in1muywd" in separate test because error is a checksum error. + ("mm1crxm3i", + E::Char(CharError::InvalidChar('i'))), + ("au1s5cgom", + E::Char(CharError::InvalidChar('o'))), + // "M1VUXWEZ" in separate test because error is a checksum error. + ("16plkw9", + E::Hrp(hrp::Error::Empty)), + ("1p2gdwpf", + E::Hrp(hrp::Error::Empty)), + + ); + + for (s, want) in invalid { + let got = UncheckedHrpstring::new(s).unwrap_err(); + assert_eq!(got, want); + } + } + + /* + #[test] + fn bip_350_invalid_because_of_invalid_checksum() { + use ChecksumError::*; + + // Note the "bc1p2" test case is not from the bip test vectors. + let invalid: Vec<&str> = vec!["in1muywd", "bc1p2"]; + + for s in invalid { + let err = + UncheckedHrpstring::new(s).unwrap().validate_checksum::().unwrap_err(); + assert_eq!(err, InvalidChecksumLength); + } + + let err = UncheckedHrpstring::new("M1VUXWEZ") + .unwrap() + .validate_checksum::() + .unwrap_err(); + assert_eq!(err, InvalidChecksum); + } + */ + + #[test] + fn check_hrp_uppercase_returns_lower() { + let addr = "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4"; + let unchecked = UncheckedHrpstring::new(addr).expect("failed to parse address"); + assert_eq!(unchecked.hrp(), Hrp::parse_unchecked("bc")); + } + + #[test] + fn check_hrp_max_length() { + let hrps = + "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio"; + + let hrp = Hrp::parse_unchecked(hrps); + let s = bech32::encode::(hrp, &[]).expect("failed to encode empty buffer"); + + let unchecked = UncheckedHrpstring::new(&s).expect("failed to parse address"); + assert_eq!(unchecked.hrp(), hrp); + } + + /* + #[test] + fn mainnet_valid_addresses() { + let addresses = vec![ + "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq", + "23451QAR0SRRR7XFKVY5L643LYDNW9RE59GTZZLKULZK", + ]; + for valid in addresses { + assert!(CheckedHrpstring::new::(valid).is_ok()) + } + } + */ + + macro_rules! check_invalid_segwit_addresses { + ($($test_name:ident, $reason:literal, $address:literal);* $(;)?) => { + $( + #[test] + fn $test_name() { + let res = SegwitHrpstring::new($address); + if res.is_ok() { + panic!("{} sting should not be valid: {}", $address, $reason); + } + } + )* + } + } + check_invalid_segwit_addresses! { + invalid_segwit_address_0, "missing hrp", "1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; + invalid_segwit_address_1, "missing data-checksum", "91111"; + invalid_segwit_address_2, "invalid witness version", "bc14r0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq"; + invalid_segwit_address_3, "invalid checksum length", "bc1q5mdq"; + invalid_segwit_address_4, "missing data", "bc1qwf5mdq"; + invalid_segwit_address_5, "invalid program length", "bc14r0srrr7xfkvy5l643lydnw9rewf5mdq"; + } +} diff --git a/src/blech32/mod.rs b/src/blech32/mod.rs new file mode 100644 index 00000000..c4c2d5e6 --- /dev/null +++ b/src/blech32/mod.rs @@ -0,0 +1,61 @@ +// This file is an adaptation of the segwit-specific parts of the bech32 crate. +// Rust Elements Library +// Written in 2024 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Blech32-Encoding (Elements Segwit) Support +//! +//! A variation of the bech32 encoding for blinded Elements addresses. +//! + +pub mod decode; + +// *** Definitions of checksums *** + +/// The blech32 checksum algorithm. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Blech32 {} + +impl bech32::Checksum for Blech32 { + type MidstateRepr = u64; + const CHECKSUM_LENGTH: usize = 12; + const GENERATOR_SH: [u64; 5] = [ + 0x7d_52fb_a40b_d886, + 0x5e_8dbf_1a03_950c, + 0x1c_3a3c_7407_2a18, + 0x38_5d72_fa0e_5139, + 0x70_93e5_a608_865b, + ]; + const TARGET_RESIDUE: u64 = 1; + + const CODE_LENGTH: usize = 1024; +} + +/// The blech32m checksum algorithm. +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Blech32m {} + +impl bech32::Checksum for Blech32m { + type MidstateRepr = u64; + const CHECKSUM_LENGTH: usize = 12; + const GENERATOR_SH: [u64; 5] = [ + 0x7d_52fb_a40b_d886, + 0x5e_8dbf_1a03_950c, + 0x1c_3a3c_7407_2a18, + 0x38_5d72_fa0e_5139, + 0x70_93e5_a608_865b, + ]; + const TARGET_RESIDUE: u64 = 0x455_972a_3350_f7a1; + + const CODE_LENGTH: usize = 1024; +} diff --git a/src/blind.rs b/src/blind.rs index 6fb7ce85..65ae360e 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -15,6 +15,7 @@ //! # Transactions Blinding //! +use internals::slice::SliceExt; use std::{self, collections::BTreeMap, fmt}; use secp256k1_zkp::{ @@ -22,17 +23,15 @@ use secp256k1_zkp::{ rand::{CryptoRng, RngCore}, PedersenCommitment, SecretKey, Tag, Tweak, Verification, ZERO_TWEAK, }; -use secp256k1_zkp::{Generator, RangeProof, Secp256k1, Signing, SurjectionProof}; +use secp256k1_zkp::{Generator, Secp256k1, Signing}; -use crate::{AddressParams, Script, TxIn}; +use crate::{AddressParams, RangeProof, Script, TxIn, SurjectionProof}; use crate::{ confidential::{Asset, AssetBlindingFactor, Nonce, Value, ValueBlindingFactor}, Address, AssetId, Transaction, TxOut, TxOutWitness, }; -use crate::hashes; - /// Transaction Output related errors #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub enum TxOutError { @@ -40,8 +39,6 @@ pub enum TxOutError { UnExpectedNullValue, /// Unexpected Null asset UnExpectedNullAsset, - /// Money should be between 0 and 21_000_000 - MoneyOutofRange, /// Zero value explicit txout with non-provably unspendable script NonUnspendableZeroValue, /// Zero value pedersen commitment with provably unspendable script @@ -57,11 +54,6 @@ impl fmt::Display for TxOutError { match self { TxOutError::UnExpectedNullValue => write!(f, "UnExpected Null Value"), TxOutError::UnExpectedNullAsset => write!(f, "UnExpected Null Asset"), - TxOutError::MoneyOutofRange => write!( - f, - "Explicit amount must be\ - less than 21 million" - ), TxOutError::NonUnspendableZeroValue => { write!( f, @@ -91,7 +83,7 @@ pub enum VerificationError { RangeProofError(usize, secp256k1_zkp::Error), /// Missing Range Proof RangeProofMissing(usize), - /// Verification of SurjectionProof failed + /// Verification of `SurjectionProof` failed SurjectionProofError(usize, secp256k1_zkp::Error), /// Surjection Proof verification error SurjectionProofVerificationError(usize), @@ -166,7 +158,7 @@ pub enum ConfidentialTxOutError { NoBlindingKeyInAddress, /// Error originated in `secp256k1_zkp`. Upstream(secp256k1_zkp::Error), - /// General TxOut errors + /// General `TxOut` errors TxOutError(usize, TxOutError), /// Expected Explicit Asset for blinding ExpectedExplicitAsset, @@ -208,29 +200,150 @@ impl From for ConfidentialTxOutError { ConfidentialTxOutError::Upstream(from) } } -/// The Rangeproof message -#[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] -pub struct RangeProofMessage { - /// The asset id - pub asset: AssetId, - /// The asset blinding factor - pub bf: AssetBlindingFactor, -} -impl RangeProofMessage { - /// Converts the message to bytes - pub fn to_bytes(&self) -> [u8; 64] { - let mut message = [0u8; 64]; +mod range_proof_message { + use core::fmt; + use internals::array::ArrayExt; + use secp256k1_zkp::{Generator, Signing, Secp256k1}; + use super::{Asset, AssetId, AssetBlindingFactor}; + + /// The Rangeproof message + #[derive(Debug, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)] + pub struct RangeProofMessage { + /// The asset id + asset_id: AssetId, + /// The asset blinding factor + asset_bf: AssetBlindingFactor, + } + + impl RangeProofMessage { + /// Constructs a [`RangeProofMessage`] from an asset ID and blinding factor. + pub fn new(asset_id: AssetId, asset_bf: AssetBlindingFactor) -> Self { + Self { asset_id, asset_bf } + } - message[..32].copy_from_slice(self.asset.into_tag().as_ref()); - message[32..].copy_from_slice(self.bf.into_inner().as_ref()); + /// The asset ID embedded in the rangeproof message. + pub fn asset_id(&self) -> &AssetId { + &self.asset_id + } + + /// The asset blinding factor embedded in the rangeproof message. + pub fn blinding_factor(&self) -> &AssetBlindingFactor { + &self.asset_bf + } + + /// Computes the commmitment of the rangeproof message. + pub fn commitment(&self, secp: &Secp256k1) -> Generator { + Generator::new_blinded(secp, self.asset_id.into_tag(), self.asset_bf.into_inner()) + } + + /// Parses a message from bytes + pub fn from_byte_array( + secp: &Secp256k1, + inner: [u8; 64], + expected_asset: &Asset, + ) -> Result { + let (asset_id, asset_bf) = inner.split_array::<32, 32>(); + let ret = Self { + asset_id: AssetId::from_byte_array(*asset_id), + asset_bf: AssetBlindingFactor::from_byte_array(*asset_bf) + .map_err(RangeProofMessageError::BlindingFactorOutOfRange)?, + }; + + match expected_asset { + Asset::Null => return Err(RangeProofMessageError::NullExpectedAsset), + Asset::Explicit(asset_id) => { + if ret.asset_id != *asset_id { + return Err(RangeProofMessageError::ExplicitAssetMismatch { + in_txout: *asset_id, + in_message: ret.asset_id, + }) + } + if ret.asset_bf != AssetBlindingFactor::zero() { + return Err(RangeProofMessageError::ExplicitAssetNonzeroBf { + blinding_factor: ret.asset_bf, + }); + } + } + Asset::Confidential(commitment) => { + let ret_commitment = ret.commitment(secp); + if ret_commitment != *commitment { + return Err(RangeProofMessageError::ConfidentialAssetMismatch { + in_txout: *commitment, + in_message: ret_commitment, + }) + } + } + } + + Ok(ret) + } + + /// Converts the message to bytes + pub fn to_byte_array(&self) -> [u8; 64] { + let mut message = [0u8; 64]; + message[..32].copy_from_slice(self.asset_id.into_tag().as_ref()); + message[32..].copy_from_slice(self.asset_bf.into_inner().as_ref()); + message + } + } + + #[non_exhaustive] + #[derive(PartialEq, Eq, Clone, Debug)] + pub enum RangeProofMessageError { + NullExpectedAsset, + ExplicitAssetMismatch { + in_txout: AssetId, + in_message: AssetId, + }, + ExplicitAssetNonzeroBf { + blinding_factor: AssetBlindingFactor, + }, + ConfidentialAssetMismatch { + in_txout: Generator, + in_message: Generator, + }, + BlindingFactorOutOfRange(secp256k1_zkp::Error), + } + + impl fmt::Display for RangeProofMessageError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Self::NullExpectedAsset => f.write_str("rangeproof associated with null asset"), + Self::ExplicitAssetMismatch { in_txout, in_message } => { + write!(f, "txout had explicit asset ID {in_txout}, but rangeproof encoded asset ID {in_message}") + } + Self::ExplicitAssetNonzeroBf { blinding_factor } => { + write!(f, "txout had explicit asset ID, but rangeproof encoded a nonzero asset blinding factor {blinding_factor}") + } + Self::ConfidentialAssetMismatch { in_txout, in_message } => { + write!(f, "txout had asset commitment {in_txout}, but rangeproof encoded asset commitment {in_message}") + } + Self::BlindingFactorOutOfRange(ref e) => e.fmt(f), + } + } + } - message + impl std::error::Error for RangeProofMessageError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Self::NullExpectedAsset => None, + Self::ExplicitAssetMismatch { .. } => None, + Self::ExplicitAssetNonzeroBf { .. } => None, + Self::ConfidentialAssetMismatch { .. } => None, + Self::BlindingFactorOutOfRange(ref e) => Some(e), + } + + } } } +pub use self::range_proof_message::{RangeProofMessage, RangeProofMessageError}; /// Information about Transaction Input Asset -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "actual_serde"))] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), +)] #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct TxOutSecrets { /// Asset @@ -259,8 +372,9 @@ impl TxOutSecrets { } } - /// Gets the surjection inputs from [`TxOutSecrets`] - /// Returns a tuple (assetid, blind_factor, generator) if the blinds are + /// Gets the surjection inputs from [`TxOutSecrets`]. + /// + /// Returns a tuple `(assetid, blind_factor, generator)` if the blinds are /// consistent with asset commitment /// Otherwise, returns an error pub fn surjection_inputs(&self, secp: &Secp256k1) -> (Generator, Tag, Tweak) { @@ -272,11 +386,12 @@ impl TxOutSecrets { /// Gets the required fields for last value blinding factor calculation from [`TxOutSecrets`] pub fn value_blind_inputs(&self) -> (u64, AssetBlindingFactor, ValueBlindingFactor) { - return (self.value, self.asset_bf, self.value_bf); + (self.value, self.asset_bf, self.value_bf) } } /// Data structure used to provide inputs to [`SurjectionProof`] methods. +/// /// Inputs for which we don't know the secrets can be [`SurjectionInput::Unknown`], /// while inputs from user's wallet should be [`SurjectionInput::Known`] /// @@ -322,7 +437,7 @@ impl SurjectionInput { } /// Handy method to convert [`SurjectionInput`] into a surjection target - /// that can be used while creating a new [SurjectionProof]. + /// that can be used while creating a new [`SurjectionProof`]. /// /// Only errors when the input asset is Null. pub fn surjection_target( @@ -382,13 +497,7 @@ impl Asset { }) .collect::, _>>()?; - let surjection_proof = SurjectionProof::new( - secp, - rng, - asset.into_tag(), - asset_bf.into_inner(), - inputs.as_ref(), - )?; + let surjection_proof = SurjectionProof::new(secp, rng, asset, asset_bf, inputs)?; Ok((out_asset, surjection_proof)) } @@ -400,8 +509,8 @@ impl Value { /// /// # Returns: /// - /// A pair of blinded asset, nonce and corresponding proof as ([`Value`], [`Nonce`], [`RangeProof`]) - /// The nonce here refers to public key corresponding to the input `ephemeral_sk` + /// * A pair of blinded value, nonce and corresponding proof as ([`Value`], [`Nonce`], [`RangeProof`]) + /// * The nonce here refers to public key corresponding to the input `ephemeral_sk` pub fn blind( self, secp: &Secp256k1, @@ -419,7 +528,8 @@ impl Value { Ok((value_commit, nonce, rangeproof)) } - /// Blinds with the given shared_secret(instead of computing it via ECDH) + /// Blinds with the given `shared_secret` (instead of computing it via ECDH). + /// /// This is useful while blinding assets as there is no counter party to provide /// the blinding key. pub fn blind_with_shared_secret( @@ -433,8 +543,7 @@ impl Value { let value = self .explicit() .ok_or(ConfidentialTxOutError::ExpectedExplicitValue)?; - let out_asset_commitment = - Generator::new_blinded(secp, msg.asset.into_tag(), msg.bf.into_inner()); + let out_asset_commitment = msg.commitment(secp); let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, vbf); let rangeproof = RangeProof::new( @@ -443,7 +552,7 @@ impl Value { value_commitment.commitment().expect("confidential value"), value, vbf.into_inner(), - &msg.to_bytes(), + &msg.to_byte_array(), spk.as_bytes(), shared_secret, TxOut::RANGEPROOF_EXP_SHIFT, @@ -461,15 +570,13 @@ impl TxOut { pub const RANGEPROOF_EXP_SHIFT: i32 = 0; /// Rangeproof Minimum private bits pub const RANGEPROOF_MIN_PRIV_BITS: u8 = 52; - /// Maximum explicit amount in a bitcoin TxOut - pub const MAX_MONEY: u64 = 21_000_000 * 100_000_000; /// Creates a new confidential output that is **not** the last one in the transaction. /// Provide input secret information by creating [`SurjectionInput`] for each input. /// Inputs for issuances must be provided in the followed by inputs for input asset. /// /// For example, if the second input contains non-null issuance and re-issuance tokens, - /// the `spent_utxo_secrets` should be of the form [inp_1, inp_2, inp_2_issue, inp2_reissue,...] + /// the `spent_utxo_secrets` should be of the form [`inp_1`, `inp_2`, `inp_2_issue`, `inp2_reissue`,...] /// /// If the issuance or re-issuance is null, it should not be added to `spent_utxo_secrets` /// @@ -481,7 +588,7 @@ impl TxOut { rng: &mut R, secp: &Secp256k1, value: u64, - address: Address, + address: &Address, asset: AssetId, spent_utxo_secrets: &[S], ) -> Result<(Self, AssetBlindingFactor, ValueBlindingFactor, SecretKey), ConfidentialTxOutError> @@ -539,12 +646,12 @@ impl TxOut { let (out_asset, surjection_proof) = exp_asset.blind(rng, secp, out_secrets.asset_bf, spent_utxo_secrets)?; - let msg = RangeProofMessage { - asset: out_secrets.asset, - bf: out_secrets.asset_bf, - }; + let msg = RangeProofMessage::new( + out_secrets.asset, + out_secrets.asset_bf, + ); let exp_value = Value::Explicit(out_secrets.value); - let (out_value, nonce, range_proof) = exp_value.blind( + let (out_value, nonce, rangeproof) = exp_value.blind( secp, out_secrets.value_bf, receiver_blinding_pk, @@ -559,14 +666,14 @@ impl TxOut { nonce, script_pubkey: spk, witness: TxOutWitness { - surjection_proof: Some(Box::new(surjection_proof)), - rangeproof: Some(Box::new(range_proof)), + surjection_proof, + rangeproof, }, }; Ok(txout) } - /// Convert a explicit TxOut into a Confidential TxOut. + /// Convert a explicit `TxOut` into a Confidential `TxOut`. /// The blinding key is provided by the blinder parameter. /// The initial value of nonce is ignored and is set to the ECDH pubkey /// sampled by the sender. @@ -593,7 +700,7 @@ impl TxOut { self.value .explicit() .ok_or(ConfidentialTxOutError::ExpectedExplicitValue)?, - Address::from_script(&self.script_pubkey, Some(blinder), &AddressParams::ELEMENTS) + &Address::from_script(&self.script_pubkey, Some(blinder), &AddressParams::ELEMENTS) .ok_or(ConfidentialTxOutError::InvalidAddress)?, self.asset .explicit() @@ -623,19 +730,15 @@ impl TxOut { // Only error is Null error which is dealt with later // when we have more context information about it. match self.value { - Value::Null => return Err(TxOutError::UnExpectedNullValue), + Value::Null => Err(TxOutError::UnExpectedNullValue), Value::Explicit(value) => { - if value > Self::MAX_MONEY { - return Err(TxOutError::MoneyOutofRange); - } if value == 0 { // zero values are only allowed if they are provably // unspendable. if self.script_pubkey.is_provably_unspendable() { return Err(TxOutError::ZeroValueCommitment); - } else { - return Err(TxOutError::NonUnspendableZeroValue); } + return Err(TxOutError::NonUnspendableZeroValue); } let asset_comm = self.get_asset_gen(secp)?; Ok(PedersenCommitment::new_unblinded(secp, value, asset_comm)) @@ -648,13 +751,14 @@ impl TxOut { /// /// Inputs for issuances must be provided in the followed by inputs for input asset. /// For example, if the second input contains non-null issuance and re-issuance tokens, - /// the `spent_utxo_secrets` should be of the form [inp_1, inp_2, inp_2_issue, inp2_reissue,...] + /// the `spent_utxo_secrets` should be of the form [`inp_1`, `inp_2`, `inp_2_issue`, `inp2_reissue`,...] /// If the issuance or re-issuance is null, it should not be added to `spent_utxo_secrets` /// /// # Returns: /// /// A tuple of ([`AssetBlindingFactor`], [`ValueBlindingFactor`], ephemeral secret key [`SecretKey`]) /// sampled from the given rng + #[allow(clippy::too_many_arguments)] pub fn new_last_confidential( rng: &mut R, secp: &Secp256k1, @@ -687,8 +791,9 @@ impl TxOut { Ok((txout, out_abf, out_vbf, ephemeral_sk)) } - /// Similar to [TxOut::new_last_confidential], but allows specifying the asset blinding factor + /// Similar to [`TxOut::new_last_confidential`], but allows specifying the asset blinding factor /// and the ephemeral key. The value-blinding factor is computed adaptively + #[allow(clippy::too_many_arguments)] pub fn with_secrets_last( rng: &mut R, secp: &Secp256k1, @@ -707,12 +812,13 @@ impl TxOut { { let value_blind_inputs = spent_utxo_secrets .iter() - .map(|utxo_sec| utxo_sec.value_blind_inputs()) + .map(TxOutSecrets::value_blind_inputs) .collect::>(); let value_blind_outputs = output_secrets .iter() - .map(|e| e.value_blind_inputs()) + .copied() + .map(TxOutSecrets::value_blind_inputs) .collect::>(); let out_vbf = ValueBlindingFactor::last( @@ -739,14 +845,13 @@ impl TxOut { /// Unblinds a transaction output, if it is confidential. /// /// It returns the secret elements of the value and asset Pedersen commitments. - pub fn unblind( + pub fn unblind( &self, secp: &Secp256k1, blinding_key: SecretKey, ) -> Result { - let (commitment, additional_generator) = match (self.value, self.asset) { - (Value::Confidential(com), Asset::Confidential(gen)) => (com, gen), - _ => return Err(UnblindError::NotConfidential), + let (Value::Confidential(commitment), Asset::Confidential(additional_generator)) = (self.value, self.asset) else { + return Err(UnblindError::NotConfidential); }; let shared_secret = self @@ -765,18 +870,22 @@ impl TxOut { shared_secret, self.script_pubkey.as_bytes(), additional_generator, - )?; - - let (asset, asset_bf) = opening.message.as_ref().split_at(32); - let asset = AssetId::from_slice(asset)?; - let asset_bf = AssetBlindingFactor::from_slice(&asset_bf[..32])?; + ).map_err(UnblindError::Rewind)?; let value = opening.value; let value_bf = ValueBlindingFactor(opening.blinding_factor); + let asset_and_bf = SliceExt::split_first_chunk::<64>(opening.message.as_ref()) + .ok_or(UnblindError::MissingRangeproof)? + .0; + let message = RangeProofMessage::from_byte_array( + secp, + *asset_and_bf, + &self.asset, + ).map_err(UnblindError::RangeProofMessage)?; Ok(TxOutSecrets { - asset, - asset_bf, + asset: *message.asset_id(), + asset_bf: *message.blinding_factor(), value, value_bf, }) @@ -785,6 +894,7 @@ impl TxOut { /// Errors encountered when unblinding `TxOut`s. #[derive(Debug)] +#[non_exhaustive] pub enum UnblindError { /// The `TxOut` is not fully confidential. NotConfidential, @@ -792,18 +902,18 @@ pub enum UnblindError { MissingNonce, /// Transaction output does not have a rangeproof. MissingRangeproof, - /// Malformed asset ID. - MalformedAssetId(hashes::Error), + /// Malformed rangeproof message. + RangeProofMessage(RangeProofMessageError), /// Error originated in `secp256k1_zkp`. - Upstream(secp256k1_zkp::Error), + Rewind(secp256k1_zkp::Error), } impl fmt::Display for UnblindError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { UnblindError::MissingNonce => write!(f, "missing nonce in txout"), - UnblindError::MalformedAssetId(_) => write!(f, "malformed asset id"), - UnblindError::Upstream(e) => write!(f, "{}", e), + UnblindError::RangeProofMessage(_) => f.write_str("failed to parse message embedded in rangeproof"), + UnblindError::Rewind(_) => f.write_str("failed to rewind rangeproof"), UnblindError::NotConfidential => write!(f, "cannot unblind non-confidential txout"), UnblindError::MissingRangeproof => write!(f, "missing rangeproof in txout"), } @@ -814,26 +924,14 @@ impl std::error::Error for UnblindError { fn cause(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { UnblindError::MissingNonce => None, - UnblindError::MalformedAssetId(e) => Some(e), - UnblindError::Upstream(e) => Some(e), + UnblindError::RangeProofMessage(e) => Some(e), + UnblindError::Rewind(e) => Some(e), UnblindError::NotConfidential => None, UnblindError::MissingRangeproof => None, } } } -impl From for UnblindError { - fn from(from: secp256k1_zkp::Error) -> Self { - UnblindError::Upstream(from) - } -} - -impl From for UnblindError { - fn from(from: hashes::Error) -> Self { - UnblindError::MalformedAssetId(from) - } -} - impl TxIn { /// Blind issuances for this [`TxIn`]. Asset amount and token amount must be /// set in [`AssetIssuance`](crate::AssetIssuance) field for this input @@ -866,17 +964,17 @@ impl TxIn { Value::Explicit(v) => Value::Explicit(v), }; let spk = Script::new(); - let msg = RangeProofMessage { + let msg = RangeProofMessage::new( asset, - bf: AssetBlindingFactor::zero(), - }; + AssetBlindingFactor::zero(), + ); let (comm, prf) = v.blind_with_shared_secret(secp, bf, blind_sk, &spk, &msg)?; if i == 0 { self.asset_issuance.amount = comm; - self.witness.amount_rangeproof = Some(Box::new(prf)); + self.witness.amount_rangeproof = prf; } else { self.asset_issuance.inflation_keys = comm; - self.witness.inflation_keys_rangeproof = Some(Box::new(prf)); + self.witness.inflation_keys_rangeproof = prf; } } Ok(()) @@ -885,7 +983,7 @@ impl TxIn { /// Blind issuances for this [`TxIn`]. Asset amount and token amount must be /// set in [`AssetIssuance`](crate::AssetIssuance) field for this input /// - /// Returns (issuance_blinding_factor, issue_blind_sec_key, token_blinding_factor, token_blind_sec_key) + /// Returns (`issuance_blinding_factor`, `issue_blind_sec_key`, `token_blinding_factor`, `token_blind_sec_key`) pub fn blind_issuances( &mut self, secp: &Secp256k1, @@ -908,47 +1006,60 @@ impl TxIn { } } -/// Data structure for Unifying inputs and pseudo-inputs. +/// Inputs or pseudo-inputs. #[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] -pub enum TxInType { +pub enum CtLocationType { /// Regular input - Input(usize), - /// Issuance Pseudo-input - Issuance(usize), - /// Re-issuance pseudo-input - ReIssuance(usize), + Input, + + /// Issuance pseudo-input + Issuance, + + /// Reissuance pseudo-input + Reissuance, } +/// Data structure for Unifying inputs and pseudo-inputs. +#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub struct CtLocation { + /// Input index + pub input_index: usize, + + /// Input or pseudo-input type + pub ty: CtLocationType, +} + + impl Transaction { /// Verify that the transaction has correctly calculated blinding /// factors and they CT verification equation holds. /// This is *NOT* a complete Transaction verification check - /// It does *NOT* check whether input witness/script satifies + /// It does *NOT* check whether input witness/script satisfies /// the script pubkey, or inputs are double-spent and other /// consensus checks. /// This method only checks if the [Transaction] verification /// equation for Confidential transactions holds. /// i.e Sum of inputs = Sum of outputs + fees. /// And the corresponding surjection/rangeproofs are correct. - /// For checking of surjection proofs and amounts, spent_utxos parameter + /// For checking of surjection proofs and amounts, `spent_utxos` parameter /// should contain information about the prevouts. Note that the order of - /// spent_utxos should be consistent with transaction inputs. + /// `spent_utxos` should be consistent with transaction inputs. /// ## Examples /// /// ``` /// # use std::str::FromStr; - /// # use elements::hashes::hex::FromHex; + /// # use elements::hex; /// # use elements::encode::deserialize; /// # use elements::secp256k1_zkp; /// # use elements::{confidential, script, Transaction, TxOut, TxOutWitness}; /// # fn body() -> Result<(), Box> { /// let secp = secp256k1_zkp::Secp256k1::new(); - /// let tx: Transaction = deserialize(&Vec::::from_hex( + /// let tx: Transaction = deserialize(&hex::hex!( /// "0200000001014166d8bc73e9f6bf833f6372b021d6e412ae773cdd722467db163ff06d1e1fcb0100000000fdffffff030bbc8258e21ddcfa93f8b13e26675ce0696bab13e48b6e570087d27b8c2e58229108a6dd1a702dc30f897e040004def8dd2e67b7c6567a77b7c4d88e71d837531d76021d91021fab6f42fbae69c1ef0fe51ed088f08f69f9e658c5f702ab8a512334cb160014bea76c13404321e84760d712218e455b559f2ea20b637f6c0c63b8403cb889ee0502f2b4d8f391b8230798e938ea0aff882758f5fc09674f64e8313722b6fda15d4e3be5845a2c8fd7a243312413f026f6dc9541bb6e031465dee3abbf0059ebabf7933cd5bc8725a8e284f9f2868df84dcd78af6e15741600149928d95e500cf680ab923370529b5110b4c6b35501230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000000f900006f000000000002473044022018a96048b7b3d732fe44421655a26c076a84adc9b0d210856734ef52093b0ba8022045f3645b9d9f40a47963f56c5fe599a23079bd14fceaa2432750a235c623485e01210232edc2082acf05281c9ddb1d8e024de805ae65f6547848ea34441c09173bee3000430100012bfbd82937b25fc506c0b016d904d63eb15301c8fabc6e796549f0383cc08c7effd3d398b04e4feb1279f90f7c32d9a87907155514d69f9612b059f6fdf2a62ffd4e10603300000000000000013f2817003c824b0c690dbde0739e2e1263411a92287701d3bcb9917b2562397755e3945a5f0e917e4eb71b9245d225abcb97a43c7c8bae23fe9b389991db5e693937b2dc225072e5177673ba18545a9dcafd1f550501543ca45d11eaabd2769e49bc5a26fbe64eedaf38d56ef5f6d5cbac587c97c9846a6472a0525104dbabf24d34fb3db29244ef5dce6c9b71a12d9bd48bb796f6b4f89c1130e767ea70842402a1d1c1e4fa2ab18db1f407587e2b5ecbfc34c05b5fe5d10ab43fc9fda482ae7b08eb0115e9758b9d14de8f8d567133779a6153411e32456241f149e0731dbc263a5831317bb394a0efbed52cbf121abafce5dcde0949b619dffea3a942e0ce237a4725a865f4d3a7dd18f6c3baceefd2504fe03d898687ed44237e6105a237621a71f68a22d43ade7c59e14ebc5ea93e236f29b7d8cdaf933c8dc2df72dfd4b74c4469ce34a263373c7154ee89a66387bda99794ecfda0ac03298f1be66f60a67202053fcc98eea44839f8f6e5e0aa3657b72bd54a236362222ab91b5bc0abc7bf1b19138dbead02f904e0417b45699fa3699f7c8af319db491b7c0990a119ae0fbb0591e5c1185c50aa6b8a6ee85cbb841bc4fb99164e0b4d0e18fd8f61d41e05af83c4e94091256f27fe2838d8b44c657f0035c11c3c7636f62e0e3b2188faed87daa23b01f690a0712a06f06f464be8b5ae955f3f880f750d01d23c3f6ee7472b1a8508b0b7a200675dd347c14d520e77f7d61dc28e87f63129aeeb15c32fce6fc109ea7582bedef9594685b8290ead0fec8f7a0477babf661c61d7e651b19b46a59b06d92472ff1e30033534acfdc6a23bdd287e0582d3ca45be95436120ebae081c0c81da96d60db6870d02e386e5cf0ceeb0d5d746ab7178f9297c47be7d9fae7b73eda435319ed7865fd3357120645cc46bbb8ab4ff58fbffc3252888ac7e1bf6f61ef113c1823e38494c2344897f31f65374b13b677656b37386d95a66089b2d1c557b6f70d0354fb71a903c530ead4a0199d71052f589be243ccbc890537b9412f2d70a4d9585c8af6af9844f403acc40ff8633bd2232c032fff6538aa45218ecac24eb62a48f852b91dab12efea26f2b96f368fd850da25ba319d09289df0ee8052ebc0e39bb006966cc51757a3bc3da2a7483ccc7b82f4b13fdc5222a72f60bb750ffb469fa1821cff233ce8078676a9c8dc4cbdd89337275af2e51fe5351d7ed81ef62dcd944022da3ae3cc501e777d567217b21e13d8f1cc2cfac2d8494343e3b6ae80cff461ea20a3604bd41b42d2ead953bdbd75170c7129139724a6669ae77ac234370625a9533168bc1cad48839ef2abe517d2955d75b5d573e6b6e52e5342d9b6d9e2ed5413bd2cb8190fbbfa22a090da3eb462a886e2c2fbc076fc47c8778d3d10b15b7afdc2fd8886b4f85a565c2f517005b125df57a444a4d2528b7d3ac67a56454fdb2fc764f29d6e9fa8ec0bfb78f562ed9331d24f541c03a51628a932457e0287189ea36a35be4d01577e7a2a766f2081366840780abfd2aff20a9d4cc826fd751fbb0e7b353d518a3cd90bd61e694a7645a7859e21532d8f9759d32804c3341e41164f8815a38411484f4b394db6a8150591faa9d37cc76e812fc2b6cfb1e605d91552a31fe88b9d18cbbc7d2e558db74987564209ce126241c38a80da1df34db585a9d7a1330080cb16fdaf9ec1c37f309a8a0e2e7748ede59e7910b8b6b5caee51532ee6c5bd851db455c150b8d90445a36fbac9858798d73abf605dd0f515c23a0f975d50d09e6d7f541f0250c23c414219c87b15929ada1974a71709809db49ad8cab72958c31b1ddd0064d264641327a433d748713c57bc0063ec45b852a9beb2cc2be7465bd48b3d113ecf3d4981f2e3ddc7494714d21b860eb336ab91e894af0a5ff727f2b8d85c12f7ed321c90038e8f26f544c4918562b7c65e7cc1cecd5c6961ea2d1f806268d1dca92337f127a03c2626fcac5b8caf0a7abc3a6cc4d362f040572b56c5ad09e0dfd92235dbb2c1a8fbccdee13b2cf55681ec8f053575214c9200bee18428b25440b220079ca8708d23c84927103a8afac4bee58b95cce012ea23b175bd2a12636afc07e649675568ef945d352402f222fa62dcbad7f9f0dae132c089aea82d4c8b905655943e8b61f55475b3c3755d5967b01236e2c4b833ff6945031b50f0a68f0ec7130055761f8e9c2ebb2c9462d0b8aa9e99c7ebed1d3213abc471bbd49f44e78e057c750590f3ac02d3baa776d9f9ef39bc1a166dd7cb1cc3e63d94d7bb51544b9b02ad1278f7691973030612534ff513d2cf7988b3235b4449976b90fa5932639bafb7d0d33b58a167617ab4a46189e251bd488cc36a4b40968aa34c20a80f3ee5380152b7dd62de83c1e2a07307861b3713506115c1021880c19f89f45e40a16cebfc1980ebbab7bb9288eb33d4696c39bedada35a41f9e6832a0f5e12f2968303533c8186cc09da7c3ce4e96410f7a9bc5941706fd513a908ace16cd3605c5d0f4b872e931d79361134371285b6d223e1601ad129daa776ab58cb4dc7eaddd45cbf722d4cc4229d75a52e18c7470ed1a7a84671bd8bca4b9bdb0d7b0a34779d1a538fad3b6e2d7cc2972ab1bb98613b8ecc27f9fc25fd797aca4e5d0de4aa6df7f6607ad268baea41f3c1f9afc9dc5da929b29e4ac203a7476fe2c185ac25b835b6f2037c3f540852bfd2d643c179e0951cebc35743f6315a56d2965de934695ef5dbae70924f8548a858518340d7add64f45d2c7b69bfdbb8fb640a2284619fa857d2f5f8bbf615822e45b924622d32e78caedead8ab69fdfe142c97d7fb61af8cd32767d1006c6331d316e0e66b112a765388ff027eecd1181913ef9c701f2a46d0bf35abcaa3fe0cd26863e3d65ded79b262d1e914c538ae7d05ab30390465ba25a73a6ab53ce4296a3f5cbfd3901cca44bdd6818fa8d427ab2ceddc315f0adcd3fcae63074ea9e42f15fa02f1e3ec6276adc953beaadbce84ca6ef04966c882e3e7802436e4042fa5be4c3773d685bed66eec0c5a6cc463770c00740fef403badedadd28984e22027b38a0c66b7a409080adea574cb95ca236d701751056f7ea1e1b3082f0a26acbc630526053be88bd88267e23c2787daef884d709a5ac2c118c8c8b4b1eb6cd9d829ec42d9f249f754cf93b6c4cfdcf99cec0f9d742af36300274df095a35efa8ba95d412d2e61db8776660882519d4500f615e6a3e88640e93e921dc1fc4b74b5776cfdd47eb8b00d422fb0ee5c889c419bd352cef4573dab05fe44abd39bb7014e4ad0af6e5c9ed8236edb16d05057a6794ee76e923b6d1bef5e17e7078f6696af0f23064b1a592e89ff7073f040e9ea236450fa8d8fad6d04606b1a1a407ea14868b0c81c76c0f5fe9047e9c60dd150f9164533a7e4cbcc87f5c58e9e9ce317ce694cdd816b45fc497ec5c66736050ba925fa7ec598274f23a4ff022e7970dc520e4baefb30a26a5464fd5c75906e88a2c245cfcf00807c3b5e0deeb463886d606bfe7f30b73a512c5488f7c586dbcecd03ecc6cfc3caef921930aa01d2ca21ae3acfe5af26003acd436c344f80a4a9d9371a51b5b19b984d12b2a134f2ed89a6c5cf9905b2626d926bee7fd39988282411a0ea0ec1b61c22eceee21cd264c1fede96d9460cddc0624c9e22ccf42ff18bb13f8686bd6ef528aeda6181647a1c6c6ebd90fe05a69cbf169a971ae616d9d74840f1a7a3cc48c7a27b07a14e58bace67aa61dc594f0dc909283fc39c77a5dfadd50a358fc2c05118cb6197f3aba31d75fe2681219bff02e70f6d968f99d59a6c80a2af8bc09c21a2874ecfa47850844221fe066a1e6b40c0c5c4b59f8a8c22b78419c77be2306a9e085a76dbf9552ac9a575b872df0f9834f7aa8d89d585ec34ddb7c1d76c6d132356679263c8458a288c95f36631ca460ef925fdd9801154f886beae75dfb5e794ee58813cd1748e932e279ad65a3e2e894d190e221f07207d6ab5d2c7e328661746bc12e72d6075eb2a4e1f91a3b27d3309f9825d2467aef6f5236c38b071e5a6d17e3a7a88033a3ddb756e7aceb2c7d4fcca92a077a110684337fd8222f9c38e806554d30d9c3fcb647faa000f72adcc8e1d6c811634757a74b4d52f9e47826319f3954756d0623149a9f62f838feb135b1d26fe00e299a96dd94106fd39c9aa14360792870f33b8cde870e98353b27c1bbff569b79ef1d5f0161a4b585f4002b42c970b3e84912bf707c8d49fe56adfca407f8a039314a5c0720060061ace5a8144224bf52d5458e1fff84306c2c88b86061e18116a8b46cfb2adb6b33f704ae6d83aa2aac13bcd61b64c93cb6d2d5c3acc990626f891f9b7befa0f25c1a2665290309add936ff62d4fd182d68adeafb49f75fea798d8572444253886bc936589bda972b5e5625db267de1b30a8501ef215ab8a320574ce27a33fe603a67656ed8744f048b022cd61c132ece087fa0d94c2d4dfbb92a46ea5403341df3896ac49932955c6b3d700bc475c9d173c6173c8d883bd9499aede17e7840294334f1b0585b66f00e121ca298933703951801584c5db57854ef87802d254fc75d31319b8560f5ebee2ea80ffefa63e2a4c4e3d53b007ca18f83539f3078b2736f4fb4f8fff41823912227b04bab8aeeb7d95eb0db3dda58a98077f25e1db6dd454cfcd41068dcfb54f1d1e0b478013e58ca7efe874e98205d7c59ceeeb28cdd55cab4fa3a01ebaa957effe330364f75c0d6728b769dad34e58e9f217d1e8e96d79d896c193b425236ef2303eed072d114c06c198fd6a12a28f4d436dd126d1ab98c7e1621e0f59cc7276dbe7cf267ce2c6c0ed164deb57039fc5be8ae4e72efe26115e0fd59ae6fed9743eebbea873fca30c9d7eda201e73fe22e509b11c19580d368bca3f4cc59b949c8d03fa63e7b2b79f39983235d7ea3fc6fc92fab4c66a7680ec57f998fd818db6fa88ac2913f4a48a4cfbf68f1f565f799a6a95a22c8f6a4d6ba2a5307e51c99d22bcdb399520306446a6804f7cccff394986341187c4a72813392984e57ce3cab06c540722e25be50ba138ca0a54686c8960e2f6118380b3b9255d14071b1e2131d6cda16eb463bc4642cc11391a7c8a75a6da8c5f1424f7752ca3dbd37f2d9a2a3855f3b3aa7104c7a5be0c334df618dd478c43b9bb0fe7d774e93cbc8323816e1f0e1290e52079009150761207d99b1ec995ec7897a0385513abb96ec5f9d2c757662cd946d8e340646944b5fc6cf92f606ed2bd6872fe89c1cdabfa7f755b27dadce1337a18ecad4b78c0291f34e1cf0577eab17c70b05eea58bfb7bb3cdb46d46507db8fe8cbe3dca07d92d1ea2c5a6e354cabba8d532a47f6d9ba6ca48add4df8bf8ebbf3ccf8b0417f4b90756faf134ee210511d40314f218f9902897390fb0f405eabfc4a7ed4467e4fd14edd3ce4ee0337a5f0c21590816126768d5a85d67c8a7ef6b07c8d40dd13978cecbbfff0507d6030167e01bfbcb3557610ebad49f2f98cd9e003d3fe0a4dbc64d4202d4260ece552b7f7dad2be1dc61e3e7fbd5ae5be1f9bc0599807727c1e30eaa6d80ce79bc6d5a28c5c3efb8c1e99ffadde8ba7f42c2f27e3fbadec5b13e8673256b2aea4ea1a7b92d909ac4fd06fc3f03098cff0667224949eb1fc242ee42da5a9a06d93c5c4896e3c54e8815602d2a42c4bcad7a597e9261d24a404ba0f4645eb68f0521b7eb73d7b26ce2f802ba54674d011c07c485c1f7b6e31197c40a39c53e94cbd0b3de605c76ec9d7272a53ea5547b2131da2b51b2b0c099bc93214f02c2469c396dbc6fda284126d7d069fc3d51750037e545cf2ffe35b308d1c515870bf0fb062c2c666367061430100011850912d035bb6962d10e126e5a9666eb4128d7fefc4a0633ba0f388c5f28302a7a2e02653aebda6a6bad0cbdd972b57201a14a7f879c480fe5e1c36db90f749fd4e1060330000000000000001bdbe9301cb099b5fb8baac78310a3529d6677700658a6a87eb00a5f66dceb0862dc11cdcf845e07ba63ea84308200d309ffc3211996c507208560b7f65fa70f0a176dd0cb179ca911797c66d6fb56d27728a4fcc9998919a89c52d8bded3f732a5360861a6639c839f39503ea1457ae5d4ff7e1811ac2d33047823c5c118768343620abe7fd8cf459e5fca0f490ed91d9c09b37303662c201fff61247e8fefed8ffb29f999cddf601670469de151617021352c0dfe54512bc44c3b8e1a4ece73d2eaa727b28093d4741bd01dba0d9c7a4cb69e8a63bf6e887fcf6a57812ae40fd829dcfb6bab3a2a4ac678418e15179613f2436f53ae806ecb8f44115847b16cf935efadee467aa6de4a0cccaf8e4b1835b8dd1f9d04f0bbd4b3164dbc58a3a10db4537c94bafeb0289c6a192b28d5ba48df580a44a0d044ba82140295bfae70214127b73c7efac419bccaa75716867bc0bc75171e54dc3c439635d832cd052c4e9fa7370b1794b3da8a1a740b50cbcc605dc840f4d996f1283018a6356c437e79218a191ec68a48b193d3560b690d44741b354b13320ea16286405dc6476e8e8231d667978ef9c36e84f09e74387b4d557e39a40f51a62f70b5be58415c256f262486fd144489de4b8605a0d53945ed08daf543f3fac38888dcd4650903b95ebfcb4e0f57ca89b3f0132400e4012e00854c41b2788bd7c0d5d40845f48571d954e12013f6cf7ece536f32ff9a3c94ed10f1a2fd50b3f38a0f1272489d583deec9da33d9ac46914efea240aa004a8f17e1e168136b4ada57309b91e10c716eae0a5789c64747c0e09a696b67e8c7bba12c2b8d80248da93acfc7c1455a33b40f8761fd37812e74e572a9a21b0e2d7bc37ccad146d847a53d7a650122d96d00b179e353db2864e5ec929173550e0edf2c02b2ccb595b326582758d700009f4c433cf86837d1070686a6eeee6f4ab1e6ffc44bda783d7e2ff81f289991cdf982b61a73660020e544e5897c3021a446c8a4966ca625bbd6bfdc505e85d1f5acc663607cc2ba18945b74662be550b215878b35d9932f11cbe509456461ffa2b3bad33405f51c5b17b5081bbb4874c2656e0efecb2647d22028e53f263401e779e92ea3f70c860b0405e8109e10e27c2f4986e66965f4ddb895b943ba4dd0315372ac0460f78ec408cd562c21537d7f0f5e12c95e3d86066db77390b02e9073e241aa97af7588bbd6fa967a6776a3e226c0e56936fccae5ecd8aa20af1cc9b74a4579d8bd4fba988438fe455da8261f9aa96bf222e5af2fe297ce1901f0845334e856fa928119e23cc64f5ac541c699befad140a3e2cf53f591112d1ae57391eecf6fb729654fa98ba946ba8a532c3cb8b0dad5f08e3158f128cb065379b2ffc78d8394e5d2f1c7f4fa7b8a5031a7053f0144835e7ef53f4d60c8953bfde31e75fa3ab6bab86bc37617585db21f16318902138c1a7c25db16c212bef0de8aed1575e1c8e1064755b4b493adaec2dd320f8f8b9a240352f7a8a409ff3ac3beaf08114ba7294502f8f0f529e039ac7cda8a9d8e45b9aeb4e7a83d2de5a4edcb363a15020a5d285cf6acc3f43dfa724fd6c8ce76c33d485db88cdf379faaa7a0eb65f52c99daf2fb0edd8eed2e38e2990b044bb4ab7cda75d23b04fb72e842b54d88ed8a7ed236f61ce58e7a7fc34aa94e9100157cda06dcf81722058369df2b912442ea0768383b7c673f239a7f56dd4ec8739cd14698000747b979f22852e72352b0287fe7c0bddb68bd494341a0cbbd0df4fec1d613f7160f8fae32b9009c4d7146a8004a158763efea270c6724d022bd3d5954789e2ebcb50663b98a619182d800263485faee5e621d99f6819068f6d032aba95f7294aa6bb297f93fbd0e1def781351fe5ed7330ffb203d48aef9c6e6af244cca568dd164226131343f37977f11a770bea7f40e8b0593f5efd23ca0ff18594512004a9a34582de5ecfe06519f6223b5576ba1492d817e6da30791abfd2f4e85d235fc16f43ffa1879afd6f3c3aa252a232d567502dbdd70005997da48f8a0c64911af8ba5e8c123a3e81247de4a536ef41330d345c2681e1a508e4accee45140a194a1eedbe6559e67a9daa34580f00166db39d3f6e92d0b996754bb1d8cd3d68d692b872a0e9b086c14c1d143e03ffe5279a6687e5fe139534e59d43e2204ad9794a38a3d9cc9e63245c89123977e66dde7e33800f62a9ab3aa725b09670cbd58890056d62b459473ecdcca375d4784f278042fecd626c635414ed1ed1a1e2cec075d1a495004debb13df0c61e0bfc2f10ac84d94c404400559c6b4209fcc4fd0f4e041fc5101fa8265478fb794e7c008af8172d267e495d314d65b9dc1dc3ede3be27e4c80840ee7b75c31355bb4c940049bb0e02234370a2cd009753983409d87604ff5bd2d179061f9629be6663ffad62e3aed59de373892140475cf491a6482da6d9a1cc1031b4fce7737accce613a01fccaf36f0ac6fe1323828f3cf2a3e8c64cd0f95916c3db7176200e8f6384e6527f8020a761c0e46d388c4c1424118a69afc6bc5884d9ca3a19b5a65f95d3cc476b1f8e1c7bd41969b0f42d6b121816c1f3ebcff888c0c93d582d6f9b1bb5acc1cbdef4db323585ee059b4a68b37dd6ec85fa3a7bdc0ff7cd5e903cc76bc6a30b7965132e551bd5ac1c11ef069da69064086baa14435a9492444619dc3df5466bfb2cda341ff630d767ab55ec2bec5f92fa0e23cd8b4a5386c85cf540fdc4a15e9a27f7ea48c29d92a58c738eb2133005ab4b787d849acca740d58d258e5fe32dac3f2499773ffe3b362cb384632a8f24b9380c1ec1566108052ac157691ccdfe8ee497c57fcb8db7799ff2688288f07dcd7af020e3b21ce8ce9a730fa23f88fe2ade8291a439fd3b5769ff98284e042a1d795b1920b10cf755d3073a7a8e7f9b78b62baea353b77fc4be39caf38c709ad8c548432a7ed102e114f44b0ff22c7c04d4299f9d47bd81172385ddb1e5c9019809968a7638bde0b766d63514f85b22b1a795fb97b9c367b9693299c7447630e333265faf35ea516247d1d1ef7f9ded1219f9cba746100cbf6470becbe5e73fb817e7979bd1d502e9b7ab62bd70a70115cf3f9eee4e7ba131040a4baf9139e7bc6968b0053075f75afc787e2a083caff88d5b627d81d5e8584bb30334211866dceb96a2f03db6734e5a8cd28d4000119a55e32ac45dc38080c5fa05200e0054bea35713648f17634b6954b7be38e38fb29c3f5251f33c3f6531855f393fad9568b3f3ee02fdcf02f2172de1f8007a479d235091c4c39b76941bcf563b46e32248f9adbbb247bc62d552c5444ffdbd0cebf3e5a08212400cd7155236c21a410bb1dc9aca0f82b438c5e1d2c1632c577801ca18d30371e39efd3135092d008e290dba376f799a85dbfa317e9490bf2ee52444567ef8f74c5350e69331c03d51ddc8151822656cf7bbc054dcf9a5166a2bc72b01c5778c4c8f3076343ec1a6f7f3380d3e19bde7c248b23789bc724af6fe17c2173b0c204ff8b6342e5c9bd8f652ff0c80077fce0ce258b0879c74986f5d51e59eae4b946de1e2b785f039ef9bde314753011e35dd9ab9278fd95e4b10f04b4a157a16a6de9ba5a1793a4be0f2e2fd3cf2fd9366b2b7cd358f7a9a9397899833839c8bc968c5c37660488cf391637702de780b5ac526ab11fc895a5c783931dd0d486cc7672f417ceb30914b8f6fc51d270c932c81c7fda22b2b88ff14f7c9285f6341053fc529aefc97755fc5962a5e094b10291b98c991b0bee8c28b639f642bbcbd7c27d9217c19e11a94ecd58bc79c220feb9f1799f952e8a31b9607bb9fc4ababb8515aed1112050e0a75cb5c7811a63b90eafe411e92f2864fdbc4f23ac3c335d300440f9b6c0e7402a825fd5f4eb32ccb76ae4ef43441282356cac29b0147f780252427c9d21abdf7306c9f2836f599cef0a2c2eb4596df9498d661bcf8bcc88d5a7f0779489f9423ad045b735e421ba0f861744beecb7c1efcb5989386e9c2e4e07ef033ce75d5e3ae6b78775cbb3d5ad8fa5b29ee2137f6b59f865c71af63ac98c2f0cecaec428c773a747ba188f8f3dc30ede05077b422a920a313b885c31d247328c691f66a7440bb27581baea7d9365f8da23bf71a7cbbca02c74391928daf1f6b2b0f74acd3b89ad53e3dc87aa2716a25c89c4a6f1a363cbd97745910e399cfa766ddc5b25fe4f40412690316acab688493a1c882ccabf7ba2b1496f77ba6a34133b3a6d9f5c6526309f1d2e14c261959dae012420bf9fa0aa2b3e3862308f6c6f65e7f0660eed3b5fbf3278eb5b2893b1c8dbef827e7bee0fa92fad9393f037c4b90babf62a159e2637f22a46a18bee1baf9c4c3344186083b167de5936437a5fb379ae305e8a502fc22f849398daa9547c8f6c2554d231daba90a5f729069c24bdb2b749cd1a16489f7c0940a59ba7f324dcb2c42e47652179208bfff6d64a1ac5e557a34321a4c3bda6eae6f461e1a6ecc9cee9fbf36e2b6c504ae2e0a3bd03235fb6032725280d64fa1a33f58a7196b56c0a65dc7bbf0a429889d0ee2467796515f857800c78fe21bd17f65807342a7f4a0a7926d064f073d1a7dc76295b9e35195b7880fd83db7e2335709179cca4f285b0b14693fd0b6cef9958cc6906adacdf1ad1b572eaf9e6ef42b574d75a1f58927d49113726572b40bd792b6ca621daa7c5f56645b1f8ed5b0a7ac26f71c4c665b89b975b3c38ed6949271df097c8018e772b2a3f8bba41424bd9c1b06e111cfdedbac883dbdc5345e5a6aec531c722c94b0c6b072288b706afe77a6e179fd38c7f6a0051246f0b161d0a5c696374755b01822181bd843ee8d4f476ef5bf3efbf9cae0f3162ee5de88c0aeafcb3b5fb34a7edaad5292428a612eeea80a54c0dc3ac1ac7b00f103aa39015a038d4537271bdc277e4a8f8648797b6a67cebb7485625406f9963d17b51d1f4706674e58da1b5e4c415eb403791c72a62a2e6a5a4cf50d26fe78d7e9620c50f718dbd0d075efccbeb8b731f8b1ecc988f2b38dce9cda9a644441391728a47ccd8975dac442c39f59726802474ec44a45afb5e545512e8f069e139da079c6e0bafce31f30bef474bb2deeeb6a035ab37757b02d6f4de3ff85a0cd5109290259c1be2a8288a33dcad5518d9d0413a393f659095beef0572193af15d909acbff828b56adba008a3fdb653ee5fcb5653884ef69d8f8f6588b2b46a3dd361dd4983d205d22f9351f4ebc049f867832b35181a70f390158a2960fc9bc2551e23925f2c9a262b7b9e9da6c97f35e5ba21986f5cface9e8d829751921a9e6fdfbe084197a97a778c795c0a9e293c7a07033ac2a34e0f27ca53b4cc7833fc7682c0da1f5784e812e4223933dc2b4b20f77c4d01c40c4f7db5a556ad3de59605e45c02928f5b8a7720eb83f750c9789b062826bfc895ee526f0c813d3a3bdc3a6a03c27c9f3eeaec747bfa72c19da03af860ea21986df7c0509575c2b7f47f036758d9777e6843f620084bafac9c6a2f7910f703c8dff42c9f160a15d852cc4bf2f33b1b53c392959e804d34a3b56c1bf47a4813c7d67d36e66859b1eeb8f105c79aadab1cd2ea927cb9cc48a70da61241b5e3d3a5802c3ba003e7d318628f6a6a37725b6fc721899b30c9dd2b3d6f18d70df0f363ddac2fd3cc79fdde2ac26ccf16462534ba1a8d1baea51b789bc00226febe51678af19898e4f4456f072e5d79345323f8231b085b94419dd812bfae12c4defd363fa4baf09c4ceac1d543365ab52f230925f56840efff264b3b8e961c8aa8e62a8f3df6f204331a1dc08375c6521ebede7a0eaa92d378830d11a989681bd6e07b7a870195f4c2fb579a800000" - /// ).unwrap()).unwrap(); - /// let conf_asset : confidential::Asset = deserialize(&Vec::::from_hex("0b37d4818b8ce1df5d3d0b88d140c6848029d6d85fb0f6ee270865caf53d0b82d4").unwrap()).unwrap(); - /// let conf_value : confidential::Value = deserialize(&Vec::::from_hex("094e2cceeb8005ac14b611821c37fca757b47426afb0bb4eabe41c275d3997c046").unwrap()).unwrap(); - /// let spk : script::Script = deserialize(&Vec::::from_hex("16001475f578ed4f7a0103182a6e92942c66350dd949dc").unwrap()).unwrap(); + /// )).unwrap(); + /// let conf_asset : confidential::Asset = deserialize(&hex::hex!("0b37d4818b8ce1df5d3d0b88d140c6848029d6d85fb0f6ee270865caf53d0b82d4")).unwrap(); + /// let conf_value : confidential::Value = deserialize(&hex::hex!("094e2cceeb8005ac14b611821c37fca757b47426afb0bb4eabe41c275d3997c046")).unwrap(); + /// let spk : script::Script = deserialize(&hex::hex!("16001475f578ed4f7a0103182a6e92942c66350dd949dc")).unwrap(); /// /// let txout = TxOut { /// asset: conf_asset, @@ -994,19 +1105,19 @@ impl Transaction { (inp.asset_issuance.amount, asset_id), (inp.asset_issuance.inflation_keys, token_id), ]; - for (amt, asset) in arr.iter() { + for (amt, asset) in &arr { match amt { - Value::Null => continue, + Value::Null => {}, Value::Explicit(v) => { let gen = Generator::new_unblinded(secp, asset.into_tag()); domain.push(gen); let comm = PedersenCommitment::new_unblinded(secp, *v, gen); - in_commits.push(comm) + in_commits.push(comm); } Value::Confidential(comm) => { let gen = Generator::new_unblinded(secp, asset.into_tag()); domain.push(gen); - in_commits.push(*comm) + in_commits.push(*comm); } } } @@ -1070,7 +1181,7 @@ impl Transaction { secp: &Secp256k1, spent_utxo_secrets: &[TxOutSecrets], blind_issuances: bool, - ) -> Result, BlindError> + ) -> Result, BlindError> where R: RngCore + CryptoRng, C: Signing, @@ -1082,13 +1193,13 @@ impl Transaction { let (iss_vbf, iss_sk, tkn_vbf, tkn_sk) = txin.blind_issuances(secp, rng)?; if txin.asset_issuance.amount.is_confidential() { blinds.insert( - TxInType::Issuance(i), + CtLocation{ input_index: i, ty: CtLocationType::Issuance }, (AssetBlindingFactor::zero(), iss_vbf, iss_sk), ); } if txin.asset_issuance.inflation_keys.is_confidential() { blinds.insert( - TxInType::ReIssuance(i), + CtLocation{ input_index: i, ty: CtLocationType::Reissuance }, (AssetBlindingFactor::zero(), tkn_vbf, tkn_sk), ); } @@ -1131,12 +1242,13 @@ impl Transaction { rng, secp, out.value.explicit().unwrap(), - address, + &address, out.asset.explicit().unwrap(), - &spent_utxo_secrets, + spent_utxo_secrets, )?; - blinds.insert(TxInType::Input(i), (abf, vbf, ephemeral_sk)); + let location = CtLocation { input_index: i, ty: CtLocationType::Input}; + blinds.insert(location, (abf, vbf, ephemeral_sk)); out_secrets.push(TxOutSecrets::new( out.asset.explicit().unwrap(), abf, @@ -1176,7 +1288,8 @@ impl Transaction { &out_secrets, )?; - blinds.insert(TxInType::Input(last_index), (abf, vbf, ephemeral_sk)); + let location = CtLocation{ input_index: last_index, ty: CtLocationType::Input }; + blinds.insert(location, (abf, vbf, ephemeral_sk)); self.output[last_index] = conf_out; Ok(blinds) } @@ -1193,9 +1306,9 @@ pub enum BlindError { TooFewBlindingOutputs, /// All outputs must be explicit asset/amounts MustHaveAllExplicitTxOuts, - /// General TxOut errors + /// General `TxOut` errors ConfidentialTxOutError(ConfidentialTxOutError), - /// No Issuances to blind in this TxIn + /// No Issuances to blind in this `TxIn` NoIssuanceToBlind, /// Zero Value Blinding not allowed ZeroValueBlindingNotAllowed, @@ -1246,145 +1359,29 @@ impl From for BlindError { } } -/// A trait to create and verify explicit rangeproofs -pub trait BlindValueProofs: Sized { - /// Outputs a `[RangeProof]` that blinded value - /// corresponfs to unblinded explicit value - fn blind_value_proof( - rng: &mut R, - secp: &Secp256k1, - explicit_val: u64, - value_commit: PedersenCommitment, - asset_gen: Generator, - vbf: ValueBlindingFactor, - ) -> Result; - - /// Verify that the Rangeproof proves that commitment - /// is actually bound to the explicit value - fn blind_value_proof_verify( - &self, - secp: &Secp256k1, - explicit_val: u64, - asset_gen: Generator, - value_commit: PedersenCommitment, - ) -> bool; -} - -impl BlindValueProofs for RangeProof { - /// Outputs a `[RangeProof]` that blinded value_commit - /// corresponds to explicit value - fn blind_value_proof( - rng: &mut R, - secp: &Secp256k1, - explicit_val: u64, - value_commit: PedersenCommitment, - asset_gen: Generator, - vbf: ValueBlindingFactor, - ) -> Result { - RangeProof::new( - secp, - explicit_val, // min_value - value_commit, // value_commit - explicit_val, // value - vbf.into_inner(), // blinding factor - &[], // message - &[], // add commitment - SecretKey::new(rng), // nonce - -1, // exp - 0, // min bits - asset_gen, // additional gen - ) - } - - /// Verify that the Rangeproof proves that commitment - /// is actually bound to the explicit value - fn blind_value_proof_verify( - &self, - secp: &Secp256k1, - explicit_val: u64, - asset_gen: Generator, - value_commit: PedersenCommitment, - ) -> bool { - let r = self.verify(secp, value_commit, &[], asset_gen); - match r { - Ok(e) => e.start == explicit_val && e.end - 1 == explicit_val, - Err(..) => return false, - } - } -} - -/// A trait to create and verify explicit surjection proofs -pub trait BlindAssetProofs: Sized { - /// Outputs a `[SurjectionProof]` that blinded asset - /// corresponfs to unblinded explicit asset - fn blind_asset_proof( - rng: &mut R, - secp: &Secp256k1, - asset: AssetId, - abf: AssetBlindingFactor, - ) -> Result; - - /// Verify that the Surjection proves that asset commitment - /// is actually bound to the explicit asset - fn blind_asset_proof_verify( - &self, - secp: &Secp256k1, - asset: AssetId, - asset_commit: Generator, - ) -> bool; -} - -impl BlindAssetProofs for SurjectionProof { - fn blind_asset_proof( - rng: &mut R, - secp: &Secp256k1, - asset: AssetId, - abf: AssetBlindingFactor, - ) -> Result { - let gen = Generator::new_unblinded(secp, asset.into_tag()); - SurjectionProof::new( - secp, - rng, - asset.into_tag(), - abf.into_inner(), - &[(gen, asset.into_tag(), ZERO_TWEAK)], - ) - } - - fn blind_asset_proof_verify( - &self, - secp: &Secp256k1, - asset: AssetId, - asset_commit: Generator, - ) -> bool { - let gen = Generator::new_unblinded(secp, asset.into_tag()); - self.verify(secp, asset_commit, &[gen]) - } -} - #[cfg(test)] mod tests { use super::*; use crate::confidential; use crate::encode; use crate::encode::deserialize; - use crate::hashes::hex::FromHex; use crate::Script; - use bitcoin::{self, Network, PrivateKey, PublicKey}; + use bitcoin::{PrivateKey, PublicKey}; use rand::thread_rng; use secp256k1_zkp::SECP256K1; + use std::str::FromStr; #[test] fn test_blind_tx() { // tested with elements 0.20 rebase branch - let tx_hex = "020000000001741498f6da8f47eb438d0fb9de099b7e29c0e011b9ab64c3e0eb097a09a6a9220100000000fdffffff0301230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000775f04dedb2d102a11e47fd7a0edfb424a43b2d3cf29d700d4b168c92e115709ff7d15070e201dd16001483641e58db3de6067f010d71c9782874572af9fb01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000f42400206a1039b0fe0d110d2108f2cc49d637f95b6ac18045af5b302b3c14bf8457994160014ad65ebbed8416659141cc788c1b917d6ff3e059901230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000000f9000000000000"; - let mut tx: Transaction = deserialize(&Vec::::from_hex(tx_hex).unwrap()[..]).unwrap(); + const TX_HEX: &str = "020000000001741498f6da8f47eb438d0fb9de099b7e29c0e011b9ab64c3e0eb097a09a6a9220100000000fdffffff0301230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201000775f04dedb2d102a11e47fd7a0edfb424a43b2d3cf29d700d4b168c92e115709ff7d15070e201dd16001483641e58db3de6067f010d71c9782874572af9fb01230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000f42400206a1039b0fe0d110d2108f2cc49d637f95b6ac18045af5b302b3c14bf8457994160014ad65ebbed8416659141cc788c1b917d6ff3e059901230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000000f9000000000000"; + let mut tx: Transaction = deserialize(&hex::hex!(TX_HEX)).unwrap(); let spent_utxo_secrets = TxOutSecrets { - asset: AssetId::from_hex( + asset: AssetId::from_str( "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23", ) .unwrap(), - asset_bf: AssetBlindingFactor::from_hex( + asset_bf: AssetBlindingFactor::from_str( "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f", ) .unwrap(), @@ -1394,7 +1391,7 @@ mod tests { ) .unwrap() .to_sat(), - value_bf: ValueBlindingFactor::from_hex( + value_bf: ValueBlindingFactor::from_str( "e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d", ) .unwrap(), @@ -1423,27 +1420,18 @@ mod tests { let spent_utxo = TxOut { asset: Asset::from_commitment( - &Vec::::from_hex( - "0baf634b18e1880c96dcf9947b0e0fd2d38d66d723339174df3fd980148c2f0bb3", - ) - .unwrap(), + &hex::hex!("0baf634b18e1880c96dcf9947b0e0fd2d38d66d723339174df3fd980148c2f0bb3"), ) .unwrap(), value: Value::from_commitment( - &Vec::::from_hex( - "093baba9076190867fbc5e43132cb2f82245caf603b493d7c0da8b7eda7912fa2c", - ) - .unwrap(), + &hex::hex!("093baba9076190867fbc5e43132cb2f82245caf603b493d7c0da8b7eda7912fa2c"), ) .unwrap(), nonce: Nonce::from_commitment( - &Vec::::from_hex( - "02a96a456f4936dcf0afbc325ac3798c4464e7b66dd460d564f3f91882d6089a3b", - ) - .unwrap(), + &hex::hex!("02a96a456f4936dcf0afbc325ac3798c4464e7b66dd460d564f3f91882d6089a3b"), ) .unwrap(), - script_pubkey: Script::from_hex("0014d2bcde17e7744f6377466ca1bd35d212954674c8") + script_pubkey: Script::from_hex_no_prefix("0014d2bcde17e7744f6377466ca1bd35d212954674c8") .unwrap(), witness: TxOutWitness::default(), }; @@ -1457,19 +1445,19 @@ mod tests { let (address, blinding_sk) = { let sk = SecretKey::new(&mut thread_rng()); let pk = PublicKey::from_private_key( - &SECP256K1, + SECP256K1, &PrivateKey { compressed: true, - network: Network::Regtest, + network: bitcoin::NetworkKind::Test, inner: sk, }, ); let blinding_sk = SecretKey::new(&mut thread_rng()); let blinding_pk = PublicKey::from_private_key( - &SECP256K1, + SECP256K1, &PrivateKey { compressed: true, - network: Network::Regtest, + network: bitcoin::NetworkKind::Test, inner: blinding_sk, }, ); @@ -1501,7 +1489,7 @@ mod tests { &mut thread_rng(), SECP256K1, value, - address, + &address, asset, &spent_utxo_secrets, ) @@ -1515,7 +1503,7 @@ mod tests { #[test] fn blind_value_proof_test() { - let id = AssetId::from_slice(&[1u8; 32]).unwrap(); + let id = AssetId::from_byte_array([1u8; 32]); let abf = AssetBlindingFactor::new(&mut thread_rng()); let asset = confidential::Asset::new_confidential(SECP256K1, id, abf); @@ -1541,7 +1529,7 @@ mod tests { #[test] fn blind_asset_proof_test() { - let id = AssetId::from_slice(&[1u8; 32]).unwrap(); + let id = AssetId::from_byte_array([1u8; 32]); let abf = AssetBlindingFactor::new(&mut thread_rng()); let asset = confidential::Asset::new_confidential(SECP256K1, id, abf); @@ -1560,7 +1548,7 @@ mod tests { let secp = secp256k1_zkp::Secp256k1::new(); let tx_str = include_str!("../tests/data/issue_tx.hex"); - let bytes = Vec::::from_hex(tx_str).unwrap(); + let bytes = hex::decode_to_vec(tx_str).unwrap(); let tx = encode::deserialize::(&bytes).unwrap(); let mut utxos = [ @@ -1571,50 +1559,32 @@ mod tests { ]; { utxos[0].asset = Asset::from_commitment( - &Vec::::from_hex( - "0ae7a52e8e4b07e00548bab151a83e5c9ab2f9a910e10dcee930a1a152a939f99e", - ) - .unwrap(), + &hex::hex!("0ae7a52e8e4b07e00548bab151a83e5c9ab2f9a910e10dcee930a1a152a939f99e"), ) .unwrap(); utxos[0].value = Value::Explicit(1); utxos[1].asset = Asset::from_commitment( - &Vec::::from_hex( - "0bc226167e9ee0bb5a86c8f1478ee7d7becb7bfd4d97c26a041e628c5486a8c67a", - ) - .unwrap(), + &hex::hex!("0bc226167e9ee0bb5a86c8f1478ee7d7becb7bfd4d97c26a041e628c5486a8c67a"), ) .unwrap(); utxos[1].value = Value::Explicit(1); utxos[2].asset = Asset::from_commitment( - &Vec::::from_hex( - "0b495dbfc356993c5ac157c3d04fadf6f198a7e35a873df482ad9e4e95daa8aa7e", - ) - .unwrap(), + &hex::hex!("0b495dbfc356993c5ac157c3d04fadf6f198a7e35a873df482ad9e4e95daa8aa7e"), ) .unwrap(); utxos[2].value = Value::from_commitment( - &Vec::::from_hex( - "08e0ac2ab5f3c173d5e0652a2ec209a9a370a4e510178e73c2f22f9e132341abf4", - ) - .unwrap(), + &hex::hex!("08e0ac2ab5f3c173d5e0652a2ec209a9a370a4e510178e73c2f22f9e132341abf4"), ) .unwrap(); utxos[3].asset = Asset::from_commitment( - &Vec::::from_hex( - "0aa0956d60687982d5e73d52f8c5902478754e5f0e2e5ceff5ae53fa9681c12ae1", - ) - .unwrap(), + &hex::hex!("0aa0956d60687982d5e73d52f8c5902478754e5f0e2e5ceff5ae53fa9681c12ae1"), ) .unwrap(); utxos[3].value = Value::from_commitment( - &Vec::::from_hex( - "094b35f1e86b097ccf0b3a826570c089c724ed9cf22620937500b14acdd169e7bf", - ) - .unwrap(), + &hex::hex!("094b35f1e86b097ccf0b3a826570c089c724ed9cf22620937500b14acdd169e7bf"), ) .unwrap(); } diff --git a/src/block.rs b/src/block.rs index cdf2efe2..c2153b33 100644 --- a/src/block.rs +++ b/src/block.rs @@ -17,14 +17,19 @@ use std::io; -use bitcoin::hashes::{Hash, sha256}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde")] use std::fmt; use crate::dynafed; +use crate::hashes::{HashEngine as _, sha256d}; use crate::Transaction; -use crate::encode::{self, Encodable, Decodable, serialize}; -use crate::{BlockHash, Script, TxMerkleNode, VarInt}; +use crate::encode::{self, serialize, Decodable, Encodable, VarInt}; +use crate::{BlockHash, Script, TxMerkleNode}; + +impl_sha256_midstate_wrapper! { + /// The Merkle root of a set of dynafed parameters. + pub struct DynafedRoot([u8; 32]); +} /// Data related to block signatures #[derive(Clone, Debug, Eq, Hash, PartialEq)] @@ -55,7 +60,7 @@ impl<'de> Deserialize<'de> for ExtData { enum Enum { Unknown, Challenge, Solution, Current, Proposed, Witness } struct EnumVisitor; - impl<'de> de::Visitor<'de> for EnumVisitor { + impl de::Visitor<'_> for EnumVisitor { type Value = Enum; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -230,7 +235,7 @@ impl BlockHeader { }; // Everything except the signblock witness goes into the hash - let mut enc = BlockHash::engine(); + let mut enc = sha256d::Hash::engine(); version.consensus_encode(&mut enc).unwrap(); self.prev_blockhash.consensus_encode(&mut enc).unwrap(); self.merkle_root.consensus_encode(&mut enc).unwrap(); @@ -245,19 +250,12 @@ impl BlockHeader { proposed.consensus_encode(&mut enc).unwrap(); }, } - BlockHash::from_engine(enc) + BlockHash(enc.finalize()) } /// Returns true if this is a block with dynamic federations enabled. pub fn is_dynafed(&self) -> bool { - if let ExtData::Dynafed { - .. - } = self.ext - { - true - } else { - false - } + matches!(self.ext, ExtData::Dynafed { .. }) } /// Remove the witness data of the block header. @@ -275,15 +273,15 @@ impl BlockHeader { } /// Calculate the root of the dynafed params. Returns [None] when not dynafed. - pub fn calculate_dynafed_params_root(&self) -> Option { + pub fn calculate_dynafed_params_root(&self) -> Option { match self.ext { ExtData::Proof { .. } => None, ExtData::Dynafed { ref current, ref proposed, .. } => { let leaves = [ - current.calculate_root().into_inner(), - proposed.calculate_root().into_inner(), + current.calculate_root().to_byte_array(), + proposed.calculate_root().to_byte_array(), ]; - Some(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) + Some(DynafedRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..]))) } } } @@ -362,7 +360,6 @@ pub struct Block { /// Complete list of transaction in the block pub txdata: Vec, } -serde_struct_impl!(Block, header, txdata); impl_consensus_encoding!(Block, header, txdata); impl Block { @@ -380,7 +377,7 @@ impl Block { /// Get the size of the block pub fn size(&self) -> usize { // The size of the header + the size of the varint with the tx count + the txs themselves - let base_size = serialize(&self.header).len() + VarInt(self.txdata.len() as u64).len(); + let base_size = serialize(&self.header).len() + VarInt(self.txdata.len() as u64).size(); let txs_size: usize = self.txdata.iter().map(Transaction::size).sum(); base_size + txs_size } @@ -393,7 +390,7 @@ impl Block { /// Get the weight of the block pub fn weight(&self) -> usize { - let base_weight = 4 * (serialize(&self.header).len() + VarInt(self.txdata.len() as u64).len()); + let base_weight = 4 * (serialize(&self.header).len() + VarInt(self.txdata.len() as u64).size()); let txs_weight: usize = self.txdata.iter().map(Transaction::weight).sum(); base_weight + txs_weight } @@ -402,11 +399,10 @@ impl Block { #[cfg(test)] mod tests { use crate::Block; - use crate::hashes::hex::FromHex; use super::*; - const SIMPLE_BLOCK: &'static str = "\ + const SIMPLE_BLOCK: &str = "\ 00000020a66e4a4baff69735267346d12e59e8a0da848b593813554deb16a6f3\ 6cd035e9aab0e2451724598471dd4e45f0dca40ca5f4ac62e61957e50925af08\ 59891fcc8842805b020000000151000102000000010100000000000000000000\ @@ -418,7 +414,7 @@ mod tests { 7d45000000000000012000000000000000000000000000000000000000000000\ 000000000000000000000000000000\ "; - const DYNAFED_BLOCK: &'static str = "\ + const DYNAFED_BLOCK: &str = "\ 000000a0da9d569617d1d65c3390a01c18c4fa7c4d0f4738b6fc2b5c5faf2e8a\ 463abbaa46eb9123808e1e2ff75e9472fa0f0589b53b7518a69d3d6fcb9228ed\ 345734ea06b9c45d070000000122002057c555a91edf9552282d88624d1473c2\ @@ -441,7 +437,7 @@ mod tests { 000000000000000000000000000000000000\ "; - #[cfg(feature = "serde-feature")] + #[cfg(feature = "serde")] #[test] fn blockheader_serde() { let block: Block = hex_deserialize!(&SIMPLE_BLOCK); @@ -449,7 +445,7 @@ mod tests { let block: Block = hex_deserialize!(&DYNAFED_BLOCK); roundtrip_header(&block.header); } - #[cfg(feature = "serde-feature")] + #[cfg(feature = "serde")] fn roundtrip_header(header: &BlockHeader) { let header_ser = serde_json::to_string(header).unwrap(); let header_deser: BlockHeader = serde_json::from_str(&header_ser).unwrap(); @@ -470,7 +466,7 @@ mod tests { block.block_hash().to_string(), "287ca47e8da47eb8c28d870663450bb026922eadb30a1b2f8293e6e9d1ca5322" ); - assert_eq!(block.header.version, 0x20000000); + assert_eq!(block.header.version, 0x2000_0000); assert_eq!(block.header.height, 2); assert_eq!(block.txdata.len(), 1); assert_eq!(block.size(), serialize(&block).len()); @@ -682,7 +678,7 @@ mod tests { block.block_hash().to_string(), "e935d06cf3a616eb4d551338598b84daa0e8592ed14673263597f6af4b4a6ea6" ); - assert_eq!(block.header.version, 0x20000000); + assert_eq!(block.header.version, 0x2000_0000); assert_eq!(block.header.height, 1); assert_eq!(block.txdata.len(), 3); assert_eq!(block.size(), serialize(&block).len()); @@ -713,7 +709,7 @@ mod tests { block.block_hash().to_string(), "bcc6eb2ab6c97b9b4590825b9136f100b22e090c0469818572b8b93926a79f28" ); - assert_eq!(block.header.version, 0x20000000); + assert_eq!(block.header.version, 0x2000_0000); if let ExtData::Proof { challenge, solution } = block.header.ext { assert_eq!(challenge.len(), 1 + 3 * 34 + 2); assert_eq!(solution.len(), 144); @@ -734,7 +730,7 @@ mod tests { } else { panic!("Current block dynafed params not compact"); } - if let dynafed::Params::Null { .. } = proposed { + if let dynafed::Params::Null = proposed { /* pass */ } else { panic!("Proposed block dynafed params not compact"); @@ -747,7 +743,7 @@ mod tests { block.block_hash().to_string(), "4961df970cf12d789383974e6ab439f780d956b5a50162ca9d281362e46c605a" ); - assert_eq!(block.header.version, 0x20000000); + assert_eq!(block.header.version, 0x2000_0000); // Full current and proposal let block: Block = hex_deserialize!("\ @@ -803,7 +799,7 @@ mod tests { fn test_failed_block() { let block_str = include_str!("../tests/data/failedblock.hex"); - let bytes = Vec::::from_hex(block_str).unwrap(); + let bytes = hex::decode_to_vec(block_str).unwrap(); let _block = encode::deserialize::(&bytes).unwrap(); } } diff --git a/src/confidential.rs b/src/confidential.rs deleted file mode 100644 index 0116c736..00000000 --- a/src/confidential.rs +++ /dev/null @@ -1,1454 +0,0 @@ -// Rust Elements Library -// Written in 2018 by -// Andrew Poelstra -// -// To the extent possible under law, the author(s) have dedicated all -// copyright and related and neighboring rights to this software to -// the public domain worldwide. This software is distributed without -// any warranty. -// -// You should have received a copy of the CC0 Public Domain Dedication -// along with this software. -// If not, see . -// - -//! # Confidential Commitments -//! -//! Structures representing Pedersen commitments of various types -//! - -use crate::hashes::{sha256d, Hash, hex}; -use secp256k1_zkp::{self, CommitmentSecrets, Generator, PedersenCommitment, - PublicKey, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, - compute_adaptive_blinding_factor, - rand::{CryptoRng, Rng, RngCore} -}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use std::{fmt, io, ops::{AddAssign, Neg}, str}; - -use crate::encode::{self, Decodable, Encodable}; -use crate::issuance::AssetId; - -/// A CT commitment to an amount -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -pub enum Value { - /// No value - Null, - /// Value is explicitly encoded - Explicit(u64), - /// Value is committed - Confidential(PedersenCommitment), -} - -impl Value { - /// Create value commitment. - pub fn new_confidential( - secp: &Secp256k1, - value: u64, - asset: Generator, - bf: ValueBlindingFactor, - ) -> Self { - Value::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) - } - - /// Create value commitment from assetID, asset blinding factor, - /// value and value blinding factor - pub fn new_confidential_from_assetid( - secp: &Secp256k1, - value: u64, - asset: AssetId, - v_bf: ValueBlindingFactor, - a_bf: AssetBlindingFactor, - ) -> Self { - let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); - let comm = PedersenCommitment::new(secp, value, v_bf.0, generator); - - Value::Confidential(comm) - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Value::Null => 1, - Value::Explicit(..) => 9, - Value::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Value::Confidential(PedersenCommitment::from_slice(bytes)?)) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - match self { - Value::Null => true, - _ => false - } - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - match self { - Value::Explicit(_) => true, - _ => false - } - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - match self { - Value::Confidential(_) => true, - _ => false - } - } - - /// Returns the explicit inner value. - /// Returns [None] if [is_explicit] returns false. - pub fn explicit(&self) -> Option { - match *self { - Value::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [is_confidential] returns false. - pub fn commitment(&self) -> Option { - match *self { - Value::Confidential(i) => Some(i), - _ => None, - } - } -} - -impl From for Value { - fn from(from: PedersenCommitment) -> Self { - Value::Confidential(from) - } -} - -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Value::Null => f.write_str("null"), - Value::Explicit(n) => write!(f, "{}", n), - Value::Confidential(commitment) => write!(f, "{:02x}", commitment), - } - } -} - -impl Default for Value { - fn default() -> Self { - Value::Null - } -} - -impl Encodable for Value { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Value::Null => 0u8.consensus_encode(s), - Value::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) - } - Value::Confidential(commitment) => commitment.consensus_encode(&mut s), - } - } -} - -impl Encodable for PedersenCommitment { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - -impl Decodable for Value { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Value::Null), - 1 => { - let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); - Ok(Value::Explicit(explicit)) - } - p if p == 0x08 || p == 0x09 => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Value::Confidential(PedersenCommitment::from_slice(&comm)?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -impl Decodable for PedersenCommitment { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(PedersenCommitment::from_slice(&bytes)?) - } -} - -#[cfg(feature = "serde")] -impl Serialize for Value { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Value::Null => 1, - Value::Explicit(_) | Value::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Value::Null => seq.serialize_element(&0u8)?, - Value::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&u64::swap_bytes(n))?; - } - Value::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Value { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Value; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Value::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), - None => Err(A::Error::custom("missing explicit value")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Value::Confidential(x)), - None => Err(A::Error::custom("missing pedersen commitment")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")), - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// A CT commitment to an asset -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] -pub enum Asset { - /// No value - Null, - /// Asset entropy is explicitly encoded - Explicit(AssetId), - /// Asset is committed - Confidential(Generator), -} - -impl Asset { - /// Create asset commitment. - pub fn new_confidential( - secp: &Secp256k1, - asset: AssetId, - bf: AssetBlindingFactor, - ) -> Self { - Asset::Confidential(Generator::new_blinded( - secp, - asset.into_tag(), - bf.into_inner(), - )) - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Asset::Null => 1, - Asset::Explicit(..) => 33, - Asset::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Asset::Confidential(Generator::from_slice(bytes)?)) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - match *self { - Asset::Null => true, - _ => false - } - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - match *self { - Asset::Explicit(_) => true, - _ => false - } - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - match *self { - Asset::Confidential(_) => true, - _ => false - } - } - - /// Returns the explicit inner value. - /// Returns [None] if [is_explicit] returns false. - pub fn explicit(&self) -> Option { - match *self { - Asset::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [is_confidential] returns false. - pub fn commitment(&self) -> Option { - match *self { - Asset::Confidential(i) => Some(i), - _ => None, - } - } - - /// Internally used function for getting the generator from asset - /// Used in the amount verification check - /// Returns [`None`] is the asset is [`Asset::Null`] - /// Converts a explicit asset into a generator and returns the confidential - /// generator as is. - pub fn into_asset_gen ( - self, - secp: &Secp256k1, - ) -> Option { - match self { - // Only error is Null error which is dealt with later - // when we have more context information about it. - Asset::Null => return None, - Asset::Explicit(x) => { - Some(Generator::new_unblinded(secp, x.into_tag())) - } - Asset::Confidential(gen) => Some(gen), - } - } -} - -impl From for Asset { - fn from(from: Generator) -> Self { - Asset::Confidential(from) - } -} - -impl fmt::Display for Asset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Asset::Null => f.write_str("null"), - Asset::Explicit(n) => write!(f, "{}", n), - Asset::Confidential(generator) => write!(f, "{:02x}", generator), - } - } -} - -impl Default for Asset { - fn default() -> Self { - Asset::Null - } -} - -impl Encodable for Asset { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Asset::Null => 0u8.consensus_encode(s), - Asset::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) - } - Asset::Confidential(generator) => generator.consensus_encode(&mut s) - } - } -} - -impl Encodable for Generator { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - -impl Decodable for Asset { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Asset::Null), - 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Asset::Explicit(explicit)) - } - p if p == 0x0a || p == 0x0b => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Asset::Confidential(Generator::from_slice(&comm[..])?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -impl Decodable for Generator { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(Generator::from_slice(&bytes)?) - } -} - - -#[cfg(feature = "serde")] -impl Serialize for Asset { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Asset::Null => 1, - Asset::Explicit(_) | Asset::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Asset::Null => seq.serialize_element(&0u8)?, - Asset::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&n)?; - } - Asset::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Asset { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Asset; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Asset::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Asset::Explicit(x)), - None => Err(A::Error::custom("missing explicit asset")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Asset::Confidential(x)), - None => Err(A::Error::custom("missing generator")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")), - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// A CT commitment to an output nonce (i.e. a public key) -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Nonce { - /// No value - Null, - /// There should be no such thing as an "explicit nonce", but Elements will deserialize - /// such a thing (and insists that its size be 32 bytes). So we stick a 32-byte type here - /// that implements all the traits we need. - Explicit([u8; 32]), - /// Nonce is committed - Confidential(PublicKey), -} - -impl Nonce { - /// Create nonce commitment. - pub fn new_confidential( - rng: &mut R, - secp: &Secp256k1, - receiver_blinding_pk: &PublicKey, - ) -> (Self, SecretKey) { - let ephemeral_sk = SecretKey::new(rng); - Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) - } - - /// Similar to [Nonce::new_confidential], but with a given `ephemeral_sk` - /// instead of sampling it from rng. - pub fn with_ephemeral_sk( - secp: &Secp256k1, - ephemeral_sk: SecretKey, - receiver_blinding_pk: &PublicKey - ) -> (Self, SecretKey) { - let sender_pk = PublicKey::from_secret_key(&secp, &ephemeral_sk); - let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); - (Nonce::Confidential(sender_pk), shared_secret) - } - - /// Calculate the shared secret. - pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { - match self { - Nonce::Confidential(sender_pk) => { - Some(Self::make_shared_secret(&sender_pk, receiver_blinding_sk)) - } - _ => None, - } - } - - /// Create the shared secret. - fn make_shared_secret(pk: &PublicKey, sk: &SecretKey) -> SecretKey { - let xy = secp256k1_zkp::ecdh::shared_secret_point(pk, sk); - let shared_secret = { - // Yes, what follows is the compressed representation of a Bitcoin public key. - // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 - - let mut dh_secret = [0u8; 33]; - dh_secret[0] = if xy.last().unwrap() % 2 == 0 { - 0x02 - } else { - 0x03 - }; - dh_secret[1..].copy_from_slice(&xy[0..32]); - - sha256d::Hash::hash(&dh_secret).into_inner() - }; - - SecretKey::from_slice(&shared_secret.as_ref()[..32]).expect("always has exactly 32 bytes") - } - - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { - match *self { - Nonce::Null => 1, - Nonce::Explicit(..) => 33, - Nonce::Confidential(..) => 33, - } - } - - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result { - Ok(Nonce::Confidential( - PublicKey::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, - )) - } - - /// Check if the object is null. - pub fn is_null(&self) -> bool { - match *self { - Nonce::Null => true, - _ => false - } - } - - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - match *self { - Nonce::Explicit(_) => true, - _ => false - } - } - - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - match *self { - Nonce::Confidential(_) => true, - _ => false - } - } - - /// Returns the explicit inner value. - /// Returns [None] if [is_explicit] returns false. - pub fn explicit(&self) -> Option<[u8; 32]> { - match *self { - Nonce::Explicit(i) => Some(i), - _ => None, - } - } - - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [is_confidential] returns false. - pub fn commitment(&self) -> Option { - match *self { - Nonce::Confidential(i) => Some(i), - _ => None, - } - } -} - -impl From for Nonce { - fn from(from: PublicKey) -> Self { - Nonce::Confidential(from) - } -} - -impl fmt::Display for Nonce { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Nonce::Null => f.write_str("null"), - Nonce::Explicit(n) => { - for b in n.iter() { - write!(f, "{:02x}", b)?; - } - Ok(()) - } - Nonce::Confidential(pk) => write!(f, "{:02x}", pk), - } - } -} - -impl Default for Nonce { - fn default() -> Self { - Nonce::Null - } -} - -impl Encodable for Nonce { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - Nonce::Null => 0u8.consensus_encode(s), - Nonce::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - Ok(1 + n.consensus_encode(&mut s)?) - } - Nonce::Confidential(commitment) => commitment.consensus_encode(&mut s), - } - } -} - -impl Encodable for PublicKey { - fn consensus_encode(&self, mut e: W) -> Result { - e.write_all(&self.serialize())?; - Ok(33) - } -} - -impl Decodable for Nonce { - fn consensus_decode(mut d: D) -> Result { - let prefix = u8::consensus_decode(&mut d)?; - - match prefix { - 0 => Ok(Nonce::Null), - 1 => { - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Nonce::Explicit(explicit)) - } - p if p == 0x02 || p == 0x03 => { - let mut comm = [0u8; 33]; - comm[0] = p; - d.read_exact(&mut comm[1..])?; - Ok(Nonce::Confidential(PublicKey::from_slice(&comm)?)) - } - p => Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } -} - -impl Decodable for PublicKey { - fn consensus_decode(d: D) -> Result { - let bytes = <[u8; 33]>::consensus_decode(d)?; - Ok(PublicKey::from_slice(&bytes)?) - } -} - -#[cfg(feature = "serde")] -impl Serialize for Nonce { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = match *self { - Nonce::Null => 1, - Nonce::Explicit(_) | Nonce::Confidential(_) => 2 - }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - Nonce::Null => seq.serialize_element(&0u8)?, - Nonce::Explicit(n) => { - seq.serialize_element(&1u8)?; - seq.serialize_element(&n)?; - } - Nonce::Confidential(commitment) => { - seq.serialize_element(&2u8)?; - seq.serialize_element(&commitment)?; - } - } - seq.end() - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for Nonce { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, SeqAccess, Visitor}; - struct CommitVisitor; - - impl<'de> Visitor<'de> for CommitVisitor { - type Value = Nonce; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix = access.next_element::()?; - match prefix { - Some(0) => Ok(Nonce::Null), - Some(1) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Explicit(x)), - None => Err(A::Error::custom("missing explicit nonce")), - } - } - Some(2) => { - match access.next_element()? { - Some(x) => Ok(Nonce::Confidential(x)), - None => Err(A::Error::custom("missing nonce")), - } - } - _ => Err(A::Error::custom("wrong or missing prefix")) - } - } - } - - d.deserialize_seq(CommitVisitor) - } -} - -/// Blinding factor used for asset commitments. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct AssetBlindingFactor(pub(crate) Tweak); - -impl AssetBlindingFactor { - /// Generate random asset blinding factor. - pub fn new(rng: &mut R) -> Self { - AssetBlindingFactor(Tweak::new(rng)) - } - - /// Create from bytes. - pub fn from_slice(bytes: &[u8]) -> Result { - Ok(AssetBlindingFactor(Tweak::from_slice(bytes)?)) - } - - /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } - - /// Get a unblinded/zero AssetBlinding factor - pub fn zero() -> Self { - AssetBlindingFactor(ZERO_TWEAK) - } -} - -impl hex::FromHex for AssetBlindingFactor { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + - ExactSizeIterator + - DoubleEndedIterator - { - let slice = <[u8; 32]>::from_byte_iter(iter.rev())?; - // Incorrect Return Error - // See: https://github.com/rust-bitcoin/bitcoin_hashes/issues/124 - let inner = Tweak::from_inner(slice) - .map_err(|_e| hex::Error::InvalidChar(0))?; - Ok(AssetBlindingFactor(inner)) - } -} - -impl fmt::Display for AssetBlindingFactor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - hex::format_hex_reverse(self.0.as_ref(), f) - } -} - -impl fmt::LowerHex for AssetBlindingFactor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - hex::format_hex_reverse(self.0.as_ref(), f) - } -} - -impl str::FromStr for AssetBlindingFactor { - type Err = encode::Error; - - fn from_str(s: &str) -> Result { - Ok(hex::FromHex::from_hex(s)?) - } -} - -#[cfg(feature = "serde")] -impl Serialize for AssetBlindingFactor { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(&self) - } else { - s.serialize_bytes(&self.0[..]) - } - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for AssetBlindingFactor { - fn deserialize>(d: D) -> Result { - use bitcoin::hashes::hex::FromHex; - - if d.is_human_readable() { - struct HexVisitor; - - impl<'de> ::serde::de::Visitor<'de> for HexVisitor { - type Value = AssetBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(hex) = ::std::str::from_utf8(v) { - AssetBlindingFactor::from_hex(hex).map_err(E::custom) - } else { - return Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)); - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - AssetBlindingFactor::from_hex(v).map_err(E::custom) - } - } - - d.deserialize_str(HexVisitor) - } else { - struct BytesVisitor; - - impl<'de> ::serde::de::Visitor<'de> for BytesVisitor { - type Value = AssetBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("a bytestring") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if v.len() != 32 { - Err(E::invalid_length(v.len(), &stringify!($len))) - } else { - let mut ret = [0; 32]; - ret.copy_from_slice(v); - let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(AssetBlindingFactor(inner)) - } - } - } - - d.deserialize_bytes(BytesVisitor) - } - } -} - -/// Blinding factor used for value commitments. -#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] -pub struct ValueBlindingFactor(pub(crate) Tweak); - -impl ValueBlindingFactor { - /// Generate random value blinding factor. - pub fn new(rng: &mut R) -> Self { - ValueBlindingFactor(Tweak::new(rng)) - } - - /// Create the value blinding factor of the last output of a transaction. - pub fn last( - secp: &Secp256k1, - value: u64, - abf: AssetBlindingFactor, - inputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], - outputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], - ) -> Self { - let set_a = inputs - .iter() - .map(|(value, abf, vbf)| CommitmentSecrets { - value: *value, - value_blinding_factor: vbf.0, - generator_blinding_factor: abf.into_inner(), - }) - .collect::>(); - let set_b = outputs - .iter() - .map(|(value, abf, vbf)| CommitmentSecrets { - value: *value, - value_blinding_factor: vbf.0, - generator_blinding_factor: abf.into_inner(), - }) - .collect::>(); - - ValueBlindingFactor(compute_adaptive_blinding_factor( - secp, value, abf.0, &set_a, &set_b, - )) - } - - /// Create from bytes. - pub fn from_slice(bytes: &[u8]) -> Result { - Ok(ValueBlindingFactor(Tweak::from_slice(bytes)?)) - } - - /// Returns the inner value. - pub fn into_inner(self) -> Tweak { - self.0 - } - - /// Get a unblinded/zero AssetBlinding factor - pub fn zero() -> Self { - ValueBlindingFactor(ZERO_TWEAK) - } -} - -impl AddAssign for ValueBlindingFactor { - fn add_assign(&mut self, other: Self) { - if self.0.as_ref() == &[0u8; 32] { - *self = other; - } else if other.0.as_ref() == &[0u8; 32] { - // nothing to do - } else { - // Since libsecp does not expose low level APIs - // for scalar arethematic, we need to abuse secret key - // operations for this - let sk2 = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key"); - let sk = SecretKey::from_slice(other.into_inner().as_ref()).expect("Valid key"); - // The only reason that secret key addition can fail - // is when the keys add up to zero since we have already checked - // keys are in valid secret keys - match sk.add_tweak(&sk2.into()) { - Ok(sk_tweaked) => *self = ValueBlindingFactor::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), - Err(_) => *self = Self::zero(), - } - } - } -} - -impl Neg for ValueBlindingFactor { - type Output = Self; - - fn neg(self) -> Self::Output { - if self.0.as_ref() == &[0u8; 32] { - self - } else { - let sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key").negate(); - ValueBlindingFactor::from_slice(sk.as_ref()).expect("Valid Tweak") - } - } -} - -impl hex::FromHex for ValueBlindingFactor { - fn from_byte_iter(iter: I) -> Result - where I: Iterator> + - ExactSizeIterator + - DoubleEndedIterator - { - let slice = <[u8; 32]>::from_byte_iter(iter.rev())?; - // Incorrect Return Error - // See: https://github.com/rust-bitcoin/bitcoin_hashes/issues/124 - let inner = Tweak::from_inner(slice) - .map_err(|_e| hex::Error::InvalidChar(0))?; - Ok(ValueBlindingFactor(inner)) - } -} - -impl fmt::Display for ValueBlindingFactor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - hex::format_hex_reverse(self.0.as_ref(), f) - } -} - -impl fmt::LowerHex for ValueBlindingFactor { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - hex::format_hex_reverse(self.0.as_ref(), f) - } -} - -impl str::FromStr for ValueBlindingFactor { - type Err = encode::Error; - - fn from_str(s: &str) -> Result { - Ok(hex::FromHex::from_hex(s)?) - } -} - -#[cfg(feature = "serde")] -impl Serialize for ValueBlindingFactor { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(&self) - } else { - s.serialize_bytes(&self.0[..]) - } - } -} - -#[cfg(feature = "serde")] -impl<'de> Deserialize<'de> for ValueBlindingFactor { - fn deserialize>(d: D) -> Result { - use bitcoin::hashes::hex::FromHex; - - if d.is_human_readable() { - struct HexVisitor; - - impl<'de> ::serde::de::Visitor<'de> for HexVisitor { - type Value = ValueBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("an ASCII hex string") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if let Ok(hex) = ::std::str::from_utf8(v) { - ValueBlindingFactor::from_hex(hex).map_err(E::custom) - } else { - return Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)); - } - } - - fn visit_str(self, v: &str) -> Result - where - E: ::serde::de::Error, - { - ValueBlindingFactor::from_hex(v).map_err(E::custom) - } - } - - d.deserialize_str(HexVisitor) - } else { - struct BytesVisitor; - - impl<'de> ::serde::de::Visitor<'de> for BytesVisitor { - type Value = ValueBlindingFactor; - - fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - formatter.write_str("a bytestring") - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: ::serde::de::Error, - { - if v.len() != 32 { - Err(E::invalid_length(v.len(), &stringify!($len))) - } else { - let mut ret = [0; 32]; - ret.copy_from_slice(v); - let inner = Tweak::from_inner(ret).map_err(E::custom)?; - Ok(ValueBlindingFactor(inner)) - } - } - } - - d.deserialize_bytes(BytesVisitor) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use bitcoin::hashes::sha256; - - #[cfg(feature = "serde")] - use bincode; - - #[test] - fn encode_length() { - let vals = [ - Value::Null, - Value::Explicit(1000), - Value::from_commitment(&[ - 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), - ]; - for v in &vals[..] { - let mut x = vec![]; - assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); - assert_eq!(x.len(), v.encoded_length()); - } - - let nonces = [ - Nonce::Null, - Nonce::Explicit([0; 32]), - Nonce::from_commitment(&[ - 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), - ]; - for v in &nonces[..] { - let mut x = vec![]; - assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); - assert_eq!(x.len(), v.encoded_length()); - } - - let assets = [ - Asset::Null, - Asset::Explicit(AssetId::from_inner(sha256::Midstate::from_inner([0; 32]))), - Asset::from_commitment(&[ - 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, - ]) - .unwrap(), - ]; - for v in &assets[..] { - let mut x = vec![]; - assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); - assert_eq!(x.len(), v.encoded_length()); - } - } - - #[test] - fn commitments() { - let x = Value::from_commitment(&[ - 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); - let commitment = x.commitment().unwrap(); - let mut commitment = commitment.serialize(); - assert_eq!(x, Value::from_commitment(&commitment[..]).unwrap()); - commitment[0] = 42; - assert!(Value::from_commitment(&commitment[..]).is_err()); - - let x = Asset::from_commitment(&[ - 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); - let commitment = x.commitment().unwrap(); - let mut commitment = commitment.serialize(); - assert_eq!(x, Asset::from_commitment(&commitment[..]).unwrap()); - commitment[0] = 42; - assert!(Asset::from_commitment(&commitment[..]).is_err()); - - let x = Nonce::from_commitment(&[ - 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, - ]) - .unwrap(); - let commitment = x.commitment().unwrap(); - let mut commitment = commitment.serialize(); - assert_eq!(x, Nonce::from_commitment(&commitment[..]).unwrap()); - commitment[0] = 42; - assert!(Nonce::from_commitment(&commitment[..]).is_err()); - } - - #[cfg(feature = "serde")] - #[test] - fn value_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - let value = Value::Explicit(100_000_000); - assert_tokens( - &value, - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::U64(63601271583539200), - Token::SeqEnd - ] - ); - - let value = Value::from_commitment(&[ - 0x08, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); - assert_tokens( - &value.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Str( - "080101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &value.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Bytes( - &[ - 8, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ] - ), - Token::SeqEnd - ] - ); - - let value = Value::Null; - assert_tokens( - &value, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); - } - - #[cfg(feature = "serde")] - #[test] - fn asset_serde() { - use bitcoin::hashes::hex::FromHex; - use serde_test::{assert_tokens, Configure, Token}; - - let asset_id = AssetId::from_hex( - "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8" - ).unwrap(); - let asset = Asset::Explicit(asset_id); - assert_tokens( - &asset.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::Str( - "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &asset.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::Bytes( - &[ - 248, 11, 176, 3, 143, 72, 34, 67, 32, 47, 11, 45, 207, 136, 217, 180, - 231, 249, 48, 164, 138, 63, 205, 192, 3, 175, 118, 177, 249, 214, 14, 99 - ] - ), - Token::SeqEnd - ] - ); - - let asset = Asset::from_commitment(&[ - 0x0a, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); - assert_tokens( - &asset.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Str( - "0a0101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &asset.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Bytes( - &[ - 10, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 - ] - ), - Token::SeqEnd - ] - ); - - let asset = Asset::Null; - assert_tokens( - &asset, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); - } - - #[cfg(feature = "serde")] - #[test] - fn nonce_serde() { - use serde_test::{assert_tokens, Configure, Token}; - - let nonce = Nonce::Explicit([ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]); - assert_tokens( - &nonce, - &[ - Token::Seq { len: Some(2) }, - Token::U8(1), - Token::Tuple { len: 32 }, - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::TupleEnd, - Token::SeqEnd - ] - ); - - let nonce = Nonce::from_commitment(&[ - 0x02, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - ]).unwrap(); - assert_tokens( - &nonce.readable(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Str( - "020101010101010101010101010101010101010101010101010101010101010101" - ), - Token::SeqEnd - ] - ); - assert_tokens( - &nonce.compact(), - &[ - Token::Seq { len: Some(2) }, - Token::U8(2), - Token::Tuple { len: 33 }, - Token::U8(2), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), - Token::U8(1), - Token::TupleEnd, - Token::SeqEnd - ] - ); - - let nonce = Nonce::Null; - assert_tokens( - &nonce, - &[ - Token::Seq { len: Some(1) }, - Token::U8(0), - Token::SeqEnd - ] - ); - } - - #[cfg(feature = "serde")] - #[test] - fn bf_serde() { - use serde_json; - use std::str::FromStr; - - let abf_str = "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f"; - let abf_str_quoted = format!("\"{}\"", abf_str); - let abf_from_serde: AssetBlindingFactor = serde_json::from_str(&abf_str_quoted).unwrap(); - let abf_from_str = AssetBlindingFactor::from_str(abf_str).unwrap(); - assert_eq!(abf_from_serde, abf_from_str); - assert_eq!(abf_str_quoted, serde_json::to_string(&abf_from_serde).unwrap()); - - let vbf_str = "e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d"; - let vbf_str_quoted = format!("\"{}\"", vbf_str); - let vbf_from_serde: ValueBlindingFactor = serde_json::from_str(&vbf_str_quoted).unwrap(); - let vbf_from_str = ValueBlindingFactor::from_str(vbf_str).unwrap(); - assert_eq!(vbf_from_serde, vbf_from_str); - assert_eq!(vbf_str_quoted, serde_json::to_string(&vbf_from_serde).unwrap()); - } - - #[cfg(feature = "serde")] - #[test] - fn test_value_bincode_be() { - let value = Value::Explicit(500); - let bytes = bincode::serialize(&value).unwrap(); - let decoded: Value = bincode::deserialize(&bytes).unwrap(); - assert_eq!(value, decoded); - } - - #[cfg(feature = "serde")] - #[test] - fn test_value_bincode_le() { - use bincode::Options; - let value = Value::Explicit(500); - let bytes = bincode::DefaultOptions::default() - .with_little_endian() - .serialize(&value) - .unwrap(); - let decoded: Value = bincode::DefaultOptions::default() - .with_little_endian() - .deserialize(&bytes) - .unwrap(); - assert_eq!(value, decoded); - } -} diff --git a/src/confidential/asset.rs b/src/confidential/asset.rs new file mode 100644 index 00000000..5636b4bf --- /dev/null +++ b/src/confidential/asset.rs @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Assets + +use core::{fmt, str}; +use std::io; + +use secp256k1_zkp::rand::Rng; +use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::CommitmentEncoder; +use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; +use crate::issuance::AssetId; + +type ExplicitInner = AssetId; +type ConfInner = Generator; + +const EXPLICIT_LEN: usize = 32; +const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; +const CONF_PREFIX_1: u8 = 0x0a; +const CONF_PREFIX_2: u8 = 0x0b; + +/// A CT commitment to an asset +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Asset { + /// No value + #[default] + Null, + /// Asset entropy is explicitly encoded + Explicit(ExplicitInner), + /// Asset is committed + Confidential(ConfInner), +} + +impl Asset { + /// Create asset commitment. + pub fn new_confidential( + secp: &Secp256k1, + asset: AssetId, + bf: BlindingFactor, + ) -> Self { + Self::Confidential(ConfInner::new_blinded(secp, asset.into_tag(), bf.into_inner())) + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Self::Null => 1, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Self::Confidential(ConfInner::from_slice(bytes)?)) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } + + /// Returns the explicit inner value. + /// Returns [None] if [`Self::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Self::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Self::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Self::Confidential(i) => Some(i), + _ => None, + } + } + + /// Internally used function for getting the generator from asset + /// Used in the amount verification check + /// Returns [`None`] is the asset is [`Self::Null`] + /// Converts a explicit asset into a generator and returns the confidential + /// generator as is. + pub fn into_asset_gen( + self, + secp: &Secp256k1, + ) -> Option { + match self { + // Only error is Null error which is dealt with later + // when we have more context information about it. + Self::Null => None, + Self::Explicit(x) => Some(ConfInner::new_unblinded(secp, x.into_tag())), + Self::Confidential(gen) => Some(gen), + } + } +} + +impl From for Asset { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } +} + +impl fmt::Display for Asset { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Null => f.write_str("null"), + Self::Explicit(n) => write!(f, "{}", n), + Self::Confidential(generator) => write!(f, "{:02x}", generator), + } + } +} + +impl Encodable for Asset { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } + Self::Explicit(n) => { + s.write_all(&[1u8])?; + s.write_all(n.as_byte_array())?; + Ok(1 + EXPLICIT_LEN) + } + Self::Confidential(generator) => { + s.write_all(&generator.serialize())?; + Ok(CONFIDENTIAL_LEN) + } + } + } +} + +impl Decodable for Asset { + fn consensus_decode(mut d: D) -> Result { + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; + + match buf[0] { + 0 => Ok(Self::Null), + 1 => { + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(AssetId::from_byte_array(buf))) + } + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf[..])?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Asset { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Self::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Asset { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Asset; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Self::Value::Null), + Some(1) => match access.next_element()? { + Some(x) => Ok(Self::Value::Explicit(x)), + None => Err(A::Error::custom("missing explicit asset")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Self::Value::Confidential(x)), + None => Err(A::Error::custom("missing generator")), + }, + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + +/// Blinding factor used for asset commitments. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct BlindingFactor(pub(crate) Tweak); + +impl BlindingFactor { + /// Generate random asset blinding factor. + pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } + + /// Parse a blinding factor from a 64-character hex string. + #[deprecated(since = "0.27.0", note = "use s.parse() instead")] + pub fn from_hex(s: &str) -> Result { s.parse() } + + /// Create from bytes. + pub fn from_byte_array(bytes: [u8; 32]) -> Result { + Ok(Self(Tweak::from_inner(bytes)?)) + } + + /// Create from bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + Ok(Self(Tweak::from_slice(bytes)?)) + } + + /// Returns the inner value. + pub fn into_inner(self) -> Tweak { self.0 } + + /// Get a unblinded/zero `AssetBlinding` factor + pub fn zero() -> Self { Self(ZERO_TWEAK) } +} + +impl core::borrow::Borrow<[u8]> for BlindingFactor { + fn borrow(&self) -> &[u8] { &self.0[..] } +} + +hex::impl_fmt_traits! { + #[display_backward(true)] + impl fmt_traits for BlindingFactor { + const LENGTH: usize = 32; + } +} + +impl str::FromStr for BlindingFactor { + type Err = encode::Error; + + fn from_str(s: &str) -> Result { + let mut slice: [u8; 32] = hex::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; + Ok(Self(inner)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for BlindingFactor { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(&self) + } else { + s.serialize_bytes(&self.0[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for BlindingFactor { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct HexVisitor; + + impl ::serde::de::Visitor<'_> for HexVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII hex string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(hex) = ::std::str::from_utf8(v) { + hex.parse().map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + v.parse().map_err(E::custom) + } + } + + d.deserialize_str(HexVisitor) + } else { + struct BytesVisitor; + + impl ::serde::de::Visitor<'_> for BytesVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("a bytestring") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + use core::convert::TryFrom; + + match <[u8; 32]>::try_from(v) { + Ok(ret) => { + let inner = Tweak::from_inner(ret).map_err(E::custom)?; + Ok(BlindingFactor(inner)) + } + Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), + } + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Asset`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Asset { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit32(Some(1), id.as_byte_array()), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Asset`] type. + pub struct Decoder(enum DecoderInner { + Done(Asset), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Asset::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Asset::Explicit(AssetId::from_byte_array(bytes)))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Asset::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Asset`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(secp256k1_zkp::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/confidential/mod.rs b/src/confidential/mod.rs new file mode 100644 index 00000000..22fe9542 --- /dev/null +++ b/src/confidential/mod.rs @@ -0,0 +1,560 @@ +// Rust Elements Library +// Written in 2018 by +// Andrew Poelstra +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! # Confidential Commitments +//! +//! Structures representing Pedersen commitments of various types +//! + +#![warn(clippy::use_self)] + +mod asset; +mod nonce; +mod range_proof; +mod surjection_proof; +mod value; + +use core::{fmt, slice}; + +use secp256k1_zkp; + +pub use self::asset::{ + Asset, BlindingFactor as AssetBlindingFactor, Decoder as AssetDecoder, + DecoderError as AssetDecoderError, Encoder as AssetEncoder, +}; +pub use self::nonce::{ + Decoder as NonceDecoder, DecoderError as NonceDecoderError, Encoder as NonceEncoder, Nonce, +}; +pub use self::range_proof::{ + Decoder as RangeProofDecoder, DecoderError as RangeProofDecoderError, + Encoder as RangeProofEncoder, RangeProof, +}; +pub use self::surjection_proof::{ + Decoder as SurjectionProofDecoder, DecoderError as SurjectionProofDecoderError, + Encoder as SurjectionProofEncoder, SurjectionProof, +}; +pub use self::value::{ + BlindingFactor as ValueBlindingFactor, Decoder as ValueDecoder, + DecoderError as ValueDecoderError, Encoder as ValueEncoder, Value, +}; +use crate::issuance::AssetId; +use crate::{encode, encoding}; + +#[derive(Clone, Debug)] +enum CommitmentEncoder<'e> { + Null(u8), + Explicit8(Option, [u8; 8]), + Explicit32(Option, &'e [u8; 32]), + Explicit33([u8; 33]), +} + +impl encoding::Encoder for CommitmentEncoder<'_> { + fn current_chunk(&self) -> &[u8] { + match *self { + Self::Null(ref prefix) => slice::from_ref(prefix), + Self::Explicit8(ref prefix, ref arr) => prefix.as_ref().map_or(arr, slice::from_ref), + Self::Explicit32(ref prefix, arr) => prefix.as_ref().map_or(arr, slice::from_ref), + Self::Explicit33(ref arr) => arr, + } + } + + fn advance(&mut self) -> encoding::EncoderStatus { + match *self { + Self::Explicit8(ref mut prefix @ Some(_), _) + | Self::Explicit32(ref mut prefix @ Some(_), _) => { + *prefix = None; + encoding::EncoderStatus::HasMore + } + _ => encoding::EncoderStatus::Finished, + } + } +} + +impl encoding::ExactSizeEncoder for CommitmentEncoder<'_> { + fn len(&self) -> usize { + match *self { + Self::Null(_) => 1, + Self::Explicit8(Some(_), _) => 9, + Self::Explicit8(None, _) => 8, + Self::Explicit32(Some(_), _) => 33, + Self::Explicit32(None, _) => 32, + Self::Explicit33(_) => 33, + } + } +} + +/// Because the rust-secp256k1-zkp proof types have no `as_bytes()` method, we need +/// to serialize them to a byte vector before encoding them. +/// +/// This encoder accomplishes that -- this situation never happens in rust-bitcoin +/// so there is no "owned bytes encoder" shipped with bitcoin-consensus-encoding. +#[derive(Clone, Debug)] +struct PrefixedByteVecEncoder { + prefix_encoder: Option, + data: Vec, +} + +impl PrefixedByteVecEncoder { + pub fn new(data: Vec) -> Self { + Self { prefix_encoder: Some(encoding::CompactSizeEncoder::new(data.len())), data } + } +} + +impl encoding::Encoder for PrefixedByteVecEncoder { + fn current_chunk(&self) -> &[u8] { + if let Some(ref enc) = self.prefix_encoder { + return enc.current_chunk(); + } + &self.data + } + + fn advance(&mut self) -> encoding::EncoderStatus { + if let Some(ref mut enc) = self.prefix_encoder { + if enc.advance().has_finished() { + self.prefix_encoder = None; + if self.data.is_empty() { + return encoding::EncoderStatus::Finished; + } + } + encoding::EncoderStatus::HasMore + } else { + encoding::EncoderStatus::Finished + } + } +} + +impl encoding::ExactSizeEncoder for PrefixedByteVecEncoder { + fn len(&self) -> usize { + self.prefix_encoder.as_ref().map_or(0, encoding::CompactSizeEncoder::len) + self.data.len() + } +} + +/// Error decoding hexadecimal string into tweak-like value. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum TweakHexDecodeError { + /// Invalid hexadecimal string. + InvalidHex(hex::DecodeFixedLengthBytesError), + /// Invalid tweak after decoding hexadecimal string. + InvalidTweak(secp256k1_zkp::Error), +} + +impl fmt::Display for TweakHexDecodeError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::InvalidHex(err) => { + write!(f, "Invalid hex: {}", err) + } + Self::InvalidTweak(err) => { + write!(f, "Invalid tweak: {}", err) + } + } + } +} + +#[doc(hidden)] +impl From for TweakHexDecodeError { + fn from(err: hex::DecodeFixedLengthBytesError) -> Self { Self::InvalidHex(err) } +} + +#[doc(hidden)] +impl From for TweakHexDecodeError { + fn from(err: secp256k1_zkp::Error) -> Self { Self::InvalidTweak(err) } +} + +impl From for encode::Error { + fn from(value: TweakHexDecodeError) -> Self { + match value { + TweakHexDecodeError::InvalidHex(err) => Self::HexFixedError(err), + TweakHexDecodeError::InvalidTweak(err) => Self::Secp256k1zkp(err), + } + } +} + +impl std::error::Error for TweakHexDecodeError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::InvalidHex(err) => Some(err), + Self::InvalidTweak(err) => Some(err), + } + } +} +#[cfg(test)] +mod tests { + #[cfg(feature = "serde")] + use std::str::FromStr; + + #[cfg(feature = "serde")] + use bincode; + + use super::*; + use crate::encode::Encodable as _; + use crate::encoding; + + const VALUE_EXPLICIT: [u8; 9] = [1, 0, 0, 0, 0, 0, 0, 3, 232]; + + const VALUE_COMMITMENT1: [u8; 33] = [ + 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const VALUE_COMMITMENT2: [u8; 33] = [ + 0x09, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const NONCE_EXPLICIT: [u8; 33] = [ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + const NONCE_COMMITMENT1: [u8; 33] = [ + 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const NONCE_COMMITMENT2: [u8; 33] = [ + 0x03, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const ASSET_EXPLICIT: [u8; 33] = [ + 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]; + + const ASSET_COMMITMENT1: [u8; 33] = [ + 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + const ASSET_COMMITMENT2: [u8; 33] = [ + 0x0b, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, + ]; + + #[test] + fn prefixed_byte_encoder() { + assert_eq!(encoding::drain_to_vec(&mut PrefixedByteVecEncoder::new(vec![])), [0]); + assert_eq!( + encoding::drain_to_vec(&mut PrefixedByteVecEncoder::new(vec![1, 2, 3])), + [3, 1, 2, 3] + ); + } + + #[test] + fn encode_length() { + let val_encodings = [ + vec![0], + VALUE_EXPLICIT.to_vec(), + VALUE_COMMITMENT1.to_vec(), + VALUE_COMMITMENT2.to_vec(), + ]; + let vals = [ + Value::Null, + Value::Explicit(1000), + Value::from_commitment(&VALUE_COMMITMENT1).unwrap(), + Value::from_commitment(&VALUE_COMMITMENT2).unwrap(), + ]; + for (v, enc) in vals.iter().zip(val_encodings.iter()) { + let mut x = vec![]; + assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); + assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); + } + + let nonce_encodings = [ + vec![0], + NONCE_EXPLICIT.to_vec(), + NONCE_COMMITMENT1.to_vec(), + NONCE_COMMITMENT2.to_vec(), + ]; + let nonces = [ + Nonce::Null, + Nonce::Explicit([0; 32]), + Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(), + Nonce::from_commitment(&NONCE_COMMITMENT2).unwrap(), + ]; + for (v, enc) in nonces.iter().zip(nonce_encodings.iter()) { + let mut x = vec![]; + assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); + assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); + } + + let asset_encodings = [ + vec![0], + ASSET_EXPLICIT.to_vec(), + ASSET_COMMITMENT1.to_vec(), + ASSET_COMMITMENT2.to_vec(), + ]; + let assets = [ + Asset::Null, + Asset::Explicit(AssetId::from_byte_array([0; 32])), + Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(), + Asset::from_commitment(&ASSET_COMMITMENT2).unwrap(), + ]; + for (v, enc) in assets.iter().zip(asset_encodings.iter()) { + let mut x = vec![]; + assert_eq!(v.consensus_encode(&mut x).unwrap(), v.encoded_length()); + assert_eq!(x.len(), v.encoded_length()); + assert_eq!(x, *enc); + + assert_eq!(encoding::encode_to_vec(v), *enc); + assert_eq!(encoding::decode_from_slice(enc), Ok(*v)); + } + } + + #[test] + fn commitments() { + let x = Value::from_commitment(&VALUE_COMMITMENT1).unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); + assert_eq!(x, Value::from_commitment(&commitment[..]).unwrap()); + commitment[0] = 42; + assert!(Value::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), VALUE_COMMITMENT1); + + let x = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); + assert_eq!(x, Asset::from_commitment(&commitment[..]).unwrap()); + commitment[0] = 42; + assert!(Asset::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), ASSET_COMMITMENT1); + + let x = Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); + assert_eq!(x, Nonce::from_commitment(&commitment[..]).unwrap()); + commitment[0] = 42; + assert!(Nonce::from_commitment(&commitment[..]).is_err()); + assert_eq!(encoding::encode_to_vec(&x), NONCE_COMMITMENT1); + } + + #[cfg(feature = "serde")] + #[test] + fn value_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + let value = Value::Explicit(100_000_000); + assert_tokens( + &value, + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::U64(63_601_271_583_539_200), + Token::SeqEnd, + ], + ); + + let value = Value::from_commitment(&VALUE_COMMITMENT1).unwrap(); + assert_tokens( + &value.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Str("080101010101010101010101010101010101010101010101010101010101010101"), + Token::SeqEnd, + ], + ); + assert_tokens( + &value.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Bytes(&VALUE_COMMITMENT1), + Token::SeqEnd, + ], + ); + + let value = Value::Null; + assert_tokens(&value, &[Token::Seq { len: Some(1) }, Token::U8(0), Token::SeqEnd]); + } + + #[cfg(feature = "serde")] + #[test] + fn asset_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + let asset_id = + AssetId::from_str("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8") + .unwrap(); + let asset = Asset::Explicit(asset_id); + assert_tokens( + &asset.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::Str("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8"), + Token::SeqEnd, + ], + ); + assert_tokens( + &asset.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::Bytes(&[ + 248, 11, 176, 3, 143, 72, 34, 67, 32, 47, 11, 45, 207, 136, 217, 180, 231, 249, + 48, 164, 138, 63, 205, 192, 3, 175, 118, 177, 249, 214, 14, 99, + ]), + Token::SeqEnd, + ], + ); + + let asset = Asset::from_commitment(&ASSET_COMMITMENT1).unwrap(); + assert_tokens( + &asset.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Str("0a0101010101010101010101010101010101010101010101010101010101010101"), + Token::SeqEnd, + ], + ); + assert_tokens( + &asset.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Bytes(&ASSET_COMMITMENT1), + Token::SeqEnd, + ], + ); + + let asset = Asset::Null; + assert_tokens(&asset, &[Token::Seq { len: Some(1) }, Token::U8(0), Token::SeqEnd]); + } + + #[cfg(feature = "serde")] + #[test] + #[rustfmt::skip] + fn nonce_serde() { + use serde_test::{assert_tokens, Configure, Token}; + + let nonce = Nonce::Explicit([ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + ]); + assert_tokens( + &nonce, + &[ + Token::Seq { len: Some(2) }, + Token::U8(1), + Token::Tuple { len: 32 }, + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::TupleEnd, + Token::SeqEnd + ] + ); + + let nonce = Nonce::from_commitment(&NONCE_COMMITMENT1).unwrap(); + assert_tokens( + &nonce.readable(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Str( + "020101010101010101010101010101010101010101010101010101010101010101" + ), + Token::SeqEnd + ] + ); + assert_tokens( + &nonce.compact(), + &[ + Token::Seq { len: Some(2) }, + Token::U8(2), + Token::Tuple { len: 33 }, + Token::U8(2), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), Token::U8(1), Token::U8(1), Token::U8(1), + Token::U8(1), + Token::TupleEnd, + Token::SeqEnd + ] + ); + + let nonce = Nonce::Null; + assert_tokens( + &nonce, + &[ + Token::Seq { len: Some(1) }, + Token::U8(0), + Token::SeqEnd + ] + ); + } + + #[cfg(feature = "serde")] + #[test] + fn bf_serde() { + use std::str::FromStr; + + use serde_json; + + let abf_str = "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f"; + let abf_str_quoted = format!("\"{}\"", abf_str); + let abf_from_serde: AssetBlindingFactor = serde_json::from_str(&abf_str_quoted).unwrap(); + let abf_from_str = AssetBlindingFactor::from_str(abf_str).unwrap(); + assert_eq!(abf_from_serde, abf_from_str); + assert_eq!(abf_str_quoted, serde_json::to_string(&abf_from_serde).unwrap()); + + let vbf_str = "e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d"; + let vbf_str_quoted = format!("\"{}\"", vbf_str); + let vbf_from_serde: ValueBlindingFactor = serde_json::from_str(&vbf_str_quoted).unwrap(); + let vbf_from_str = ValueBlindingFactor::from_str(vbf_str).unwrap(); + assert_eq!(vbf_from_serde, vbf_from_str); + assert_eq!(vbf_str_quoted, serde_json::to_string(&vbf_from_serde).unwrap()); + } + + #[cfg(feature = "serde")] + #[test] + fn test_value_bincode_be() { + let value = Value::Explicit(500); + let bytes = bincode::serialize(&value).unwrap(); + let decoded: Value = bincode::deserialize(&bytes).unwrap(); + assert_eq!(value, decoded); + } + + #[cfg(feature = "serde")] + #[test] + fn test_value_bincode_le() { + use bincode::Options; + let value = Value::Explicit(500); + let bytes = + bincode::DefaultOptions::default().with_little_endian().serialize(&value).unwrap(); + let decoded: Value = + bincode::DefaultOptions::default().with_little_endian().deserialize(&bytes).unwrap(); + assert_eq!(value, decoded); + } +} diff --git a/src/confidential/nonce.rs b/src/confidential/nonce.rs new file mode 100644 index 00000000..12310e0c --- /dev/null +++ b/src/confidential/nonce.rs @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Nonces + +use core::fmt; +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, PublicKey, Secp256k1, SecretKey, Signing}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::CommitmentEncoder; +use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; +use crate::hashes::sha256d; + +type ExplicitInner = [u8; 32]; +type ConfInner = PublicKey; + +const EXPLICIT_LEN: usize = 32; +const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; +const CONF_PREFIX_1: u8 = 0x02; +const CONF_PREFIX_2: u8 = 0x03; + +/// A CT commitment to an output nonce (i.e. a public key) +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Nonce { + /// No value + #[default] + Null, + /// There should be no such thing as an "explicit nonce", but Elements will deserialize + /// such a thing (and insists that its size be 32 bytes). So we stick a 32-byte type here + /// that implements all the traits we need. + Explicit(ExplicitInner), + /// Nonce is committed + Confidential(ConfInner), +} + +impl Nonce { + /// Create nonce commitment. + pub fn new_confidential( + rng: &mut R, + secp: &Secp256k1, + receiver_blinding_pk: &ConfInner, + ) -> (Self, SecretKey) { + let ephemeral_sk = SecretKey::new(rng); + Self::with_ephemeral_sk(secp, ephemeral_sk, receiver_blinding_pk) + } + + /// Similar to [`Self::new_confidential`], but with a given `ephemeral_sk` + /// instead of sampling it from rng. + pub fn with_ephemeral_sk( + secp: &Secp256k1, + ephemeral_sk: SecretKey, + receiver_blinding_pk: &ConfInner, + ) -> (Self, SecretKey) { + let sender_pk = ConfInner::from_secret_key(secp, &ephemeral_sk); + let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &ephemeral_sk); + (Self::Confidential(sender_pk), shared_secret) + } + + /// Calculate the shared secret. + pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { + match self { + Self::Confidential(sender_pk) => + Some(Self::make_shared_secret(sender_pk, receiver_blinding_sk)), + _ => None, + } + } + + /// Create the shared secret. + fn make_shared_secret(pk: &ConfInner, sk: &SecretKey) -> SecretKey { + let xy = secp256k1_zkp::ecdh::shared_secret_point(pk, sk); + let shared_secret = { + // Yes, what follows is the compressed representation of a Bitcoin public key. + // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 + + let mut dh_secret = [0u8; CONFIDENTIAL_LEN]; + dh_secret[0] = if xy.last().unwrap() % 2 == 0 { CONF_PREFIX_1 } else { CONF_PREFIX_2 }; + dh_secret[1..].copy_from_slice(&xy[0..32]); + + sha256d::Hash::hash(&dh_secret).to_byte_array() + }; + + SecretKey::from_slice(&shared_secret[..32]).expect("always has exactly 32 bytes") + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Self::Null => 1, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Self::Confidential( + ConfInner::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, + )) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } + + /// Returns the explicit inner value. + /// Returns [None] if [`Self::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Self::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Self::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Self::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Nonce { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } +} + +impl fmt::Display for Nonce { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Null => f.write_str("null"), + Self::Explicit(n) => { + for b in &n { + write!(f, "{:02x}", b)?; + } + Ok(()) + } + Self::Confidential(pk) => write!(f, "{:02x}", pk), + } + } +} + +impl Encodable for Nonce { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } + Self::Explicit(n) => { + s.write_all(&[1u8])?; + s.write_all(&n)?; + Ok(1 + EXPLICIT_LEN) + } + Self::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(CONFIDENTIAL_LEN) + } + } + } +} + +impl Decodable for Nonce { + fn consensus_decode(mut d: D) -> Result { + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; + + match buf[0] { + 0 => Ok(Self::Null), + 1 => { + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(buf)) + } + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf)?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Nonce { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Self::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Nonce { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Nonce; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Self::Value::Null), + Some(1) => match access.next_element()? { + Some(x) => Ok(Self::Value::Explicit(x)), + None => Err(A::Error::custom("missing explicit nonce")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Self::Value::Confidential(x)), + None => Err(A::Error::custom("missing nonce")), + }, + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Nonce`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Nonce { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit32(Some(1), id), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Nonce`] type. + pub struct Decoder(enum DecoderInner { + Done(Nonce), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Nonce::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Nonce::Explicit(bytes))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Nonce::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Nonce`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(bitcoin::secp256k1::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/confidential/range_proof.rs b/src/confidential/range_proof.rs new file mode 100644 index 00000000..441b8c4f --- /dev/null +++ b/src/confidential/range_proof.rs @@ -0,0 +1,246 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Range Proofs + +use core::convert::TryInto; +use core::fmt; +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, Generator, PedersenCommitment, Secp256k1, SecretKey, Signing, Tweak}; +#[cfg(feature = "serde")] +use serde::{Deserializer, Serializer}; + +use crate::confidential::ValueBlindingFactor; +use crate::{encode, encoding}; + +/// A range proof, which represents a proof that a confidential value lies within +/// some range (typically `[0, 2^64)`). +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct RangeProof { + inner: Option>, +} + +impl RangeProof { + /// No range proof. + pub const EMPTY: Self = Self { inner: None }; + + /// Constructs a new [`RangeProof`]. + #[allow(clippy::too_many_arguments)] + pub fn new( + secp: &Secp256k1, + min_value: u64, + commitment: PedersenCommitment, + value: u64, + commitment_blinding: Tweak, + message: &[u8], + additional_commitment: &[u8], + sk: SecretKey, + exp: i32, + min_bits: u8, + additional_generator: Generator, + ) -> Result { + secp256k1_zkp::RangeProof::new( + secp, + min_value, + commitment, + value, + commitment_blinding, + message, + additional_commitment, + sk, + exp, + min_bits, + additional_generator, + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Parses a [`RangeProof`] from a byte slice (with no length prefix). + pub fn from_slice(sl: &[u8]) -> Result { + if sl.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::RangeProof::from_slice(sl) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + } + + /// Outputs a [`RangeProof`] proving that a commitment matches an exact value. + pub fn blind_value_proof( + rng: &mut R, + secp: &Secp256k1, + explicit_val: u64, + value_commit: PedersenCommitment, + asset_gen: Generator, + vbf: ValueBlindingFactor, + ) -> Result { + secp256k1_zkp::RangeProof::new( + secp, + explicit_val, // min_value + value_commit, // value_commit + explicit_val, // value + vbf.into_inner(), // blinding factor + &[], // message + &[], // add commitment + SecretKey::new(rng), // nonce + -1, // exp + 0, // min bits + asset_gen, // additional gen + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Verifies a [`RangeProof`] proving that a commitment matches an exact value. + pub fn blind_value_proof_verify( + &self, + secp: &Secp256k1, + explicit_val: u64, + asset_gen: Generator, + value_commit: PedersenCommitment, + ) -> bool { + let Some(inner) = self.inner.as_deref() else { + return false; + }; + if explicit_val == u64::MAX { + // FIXME upstream will panic on this input; we should be able to validate + // proofs with this value. + return false; + } + + let Ok(range) = inner.verify(secp, value_commit, &[], asset_gen) else { + return false; + }; + range == (explicit_val..explicit_val + 1) + } + + /// The length of the range proof (zero if it is empty/absent). + pub fn len(&self) -> usize { self.inner.as_deref().map_or(0, secp256k1_zkp::RangeProof::len) } + + /// Whether the range proof is absent. + pub fn is_empty(&self) -> bool { self.inner.is_none() } + + /// Serializes the range proof as a byte vector. + pub fn to_vec(&self) -> Vec { + match self.inner.as_deref() { + Some(prf) => secp256k1_zkp::RangeProof::serialize(prf), + None => Vec::new(), + } + } + + /// Extracts the minimum value encoded in the range proof. + pub fn minimim_value(&self) -> Option { + // inefficient, consider implementing index on rangeproof + let prf = self.to_vec(); + let byte0 = prf.first()?; + + let has_nonzero_range = byte0 & 64 == 64; + let has_min = byte0 & 32 == 32; + + if !has_min { + None + } else if has_nonzero_range { + let bytes: [u8; 8] = prf.get(2..10)?.try_into().ok()?; + Some(u64::from_be_bytes(bytes)) + } else { + let bytes: [u8; 8] = prf.get(1..9)?.try_into().ok()?; + Some(u64::from_be_bytes(bytes)) + } + } + + /// Obtains a reference to the underlying secp256k1-zkp object. + pub fn as_ref(&self) -> Option<&secp256k1_zkp::RangeProof> { self.inner.as_deref() } +} + +impl crate::encode::Encodable for RangeProof { + fn consensus_encode(&self, e: W) -> Result { + self.to_vec().consensus_encode(e) + } +} + +impl crate::encode::Decodable for RangeProof { + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + if v.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::RangeProof::from_slice(&v) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + .map_err(encode::Error::Secp256k1zkp) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for RangeProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for RangeProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer) + .map(|inner| Self { inner: inner.map(Box::new) }) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`RangeProof`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(super::PrefixedByteVecEncoder); +} + +impl encoding::Encode for RangeProof { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(super::PrefixedByteVecEncoder::new(self.to_vec())) + } +} + +decoder_newtype! { + /// Decoder for the [`RangeProof`] type. + #[derive(Default)] + pub struct Decoder(encoding::ByteVecDecoder); + + /// Decoder error for the [`RangeProof`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + Decode(encoding::ByteVecDecoderError), + RangeProof(secp256k1_zkp::Error), + }); + + impl Decode for RangeProof { + fn convert_inner(v) -> Result<_, DecoderErrorInner> { + Self::Output::from_slice(&v).map_err(DecoderErrorInner::RangeProof) + } + } +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(..) => f.write_str("error decoding byte vector"), + Inner::RangeProof(..) => f.write_str("error decoding range proof"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(ref e) => Some(e), + Inner::RangeProof(ref e) => Some(e), + } + } +} diff --git a/src/confidential/surjection_proof.rs b/src/confidential/surjection_proof.rs new file mode 100644 index 00000000..9a9ceed5 --- /dev/null +++ b/src/confidential/surjection_proof.rs @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Surjection Proofs + +use core::fmt; +use std::io; + +use secp256k1_zkp::rand::{CryptoRng, RngCore}; +use secp256k1_zkp::{self, Generator, Secp256k1, Signing, Tweak, ZERO_TWEAK}; +#[cfg(feature = "serde")] +use serde::{Deserializer, Serializer}; + +use crate::confidential::{AssetBlindingFactor, AssetId}; +use crate::{encode, encoding}; + +/// A surjection proof, proving that an asset commitment commits to the same asset ID +/// as a commitment from a given set. +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)] +pub struct SurjectionProof { + inner: Option>, +} + +impl SurjectionProof { + /// No surjection proof. + pub const EMPTY: Self = Self { inner: None }; + + /// Constructs a new [`SurjectionProof`]. + pub fn new( + secp: &Secp256k1, + rng: &mut R, + asset: AssetId, + asset_bf: AssetBlindingFactor, + inputs: S, + ) -> Result + where + R: RngCore + CryptoRng, + C: Signing, + S: AsRef<[(Generator, secp256k1_zkp::Tag, Tweak)]>, + { + secp256k1_zkp::SurjectionProof::new( + secp, + rng, + asset.into_tag(), + asset_bf.into_inner(), + inputs.as_ref(), + ) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + + /// Parses a [`SurjectionProof`] from a byte slice (with no length prefix). + pub fn from_slice(sl: &[u8]) -> Result { + if sl.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::SurjectionProof::from_slice(sl) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + } + } + + /// Serializes the surjection proof as a byte vector. + pub fn to_vec(&self) -> Vec { + match self.inner.as_deref() { + Some(prf) => secp256k1_zkp::SurjectionProof::serialize(prf), + None => Vec::new(), + } + } + + /// Outputs a [`SurjectionProof`] proving that an asset matches an exact asset ID. + pub fn blind_asset_proof( + rng: &mut R, + secp: &Secp256k1, + asset: AssetId, + abf: AssetBlindingFactor, + ) -> Result { + let gen = Generator::new_unblinded(secp, asset.into_tag()); + Self::new(secp, rng, asset, abf, [(gen, asset.into_tag(), ZERO_TWEAK)]) + } + + /// Verifies a [`SurjectionProof`] proving that an asset matches an exact asset ID. + pub fn blind_asset_proof_verify( + &self, + secp: &Secp256k1, + asset: AssetId, + asset_commit: Generator, + ) -> bool { + let gen = Generator::new_unblinded(secp, asset.into_tag()); + match self.inner.as_deref() { + Some(inner) => inner.verify(secp, asset_commit, &[gen]), + None => false, + } + } + + /// The length of the range proof (zero if it is empty/absent). + pub fn len(&self) -> usize { + self.inner.as_deref().map_or(0, secp256k1_zkp::SurjectionProof::len) + } + + /// Whether the surjectionproof is absent. + pub fn is_empty(&self) -> bool { self.inner.is_none() } + + /// Obtains a reference to the underlying secp256k1-zkp object. + pub fn as_ref(&self) -> Option<&secp256k1_zkp::SurjectionProof> { self.inner.as_deref() } +} + +impl crate::encode::Encodable for SurjectionProof { + fn consensus_encode(&self, e: W) -> Result { + match self.inner.as_ref() { + Some(prf) => secp256k1_zkp::SurjectionProof::serialize(prf).consensus_encode(e), + None => <[u8]>::consensus_encode(&[], e), + } + } +} + +impl crate::encode::Decodable for SurjectionProof { + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + if v.is_empty() { + Ok(Self { inner: None }) + } else { + secp256k1_zkp::SurjectionProof::from_slice(&v) + .map(|inner| Self { inner: Some(Box::new(inner)) }) + .map_err(encode::Error::Secp256k1zkp) + } + } +} + +#[cfg(feature = "serde")] +impl serde::Serialize for SurjectionProof { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + self.inner.serialize(serializer) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for SurjectionProof { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Option::::deserialize(deserializer) + .map(|inner| Self { inner: inner.map(Box::new) }) + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`SurjectionProof`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(super::PrefixedByteVecEncoder); +} + +impl encoding::Encode for SurjectionProof { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(super::PrefixedByteVecEncoder::new(self.to_vec())) + } +} + +decoder_newtype! { + /// Decoder for the [`SurjectionProof`] type. + #[derive(Default)] + pub struct Decoder(encoding::ByteVecDecoder); + + /// Decoder error for the [`SurjectionProof`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + Decode(encoding::ByteVecDecoderError), + SurjectionProof(secp256k1_zkp::Error), + }); + + impl Decode for SurjectionProof { + fn convert_inner(v) -> Result<_, DecoderErrorInner> { + Self::Output::from_slice(&v).map_err(DecoderErrorInner::SurjectionProof) + } + } +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(..) => f.write_str("error decoding byte vector"), + Inner::SurjectionProof(..) => f.write_str("error decoding surjection proof"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::Decode(ref e) => Some(e), + Inner::SurjectionProof(ref e) => Some(e), + } + } +} diff --git a/src/confidential/value.rs b/src/confidential/value.rs new file mode 100644 index 00000000..930e20c3 --- /dev/null +++ b/src/confidential/value.rs @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! Confiential Values + +use core::ops::{AddAssign, Neg}; +use core::{fmt, str}; +use std::io; + +use secp256k1_zkp::rand::Rng; +use secp256k1_zkp::{ + self, compute_adaptive_blinding_factor, CommitmentSecrets, Generator, PedersenCommitment, + Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, +}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use super::CommitmentEncoder; +use crate::confidential::AssetBlindingFactor; +use crate::encode::{self, Decodable, Encodable}; +use crate::encoding; +use crate::issuance::AssetId; + +type ExplicitInner = u64; +type ConfInner = PedersenCommitment; + +const EXPLICIT_LEN: usize = 8; +const CONFIDENTIAL_LEN: usize = 33; +const CONFIDENTIAL_LEN_LESS_PREFIX: usize = CONFIDENTIAL_LEN - 1; +const CONF_PREFIX_1: u8 = 0x08; +const CONF_PREFIX_2: u8 = 0x09; + +/// A CT commitment to an amount +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub enum Value { + /// No value + #[default] + Null, + /// Value is explicitly encoded + Explicit(ExplicitInner), + /// Value is committed + Confidential(ConfInner), +} + +impl Value { + /// Create value commitment. + pub fn new_confidential( + secp: &Secp256k1, + value: u64, + asset: Generator, + bf: BlindingFactor, + ) -> Self { + Self::Confidential(ConfInner::new(secp, value, bf.0, asset)) + } + + /// Create value commitment from assetID, asset blinding factor, + /// value and value blinding factor + pub fn new_confidential_from_assetid( + secp: &Secp256k1, + value: u64, + asset: AssetId, + v_bf: BlindingFactor, + a_bf: AssetBlindingFactor, + ) -> Self { + let generator = Generator::new_blinded(secp, asset.into_tag(), a_bf.0); + let comm = ConfInner::new(secp, value, v_bf.0, generator); + + Self::Confidential(comm) + } + + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Self::Null => 1, + Self::Explicit(..) => 1 + EXPLICIT_LEN, + Self::Confidential(..) => CONFIDENTIAL_LEN, + } + } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Self::Confidential(ConfInner::from_slice(bytes)?)) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { matches!(*self, Self::Null) } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { matches!(*self, Self::Explicit(_)) } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { matches!(*self, Self::Confidential(_)) } + + /// Returns the explicit inner value. + /// Returns [None] if [`Self::is_explicit`] returns false. + pub fn explicit(&self) -> Option { + match *self { + Self::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [`Self::is_confidential`] returns false. + pub fn commitment(&self) -> Option { + match *self { + Self::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Value { + fn from(from: ConfInner) -> Self { Self::Confidential(from) } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::Null => f.write_str("null"), + Self::Explicit(n) => write!(f, "{}", n), + Self::Confidential(commitment) => write!(f, "{:02x}", commitment), + } + } +} + +impl Encodable for Value { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Self::Null => { + s.write_all(&[0u8])?; + Ok(1) + } + Self::Explicit(n) => { + s.write_all(&[1u8])?; + s.write_all(&n.to_be_bytes())?; + Ok(1 + EXPLICIT_LEN) + } + Self::Confidential(commitment) => { + s.write_all(&commitment.serialize())?; + Ok(CONFIDENTIAL_LEN) + } + } + } +} + +impl Decodable for Value { + fn consensus_decode(mut d: D) -> Result { + let mut buf = [0u8; CONFIDENTIAL_LEN]; + d.read_exact(&mut buf[0..1])?; + + match buf[0] { + 0 => Ok(Self::Null), + 1 => { + let mut buf = [0; EXPLICIT_LEN]; + d.read_exact(&mut buf)?; + Ok(Self::Explicit(u64::from_be_bytes(buf))) + } + p if p == CONF_PREFIX_1 || p == CONF_PREFIX_2 => { + d.read_exact(&mut buf[1..])?; + Ok(Self::Confidential(ConfInner::from_slice(&buf)?)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +#[cfg(feature = "serde")] +impl Serialize for Value { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = match *self { + Self::Null => 1, + Self::Explicit(_) | Self::Confidential(_) => 2, + }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Self::Null => seq.serialize_element(&0u8)?, + Self::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&u64::swap_bytes(n))?; + } + Self::Confidential(commitment) => { + seq.serialize_element(&2u8)?; + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Value { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Value; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix = access.next_element::()?; + match prefix { + Some(0) => Ok(Self::Value::Null), + Some(1) => match access.next_element()? { + Some(x) => Ok(Self::Value::Explicit(u64::swap_bytes(x))), + None => Err(A::Error::custom("missing explicit value")), + }, + Some(2) => match access.next_element()? { + Some(x) => Ok(Self::Value::Confidential(x)), + None => Err(A::Error::custom("missing pedersen commitment")), + }, + _ => Err(A::Error::custom("wrong or missing prefix")), + } + } + } + + d.deserialize_seq(CommitVisitor) + } +} + +/// Blinding factor used for value commitments. +#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)] +pub struct BlindingFactor(pub(crate) Tweak); + +impl BlindingFactor { + /// Generate random value blinding factor. + pub fn new(rng: &mut R) -> Self { Self(Tweak::new(rng)) } + + /// Parse a blinding factor from a 64-character hex string. + #[deprecated(since = "0.27.0", note = "use s.parse() instead")] + pub fn from_hex(s: &str) -> Result { s.parse() } + + /// Create the value blinding factor of the last output of a transaction. + pub fn last( + secp: &Secp256k1, + value: u64, + abf: AssetBlindingFactor, + inputs: &[(u64, AssetBlindingFactor, Self)], + outputs: &[(u64, AssetBlindingFactor, Self)], + ) -> Self { + let set_a = inputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + let set_b = outputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + + Self(compute_adaptive_blinding_factor(secp, value, abf.0, &set_a, &set_b)) + } + + /// Create from bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + Ok(Self(Tweak::from_slice(bytes)?)) + } + + /// Returns the inner value. + pub fn into_inner(self) -> Tweak { self.0 } + + /// Get a unblinded/zero `AssetBlinding` factor + pub fn zero() -> Self { Self(ZERO_TWEAK) } +} + +impl AddAssign for BlindingFactor { + fn add_assign(&mut self, other: Self) { + if self.0.as_ref() == &[0u8; 32] { + *self = other; + } else if other.0.as_ref() == &[0u8; 32] { + // nothing to do + } else { + // Since libsecp does not expose low level APIs + // for scalar arethematic, we need to abuse secret key + // operations for this + let sk2 = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key"); + let sk = SecretKey::from_slice(other.into_inner().as_ref()).expect("Valid key"); + // The only reason that secret key addition can fail + // is when the keys add up to zero since we have already checked + // keys are in valid secret keys + match sk.add_tweak(&sk2.into()) { + Ok(sk_tweaked) => + *self = Self::from_slice(sk_tweaked.as_ref()).expect("Valid Tweak"), + Err(_) => *self = Self::zero(), + } + } + } +} + +impl Neg for BlindingFactor { + type Output = Self; + + fn neg(self) -> Self::Output { + if self.0.as_ref() == &[0u8; 32] { + self + } else { + let sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key").negate(); + Self::from_slice(sk.as_ref()).expect("Valid Tweak") + } + } +} + +impl core::borrow::Borrow<[u8]> for BlindingFactor { + fn borrow(&self) -> &[u8] { &self.0[..] } +} + +hex::impl_fmt_traits! { + #[display_backward(true)] + impl fmt_traits for BlindingFactor { + const LENGTH: usize = 32; + } +} + +impl str::FromStr for BlindingFactor { + type Err = encode::Error; + + fn from_str(s: &str) -> Result { + let mut slice: [u8; 32] = hex::decode_to_array(s)?; + slice.reverse(); + + let inner = Tweak::from_inner(slice)?; + Ok(Self(inner)) + } +} + +#[cfg(feature = "serde")] +impl Serialize for BlindingFactor { + fn serialize(&self, s: S) -> Result { + if s.is_human_readable() { + s.collect_str(&self) + } else { + s.serialize_bytes(&self.0[..]) + } + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for BlindingFactor { + fn deserialize>(d: D) -> Result { + if d.is_human_readable() { + struct HexVisitor; + + impl ::serde::de::Visitor<'_> for HexVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an ASCII hex string") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + if let Ok(hex) = ::std::str::from_utf8(v) { + hex.parse().map_err(E::custom) + } else { + Err(E::invalid_value(::serde::de::Unexpected::Bytes(v), &self)) + } + } + + fn visit_str(self, v: &str) -> Result + where + E: ::serde::de::Error, + { + v.parse().map_err(E::custom) + } + } + + d.deserialize_str(HexVisitor) + } else { + struct BytesVisitor; + + impl ::serde::de::Visitor<'_> for BytesVisitor { + type Value = BlindingFactor; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("a bytestring") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: ::serde::de::Error, + { + use core::convert::TryFrom; + + match <[u8; 32]>::try_from(v) { + Ok(ret) => { + let inner = Tweak::from_inner(ret).map_err(E::custom)?; + Ok(BlindingFactor(inner)) + } + Err(_) => Err(E::invalid_length(v.len(), &stringify!($len))), + } + } + } + + d.deserialize_bytes(BytesVisitor) + } + } +} + +encoding::encoder_newtype_exact! { + /// Encoder for the [`Asset`] type. + #[derive(Clone, Debug)] + pub struct Encoder<'e>(CommitmentEncoder<'e>); +} + +impl encoding::Encode for Value { + type Encoder<'e> = Encoder<'e>; + + fn encoder(&self) -> Self::Encoder<'_> { + Encoder::new(match *self { + Self::Null => CommitmentEncoder::Null(0), + Self::Explicit(ref id) => CommitmentEncoder::Explicit8(Some(1), id.to_be_bytes()), + Self::Confidential(ref gen) => CommitmentEncoder::Explicit33(gen.serialize()), + }) + } +} + +decoder_state_machine! { + /// A decoder for the [`Value`] type. + pub struct Decoder(enum DecoderInner { + Done(Value), + Errored, + DecodePrefix { + decoder: encoding::ArrayDecoder<1>, + => transition_decode_prefix(prefix, ...) -> Result { + match prefix { + [0] => Ok(DecoderInner::Done(Value::Null)), + [1] => { + Ok(DecoderInner::DecodeExplicit { decoder: encoding::ArrayDecoder::default() }) + }, + [prefix @ (CONF_PREFIX_1 | CONF_PREFIX_2)] => { + Ok(DecoderInner::DecodeConfidential { decoder: encoding::ArrayDecoder::default(), prefix }) + }, + [prefix] => Err(DecoderErrorInner::InvalidConfidentialPrefix { prefix }) + } + } + }, + DecodeExplicit { + decoder: encoding::ArrayDecoder + => transition_decode_explicit(bytes, ...) -> Result { + Ok(DecoderInner::Done(Value::Explicit(u64::from_be_bytes(bytes)))) + } + }, + DecodeConfidential { + decoder: encoding::ArrayDecoder, + prefix: u8 + => transition_decode_confidential(x_coord, ...) -> Result { + let mut bytes = [0; CONFIDENTIAL_LEN]; + bytes[0] = prefix; + bytes[1..].copy_from_slice(&x_coord); + let gen = ConfInner::from_slice(&bytes) + .map_err(DecoderErrorInner::InvalidCommitment)?; + Ok(DecoderInner::Done(Value::Confidential(gen))) + } + }, + }); + + /// A decoder error for the [`Value`] type. + #[derive(Clone, PartialEq, Eq, Debug)] + pub struct DecoderError(enum DecoderErrorInner { + [macro-inserted decoder variants] + /// Confidential prefix was not one of the two allowable values. + InvalidConfidentialPrefix { + prefix: u8, + }, + /// Malformed confidential commitment. + InvalidCommitment(secp256k1_zkp::Error), + }); +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(_) => f.write_str("failed to decode prefix"), + Inner::DecodeExplicit(_) => f.write_str("failed to decode explicit value"), + Inner::DecodeConfidential(_) => f.write_str("failed to decode confidential value"), + Inner::InvalidConfidentialPrefix { prefix, .. } => { + write!( + f, + "confidential prefix 0x{:02x} was not one of 0, 1, 0x{:02x} or 0x{:02x}", + prefix, CONF_PREFIX_1, CONF_PREFIX_2, + ) + } + Inner::InvalidCommitment(_) => f.write_str("failed to parse confidential commitment"), + } + } +} + +impl std::error::Error for DecoderError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + use DecoderErrorInner as Inner; + match self.0 { + Inner::DecodePrefix(ref e) => Some(e), + Inner::DecodeExplicit(ref e) => Some(e), + Inner::DecodeConfidential(ref e) => Some(e), + Inner::InvalidConfidentialPrefix { .. } => None, + Inner::InvalidCommitment(ref e) => Some(e), + } + } +} + +impl Default for Decoder { + fn default() -> Self { + Self(DecoderInner::DecodePrefix { decoder: encoding::ArrayDecoder::default() }) + } +} diff --git a/src/dynafed.rs b/src/dynafed.rs index 15aabb8d..c2f0227c 100644 --- a/src/dynafed.rs +++ b/src/dynafed.rs @@ -16,60 +16,62 @@ use std::{fmt, io}; -use bitcoin; -use bitcoin::hashes::{Hash, sha256, sha256d}; +use hex::DisplayHex as _; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; #[cfg(feature = "serde")] use serde::ser::{SerializeSeq, SerializeStruct}; use crate::encode::{self, Encodable, Decodable}; +use crate::hashes::sha256d; use crate::Script; -/// ad-hoc struct to fmt in hex -struct HexBytes<'a>(&'a [u8]); -impl<'a> fmt::Display for HexBytes<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - bitcoin::hashes::hex::format_hex(&self.0[..], f) - } +impl_sha256_midstate_wrapper! { + /// The Merkle root of a set of dynafed parameters. + pub struct ParamsRoot([u8; 32]); } -impl<'a> fmt::Debug for HexBytes<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self, f) - } + +impl_sha256_midstate_wrapper! { + /// A hash of elided dynafed parameter data. + pub struct ElidedRoot([u8; 32]); } + +/// ad-hoc struct to fmt in hex #[cfg(feature = "serde")] -impl<'a> Serialize for HexBytes<'a> { +struct HexBytes<'a>(&'a [u8]); +#[cfg(feature = "serde")] +impl Serialize for HexBytes<'_> { fn serialize(&self, s: S) -> Result { if s.is_human_readable() { - s.collect_str(self) + s.collect_str(&self.0.as_hex()) } else { - s.serialize_bytes(&self.0[..]) + s.serialize_bytes(self.0) } } } /// ad-hoc struct to fmt in hex struct HexBytesArray<'a>(&'a [Vec]); -impl<'a> fmt::Display for HexBytesArray<'a> { +impl fmt::Display for HexBytesArray<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[")?; for (i, e) in self.0.iter().enumerate() { - if i != 0 { - write!(f, ", ")?; + if i == 0 { + write!(f, "{}", e.as_hex())?; + } else { + write!(f, ", {}", e.as_hex())?; } - bitcoin::hashes::hex::format_hex(&e[..], f)?; } write!(f, "]") } } -impl<'a> fmt::Debug for HexBytesArray<'a> { +impl fmt::Debug for HexBytesArray<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self, f) } } #[cfg(feature = "serde")] -impl<'a> Serialize for HexBytesArray<'a> { +impl Serialize for HexBytesArray<'_> { fn serialize(&self, s: S) -> Result { let mut seq = s.serialize_seq(Some(self.0.len()))?; for b in self.0 { @@ -83,24 +85,41 @@ impl<'a> Serialize for HexBytesArray<'a> { #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct FullParams { /// "scriptPubKey" used for block signing - signblockscript: Script, + pub signblockscript: Script, /// Maximum, in bytes, of the size of a blocksigning witness - signblock_witness_limit: u32, + pub signblock_witness_limit: u32, /// Untweaked `scriptPubKey` used for pegins - fedpeg_program: bitcoin::Script, + pub fedpeg_program: bitcoin::ScriptBuf, /// For v0 fedpeg programs, the witness script of the untweaked /// pegin address. For future versions, this data has no defined /// meaning and will be considered "anyone can spend". - fedpegscript: Vec, + pub fedpegscript: Vec, /// "Extension space" used by Liquid for PAK key entries - extension_space: Vec>, + pub extension_space: Vec>, } impl FullParams { + /// Construct a set of `FullParams` + pub fn new( + signblockscript: Script, + signblock_witness_limit: u32, + fedpeg_program: bitcoin::ScriptBuf, + fedpegscript: Vec, + extension_space: Vec>, + ) -> Self { + Self { + signblockscript, + signblock_witness_limit, + fedpeg_program, + fedpegscript, + extension_space, + } + } + /// Return the `extra root` of this params. /// The extra root commits to the consensus parameters unrelated to /// blocksigning: `fedpeg_program`, `fedpegscript` and `extension_space`. - fn extra_root(&self) -> sha256::Midstate { + fn extra_root(&self) -> ElidedRoot { fn serialize_hash(obj: &E) -> sha256d::Hash { let mut engine = sha256d::Hash::engine(); obj.consensus_encode(&mut engine).expect("engines don't error"); @@ -108,15 +127,15 @@ impl FullParams { } let leaves = [ - serialize_hash(&self.fedpeg_program).into_inner(), - serialize_hash(&self.fedpegscript).into_inner(), - serialize_hash(&self.extension_space).into_inner(), + serialize_hash(&self.fedpeg_program).to_byte_array(), + serialize_hash(&self.fedpegscript).to_byte_array(), + serialize_hash(&self.extension_space).to_byte_array(), ]; - crate::fast_merkle_root::fast_merkle_root(&leaves[..]) + ElidedRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) } - /// Calculate the root of this [FullParams]. - pub fn calculate_root(&self) -> sha256::Midstate { + /// Calculate the root of this [`FullParams`]. + pub fn calculate_root(&self) -> ParamsRoot { fn serialize_hash(obj: &E) -> sha256d::Hash { let mut engine = sha256d::Hash::engine(); obj.consensus_encode(&mut engine).expect("engines don't error"); @@ -124,19 +143,19 @@ impl FullParams { } let leaves = [ - serialize_hash(&self.signblockscript).into_inner(), - serialize_hash(&self.signblock_witness_limit).into_inner(), + serialize_hash(&self.signblockscript).to_byte_array(), + serialize_hash(&self.signblock_witness_limit).to_byte_array(), ]; let compact_root = crate::fast_merkle_root::fast_merkle_root(&leaves[..]); let leaves = [ - compact_root.into_inner(), - self.extra_root().into_inner(), + compact_root.to_parts().0, + self.extra_root().to_byte_array(), ]; - crate::fast_merkle_root::fast_merkle_root(&leaves[..]) + ParamsRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) } - /// Turns paramers into compact parameters. + /// Turns parameters into compact parameters. /// This returns self for compact params and [None] for null ones. pub fn into_compact(self) -> Params { Params::Compact { @@ -146,13 +165,13 @@ impl FullParams { } } - /// Format for [fmt::Debug]. + /// Format for [`fmt::Debug`]. fn fmt_debug(&self, f: &mut fmt::Formatter, name: &'static str) -> fmt::Result { let mut s = f.debug_struct(name); - s.field("signblockscript", &HexBytes(&self.signblockscript[..])); + s.field("signblockscript", &self.signblockscript[..].as_hex()); s.field("signblock_witness_limit", &self.signblock_witness_limit); - s.field("fedpeg_program", &HexBytes(&self.fedpeg_program[..])); - s.field("fedpegscript", &HexBytes(&self.fedpegscript[..])); + s.field("fedpeg_program", &self.fedpeg_program.as_bytes().as_hex()); + s.field("fedpegscript", &self.fedpegscript[..].as_hex()); s.field("extension_space", &HexBytesArray(&self.extension_space)); s.finish() } @@ -199,10 +218,11 @@ impl Decodable for FullParams { } } -/// Dynamic federations paramaters, as encoded in a block header -#[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] +/// Dynamic federations parameters, as encoded in a block header +#[derive(Clone, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum Params { /// Null entry, used to signal "no vote" as a proposal + #[default] Null, /// Compact params where the fedpeg data and extension space /// are not included, and are assumed to be equal to the values @@ -213,7 +233,7 @@ pub enum Params { /// Maximum, in bytes, of the size of a blocksigning witness signblock_witness_limit: u32, /// Merkle root of extra data - elided_root: sha256::Midstate, + elided_root: ElidedRoot, }, /// Full dynamic federations parameters Full(FullParams), @@ -225,7 +245,7 @@ impl fmt::Debug for Params { Params::Null => write!(f, "Null"), Params::Compact { signblockscript, signblock_witness_limit, elided_root } => { let mut s = f.debug_struct("Compact"); - s.field("signblockscript", &HexBytes(&signblockscript[..])); + s.field("signblockscript", &signblockscript[..].as_hex()); s.field("signblock_witness_limit", signblock_witness_limit); s.field("elided_root", elided_root); s.finish() @@ -236,7 +256,7 @@ impl fmt::Debug for Params { } impl Params { - /// Check whether this is [Params::Null]. + /// Check whether this is [`Params::Null`]. pub fn is_null(&self) -> bool { match *self { Params::Null => true, @@ -245,7 +265,7 @@ impl Params { } } - /// Check whether this is [Params::Compact]. + /// Check whether this is [`Params::Compact`]. pub fn is_compact(&self) -> bool { match *self { Params::Null => false, @@ -254,7 +274,7 @@ impl Params { } } - /// Check whether this is [Params::Full]. + /// Check whether this is [`Params::Full`]. pub fn is_full(&self) -> bool { match *self { Params::Null => false, @@ -263,7 +283,7 @@ impl Params { } } - /// Get the signblockscript. Is [None] for [Null] params. + /// Get the signblockscript. Is [None] for [`Params::Null`] params. pub fn signblockscript(&self) -> Option<&Script> { match *self { Params::Null => None, @@ -272,7 +292,7 @@ impl Params { } } - /// Get the signblock_witness_limit. Is [None] for [Null] params. + /// Get the `signblock_witness_limit`. Is [None] for [`Params::Null`] params. pub fn signblock_witness_limit(&self) -> Option { match *self { Params::Null => None, @@ -281,8 +301,8 @@ impl Params { } } - /// Get the fedpeg_program. Is [None] for non-[Full] params. - pub fn fedpeg_program(&self) -> Option<&bitcoin::Script> { + /// Get the `fedpeg_program`. Is [None] for non-[`Params::Full`] params. + pub fn fedpeg_program(&self) -> Option<&bitcoin::ScriptBuf> { match *self { Params::Null => None, Params::Compact { .. } => None, @@ -290,7 +310,7 @@ impl Params { } } - /// Get the fedpegscript. Is [None] for non-[Full] params. + /// Get the fedpegscript. Is [None] for non-[`Params::Full`] params. pub fn fedpegscript(&self) -> Option<&Vec> { match *self { Params::Null => None, @@ -299,7 +319,7 @@ impl Params { } } - /// Get the extension_space. Is [None] for non-[Full] params. + /// Get the `extension_space`. Is [None] for non-[`Params::Full`] params. pub fn extension_space(&self) -> Option<&Vec>> { match *self { Params::Null => None, @@ -308,8 +328,8 @@ impl Params { } } - /// Get the elided_root. Is [None] for non-[Compact] params. - pub fn elided_root(&self) -> Option<&sha256::Midstate> { + /// Get the `elided_root`. Is [None] for non-[`Params::Compact`] params. + pub fn elided_root(&self) -> Option<&ElidedRoot> { match *self { Params::Null => None, Params::Compact { ref elided_root, ..} => Some(elided_root), @@ -320,16 +340,16 @@ impl Params { /// Return the `extra root` of this params. /// The extra root commits to the consensus parameters unrelated to /// blocksigning: `fedpeg_program`, `fedpegscript` and `extension_space`. - fn extra_root(&self) -> sha256::Midstate { + fn extra_root(&self) -> ElidedRoot { match *self { - Params::Null => sha256::Midstate::from_inner([0u8; 32]), + Params::Null => ElidedRoot::from_byte_array([0u8; 32]), Params::Compact { ref elided_root, .. } => *elided_root, Params::Full(ref f) => f.extra_root(), } } /// Calculate the root of this [Params]. - pub fn calculate_root(&self) -> sha256::Midstate { + pub fn calculate_root(&self) -> ParamsRoot { fn serialize_hash(obj: &E) -> sha256d::Hash { let mut engine = sha256d::Hash::engine(); obj.consensus_encode(&mut engine).expect("engines don't error"); @@ -337,20 +357,20 @@ impl Params { } if self.is_null() { - return sha256::Midstate::from_inner([0u8; 32]); + return ParamsRoot::from_byte_array([0u8; 32]); } let leaves = [ - serialize_hash(self.signblockscript().unwrap()).into_inner(), - serialize_hash(&self.signblock_witness_limit().unwrap()).into_inner(), + serialize_hash(self.signblockscript().unwrap()).to_byte_array(), + serialize_hash(&self.signblock_witness_limit().unwrap()).to_byte_array(), ]; - let compact_root = crate::fast_merkle_root::fast_merkle_root(&leaves[..]); + let compact_root = ElidedRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])); let leaves = [ - compact_root.into_inner(), - self.extra_root().into_inner(), + compact_root.to_byte_array(), + self.extra_root().to_byte_array(), ]; - crate::fast_merkle_root::fast_merkle_root(&leaves[..]) + ParamsRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) } /// Get the full params when this params are full. @@ -371,7 +391,7 @@ impl Params { } } - /// Turns paramers into compact parameters. + /// Turns parameters into compact parameters. /// This returns self for compact params and [None] for null ones. pub fn into_compact(self) -> Option { match self { @@ -382,12 +402,6 @@ impl Params { } } -impl Default for Params { - fn default() -> Params { - Params::Null - } -} - #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Params { fn deserialize>(d: D) -> Result { @@ -404,7 +418,7 @@ impl<'de> Deserialize<'de> for Params { } struct EnumVisitor; - impl<'de> de::Visitor<'de> for EnumVisitor { + impl de::Visitor<'_> for EnumVisitor { type Value = Enum; fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -455,9 +469,7 @@ impl<'de> Deserialize<'de> for Params { } fn visit_str(self, v: &str) -> Result { - use bitcoin::hashes::hex::FromHex; - - Ok(HexBytes(FromHex::from_hex(v).map_err(E::custom)?)) + Ok(HexBytes(hex::decode_to_vec(v).map_err(E::custom)?)) } fn visit_bytes(self, v: &[u8]) -> Result { @@ -606,7 +618,7 @@ impl Encodable for Params { Encodable::consensus_encode(&1u8, &mut s)? + Encodable::consensus_encode(signblockscript, &mut s)? + Encodable::consensus_encode(signblock_witness_limit, &mut s)? + - Encodable::consensus_encode(&elided_root.into_inner(), &mut s)? + Encodable::consensus_encode(&elided_root.to_byte_array(), &mut s)? }, Params::Full(ref f) => { Encodable::consensus_encode(&2u8, &mut s)? + @@ -624,7 +636,7 @@ impl Decodable for Params { 1 => Ok(Params::Compact { signblockscript: Decodable::consensus_decode(&mut d)?, signblock_witness_limit: Decodable::consensus_decode(&mut d)?, - elided_root: sha256::Midstate::from_inner(Decodable::consensus_decode(&mut d)?), + elided_root: ElidedRoot::from_byte_array(Decodable::consensus_decode(&mut d)?), }), 2 => Ok(Params::Full(Decodable::consensus_decode(&mut d)?)), _ => Err(encode::Error::ParseFailed( @@ -638,9 +650,6 @@ impl Decodable for Params { mod tests { use std::fmt::{self, Write}; - use bitcoin::hashes::hex::ToHex; - use bitcoin::hashes::sha256; - use crate::{BlockHash, TxMerkleNode}; use super::*; @@ -676,29 +685,29 @@ mod tests { let signblockscript: Script = vec![1].into(); let signblock_wl = 2; - let fp_program: bitcoin::Script = vec![3].into(); + let fp_program: bitcoin::ScriptBuf = vec![3].into(); let fp_script = vec![4]; let ext = vec![vec![5, 6], vec![7]]; let compact_entry = Params::Compact { signblockscript: signblockscript.clone(), signblock_witness_limit: signblock_wl, - elided_root: sha256::Midstate::from_inner([0; 32]), + elided_root: ElidedRoot::from_byte_array([0; 32]), }; assert_eq!( - compact_entry.calculate_root().to_hex(), + format!("{:x}", compact_entry.calculate_root()), "f98f149fd11da6fbe26d0ee53cadd28372fa9eed2cb7080f41da7ca311531777" ); - let full_entry = Params::Full(FullParams { + let full_entry = Params::Full(FullParams::new( signblockscript, - signblock_witness_limit: signblock_wl, - fedpeg_program: fp_program, - fedpegscript: fp_script, - extension_space: ext, - }); + signblock_wl, + fp_program, + fp_script, + ext, + )); assert_eq!( - full_entry.calculate_root().to_hex(), + format!("{:x}", full_entry.calculate_root()), "8eb1b83cce69a3d8b0bfb7fbe77ae8f1d24b57a9cae047b8c0aba084ad878249" ); @@ -709,13 +718,13 @@ mod tests { signblock_witness: vec![], }, version: Default::default(), - prev_blockhash: BlockHash::all_zeros(), - merkle_root: TxMerkleNode::all_zeros(), + prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, + merkle_root: TxMerkleNode::from_byte_array([0; 32]), time: Default::default(), height: Default::default(), }; assert_eq!( - header.calculate_dynafed_params_root().unwrap().to_hex(), + format!("{:x}", header.calculate_dynafed_params_root().unwrap()), "113160f76dc17fe367a2def79aefe06feeea9c795310c9e88aeedc23e145982e" ); } diff --git a/src/encode.rs b/src/encode.rs index 9d19e49a..578e2d87 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -16,30 +16,59 @@ //! use std::io::Cursor; -use std::{error, fmt, io, mem}; -use crate::hashes::{self, Hash}; +use std::{any, error, fmt, io, mem}; -use bitcoin::consensus::encode as btcenc; -use bitcoin::hashes::sha256; -use secp256k1_zkp::{self, RangeProof, SurjectionProof, Tweak}; +use bitcoin::ScriptBuf; +use hex::{DecodeFixedLengthBytesError, DecodeVariableLengthBytesError}; +use secp256k1_zkp::{self, Tweak}; -use crate::transaction::{Transaction, TxIn, TxOut}; +use crate::hashes::{sha256, Hash}; use crate::pset; pub use bitcoin::{self, consensus::encode::MAX_VEC_SIZE}; -// Use the ReadExt/WriteExt traits as is from upstream -pub use bitcoin::consensus::encode::{ReadExt, WriteExt}; - use crate::taproot::TapLeafHash; +/// Adaptor to count bytes, used to implement Encodable/Decodable +/// in terms of the new Encode/Decode traits. +pub(crate) struct ByteCounter { + inner: W, + count: usize, +} + +impl ByteCounter { + pub(crate) fn new(inner: W) -> Self { + Self { inner, count: 0 } + } + + pub(crate) fn into_count(self) -> usize { + self.count + } +} + +impl io::Write for ByteCounter + where W: io::Write +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + let res = self.inner.write(buf); + if let Ok(size) = res { + self.count += size; + } + res + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + /// Encoding error #[derive(Debug)] pub enum Error { /// And I/O error Io(io::Error), /// A Bitcoin encoding error. - Bitcoin(btcenc::Error), + Bitcoin(bitcoin::consensus::encode::Error), /// Tried to allocate an oversized vector OversizedVectorAllocation { /// The capacity requested @@ -59,10 +88,18 @@ pub enum Error { Secp256k1zkp(secp256k1_zkp::Error), /// Pset related Errors PsetError(pset::Error), - /// Hex parsing errors - HexError(hashes::hex::Error), + /// Hex fixed parsing errors + HexFixedError(DecodeFixedLengthBytesError), + /// Hex variable parsing errors + HexVariableError(DecodeVariableLengthBytesError), /// Got a time-based locktime when expecting a height-based one, or vice-versa - BadLockTime(crate::LockTime) + BadLockTime(crate::LockTime), + /// `VarInt` was encoded in a non-minimal way. + NonMinimalVarInt, + /// Error decoding a pegin witness. + PeginWitness(crate::PeginWitnessDecoderError), + /// Error decoding a script witness. + Witness(crate::WitnessDecoderError), } impl fmt::Display for Error { @@ -73,8 +110,12 @@ impl fmt::Display for Error { Error::OversizedVectorAllocation { requested: ref r, max: ref m, - } => write!(f, "oversized vector allocation: requested {}, maximum {}", r, m), - Error::ParseFailed(ref e) => write!(f, "parse failed: {}", e), + } => write!( + f, + "oversized vector allocation: requested {}, maximum {}", + r, m + ), + Error::ParseFailed(e) => write!(f, "parse failed: {}", e), Error::UnexpectedEOF => write!(f, "unexpected EOF"), Error::InvalidConfidentialPrefix(p) => { write!(f, "invalid confidential prefix: 0x{:02x}", p) @@ -82,8 +123,12 @@ impl fmt::Display for Error { Error::Secp256k1(ref e) => write!(f, "{}", e), Error::Secp256k1zkp(ref e) => write!(f, "{}", e), Error::PsetError(ref e) => write!(f, "Pset Error: {}", e), - Error::HexError(ref e) => write!(f, "Hex error {}", e), + Error::HexFixedError(ref e) => write!(f, "Hex fixed error: {}", e), + Error::HexVariableError(ref e) => write!(f, "Hex variable error: {}", e), Error::BadLockTime(ref lt) => write!(f, "Invalid locktime {}", lt), + Error::NonMinimalVarInt => write!(f, "non-minimal varint"), + Self::PeginWitness(..) => f.write_str("error decoding pegin witness"), + Self::Witness(..) => f.write_str("error decoding script witness"), } } } @@ -91,16 +136,16 @@ impl fmt::Display for Error { impl error::Error for Error { fn cause(&self) -> Option<&dyn error::Error> { match *self { - Error::Bitcoin(ref e) => Some(e), Error::Secp256k1zkp(ref e) => Some(e), + Self::PeginWitness(ref e) => Some(e), + Self::Witness(ref e) => Some(e), _ => None, } } } - #[doc(hidden)] -impl From for Error { - fn from(e: btcenc::Error) -> Error { +impl From for Error { + fn from(e: bitcoin::consensus::encode::Error) -> Error { Error::Bitcoin(e) } } @@ -134,9 +179,16 @@ impl From for Error { } #[doc(hidden)] -impl From for Error { - fn from(e: hashes::hex::Error) -> Self { - Error::HexError(e) +impl From for Error { + fn from(e: DecodeFixedLengthBytesError) -> Self { + Error::HexFixedError(e) + } +} + +#[doc(hidden)] +impl From for Error { + fn from(e: DecodeVariableLengthBytesError) -> Self { + Error::HexVariableError(e) } } @@ -163,7 +215,7 @@ pub fn serialize(data: &T) -> Vec { /// Encode an object into a hex-encoded string pub fn serialize_hex(data: &T) -> String { - ::bitcoin::hashes::hex::ToHex::to_hex(&serialize(data)[..]) + hex::DisplayHex::to_lower_hex_string(&serialize(data)[..]) } /// Deserialize an object from a vector, will error if said deserialization @@ -175,7 +227,9 @@ pub fn deserialize(data: &[u8]) -> Result { if consumed == data.len() { Ok(rv) } else { - Err(Error::ParseFailed("data not consumed entirely when explicitly deserializing")) + Err(Error::ParseFailed( + "data not consumed entirely when explicitly deserializing", + )) } } @@ -189,55 +243,15 @@ pub fn deserialize_partial(data: &[u8]) -> Result<(T, usize), Erro Ok((rv, consumed)) } -impl Encodable for sha256::Midstate { - fn consensus_encode(&self, e: W) -> Result { - self.into_inner().consensus_encode(e) - } -} - -impl Decodable for sha256::Midstate { - fn consensus_decode(d: D) -> Result { - Ok(Self::from_inner(<[u8; 32]>::consensus_decode(d)?)) - } -} - -pub(crate) fn consensus_encode_with_size(data: &[u8], mut s: S) -> Result { - let vi_len = bitcoin::VarInt(data.len() as u64).consensus_encode(&mut s)?; - s.emit_slice(&data)?; +pub(crate) fn consensus_encode_with_size( + data: &[u8], + mut s: S, +) -> Result { + let vi_len = VarInt(data.len() as u64).consensus_encode(&mut s)?; + s.emit_slice(data)?; Ok(vi_len + data.len()) } -/// Implement Elements encodable traits for Bitcoin encodable types. -macro_rules! impl_upstream { - ($type: ty) => { - impl Encodable for $type { - fn consensus_encode(&self, mut e: W) -> Result { - Ok(btcenc::Encodable::consensus_encode(self, &mut e)?) - } - } - - impl Decodable for $type { - fn consensus_decode(mut d: D) -> Result { - Ok(btcenc::Decodable::consensus_decode(&mut d)?) - } - } - }; -} -impl_upstream!(u8); -impl_upstream!(u32); -impl_upstream!(u64); -impl_upstream!([u8; 4]); -impl_upstream!([u8; 32]); -impl_upstream!(Box<[u8]>); -impl_upstream!([u8; 33]); -impl_upstream!(Vec); -impl_upstream!(Vec>); -impl_upstream!(btcenc::VarInt); -impl_upstream!(crate::hashes::sha256d::Hash); -impl_upstream!(bitcoin::Transaction); -impl_upstream!(bitcoin::BlockHash); -impl_upstream!(bitcoin::Script); - // Specific locktime types (which appear in PSET/PSBT2 but not in rust-bitcoin PSBT) impl Encodable for crate::locktime::Height { fn consensus_encode(&self, s: S) -> Result { @@ -253,7 +267,6 @@ impl Decodable for crate::locktime::Height { } } - impl Encodable for crate::locktime::Time { fn consensus_encode(&self, s: S) -> Result { crate::LockTime::from(*self).consensus_encode(s) @@ -268,136 +281,238 @@ impl Decodable for crate::locktime::Time { } } +impl Encodable for crate::Witness { + fn consensus_encode(&self, e: W) -> Result { + let mut counter = ByteCounter::new(e); + crate::encoding::encode_to_writer(self, &mut counter)?; + Ok(counter.into_count()) + } +} -// Vectors -macro_rules! impl_vec { - ($type: ty) => { - impl Encodable for Vec<$type> { - #[inline] - fn consensus_encode(&self, mut s: S) -> Result { - let mut len = 0; - len += btcenc::VarInt(self.len() as u64).consensus_encode(&mut s)?; - for c in self.iter() { - len += c.consensus_encode(&mut s)?; - } - Ok(len) - } +impl Decodable for crate::Witness { + fn consensus_decode(d: D) -> Result { + match crate::encoding::decode_from_read_unbuffered(d) { + Ok(wit) => Ok(wit), + Err(crate::encoding::ReadError::Io(e)) => Err(Error::Io(e)), + Err(crate::encoding::ReadError::Decode(e)) => Err(Error::Witness(e)), } + } +} - impl Decodable for Vec<$type> { - #[inline] - fn consensus_decode(mut d: D) -> Result { - let len = btcenc::VarInt::consensus_decode(&mut d)?.0; - let byte_size = (len as usize) - .checked_mul(mem::size_of::<$type>()) - .ok_or(self::Error::ParseFailed("Invalid length"))?; - if byte_size > MAX_VEC_SIZE { - return Err(self::Error::OversizedVectorAllocation { - requested: byte_size, - max: MAX_VEC_SIZE, - }); - } - let mut ret = Vec::with_capacity(len as usize); - for _ in 0..len { - ret.push(Decodable::consensus_decode(&mut d)?); - } - Ok(ret) +/// A variable sized integer. +pub struct VarInt(pub u64); +impl Encodable for VarInt { + fn consensus_encode(&self, mut e: W) -> Result { + Ok(e.emit_varint(self.0)?) + } +} +impl Decodable for VarInt { + fn consensus_decode(mut d: D) -> Result { + Ok(VarInt(d.read_varint()?)) + } +} +impl VarInt { + /// returns the byte size used if this var int is serialized + pub fn size(&self) -> usize { + match self.0 { + 0..=0xFC => 1, + 0xFD..=0xFFFF => 3, + 0x10000..=0xFFFF_FFFF => 5, + _ => 9, + } + } +} + +// Primitive types +macro_rules! impl_int { + ($ty:ident, $meth_dec:ident, $meth_enc:ident) => { + impl Encodable for $ty { + fn consensus_encode(&self, mut w: W) -> Result { + w.$meth_enc(*self)?; + Ok(mem::size_of::<$ty>()) + } + } + impl Decodable for $ty { + fn consensus_decode(mut r: R) -> Result { + crate::ReadExt::$meth_dec(&mut r) } } }; } -impl_vec!(TxIn); -impl_vec!(TxOut); -impl_vec!(Transaction); -impl_vec!(TapLeafHash); +impl_int!(u8, read_u8, emit_u8); +impl_int!(u16, read_u16, emit_u16); +impl_int!(u32, read_u32, emit_u32); +impl_int!(u64, read_u64, emit_u64); -macro_rules! impl_box_option { - ($type: ty) => { - impl Encodable for Option> { - #[inline] - fn consensus_encode(&self, e: W) -> Result { - match self { - None => Vec::::new().consensus_encode(e), - Some(v) => v.serialize().consensus_encode(e), - } - } - } +impl Encodable for bitcoin::ScriptBuf { + fn consensus_encode(&self, w: W) -> Result { + consensus_encode_with_size(self.as_script().as_bytes(), w) + } +} +impl Decodable for bitcoin::ScriptBuf { + fn consensus_decode(d: D) -> Result { + let bytes = Vec::::consensus_decode(d)?; + Ok(ScriptBuf::from_bytes(bytes)) + } +} - impl Decodable for Option> { - #[inline] - fn consensus_decode(mut d: D) -> Result { - let v : Vec = Decodable::consensus_decode(&mut d)?; - if v.is_empty() { - Ok(None) - } else { - Ok(Some(Box::new(<$type>::from_slice(&v)?))) - } +impl Encodable for hashes::sha256d::Hash { + fn consensus_encode(&self, mut w: W) -> Result { + self.as_byte_array().consensus_encode(&mut w) + } +} +impl Decodable for hashes::sha256d::Hash { + fn consensus_decode(d: D) -> Result { + Ok(Self::from_byte_array(<[u8; 32]>::consensus_decode(d)?)) + } +} + +// Vectors +impl Encodable for [T] { + #[inline] + fn consensus_encode(&self, mut s: S) -> Result { + if any::TypeId::of::() == any::TypeId::of::() { + // SAFETY: checked that T is exactly u8, so &self, of type, &[T], is exactly &[u8] + let u8_slice = unsafe { + std::slice::from_raw_parts(self.as_ptr().cast::(), self.len()) + }; + consensus_encode_with_size(u8_slice, s) + } else { + let mut len = 0; + len += VarInt(self.len() as u64).consensus_encode(&mut s)?; + for c in self { + len += c.consensus_encode(&mut s)?; } + Ok(len) } } } -// special implementations for elements only fields -impl Encodable for Tweak { - fn consensus_encode(&self, e: W) -> Result { - self.as_ref().consensus_encode(e) + +impl Encodable for Vec { + #[inline] + fn consensus_encode(&self, s: S) -> Result { + self[..].consensus_encode(s) } } -impl Decodable for Tweak { - fn consensus_decode(d: D) -> Result { - Ok(Tweak::from_inner(<[u8; 32]>::consensus_decode(d)?)?) +impl Encodable for Box<[T]> { + #[inline] + fn consensus_encode(&self, s: S) -> Result { + self[..].consensus_encode(s) } } -impl Encodable for RangeProof { - fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) +impl Decodable for Vec { + #[inline] + fn consensus_decode(mut d: D) -> Result { + if any::TypeId::of::() == any::TypeId::of::() { + let s = VarInt::consensus_decode(&mut d)?.0 as usize; + if s > MAX_VEC_SIZE { + return Err(self::Error::OversizedVectorAllocation { + requested: s, + max: MAX_VEC_SIZE, + }); + } + let mut v = vec![0; s]; + d.read_slice(&mut v)?; + // SAFETY: checked that T is exactly u8, so v, of type, Vec, is exactly Vec + unsafe { + Ok(std::mem::transmute::, Vec>(v)) + } + } else { + let len = VarInt::consensus_decode(&mut d)?.0; + let byte_size = (len as usize) + .checked_mul(mem::size_of::()) + .ok_or(self::Error::ParseFailed("Invalid length"))?; + if byte_size > MAX_VEC_SIZE { + return Err(self::Error::OversizedVectorAllocation { + requested: byte_size, + max: MAX_VEC_SIZE, + }); + } + let mut ret = Vec::with_capacity(len as usize); + for _ in 0..len { + ret.push(Decodable::consensus_decode(&mut d)?); + } + Ok(ret) + } } } -impl Decodable for RangeProof { +impl Decodable for Box<[T]> { + #[inline] fn consensus_decode(d: D) -> Result { - Ok(RangeProof::from_slice(&>::consensus_decode(d)?)?) + let v = Vec::::consensus_decode(d)?; + Ok(v.into()) } } -impl Encodable for SurjectionProof { +macro_rules! impl_array { + ( $size:literal ) => { + impl Encodable for [u8; $size] { + #[inline] + fn consensus_encode( + &self, + mut w: W, + ) -> core::result::Result { + w.emit_slice(&self[..])?; + Ok($size) + } + } + + impl Decodable for [u8; $size] { + #[inline] + fn consensus_decode(mut r: R) -> core::result::Result { + let mut ret = [0; $size]; + r.read_slice(&mut ret)?; + Ok(ret) + } + } + }; +} +impl_array!(4); +impl_array!(20); +impl_array!(32); +impl_array!(33); + +// special implementations for elements only fields +impl Encodable for Tweak { fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) + self.as_ref().consensus_encode(e) } } -impl Decodable for SurjectionProof { +impl Decodable for Tweak { fn consensus_decode(d: D) -> Result { - Ok(SurjectionProof::from_slice(&>::consensus_decode(d)?)?) + Ok(Tweak::from_inner(<[u8; 32]>::consensus_decode(d)?)?) } } - impl Encodable for sha256::Hash { fn consensus_encode(&self, s: S) -> Result { - self.into_inner().consensus_encode(s) + self.to_byte_array().consensus_encode(s) } } impl Decodable for sha256::Hash { fn consensus_decode(d: D) -> Result { - Ok(Self::from_inner(<::Inner>::consensus_decode(d)?)) + Ok(Self::from_byte_array( + <::Bytes>::consensus_decode(d)?, + )) } } impl Encodable for TapLeafHash { fn consensus_encode(&self, s: S) -> Result { - self.into_inner().consensus_encode(s) + self.to_byte_array().consensus_encode(s) } } impl Decodable for TapLeafHash { fn consensus_decode(d: D) -> Result { - Ok(Self::from_inner(<::Inner>::consensus_decode(d)?)) + Ok(Self::from_byte_array( + <::Bytes>::consensus_decode(d)?, + )) } } - -impl_box_option!(RangeProof); -impl_box_option!(SurjectionProof); diff --git a/src/endian.rs b/src/endian.rs index 6a009281..fec6eed9 100644 --- a/src/endian.rs +++ b/src/endian.rs @@ -20,6 +20,6 @@ mod tests { #[test] fn endianness_test() { - assert_eq!(u32_to_array_le(0xdeadbeef), [0xef, 0xbe, 0xad, 0xde]); + assert_eq!(u32_to_array_le(0xdead_beef), [0xef, 0xbe, 0xad, 0xde]); } } diff --git a/src/error.rs b/src/error.rs index 31d381ad..6957ee8a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,19 +2,15 @@ pub use crate::parse::ParseIntError; -/// Impls std::error::Error for the specified type with appropriate attributes, possibly returning +/// Impls `std::error::Error` for the specified type with appropriate attributes, possibly returning /// source. macro_rules! impl_std_error { // No source available ($type:ty) => { - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for $type {} }; // Struct with $field as source ($type:ty, $field:ident) => { - #[cfg(feature = "std")] - #[cfg_attr(docsrs, doc(cfg(feature = "std")))] impl std::error::Error for $type { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.$field) @@ -29,18 +25,11 @@ pub(crate) use impl_std_error; /// lost for no-std builds. macro_rules! write_err { ($writer:expr, $string:literal $(, $args:expr)*; $source:expr) => { - { - #[cfg(feature = "std")] - { - let _ = &$source; // Prevents clippy warnings. - write!($writer, $string $(, $args)*) - } - #[cfg(not(feature = "std"))] - { - write!($writer, concat!($string, ": {}") $(, $args)*, $source) - } - } - } + { + let _ = &$source; // Prevents clippy warnings. + write!($writer, $string $(, $args)*) + } + } } pub(crate) use write_err; diff --git a/src/ext.rs b/src/ext.rs new file mode 100644 index 00000000..d70e1556 --- /dev/null +++ b/src/ext.rs @@ -0,0 +1,194 @@ +use std::io; + +use crate::encode; + +/// Extensions of `Write` to encode data as per Bitcoin consensus. +pub trait WriteExt: io::Write { + /// Outputs a 64-bit unsigned integer. + fn emit_u64(&mut self, v: u64) -> Result<(), io::Error>; + /// Outputs a 32-bit unsigned integer. + fn emit_u32(&mut self, v: u32) -> Result<(), io::Error>; + /// Outputs a 16-bit unsigned integer. + fn emit_u16(&mut self, v: u16) -> Result<(), io::Error>; + /// Outputs an 8-bit unsigned integer. + fn emit_u8(&mut self, v: u8) -> Result<(), io::Error>; + + /// Outputs a 64-bit signed integer. + fn emit_i64(&mut self, v: i64) -> Result<(), io::Error>; + /// Outputs a 32-bit signed integer. + fn emit_i32(&mut self, v: i32) -> Result<(), io::Error>; + /// Outputs a 16-bit signed integer. + fn emit_i16(&mut self, v: i16) -> Result<(), io::Error>; + /// Outputs an 8-bit signed integer. + fn emit_i8(&mut self, v: i8) -> Result<(), io::Error>; + + /// Outputs a variable sized integer. + fn emit_varint(&mut self, v: u64) -> Result; + + /// Outputs a boolean. + fn emit_bool(&mut self, v: bool) -> Result<(), io::Error>; + + /// Outputs a byte slice. + fn emit_slice(&mut self, v: &[u8]) -> Result; +} + +/// Extensions of `Read` to decode data as per Bitcoin consensus. +pub trait ReadExt: io::Read { + /// Reads a 64-bit unsigned integer. + fn read_u64(&mut self) -> Result; + /// Reads a 32-bit unsigned integer. + fn read_u32(&mut self) -> Result; + /// Reads a 16-bit unsigned integer. + fn read_u16(&mut self) -> Result; + /// Reads an 8-bit unsigned integer. + fn read_u8(&mut self) -> Result; + + /// Reads a 64-bit signed integer. + fn read_i64(&mut self) -> Result; + /// Reads a 32-bit signed integer. + fn read_i32(&mut self) -> Result; + /// Reads a 16-bit signed integer. + fn read_i16(&mut self) -> Result; + /// Reads an 8-bit signed integer. + fn read_i8(&mut self) -> Result; + + /// Reads a variable sized integer. + fn read_varint(&mut self) -> Result; + + /// Reads a boolean. + fn read_bool(&mut self) -> Result; + + /// Reads a byte slice. + fn read_slice(&mut self, slice: &mut [u8]) -> Result<(), encode::Error>; +} + +macro_rules! encoder_fn { + ($name:ident, $val_type:ty) => { + #[inline] + fn $name(&mut self, v: $val_type) -> core::result::Result<(), io::Error> { + self.write_all(&v.to_le_bytes()) + } + }; +} + +macro_rules! decoder_fn { + ($name:ident, $val_type:ty, $byte_len: expr) => { + #[inline] + fn $name(&mut self) -> core::result::Result<$val_type, encode::Error> { + let mut val = [0; $byte_len]; + self.read_exact(&mut val[..]).map_err(encode::Error::Io)?; + Ok(<$val_type>::from_le_bytes(val)) + } + }; +} + +impl WriteExt for W { + encoder_fn!(emit_u64, u64); + encoder_fn!(emit_u32, u32); + encoder_fn!(emit_u16, u16); + encoder_fn!(emit_i64, i64); + encoder_fn!(emit_i32, i32); + encoder_fn!(emit_i16, i16); + + #[inline] + fn emit_i8(&mut self, v: i8) -> Result<(), io::Error> { + self.write_all(&[v as u8]) + } + #[inline] + fn emit_u8(&mut self, v: u8) -> Result<(), io::Error> { + self.write_all(&[v]) + } + #[inline] + fn emit_bool(&mut self, v: bool) -> Result<(), io::Error> { + self.write_all(&[u8::from(v)]) + } + #[inline] + fn emit_slice(&mut self, v: &[u8]) -> Result { + self.write_all(v)?; + Ok(v.len()) + } + #[inline] + fn emit_varint(&mut self, v: u64) -> Result { + match v { + i @ 0..=0xFC => { + self.emit_u8(i as u8)?; + Ok(1) + } + i @ 0xFD..=0xFFFF => { + self.emit_u8(0xFD)?; + self.emit_u16(i as u16)?; + Ok(3) + } + i @ 0x10000..=0xFFFF_FFFF => { + self.emit_u8(0xFE)?; + self.emit_u32(i as u32)?; + Ok(5) + } + i => { + self.emit_u8(0xFF)?; + self.emit_u64(i)?; + Ok(9) + } + } + } +} + +impl ReadExt for R { + decoder_fn!(read_u64, u64, 8); + decoder_fn!(read_u32, u32, 4); + decoder_fn!(read_u16, u16, 2); + decoder_fn!(read_i64, i64, 8); + decoder_fn!(read_i32, i32, 4); + decoder_fn!(read_i16, i16, 2); + + #[inline] + fn read_u8(&mut self) -> Result { + let mut slice = [0u8; 1]; + self.read_exact(&mut slice)?; + Ok(slice[0]) + } + #[inline] + fn read_i8(&mut self) -> Result { + let mut slice = [0u8; 1]; + self.read_exact(&mut slice)?; + Ok(slice[0] as i8) + } + #[inline] + fn read_bool(&mut self) -> Result { + ReadExt::read_i8(self).map(|bit| bit != 0) + } + #[inline] + fn read_slice(&mut self, slice: &mut [u8]) -> Result<(), encode::Error> { + self.read_exact(slice).map_err(encode::Error::Io) + } + #[inline] + fn read_varint(&mut self) -> Result { + match self.read_u8()? { + 0xFF => { + let x = self.read_u64()?; + if x < 0x1_0000_0000 { + Err(encode::Error::NonMinimalVarInt) + } else { + Ok(x) + } + } + 0xFE => { + let x = self.read_u32()?; + if x < 0x10000 { + Err(encode::Error::NonMinimalVarInt) + } else { + Ok(u64::from(x)) + } + } + 0xFD => { + let x = self.read_u16()?; + if x < 0xFD { + Err(encode::Error::NonMinimalVarInt) + } else { + Ok(u64::from(x)) + } + } + n => Ok(u64::from(n)), + } + } +} diff --git a/src/fast_merkle_root.rs b/src/fast_merkle_root.rs index 7cc384c6..7d7c5abe 100644 --- a/src/fast_merkle_root.rs +++ b/src/fast_merkle_root.rs @@ -12,7 +12,7 @@ // If not, see . // -use bitcoin::hashes::{sha256, Hash, HashEngine}; +use crate::hashes::{sha256, HashEngine}; /// Calculate a single sha256 midstate hash of the given left and right leaves. #[inline] @@ -20,15 +20,16 @@ fn sha256midstate(left: &[u8], right: &[u8]) -> sha256::Midstate { let mut engine = sha256::Hash::engine(); engine.input(left); engine.input(right); - engine.midstate() + engine.midstate().expect("hashing exactly 64 bytes") } /// Compute the Merkle root of the give hashes using mid-state only. +/// /// The inputs must be byte slices of length 32. /// Note that the merkle root calculated with this method is not the same as the /// one computed by a normal SHA256(d) merkle root. pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { - let mut result_hash = Default::default(); + let mut result_hash = sha256::Midstate::default(); // Implementation based on ComputeFastMerkleRoot method in Elements Core. if leaves.is_empty() { return result_hash; @@ -44,14 +45,14 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { let mut inner: [sha256::Midstate; 32] = Default::default(); let mut count: u32 = 0; while (count as usize) < leaves.len() { - let mut temp_hash = sha256::Midstate::from_inner(leaves[count as usize]); + let mut temp_hash = sha256::Midstate::new(leaves[count as usize], 64); count += 1; // For each of the lower bits in count that are 0, do 1 step. Each // corresponds to an inner value that existed before processing the // current leaf, and each needs a hash to combine it. let mut level = 0; while count & (1u32 << level) == 0 { - temp_hash = sha256midstate(&inner[level][..], &temp_hash[..]); + temp_hash = sha256midstate(inner[level].as_parts().0, temp_hash.as_parts().0); level += 1; } // Store the resulting hash at inner position level. @@ -80,7 +81,7 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { count += 1 << level; level += 1; while count & (1u32 << level) == 0 { - result_hash = sha256midstate(&inner[level][..], &result_hash[..]); + result_hash = sha256midstate(inner[level].as_parts().0, result_hash.as_parts().0); level += 1; } } @@ -91,11 +92,15 @@ pub fn fast_merkle_root(leaves: &[[u8; 32]]) -> sha256::Midstate { #[cfg(test)] mod tests { use super::fast_merkle_root; - use bitcoin::hashes::hex::FromHex; - use bitcoin::hashes::sha256; #[test] fn test_fast_merkle_root() { + fn decode_hex(hex: &str) -> [u8; 32] { + let mut ret = hex::decode_to_array(hex).unwrap(); + ret.reverse(); + ret + } + // unit test vectors from Elements Core let test_leaves = [ "b66b041650db0f297b53f8d93c0e8706925bf3323f8c59c14a6fac37bfdcd06f", @@ -115,9 +120,9 @@ mod tests { let mut leaves = vec![]; for i in 0..4 { let root = fast_merkle_root(&leaves); - assert_eq!(root, FromHex::from_hex(&test_roots[i]).unwrap(), "root #{}", i); - leaves.push(sha256::Midstate::from_hex(&test_leaves[i]).unwrap().into_inner()); + assert_eq!(root.to_parts().0, decode_hex(test_roots[i]), "root #{i}"); + leaves.push(decode_hex(test_leaves[i])); } - assert_eq!(fast_merkle_root(&leaves), FromHex::from_hex(test_roots[4]).unwrap()); + assert_eq!(fast_merkle_root(&leaves).to_parts().0, decode_hex(test_roots[4])); } } diff --git a/src/genesis.rs b/src/genesis.rs new file mode 100644 index 00000000..d10367eb --- /dev/null +++ b/src/genesis.rs @@ -0,0 +1,268 @@ +// Rust Elements Library +// Written by +// The Elements developers +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to +// the public domain worldwide. This software is distributed without +// any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. +// If not, see . +// + +//! Helpers to calculate the genesis block for a given network. + +use bitcoin::secp256k1::impl_array_newtype; +use crate::hashes::{sha256, HashEngine}; +use crate::opcodes::all::OP_RETURN; +use crate::opcodes::OP_TRUE; +use crate::{confidential, script, AssetId, Block, BlockExtData, BlockHash, BlockHeader, LockTime, Script, Sequence, Transaction, TxIn, TxInWitness, TxOut, TxOutWitness}; +use crate::{AssetBlindingNonce, AssetEntropy, AssetIssuance, ContractHash, OutPoint, Txid}; +use crate::confidential::Nonce; + +/// Parameters that influence chain consensus. The contents of the genesis block for a given network +/// are defined by these values. +#[derive(Clone, Debug)] +pub struct NetworkParams { + /// The network identifier string that elementsd accepts as the `chain` config argument + pub network_id: String, + /// This network's Fedpeg script + pub fedpeg_script: Script, + /// This network's `sign_block_script` + pub sign_block_script: Script, + /// How many free coins are present in this network + pub initial_free_coins: u64, +} + +impl NetworkParams { + /// New custom network params + pub fn new( + network_id: String, + fedpeg_script: Script, + sign_block_script: Script, + initial_free_coins: u64, + ) -> NetworkParams { + NetworkParams { + network_id, + fedpeg_script, + sign_block_script, + initial_free_coins, + } + } + + /// Network params for Liquid mainnet + pub fn liquidv1() -> Self { + NetworkParams { + network_id: "liquidv1".to_string(), + // Can be verified at https://github.com/ElementsProject/elements/blob/27c2fb6b7de404908f9ef2eb5c98c9989d1ab8e4/src/chainparams.cpp#L1243 + fedpeg_script: Script::from_hex_no_prefix("745c87635b21020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b678172612102675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af992102896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d4821029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c2102a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc40102102f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf072103079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b2103111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2210318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa08401742103230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de121035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a62103bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c2103cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d175462103d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d4248282103ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a5f6702c00fb275522103aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79210291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807210386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb5368ae").expect("constant fedpeg script parse"), + // Can be verified at https://github.com/ElementsProject/elements/blob/27c2fb6b7de404908f9ef2eb5c98c9989d1ab8e4/src/chainparams.cpp#L1193 + sign_block_script: Script::from_hex_no_prefix("5b21026a2a106ec32c8a1e8052e5d02a7b0a150423dbd9b116fc48d46630ff6e6a05b92102791646a8b49c2740352b4495c118d876347bf47d0551c01c4332fdc2df526f1a2102888bda53a424466b0451627df22090143bbf7c060e9eacb1e38426f6b07f2ae12102aee8967150dee220f613de3b239320355a498808084a93eaf39a34dcd62024852102d46e9259d0a0bb2bcbc461a3e68f34adca27b8d08fbe985853992b4b104e27412102e9944e35e5750ab621e098145b8e6cf373c273b7c04747d1aa020be0af40ccd62102f9a9d4b10a6d6c56d8c955c547330c589bb45e774551d46d415e51cd9ad5116321033b421566c124dfde4db9defe4084b7aa4e7f36744758d92806b8f72c2e943309210353dcc6b4cf6ad28aceb7f7b2db92a4bf07ac42d357adf756f3eca790664314b621037f55980af0455e4fb55aad9b85a55068bb6dc4740ea87276dc693f4598db45fa210384001daa88dabd23db878dbb1ce5b4c2a5fa72c3113e3514bf602325d0c37b8e21039056d089f2fe72dbc0a14780b4635b0dc8a1b40b7a59106325dd1bc45cc70493210397ab8ea7b0bf85bc7fc56bb27bf85e75502e94e76a6781c409f3f2ec3d1122192103b00e3b5b77884bf3cae204c4b4eac003601da75f96982ffcb3dcb29c5ee419b92103c1f3c0874cfe34b8131af34699589aacec4093399739ae352e8a46f80a6f68375fae").expect("constant sign_block_script parse"), + initial_free_coins: 0, + } + } + + /// Network params for Liquid testnet + pub fn liquidtestnet() -> Self { + NetworkParams { + network_id: "liquidtestnet".to_string(), + fedpeg_script: script::Builder::new().push_opcode(OP_TRUE).into_script(), + // Can be verified at https://github.com/ElementsProject/elements/blob/27c2fb6b7de404908f9ef2eb5c98c9989d1ab8e4/src/chainparams.cpp#L1108 + sign_block_script: Script::from_hex_no_prefix("51210217e403ddb181872c32a0cd468c710040b2f53d8cac69f18dad07985ee37e9a7151ae").expect("constant sign_block_script parse"), + initial_free_coins: 2_100_000_000_000_000, + } + } + + /// Network params for a custom Elements network with defaults + pub fn custom_network(network_id: String, fedpeg_script: Option