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 be46460b..4dc44501 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -1,39 +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: stable - env: - DO_FUZZ: true - DO_INTEGRATION: true - - rust: beta - env: - DUMMY: true - - rust: nightly - env: - DUMMY: true - - rust: 1.36.0 - 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 new file mode 100644 index 00000000..ff4f1a45 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,160 @@ +# 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 + +- Add `PeginData::parse_tx` +- Add `PeginData::parse_merkle_proof` + +# 0.21.0 - 2022-10-19 + +- Copy `Sequence` and `LockTime` structures from rust-bitcoin 0.29.1 +- Add `Txin::pegin_prevout` method which returns a `bitcoin::Outpoint`; modify `PeginData::from_pegin_witness` to take a bitcoin outpoint + +# 0.20.0 - 2022-06-10 + +- Remove has_issuance field in TxIn, calculate it directly to avoid the user provide it. +- Blinding API cleanup into smaller chunks +- Issuance: add support for blinding, and surjection proof verification +- Pset: allow inserting inputs/outputs at specified position, fix Tweak serde and key bug +- Add liquid test parameters +- the feature "serde-feature" is now renamed to just "serde" +- update MSRV to 1.41.1 +- breaking change in serde in how the Nonce is serialized +- `Block`, `BlockHeader`, `PeginData`, `PegoutData` loose the Default impl +- update rust-bitcoin to 0.29.1 +- update secp256k1-zkp to 0.7.0 +- update bitcoin_hashes to 0.11.0 + +# 0.19.2 - 2022-06-16 + +- revert dynafed field `fedpeg_program` back to `bitcoin::Script` + +# 0.19.1 - 2022-06-10 + +- revert use of `io::BufRead` back to `io::Read` in `ConsensusEncodable` trait +- deprecate `Block::get_size` in favor of new `Block::size` +- deprecate `Block::get_weight` in favor of new `Block::weight` +- deprecate `Transaction::get_size` in favor of new `Transaction::size` +- deprecate `Transaction::get_weight` in favor of new `Transaction::weight` +- implement `Default` on `PartiallySignedTransaction`, `TxIn` + +# 0.19 - 2022-04-30 "The Taproot Release" + +- Taproot support for complex taptrees compatible with elements taproot signature. +- Taproot psbt support with BIP 371 +hash. Refer to spec [here](https://github.com/ElementsProject/elements/blob/master/doc/taproot-sighash.mediawiki) +- Support for new tapscript transaction introspection opcodes as per the [spec](https://github.com/ElementsProject/elements/blob/master/doc/tapscript_opcodes.md). +- Works with bitcoin 0.28 key types. 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 a1576f41..8abc3df0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,51 +1,56 @@ [package] name = "elements" -version = "0.18.0" +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" 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" ] - -json-contract = [ "serde_json" ] -"serde-feature" = [ - "bitcoin/use-serde", - "secp256k1-zkp/use-serde", - "serde" +default = ["json-contract"] + +json-contract = ["serde_json"] +"serde" = [ + "dep:serde", + "bitcoin/serde", + "bitcoin/serde", + "hashes/serde", + "secp256k1-zkp/serde", ] -"fuzztarget" = [] +base64 = ["bitcoin/base64"] [dependencies] -bitcoin = "0.27" -secp256k1-zkp = { version = "0.4.0", features = [ "global-context", "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.10.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" } -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.3.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.6.5" -serde_test = "1.0" +rand = "0.8" +rand_chacha = "0.3" +serde_test = "1.0.19" serde_json = "1.0" -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" @@ -55,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 b28a1879..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.36**. +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 1e187873..00000000 --- a/contrib/test.sh +++ /dev/null @@ -1,43 +0,0 @@ -#!/bin/sh -ex - -FEATURES="serde-feature" - -# 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/elementsd-tests/src/taproot.rs b/elementsd-tests/src/taproot.rs new file mode 100644 index 00000000..364c7230 --- /dev/null +++ b/elementsd-tests/src/taproot.rs @@ -0,0 +1,308 @@ +extern crate elements; + +extern crate elementsd; +extern crate rand; + +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::script::Builder; +use elements::secp256k1_zkp; +use elements::sighash::{self, SighashCache}; +use elements::taproot::{LeafVersion, TapTweakHash, TaprootBuilder, TaprootSpendInfo, TapLeafHash}; +use elements::OutPoint; +use elements::{ + confidential, opcodes, AssetIssuance, BlockHash, LockTime, SchnorrSig, SchnorrSighashType, Script, + Sequence, TxInWitness, TxOut, Txid, +}; +use elements::{AddressParams, Transaction, TxIn, TxOutSecrets}; +use elementsd::ElementsD; +use rand::{rngs, thread_rng}; +use secp256k1_zkp::Secp256k1; +use std::str::FromStr; + +static PARAMS: AddressParams = AddressParams::ELEMENTS; + +fn gen_keypair( + secp: &secp256k1_zkp::Secp256k1, + rng: &mut rngs::ThreadRng, +) -> (XOnlyPublicKey, Keypair) { + let keypair = Keypair::new(secp, rng); + let (pk, _) = XOnlyPublicKey::from_keypair(&keypair); + (pk, keypair) +} + +// Spend data for txout with 2 leaves +#[derive(Debug)] +struct TapTxOutData { + _blind_sk: Option, + _blind_pk: Option, + leaf1_keypair: Keypair, + _leaf1_pk: XOnlyPublicKey, + leaf1_script: Script, + internal_keypair: Keypair, + internal_pk: XOnlyPublicKey, + spend_info: TaprootSpendInfo, + utxo: TxOut, + prevout: OutPoint, + txout_secrets: TxOutSecrets, +} + +fn funded_tap_txout( + elementsd: &ElementsD, + secp: &secp256k1_zkp::Secp256k1, + blind: bool, +) -> TapTxOutData { + // Create a script pub key with a given spendinfo + let builder = TaprootBuilder::new(); + let leaf1_script_builder = Builder::new(); + let leaf2_script_builder = Builder::new(); + + let (leaf1_pk, leaf1_keypair) = gen_keypair(secp, &mut thread_rng()); + let (leaf2_pk, _leaf2_keypair) = gen_keypair(secp, &mut thread_rng()); + let (internal_pk, internal_keypair) = gen_keypair(secp, &mut thread_rng()); + + 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); + (Some(sk), Some(pk)) + } else { + (None, None) + }; + + let leaf1_script = leaf1_script_builder + .push_slice(&leaf1_pk.serialize()) + .push_opcode(opcodes::all::OP_CHECKSIG) + .into_script(); + let leaf2_script = leaf2_script_builder + .push_slice(&leaf2_pk.serialize()) + .push_opcode(opcodes::all::OP_CHECKSIG) + .into_script(); + #[rustfmt::skip] + let spend_info = builder + .add_leaf(1, leaf1_script.clone()).unwrap() + .add_leaf(1, leaf2_script).unwrap() + .finalize(secp, internal_pk).unwrap(); + + let addr = elements::Address::p2tr( + secp, + spend_info.internal_key(), + spend_info.merkle_root(), + blind_pk, + &PARAMS, + ); + let amt = Amount::from_sat(1_000_000); + let txid_hex = elementsd.send_to_address(&addr.to_string(), &amt.to_btc().to_string()); + elementsd.generate(1); + let tx_hex = elementsd.get_transaction(&txid_hex); + + let tx = deserialize::(&hex::decode_to_vec(&tx_hex).unwrap()).unwrap(); + + let mut outpoint: Option = None; + for (i, out) in tx.output.iter().enumerate() { + if addr.script_pubkey() == out.script_pubkey { + outpoint = Some(OutPoint::new(tx.txid(), i as u32)); + break; + } + } + + let prevout = outpoint.expect("Outpoint must exist in tx"); + + // If txout was blinded, try to unblind it + let out = &tx.output[prevout.vout as usize]; + let txout_secrets = if blind { + out.unblind(secp, blind_sk.unwrap()).unwrap() + } else { + TxOutSecrets { + asset: out.asset.explicit().unwrap(), + asset_bf: AssetBlindingFactor::zero(), + value: out.value.explicit().unwrap(), + value_bf: ValueBlindingFactor::zero(), + } + }; + TapTxOutData { + _blind_sk: blind_sk, + _blind_pk: blind_pk, + leaf1_keypair, + _leaf1_pk: leaf1_pk, + leaf1_script, + internal_keypair, + internal_pk, + spend_info, + utxo: tx.output[prevout.vout as usize].clone(), + prevout, + txout_secrets, + } +} + +fn taproot_spend_test( + elementsd: &ElementsD, + secp: &Secp256k1, + genesis_hash: BlockHash, + sighash_ty: SchnorrSighashType, + blind_prevout: bool, + blind_tx: bool, + key_spend: bool, +) { + 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: LockTime::ZERO, + input: vec![], + output: vec![], + }; + let inp = TxIn { + previous_output: test_data.prevout, + is_pegin: false, + script_sig: Script::new(), + sequence: Sequence::MAX, + asset_issuance: AssetIssuance::default(), + witness: TxInWitness::default(), + }; + tx.input.push(inp); + // create two outputs + // 1) Same as the one we are spending but with lesser amount as some go to fees + let fees = 1_000; // hardcoded 1000 sat fees + let btc_asset = test_data.txout_secrets.asset; + let dest_amt = test_data.txout_secrets.value; + let mut out1 = test_data.utxo.clone(); + out1.value = confidential::Value::Explicit(dest_amt - fees); + out1.asset = confidential::Asset::Explicit(btc_asset); + let fees_out = TxOut::new_fee(fees, btc_asset); + tx.output.push(out1); + tx.output.push(fees_out); + + 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); + tx.output[0].nonce = confidential::Nonce::Confidential(pk); + tx.blind( + &mut thread_rng(), + secp, + &[test_data.txout_secrets], + false + ) + .unwrap(); + } + + let mut cache = SighashCache::new(&tx); + + if key_spend { + // test key spend + let sighash_msg = cache + .taproot_sighash( + 0, // input index + &sighash::Prevouts::All(&[test_data.utxo]), + None, // annex + None, // none + sighash_ty, // sighash_ty + genesis_hash, + ) + .unwrap(); + + let output_keypair = test_data.internal_keypair; // type is copy + let tweak = TapTweakHash::from_key_and_tweak( + test_data.internal_pk, + test_data.spend_info.merkle_root(), + ); + 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_digest(sighash_msg.to_byte_array()), + &output_keypair.add_xonly_tweak(secp, &tweak).unwrap(), + ); + + let schnorr_sig = SchnorrSig { + sig, + hash_ty: sighash_ty, + }; + + tx.input[0].witness.script_witness.push(schnorr_sig.to_vec()); + } else { + // script spend + // try spending using leaf1 + let sighash_msg = cache + .taproot_script_spend_signature_hash( + 0, // input index + &sighash::Prevouts::All(&[test_data.utxo]), + TapLeafHash::from(sighash::ScriptPath::with_defaults(&test_data.leaf1_script)), + sighash_ty, // sighash_ty + genesis_hash, + ) + .unwrap(); + + let sig = secp.sign_schnorr( + &secp256k1_zkp::Message::from_digest(sighash_msg.to_byte_array()), + &test_data.leaf1_keypair, + ); + + let script_ver = (test_data.leaf1_script, LeafVersion::default()); + let ctrl_block = test_data.spend_info.control_block(&script_ver).unwrap(); + + let schnorr_sig = SchnorrSig { + sig, + hash_ty: sighash_ty, + }; + + 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); + assert!(elementsd.test_mempool_accept(&tx_hex)); + let tx_str = elementsd.send_raw_transaction(&tx_hex); + assert!(Txid::from_str(&tx_str).is_ok()); +} + +#[test] +fn taproot_tests() { + let (elementsd, _bitcoind) = setup(false); + let secp = secp256k1_zkp::Secp256k1::new(); + + // lookup genesis hash required for sighash computation + let genesis_hash_str = elementsd.get_block_hash(0); + 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, + ]; + + for &conf_prevout in &[true, false] { + // whether the input is blinded + 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 { + taproot_spend_test( + &elementsd, + &secp, + genesis_hash, + sighash_ty, + conf_prevout, + blind, + script_spend, + ); + } + } + } + } +} + diff --git a/examples/pset_blind_coinjoin.rs b/examples/pset_blind_coinjoin.rs index 5cf6572f..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() - .as_sat(), - asset: AssetId::from_hex(&v["asset"].as_str().unwrap()).unwrap(), + .to_sat(), + 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, )); @@ -129,8 +131,8 @@ fn test_data() -> HashMap { ); tests.insert(String::from("A_pset_unblinded") , String::from("70736574ff01020402000000010401010105010301fb04020000000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001030820a107000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc0470736574080400000000000103086ce2ad0d0000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc047073657408040000000000010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b201040000")); tests.insert(String::from("pset_coinjoined_unblinded") , String::from("70736574ff01020402000000010401020105010501fb04020000000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001017a0ac55f449ddb6853f2508766d5afb9f3b45e41a8ef5368cad75fb88e5e249395d1097d88c92ca814a207f73441c56cee943f0bb2556da194c14a4b912b078c2238ae025341cb5e4e2d8cb69e694cb20e5ea4cc8ddf2801180096fd071addfcd8bc4445160014011d384302576b408aa3686db874e2b17cc2b01b010e207aff956e6c2a379543b1ec82b06958f062bd9f29aa1ac1c98d1f6398b65a8555010f04010000000001030820a107000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc0470736574080400000000000103086ce2ad0d0000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc047073657408040000000000010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000001030840420f000000000007fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f010422002037831b3ee29fc96f8e61ccb98fbe2dcb03e189dd29cfecc691b5a7442d8548e807fc0470736574062103d559d2a5a4180f418a69c4bed5508971cda9313722fff71e053d3d82fee9d7bd07fc047073657408040100000000010308c0878b3b0000000007fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f0104220020e7da55d19cc85b0420c539a90b667d4d85f59ee0ed417493a947c3a2256cc0aa07fc04707365740621029e5980b4f9b9a9fd568c1c4b48631a800c310405ae8b2ac41ddaf87add3062f107fc047073657408040100000000")); - tests.insert(String::from("pset_coinjoined_B_blinded") , String::from("70736574ff01020402000000010401020105010501fb040200000027fc0470736574000f56f3a1f798b585d605591085469b0e3d0c3f46c3bdf230773056302acccdb8000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001017a0ac55f449ddb6853f2508766d5afb9f3b45e41a8ef5368cad75fb88e5e249395d1097d88c92ca814a207f73441c56cee943f0bb2556da194c14a4b912b078c2238ae025341cb5e4e2d8cb69e694cb20e5ea4cc8ddf2801180096fd071addfcd8bc4445160014011d384302576b408aa3686db874e2b17cc2b01b010e207aff956e6c2a379543b1ec82b06958f062bd9f29aa1ac1c98d1f6398b65a8555010f04010000000001030820a107000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc0470736574080400000000000103086ce2ad0d0000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc047073657408040000000000010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000001030840420f000000000007fc0470736574012109509217e1b071fbec4cb5fa3cdfb5c860197b889067967e06503b2391f40ae3ad07fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210af17cabef5f73bfab19c674c1bcb64bde67017100e7d2b1d3b877c3503aff67c7010422002037831b3ee29fc96f8e61ccb98fbe2dcb03e189dd29cfecc691b5a7442d8548e807fc047073657404fd4e10603300000000000000014fb15d00b63989058441e4842b16c6bb22c9a696efbd85b29fab7a9b3d53e8aa8465745f9724a942187af247fe6cfb7f3022b6e71316143eed2856014c7be59f599d4073a2ec1865d2845e012bbbf7383866639d3db07fb201862a50769bc3b54842151e02be410ce11b7ac9c0274a0cdcfa576c371c34a0c5e24bdd28f0657dbd7d21a927329868e30bbcbb23efd6d4bfb11d7ad35469ee052372534201265908c920bc7d914c7bba26e7375c2074993118679e1e0fe984b6f4146df255d8894140156e507c10543e641470743669c9f8969d8f38beb2d31b0619cbb531b54e3982d89e9bf66016ede12a9d7393853dfb607bcbfaa232371fc2c9730e834b669ec63d14d57e819abcb4fd2d726d0152ef897ca78a5d6016ff98de8a34d12ab84e5d0005d64548d63ed26747f4d2413215cba58504dcac6f35c57d9a5e0e7819f6cb22c76b1fe48082726de5f178ab9b7a0dcf749a559f0911cd1b33b71ec3153b634540c8587915605c94d852d744270a2068825be5b08cd968d41867c98fcdc9ea6a23eceefbdd1e15e3a7bacce90624a1e95dfd8b11b331054d7f74c57088c33bab33fe5511dd1561e086ff8f3e4a1fb355772bd406ea1d86242a796924821ce70c831ad4a1a057a173bd9d82bedb3dee0dd565c542dce7ea55947c0c48a1d10cc87c9b0d8e41bfb0f85f95c3abfe329cf127ad76c53eeaeee336086b056318d45c91555873e7daa6d88f13856b6fa0629991f86ce192a10acdcb370f33d98e5d810ca81270542a10b71312f3d179f7051e982f62aff114f15d107ea9082b577a0afeaa56274546c058ed7f345f21222c4dea005bf39ef6d458740dcab3dbbca367eb42a75c3b632d3f3e2b6fd25edfd496b966f2d22912bade07416de9c44168928e1e8ba694a09ba078cad5301cf4f6f675638c6e281e43b5f43ea9ecd42ecc3dec9bd9037bd67492cf6999dc4ba074a7d19e3c554464721aac74934f0d4631b9f1d32367f271249e8b1684ead35f9cac9bd121a13261e58aa19e65df246425a279297c93548de0893f23ec291e3240e8f9c30b80777c53f066a29d6581e0b3413246a8e5bb3eb1f99cce0e859d5366864ab9e8234b7ab311663967fdc179ad9de44d8f35b6d146a8301a7f7dadd0fa79887907abc6f28de2406d83ee50f3c55fa49369e4c178505b4c85f975e3e684463062d2e3bd8493dd28bcf693453d22b29843c9c91a2c32ef7e4719b0e073eaf129be36d575520d9e37ac1b81a1f96ffda0e7748380ee0b3399c63ef23cde17aef220f142ebe2552c425b2d39e206a42829f6d1d31b1b393bb31eb3f9835d5e1108b0a8fb6788b26abfc93ff3f368e321c62e205028038625f3ebd51872e75a528af725c55b285ec840a7bf2bff4a9f52d076310a027baa0415c032c2ff246d7942dd08019a448b1834f1d36857c0a3af27c782bc06ae568643b320ce292b4c0f6bbf44fc9760ca1b9f4e36b52cc115367fcfdd2777055dc6e98e42cef8414e12a7d300e6b60b2e3d4d244e558461536f748319d6bf1bc0f605bf7eb06d81e5768f46c05c4b666d45705f53d792ac0086c990d103d1a8e5b405347851a072c287fe9333be36cbe92ac9e16cdc08f0e7b65da846981e00256fe627d3fd74b0da87a58f1daabe54fb15389e44e8d79bf5befe1f319c882d33d1d34db9d812fed2b172326175fa082f1359599c7bb2fb9a3b81a1ecb9f9f81dfd08e26c28ac46a6a47431d5806b9e001691da3a7bccf0521051473e57597723733ccc14621e2c7fb5a0562a4804342629f9866ad71dec71e34b8f618c6b6c12fd5c227edfd3ebc77f62c8f6d82f039f95547db5226e4073d4fc8d91e4fdea73ea398763edd21e502fe6b2c01ddad50d3ef35ba50adf34a6f232d98de5564f7ef0bc16b65beeab8da4c2181205563532042e859b257ccd500c9dbfc26f9a4ca20a10ee1b94e3cfff12680f78ce7cc134a4f5cf2efcd88b99b8fcf52e2cfae220af7a7ae84362220c2e6bc3bf5e0db4d5cde8cc81948f07941a67348da2ad2c28b790551241bf6bde70f4bbd366cbbe8e0ce1a3f72e2d06224d11e41bafdf24f0f390ae129d2cf2e5228337390cb1571b747532b76aa1a6934fb680ccdf24da86cdc22a9c5db3be198a58913746d1701c9c19224da613598403d56aaf4753c8d0c74ce502034f72b81ed5d3aaeff0ee3ea0b36b7abc30a5876b3bc0ca0d8c7705fb2533b1f934cad358fb473d71fa4008e1c65f070e535a2b9ec54b123123f09759c9a16a7b2c0be92d5112b77954d89dfd0a23882250228a83435365587c49ca0c389210b39934c212ead258cddc4226a820f949f698a18388961df4246f47d12b164f298895d295cf3041aef15cf4c9e40e6a737ddff8a95bd5baf6a19fc254d4fd502d074df2035eadbe0b257ea3bce05945143decffde78ea44297a7966abae6504f760d6f1dcefc239ff98cc84cd1fbc8c9df2d1338847ada737eab43912688a127f1ae0d667beb5518974c77d35b96f00d0bb7e60d1bebc35afb99d786e764af0abe6c83b061fe09e059ceae787473771ab6c7892dc4506d25f46b3e721401d573abeb5a915452797c587de7ef97b68b330baa7b2779fa15bd64a6b9a51afa61dc579cbbb55d4ea6adc7eebbf1f0bd88162163cc467b5b7ff637dbebacf873bf9631e1311deaae65ac2f14d24c62b04d8a0c3cd6aba6d69bd65975a895b71055b98bdc560e327cce9eb05944a3251cdfdaed48fbe5d1183ba00699080b7fe549a4ace7f3091558cfb3fc20f8b444823abb0243100cca662b5fe049b89b1f1708b2ece7fced45358a8eb4fc2151b8d57099593c22a0c31d695369214fd2134df49179a31f82565bcda5711f36fb8ec735e41c42076ee6b018e10803e1c74807e4751d9510dcb43a8cfcd75341568e216e7fd3f6eb2872b9dc03a2a05fd4d7ac7fbe7e638e9119c7eb13544cfea6887c0fa0bfc9339658b66130079ea4c950a8294d51f6af904a371c0c22a1f1a7599d4ea540af8ff2c6249ef9a44da29439ff23187182a184663320e4b2e0ebef0c3453f28758dd258b1e23133b666cbbc30f85fd2cc64626e26a30125cce0d8e1f87cea15ff97b5c2f07bcbd4c9d47b80a80121c65df06186fecbf5340c915fc8d2e7e36ca1389d825f105a555e171f596e5d1e303a4c5224cfb877a48096f63c31be2af62b75d73300a31a6c30f64b0bf140af539c637cfaa566d97aac9c84f234e737fe9066170f75b53c461a7cd68ede00a9191decd786c6424f10036f2746a12ba754ee2d89a1f52568ba3ae87381f44919fce988b1cf4b2d7ba62e0b285768a42ea948bba1eb0244c098fe7417d9a5783e560359039080f888bef6063da915b2a1ce8150deca746dbd185cb38b6614e5a5669845a42db9593a6738fa8607f5a2153da62090550905623df524542ddbf104994c946204f395d5ba61bd5a436cf68bcb80a070abb7d792a3fdd2d0f6a6ff87ccd4e73aff3990d3ecd0d1bdeabd4dfb4900c4122d985e33fff0b4d208d842cdbe951f1b6f4362de838c7acd4cd2ba5c2a8e17d67b5220d9241b0851df436916a2a725da8b1c76ae745d0fff3da0c1b069f9163676a78e8aaaeea7419ed7c7e92953028797639eeb4dc25f36f94356e49af627e4945a169fa4a668cb888c106fc5c25b53648acd976caa28c8e8a8412326397650e4a5aebbdfda667184cb7f3b7d2b832e89cbdbf2e07d59d386a9eb62f145962a5e28b02cd443c8adc4f3f8a033e449451e1bd02872da37f66ada25a8985599a623cf12f50448c652a47dab1a3315a596f2c7ed3a60abd1ef19da223043159ec8e4b26af5885dc625b68397b55e5dcd7d9ad750ad35954efc0f86237b7b28dec267046eb921626f6df35323a4420760b98a43a361dea3c3c879ed787114fd0c58ac5eed564cfaee4074d5980a966c6863249ea7bd5a2bf272a4558344a01612347a2588e1501a4848c4e0f42d02f11fe40ab00acbd4f51d4717ac2c1493d860b7805631ea64b4fc4b2a127ce9d960ba435b86dc3b9cd0fbadc736245012c47e1273650c85bff9a464c3c0bc21dc4820b1a5d551a8f725df754fe7205245b4561ccca3bc68773edb377d507f2f84dda7e83bff96e2b0df81a1c972d56f4b914ad8cf9b76e47b47816230c6da79cae4018557495456db3b6be2f30a4040ef3d1bc24aab6a2cfcd3104ea3cdbc941ba91fbc9481c26cf9a8f88e0bdc92a6d705fd5dac916e293b408592ca682c292d2ff86fda40c63906e10abcc7fe0dbfaf03d93b3116a80e46b9523a3996085673964c9e206d839c9eaca4a8eb249be46dbfa237c8e54b2cfb5fbff8bc07863e1636192baa381a94067a07cc3e7ec645da22658af620a84e32276e2ce71eeabf3c4ae39c7108ee533fd070acf4616424676293f2b5377874ac6eacf474066dc7b19c06d217d7bb68bb7323d0e05d6bd8c437a0bdd3785485b513c5bee4503235a688f5ab27c330248678be9785b02e2d6c04a7601fd17ee2387e385a624a5d047671de8f439dcaa7e9bbb60a8b1ca23b6ad378b55b65fb64f1b4a2df25a073a23e08cb49f58e1f528d19c2850ffe29deee6376568af2a6ec919413bafef949c0bb4d5d7650df62dc6bc042ca7ffa4d7e064ecce7b7cbaa1b3f519080b608b33d9ff9a0a552e89655bafd7151a8cdcbc517708896932554a85dab5569defd15b593d4dd7c5d086c9ae3b719c36fd0a4d44c5971848eb7dced3b1b96ae9956fe0c177f649884d0335625e8ebffb3bead27d622a154d8a596e7a94537eae5cb487edcffbae5a48dbddb7e909e6b24b0aa14319eee52f51d2ef8c7ef1abf69ea5f023269b6562c6927e3606ef29d751615f51796ab9bf36a1d5b0e245b6ce01df75d3af3d3d5af57691bb7b7089718df8c3ddb9593b0d28d8c4f85ca44a95c9f4a707a500e779a4e5eed45a0769818ba60ad1e7d89be2bb584b2a50c3fadada806a685a5b701b7c1b7810b88e44efcf762d7f45972bc1cacf4fda5b61525d916e3e0feaf525cdc1276a9ae187a1b0234d98b835f4c79aef141160e58bf8f5cca950dbc372a2f9ebbe2e25e97af0c39440303771604924d00aeac4bdb4003bda5a36f4ef911cab7f0018bfd07e09cab255b1c74b144174ba9f5c5b307c7c4be504ad0cd1d51fedd9de1e0949ddde6fbd84f9b60319fdbe5c6d6c7d19677bfb257765d4cc471d567c2dac942bbae5756bdf6461f197a0e8ee8221fa438f9de69c64d896f2ed5e28854c03d8e816ff76d00453c7a5069c6404655463694e0b1f87579382eb95b60bafca557f5a14da4518a3997d76dee43b573449fd12cd8d2893bc6679e53fb5154256f792b4f98e712e414c6761db503689d1d9459b8d5899b656082afe0691a7408d5ed7518a29833e3376941a5ca0c70f611fb7b804c55b61db4493c91f8d1dd703d06996688843ff9112c99c66b05e52921f0e2a46ab1531e5e55f46143446eca09bf7a58b72f5caed68ddcdacb7f410aab0724e7037f2df6ffe7449092eb767ee4398f3abe6a48706349f1708b18a99f1c0ae82465dae5681f529d8e839f782c54e216f24eecfa79c68200d049b132644fca362b6a0e800926c058569561da9b75d18e067f79b9d7e39c1236e818902875ce2a515be8bcf5779229f87ca9116525bc9b6b2b759120197008be7e61b1553263a54a9d06928acf54479ffa2789e3606f7307aa5d9689ba497c2aa42995caae76caea723e18edc3d1fc6ded3a3e0c577f67fdadb943bb2b0d9d3d27b54a21125cf391c96e3e49d6279547c1954cfbe510bfe405dd3ec1729c2c74fb955923df0a8d5b3c372a8d90d0520a20be2bfd6949b3bd96b1ebce44db4acd8a188c361c07fc04707365740563020003b281b811efef8fdab596b2f9f5f9551c18e3abb759de33e0918371cb66dba3bc47d87cdde6c9702f0f0376109b3453dfa4665bd3f34fb1781c4fb87024eac601b4561d5b1d31ad1a0d43b9c34ff29c861c25da23928a05ca39033cfe6f03051e07fc0470736574062103d559d2a5a4180f418a69c4bed5508971cda9313722fff71e053d3d82fee9d7bd07fc04707365740721027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac07fc047073657408040100000007fc047073657409492000000000000f4240ebd01380d359eac86844d694bb283ffa28201397c492fb532ded8c459440f52de3f8424ef929f29132cdeb4f6d5df6089ec15c4fbaba614d4fcdbef511ae320507fc047073657410430100015227273ad2e3e885820f517805e3f88f2412f610c71515496d43ef5b57b3bcdc438c6430eea4858f66ec5547b9c4e196d2574423df87a9aa58cf393837f75e2a00010308c0878b3b0000000007fc04707365740121080d40d0e9edf648b2dc42a95dc78fb9914ea1caa7f5527dfa3bae14e1b596d51b07fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210b3c73bf89a27c01a24c6c1ef51dd63d8dcd83d589f7c2c5fd6557c365fdf0101c0104220020e7da55d19cc85b0420c539a90b667d4d85f59ee0ed417493a947c3a2256cc0aa07fc047073657404fd4e10603300000000000000012acc3a015e9d9f4debaea69c0602d611e4b10d80e7d8a80cf67061b3e07ca1841709d0eca32a81a9c0fd15bd122851d8b72cce053501e95944345c243ce86613a52c6187bdb1d1365aaa3a1dac0e91168ab05d392cc7ab56eff07afbfc6a1bfec8ade02f723dbd7f83809371994df8b49f6bcb5a92f4481991b8918ec599f32fd2a5d15c79c3cdc8e4e109c32304f255bf227b849394f368900f3af5edc1ed9366a1210d5fbf7200fc21343f547f2065e33493bd98c241b1005a55a091e2b57c0ea4e0874092b59c1e39d490e923821689f6119874f1073f7f4a615573bff6612161327b52b961113271ec5e2a76f1efe539ec450bf3915f961c4bc29cb352a9eeb6edce8a3aa869dbd1110b94ef9c7efafa601164fab10771ba30fd7c8e1558e6cd47a08eb5fa1aa62c2b805a65aa0a40268d01ea709d8cb8a62558da3595220908e8318768c6df25ce0478502cef56d2b06911aa5054013a5d1097ee01dd25aa81c8db1c0f13a9f90fddd0c2d558a7e2021f0cef8059265baea2375694f040fe122e8caa55c2f2904114094ebf219597096d613e3b338a44a834a97d5e65e111001835f44aa7a5ab911e6e35cf9b893c955a648c72f32ed033c1c74bcacffa013e5404d478b61ec0c6590233b2469fa2ec5ba9b8db23b594ef8f918cacf1039804afd2e595fb0cb20fc0ce1e190ff42e93e3128447d0b68fc515ec29a3e0fcd6e9b19f3c3b376dc78095cccb7f198ee76373498327fb3f381b46a87023f54952a0782a7fd31c2c44cbef1e6e90e80d83d2d6cbeb33538f74b8a25ab754e400e52649b6c5c8ba9a2621a44dea5b4df8811525bd3acb2ea229c6f8b5eccf431bd68c4badb7590fcf2092332aa1fee30b2395fefd0f3cdfe9a40c0e94afff8bc14dd51d8b8f001c0e5bd82fa1899b07d2690d180dfc7d5983e1b530ff7f6024db1dcd0f98eff1a12517951c0e0deabf1867cdc9532ace02fde535803df81d2aad3685c74033309717173efa32a5a40c330b79488a4974033f16e4b8a3e040b25b5337ae4cd0ad98ce72b21035d207321ef531675cb2546337ae7672b0a54b810ec359b9874e7b184b58867f095bcd9decb3e852021416b09ca3f2daa0c52fdf4220cc947a6eee29ec5d56f65e53b4a02a3b58603ba08b2584fe74c0daeb61f7cdd031de947bb7799ba5ec3039a488b591223625a88dcd466d99396052b310ca74cdc2e30dd0945d5e663b2afeebfa0a5572d24a76b570494c73ab194178368a2f12331ada92cb9a84f49eae898bee93d3e55ae7e90db1c27a2096a5132c34fc20378a83feccc707d52812f55fde0ff2e1c6932f16a9b18aab25dca5403c2c5ef94c54b996008b59b63044272a2b28d5be7a653cf50d5ddaf43722e8f691b5b409d38f61dc96ee65b3fcfafb2c0e15c14a047b5201deb971c6d6036541fb23dad3beedac3d007347a074e6cbf3acb7c7b01111eed3c29b61b8c28f3db45963504a220bee6e11873821471e021f9c870e44a50637e0460b09424f89161afcefc637933ba008d148d32eb1533e49712dae6e55c49440c1bb538dcb829d22c1a12503e7b6f2e04883d9f2a0f55566fe633a7975cace2c690e5ac873f359d7bb48c9e98dafefa4649c4cb79d6f062a5664c1ab0bd7e04a712de2f40eb68fa9992f4d072eb5fef52d29cc8e2fab7ff9ec75d3a823fcca43ad7313d6ee0496509f43970e5b887f5c8e59ac9bdb12921b1316d348b67a5d7c56b314fe2edb26571c22ed9f532228a5d2267fec066e41fde5e69e5ab177df2fc3b2f0e0b243a4aa4b3f125f65c4a01714016b6925c88d1c2878e1092f4a8f9e9461d5fcac6d1b125f049af6a47d19a4e01fbf5d6da2a304fd7c28784123ba4a3be552da363a5ea3b40b80418451a609e1311587a5d0ea16d74c52e44c611c87da8c70906f0c9e2c3321b5e4707e0abd5191038763e2806c94cc2a3968554fb39af2e8b686476ccb9c4eebe6f53c94f29036af6277d674d16fb17931ec6cc2012913fd8d3a09348627b5f553ba176e86596034208b1b512c02187b284054f1f606239dac1014a294fc6c09947a514144f2297a2d9779e711c51b2ccd846ba2e1ae3bcbfa6fc74affa83f9a4b975cf26493d761d3294619432a384ae0fcf6de9bf3615c086339006be226cc44346f712a50e5c3380f7415d3b084d42e556a1ac629bf8af4af6efa8f9ffc5cbb4e240f46438c31f63117056ac040fb777348e43ba90599d05d3216b97e9d0733510779e3debd7b595838e42dad7ea7e637afaf3d09749b29f1687a004d5570824b1c1e38af99204e667cd5dfa92fcd9d67639d3eb793a8b531a3d72b94e5fdaa225b3b9da6226e2a807dc33ed220dff6edd7e31d734c86aac2db8a886672034d73b9139c7b6a6e7b867ef0ea634f482882ba94da1f1f1f06d568ea07df16c4014b5ce60387c37a7d164f6c7900eb6ea98e885dab0c420d0812034ac6ed0df56794684bc0aec44291e83092adf6764ed85004f2d49df49e98cc6d270dd80701cc2f1eda12cd03cbe5f480591f16ff211dafa69fce7d2451edad3d341159c32f9109e8c2d25c4ad3b81a70f5147c083444ff1ba0eb603e14102706892dd3fa92097e18f81ce46f8804271f4a6c74129c733c155174d88479cff8a7d3643b85bb43d14b9098fca82528b463bebdcdc36e937009428cbf6c5e4f4ba2ef740ff2ab1674ad47efb90cd0d4e65d4d276d9a1b699aaa981840df38758a8a16a79fa1165e1974f2d9603188fe20efae0e7eafc571017a283fe15e43458e7fe8ff92e8c8f4e6a048dd9cdb801c2a6d77049839d9828ae973e0edf534b84bbb6bb03190cf996842873b42a152e338562b8c7f9c0f5d5ae58c1a5ad407c1ee61e367ca33234bfe5270a73bb44719854cb7bf4dea61be5ee990823a00f45cd4db6ea36ab2581e5c8cb1c629106e8f37e9f3f7c2ed2acad99d622447c420fe691f9c9a1cc3bc98d4c464c1a63ba92f0ea4184eed55ca47648cbbb4158a2e884faa922157dd271daf6263193721bac97753777e07905b296e9ceaa2d900cf21f04216e0281a9a30a0b1231d5ab2d6443f4f9d51e3757a050309ff6bc97ead7010b93fea6a65134f7a0339044b8e9391cd1025a5bd17885eac4d7a24dde6a579cfa1901c4040c778ae2194674728ee28dd65106d05e7e4011034a887a7730f6d226932b4fd502ce8caaed47ce21178772adf9014ba33cfb443b1ae85de686901877fb6ca52fa5875bc6ce3c32cb979e7f132fe7e9d9ab40b7e6a6a0391a56c5fc2801691b4b99f37549995690382f6d0f245134cf947e8376941303af9071fe13859a4270ca4cbdd0dafbd00c369011687443b59f696ff41c0500d3a7be4d3bff6a66d4d5e1a50c4f490343c3e50cb03195c88b95e64cc0f295350f22c3b8fa8b7d25cf2c5675df228ddd696637ef8ac4e01137dfecd9edfccc31d83dd34902bb2a54943b8ec8e45b5bc67ce207241ea557ae3edd509bc80095e502a18588a2b97d9fd03b63cc2251ce924031726ff40dc6933f9f2b66875145a80a0e896c9716aead406b369333f88485cf1368cc90aa6d1ce2f68f82cbbbf3efa8ee5696f8b2b326d41bfc0ed85e0ac9865b13c2386c5e20288e1dc58448a3b49454af33caf60c5a86c41d23dc26e7c2828ff23917ad0be9b8ec8a8cf78c6c6612f5518c8330fcff344ce7c7a21b1f89953b698cfc7a4ff263f9ccbf5db65f07e62c472f0d1419c396487c460fda06b2a002bd65f04242089e7d50b6870e57265e11b5d49a071ad1b4b458a09500d48144a8c62ee50e8d686c2acfe473301d7d0124e836e4cee32e32ac7caf6c14eefcadca6a7026caf566c0023284bf64ad701bd646e76af7664a86a228d4589eb88a5cd28aa059d91217f3a937b6e1f32afa270b53f4b1795b3f191421358aed91b942ad0ad5858bafa748646c6b9f65e401547acb20e8c6f113363f680a077720dfa730e3dfc704218861ca0fbdd928b226494b69beffc257334fd4e08f58b038c15751f9b1d41b4ccc3290b4dafb2c8ef235833f76fdc8863429276ab1dd720bb139db46bffa11219dcbd284ebd2fad0a1040d63fa37a91b2f80902f2b035f8786671a19796467bde9023c6994516424cbd04281d25c3dde5a0ef1d4575e4a00ae782df2282f9db9c544f5cc45a685fbfd89d0458c112a17cdec4e2e039183e4f630fb81ad1f2a0e33f746c49b38f298387a3ebdc226674b2735aa2b58ca60b599c4770800a796d39ba8d24d86b3efd166a069a8047836c351d829eb62f25671e82574faa1ca1bf68219d5042f3afc59081c8f16e5b4b0d00c0c388d3fa4e7ddb0b0f863c0a24c8cdbca08ec279c0086c352b12f4e386ddd8a01338f7268ba7f63489fc401df63fb742de254d85c6693e03b4d150e1478797a20de9b409a0b4e5ecf177fd68540244f5bed875a9d32eb265252d079c7a9e17f6e4416161388a4e332218d4ff3621c1b924119bac9293de73163066f1c73f0c1295b3eed9643cc966590adce42aae3578d382e621b3b1ca433c3bcfd5e9d0a27085c278ef0f187dd4e8590cafc3262164b9b5d89affc33a2387bc168d4e099f3c12ca8545de8bdcccc593a91c71abe9398cd42b4d73055350857cbad1929c2c29de1600cf5a8d83c00ab24a600075e5a0ccca035161fd8d484bb4fdfbfad3091077f17a2e3e802d8c5b36376d693c7ee06476b388e3e9478cd15a4661999670ebe4c21cc663f33892ed489849ae4f4b4b54c320636d8e3a8b3be2ab86d0854aff856da8c8e1aa7dbabeda7b56549d44f14aefc0f083d57055521177a98940afe74ded11720866a46a5134644c3530a5fc675008a08e590b4daa999c3bae8caa1b9cfecc005e7a5f98274d866adfa86f76dd947ea5d09481787c48929fddece7441b3e85ed6bfb846aec0c8fd9b4b0db2daec03a885f938afea3222f0a88343bcad7aeffb3e95310f8181c6a73d60c14dd1d8318ba7afe05a405c77c8f42cca43ab7b1f1f91a5ffdddef6929475aa15df3d23a2470be8b4c87d230c9fbb88ea348d8a8483167da2f9289c6e698177d6dc4e8a2b70b2348b0f3589a0cc508d725c7ebc0caffac14d04b2d5fb262aef8708e80dc42c9e6aaec31c12fe140afe6e404dfebc94e9e04db94c3b15b2c4d2f101de1f91240b5abe79804abcba7efe7bb57794f793a74aff82fbfd6d86bbd29bd52ab4bb7693115f3351a84d6d4ee8f097e838848623063badee9eae1650a77c9065095b195177c6651398a08944e2014a6ae7a5d61b7bd533a2240846679a68eb2863a57a9bc05dd940f0ad27df68c1db7576aae6245a04abdfc59bb66d15f75e010c99b43e478170747a4df410c0eaf779de363b9363353aa2d7452ab9cc353c38d7f78f7804fbcc1acf385da9a56cd81b3780bb9fa34c25d365912232f4cd655b3854f0bcfd88aeb7c2f8f732f0abae59bf488801d8842972dfae0fa1036e2e66f00f1cfa5beea48173a64915d8ca9ba74497a5beee3af229fda8caeb70959508d817c0f6640015caf812263de88f5a0c31d65b9022dcacd6b8bcee9f14235f9d92675eb569b5e86150246f6d11c613576cc80e11ae8447813ad313263186c42b677acc23ac701f950831c468887a126b908351dfaeb9be1ae83e013e47eedf6553c6b75d569d7489c0c827c8334df58e0f1546715740b1776545bf08e6283108050f82a9ffd98a8dc8eae42a68b58ebfad9f4d721c0b8863f57bfacf1169e2da6d37df8f0c20ab85ee16de943293e6c12d119f7404310e29360bd517de96f81b8ac30c86f60f9c59795c7a5d069985f623511bcd8d07e15d391f2f894bff809bf2f718751483db6aca5ebe658071d36fa6a5303b5a533209a07fc0470736574056302000334ce7af071342163a2d94f1d8fb109e22745c62ef02ffe6aa559067a7f2e9c0d7eb6badcd1dad503c57eea0fff44d75ef3fe4ce4060284bea46f107e62cd17245969a3b0f08613acbf3193def7940ded98c44a672c29a374a253b8e7117844c107fc04707365740621029e5980b4f9b9a9fd568c1c4b48631a800c310405ae8b2ac41ddaf87add3062f107fc04707365740721024b1909e1c80e8ac00cc4954cd2cdcb0cd1d4a38d2c16f0774480c0b50af89c7807fc047073657408040100000007fc0470736574094920000000003b8b87c05549a8cfd6b34dff0d58b5f8c6d8bdcb6e660a3ca320e377a4eb56df63e9b3c37251b3b779c3a433c57ac05905cf1782771ac4435dceb17d10be290d1e6d94ba07fc04707365741043010001e9a50ad3e77b73d6ca67913e649819d9f9f34a58392c6a4c6471ac9d95edf2f3763f7635ac35a3d8120d59a8a8d48583299540c82be7f8dc74b986a859d7483d00")); - tests.insert(String::from("pset_coinjoined_blinded") , String::from("70736574ff01020402000000010401020105010501fb04020000000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001017a0ac55f449ddb6853f2508766d5afb9f3b45e41a8ef5368cad75fb88e5e249395d1097d88c92ca814a207f73441c56cee943f0bb2556da194c14a4b912b078c2238ae025341cb5e4e2d8cb69e694cb20e5ea4cc8ddf2801180096fd071addfcd8bc4445160014011d384302576b408aa3686db874e2b17cc2b01b010e207aff956e6c2a379543b1ec82b06958f062bd9f29aa1ac1c98d1f6398b65a8555010f04010000000001030820a107000000000007fc0470736574012109a8e77f4e15d5305e7a9239c67bb6316dd09ecb48949abefbc090f2512dc00f5407fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b207fc047073657403210a5947a6d36c37663613763fd69e430e0d58bbd4ebd938b6a587a25370fb9ca8620104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657404fd4e106033000000000000000197cfbb01464d22365ddab559a1b8ff64d71017fe4b6bdd2d59855f0a77b8e25b3ef67a3680304876b1e59498c55ac18a03b1b21dc25ceec3f364bf4c9bd4cbdddefb65eceab29c92a4eeef2ae61deab5822b91b1696db64163a740830acfae296fc0fbbdec16ea924dcb3ebbae4b9e176fb8124523982512fca9d9b6ae9748639416e485325cbc3f41b38ef4df8ccb424ce0c0c8d9d29eae45aad1b07de9eba2cdfa35459352bb5ecf6a9368581f2bcc5ff0335a959534d0867a04baccb5664e5834c2394ead9c7ca94b9567bd30265062ffaf4e95ef534cbb00acf2cf8b1568af551940483b59e3de461d73bbb09b63d52da10f04fd6026f9916e47b7b2ad1ec25d09eaa9f7953f53a4d52e910207df4ca7cc248c12cd6c88cc0bd0204d152aa4576e67261c2cae80fe7e4dc83ee27adf3cd88865653ca9808d4dc9f512e270b6749f4a342ffe0a3a223a16386b9df14f930ed213e1b2152b1fe3c93b71f7b5ff2c4f906a7048d3104bd0e480ed658b508d8ffb32befe3ab7e8af1db3d1859af64899017b7218561b3d633e18283caefea14972a75e7d153eeb2f7f86ac8f1de4c3560f7b400593e07896b56bdf9e566fb63209e9fb83d093153794a86f5e7a9f5e51b5c0cb8a10710227c5b8093f77370708d4d3e67a761f46f3260038534ec8d4b27643e41f804fd2af989eb5453dff755695c3c4a7f9bec6da97dfb6ce7cd81038a427a3acfcb4a3d2b1d10d89fe8bfa10f59eedbd914635a34380e5270e137dcb9ffc91a70360e42136a36a6e9227b3dd39a5de3da0119fbc058041cdb85a062b7f86d1467f8401beab88cbdea5a4bd44639d87e36bc53ecb473c35dbb337797fb640d469b93287beab489633825f556956d12424e504b7efede611a72164af0c9628aa0eed613c11e2bbcd02580510432379045a2be2f57b71a3e57f6b3f7e418c98697c35ee7190554ab588c759251a0ff653ec53cd93962eb4622ba35dcf6e881cd4929f60ec2315632ef79b59662b1e94154ec382a9093f387f747d30260367a01c541283ff4f9f5d8516c54c9dec591fd469b3350c833b7225a2f4a02b73a5081a907b062a7b66ee478600ede49bb78b1aa16c1afb0ccac857fc17792b565447d20eb3bf9ca0d5162b4582c80bd81df28a3594d89596c6687e2a36878a8fb0de2c4efa76a83d1b42bc8ef10c52cf5a3990cb3af083e6c951f973e3e1fd91b49affc84884fd6ca76de3ab64099c5de11b99a96d10eedbade66f5ce1a43f33ca2c4e07e4719f570a9669278b2c74cc206dcc6c2f993db2871e895bc15505016ad89f935ab9e56482f06c693ac4c2b59437b1a9922f825effb10ba9bf488e08b089be75d8dfabbdafa2a1896874e07c5b1417acb344b0bcaaf2ae0b4af77afee8106be2a034d7a2ccdbee894fc89c7457517ad2fbae51cf4fe7ee88baf444211bc5709061de96df3e60648e05f4ff62b15e188b40934ae1eec643b25580e519d5e89c701a0b0634dcc811367f313bc78eb7b42323a8d6ee0448905d6ce15c2ca5f7854d39f852d7446d7fb9636d46fb7cc967fd2bdff9be00a6872b27410eb2d32b334575b635d64be8a19a5076123b390044e01429a563015c9a246701490a472114926e4dfd1f0b450e195b99199bd802f5d63383faa4b69b6968f299a130d48c489baca078a5c5cfe533ba49c233eec9edd55e3e692c60003366ab8f0e204a01411bf979a73244249c27e1b646c97575ab3b70628d3ba408f13883117c5f3e3ccdbf700dfbd20d0597b6ead957d15692b83818809930c48f7677c0cbbf9360d6e47494356853a3f210e6574b31aa2607f6417e345205a5a1cf25b77041292d593a7c41c8c9b8f5b69eea3f279966c9acc435af241bf07ed028ac0f86eb069ff1679c92b2611e10daf090ac1e62e0ae22aa70659519e81076dc9f6c7643a0e8c5def9291578ca0d54b8eece0d27d0bc240fb28ea40bc89d719322a09feda57a6cbce5237ffdbeae91cc853e400372f784169c43bc4df777d5784ebb9ebdc22fef28a19e3794ab6f61f27148ca179463690fdfac622c0062fbc43ce054524a873ac27b6322114d3d1f6272737945180085ac6f6612eff0cb53c5f7e4ecb7087a88895342ad7c055e22ffb15e2b0a03464476983293985f56a0733f874094aacb6174ed54a5ae2e68a4bf13e1f91558519acec14da3105eddeff0a50dd41ee5415cdc2bf8a31b154ec76b8682ff2fa4ec64d2368e2f981b837207814393ab79513176da4b4dda8c5ad16c618fd5f44500284c66bc15759dc89d938ce6d8206013302d2687a88b7bee9278887ecaec1a88710c61d49229b48d51ccdbfc977ada8688c8cfe8cb205493a18751a17171a4ecace2d95abc9d7de481d08d5feca3d53fc37630f46e47381842fe8dd537231f966f92ac273ac1fae1cdffcf69d2517b6fc6931cf6be4d9b9561c3010bf2331f96e142aac2a52e9d92c0d4b8bcb8c9016f15e1cb5ec10a2f6005ab901eb0ad7b53e952b31047a5cc4f48e887848a9a6ea0360b0fd27d8348e62740bd88b277eaa334c92a122b03bf6414683c43fb6afd09b37bd3035f1d481820e62d8fc81033ce0c86b55047c60b0bbebf308c5efc38162f2d53eff6ed8cba77d09d75014c7ead2bcf1e94159ca60df2dfd8ab4aa0e2ff29e9662c7eb51b3df311e01ccbda10594b99679f01a64d95364db57d4015836d5ae8ec12b5713e2d2d09ac56070817614c34dca54bd889c2a248a1b64cdd90d29d3158c9e6cf6d503a52c44ace0bdf8dd65b9716ce3a3177c536f9121a38134b60b37cd506d3214a3e086d82feda82dca20c05be5a07fbcacf73b485dd560a285690414e6b9ec8627c0d1dcfa9d73cf238bec8bdd41d771cd1f1118e2a52a36fa8c6e450d99b7f6d8510194405c273cfc6f10de892c49631a49991503b82bec3b9543f4c5c1734f0f8c023e0eb6ad4143f30e479c211738eeeb96acaf63382ab5da7ea9f9abb02f6f1d880e3ccdf3c22a7b9701e7bc50c2fad0f2136c96d4a240f85189ad3ce0d1c6b980eff3836a548f9198352c783e04a804b42e523fdf5ecf717318d8b5c18e3038fbe47dd9e0e6a220b92a0bcb07891fd79971bc8e22dc811323c0e1e9e061f4fb4b496154ad6a20f04d8d4e799ff242f4734e26ee0116b489a5df0e0c387d020ec0b683c5a08e09389b5b406255224b651f38962143af5b69633ccee54e61e961723f0ccdbd062364f1420e1fceb2662fb4a0092f85eb14f3a2a3b984bd83bb30037245c73a8f0cecce21bfadab53189f6a94dd26f992d34da8a3b39c62d96fb05b3044dbff602c653bab55d6be79a548b894c6063d330c42da06275b361229b357d76b157d13e22b7d9cd5451ab592edb4c3a6eb115e48e2068746ced370e4edc6e87146daa8df4aef101f4d9ce4685e2b6fc2aa8fe535b076651e79d6edf30b0a83065c1a0c6c3d5607d1682c72709d8dc9c7fc6d5c5a0cf3e18e95f9c765b702b9a03b8bf49f6878524310ef5a1ad3d67b43545c09830c10a8de71365e292a43ac7d7cf8cd0ec9b4fc07ff6f7ca01ed1ef5d437a1218fe4ce7d5d48796f960c6243f6e9011bcf24a0b74974415b64421a1a087ffa9c270fb0e90a231792345297366b33aca2bb6abb5d2223dcfdb276441c8431ca21306e9044ef95e2b7f6480744c7cb1f8b29026f6e2d95c8a1e469967236420949d3708ce3dd3f7c6d3709e8d4d4f0071b86143bab89fe9bdd90e8c92fdf0dbea8c4ad459a3605857626e6924719982fd34a44e56446adac66a36b6e389a85c57a805d67c08ab3038dd6ed2d1d708896b69497cc04d34422e76314133f9a6d88b5f8d3f3f440e09356e75a8122a6704f66dd26ddac84be764fae76dd0e8899595cbcef1ddec1d6ca1b7d6906c6ca30f479ad383b5c03ed558ca40c17f9a05abdca3a68486db6b5a1cbb7c8397e0cdaf95063e8e8561e97731c5c752e7bf768f3e0a469aac74f24cb5651f80198814fcaebaf8eb8d90feb06a9a864b0f9a19f4785f134f208beaebd5e02dca0dd11e1bb94ae5c0fdff8b9db3a67b2181b5fecf7f93a53f99305521fa65d5c156e8c70a25839a7265df9d50e7d5cd95526c9642a1061041fed50a2de5f1098998432e7fd8032dd9e8ce539a37d37815c3b18985bf1f2551419bbcf4b7ddaef3c4a8b0a5d9842912ea42073d49c317cfa6e77f3411303a296c7f631c41935ffa2191d19977a14918ee6f47a541fb2df0d79d3e350a324bcb0c9b605e68d560f8be92f101a81d065dd1bafac8ee59ee1261e8324f999033ea0c9acffdda910ca4a5b3c8102014cf6852ef02031c0bd70e1d4d5ea626b7b0f35f33d36ce3ee983b4da9c816fca130fa54c6f9d87511296f3624451d18a7d271ba7eb8abf39ef1a8351fdaab5a55efa7f8c43850bcda27a71697fad040808faaad189d1ba254f31bd30f8fe2d1b44bb45aaa4efa16195e2fbc53e25ce965a8f579fd4fdbf92f9265ef00e14e16df2137c2e4d36bea77377ed8695f1b4ac2d40050888d264899ccbce192afb13a2546be5e39983bc352625ee5679be797659b7006a5b496e06fe877c73fa54041f7fd35117115923b77664e3513e23f9c5637e8fc3f9fa7ac74f6c669022e97ef1c96c9836032f6ed2dfd90edb232b79fd4616b7405a0d215d2aa0961797e02ea659f5c03beb521c523af2251907b8a8c7b8349187200d99a28d20e59e4c6cbc77bb216c82fe16337d1daa4b837e92b39246d9820a21857fe19a035c805592e67d471bc31faa4f4b93ada5369adf3c7a380b5f2fb011f713a68159c2c3523e3abe755555bd86b4fe0d1ed99f555e7fba46d6f465efbdcd7be970b4839ced71a1490835968127e3318b9e8503c204a06614f901f7db74498b911929e4910b3d87e85a82bac591b519e840390379ae0d69fb8b88e201b7ba8943af91a78bc1eda1e20c3b1ac7c96bfc0b84a460d561066220ec89b61d4a13bd082d9b52d920ae9d16f6c74fd0ab7d2acab931eae047d72c1243177ca9aa04d7e9b0c1e3be6d7b68784bd64c9c868354877f3f6d54813d0f60039740de75790a88f0e6ce866ca2dce3441708a7d566c9711b75d2bd49c6bad95603ee5ee7358fec51e0aa4d1f43b42d7e0f0c13ae6c192a72407f36dc2257f4e3a73eaf062e550532b24abdb72c5c26de136b2d0870b198456f40e11647073e3676fd6e709fdad602f80ab8d4d80521b644680709595e90917f7ca410df2ceea2cd5d1cbace397efa620e4c981c073fad265cc9ef40a945d6b7dc22c58189cf6410fce33c4505b2bca344e7fbad7ba78fcaac99edeabf68039951d0c4298fe9afb00fe7ec9a1115896b80535334f902d0a8deea79f32781498aa5faf19459463ceb4b9cefbc447159de4fb0e524d6654100c30d9a5ffff033b6f700e8a61064772f5d364aa0ef33070b790501cc9e572f4ee4baa251af36dc357ae1ddaa4ed460e355523eac470196a87aa4394eef0e29f46ff5ef80a3b5e9e6f6a21b079793d14309c9966fee7d896d80afd4ec2f28da2a89c70214fb169ff1cea13d9da988491c44bb6b39a49f131cbbbec5b123450b346f6070baac1f0b7c41b483e320fd9417ecb8339b89d3ccea76a653ee6765b737b6cfdf132036733bb4b535d0d9cc42e7b0d9ee3220c4b3a7d1863ade85807c14e947ba6a2b75712e23a233e4442b863b8a340246bdb322f538dbb26cebd36d0d1c3407c109eeb78e79e3cca96d25eeee1b1df837682d231f8261d9bd3601f3f4d366d998c55d7c0fac776ec1a55005c5a4ca1b865f38f1c0bbe669ff2a4d46acfb2bc516818e17b9e689619af0bd06b1f3143eef2f03b54ecbb23549f3dafd9732b908ddf2f8a95185f95545820707fc047073657405630200037a03025dd115f49694c2d1826ab6f51a354a036cebad74b2e9af9bef052d856b2c04ebaecee5f7f7fb0c0cc33dd032c1fcfdbcbb1b192fcad09dc792d73a3f1d063919b00853e9f199fe5452fa9d49168a6af5cd6c3cc25f65c691aff1bbd87907fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc0470736574072102c089ea1e19d114d367738e8e8916c18e54aa2c23910215d551984bd3199fd73607fc047073657408040000000007fc0470736574094920000000000007a12065f43b5553b508814c7755432c68b28f58a74afb15400dd630d9260d1b2552d28c5d83332ca7cfa05539a3bbdd1e395a23339788c636b97edad794b0d0e6abc507fc04707365741043010001e4da3fc7fa33dd5f97ce90ac6c4fa2ca24510e1a0af18dd1991d986b523151e6cf30a67e99111a7ba65cb4c4b2abd3b0521547928be8613fcf1b67487287e83c000103086ce2ad0d0000000007fc0470736574012108b72e358ef0b7a5dfae191858c20f0f206fba698c2d77222d05c69033551cfa5407fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b207fc047073657403210a51f085a4066ba9050284278424ab1371ebd283fa177ae72cdb81e2a2556706790104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc047073657404fd4e1060330000000000000001d9b0ab00bce3d558c19a7037ac726158c24ef7afb6c68f3e77540c2523b3b9a10ef6703deeb2c58f9b9293ac32bfdcd9b2f19b9ef7ce704ec0c2b06336b05ee0cde239566da1e7e344806ac710bb095876bb11df65dc6cbabaafaaea27214553b67fcb6c06ad40ca5adbc1c6ea7aa6d12fd7beddb9873e1ed56ad006634bbcf395bec0b82e3794f6e6e0988ec680924757b8eaf321ce84046941b5fe963d8acefd5e2f297f19ff5d429d66772d6fbb891bc137e42220b323bcf835ec33ed800bad87d24782afe909a5ed06c82ba385761265289b80a4b7a7e82c176b89fbe3a03ed5cf1b0039dcf556b8d1aea6baaa2df8b0ae2e140b060caa8d0ad2263de385c7d3ed618f480b9808072544a10f42d55adb62cef2878469664d87e681e92aeed8b4001e89bdb0c40dbbf337b21e391a7c41b0fc50bf9cc774eb9ee080cc61a7e4cafc919c919e1b875421df818f1a8a1233a8d96b5600aabc920643047794e09408878295bd0d9ff4282f958ea0bacbae7853649daf29bff45b0f32ad2fb235c802d89d579872bb5599a6433c017c98ef87bb0e250b9e20a8bea3232d676bf56106d67fe68c858a72bef74130d313bde4878258f3c8762d515b767c1be439ea98ff3bad581e5357bbde211fa2844f62fd0087605c65abc6fd904b60b63b23b740042572b8e432a6699e5c29467ed8c76fd3ecae7dc3f732dd303bf9ab1c3ed3ccd1315b199f160115d38ef8a478437a436fb4fe5c5aee16a8a02961185816b487d89607a24eca3fd50bfdbde24c999b13320744ff1e17778d47c13880d65969429c41872f6138c3fc6a3454649776c09bc787385bb6000141dc0f3e54f42fd4a119b054f5e967d04599c987460ecb8764eaeb960b0b95ed74218f576bc55225cba0b7d14742f84010bfbbb9a68cc1982103ad011dc7d3d0ce24e83b3a7e5f972db0225ad4a8188013d9b0586594338e2bb1b59a10ca3851d10d2aff7e2b349f4f6d5ee37de05e27de1b6c7c7d172990f24f4dfa88e47111e319dbb8b2afbefe688ad5cc4978001253a9760e719940211a2aca39d528f6b749b847888676efd5c427db3510c6318fe7ebf6af83a742a8118e45b2dcbb8004de6e04d9e8d86488dfdb3f15851eea0eafd627fe01cd48cb2607abfbcd66ff5ecd66b50288010ddfb1380bc17c43ff9cef118237727c46ed210c198f21fb736a0125260f5f3c30c38ca55fe9418c35434f1e857e32dca56c4e1805a5dbefcd5c6f527038e779754ea36e114b9bd31ddee29be2513ac77c03ec29d335d2e91745641f1e693cb2e342d2891fc34122e5ab18e096e4f51f082b464d588c3b56bb87db1c5f91e8018d6a9c55062da7e3f7131f250c558e9ec7cec9d0b5e03cb18a34dc49ecfddfbd7ebb8a86cd88d76c1b3e0eaf5af8b456f8c3c07d2f3ad52bab46fa46a237c4cc0b16006b0b68151f49d363c72099ce8c20130afb60ad2e615f8cf5bc7d53be94a439f7c2917c19286c0d8ae67e83e0592768be912215b48525f67e02f3a76a092effe00dd2949fa256b6ad464addfa4fcb5a3c118ff78ed7263b1292cc6f2076cd05f9b79f121a560062a1a20a23adb394b2aa878209a71c56c04529c7756e6d297ed8d4e5976950da13051544cc1f8a8886ec34c6c60cc423a72f4fff6ecf76762500cc627d9cf3e6f89e583595356e13a9bdf9d2cf9366697f6eda71f4a5d5b092e306ee6b719a573259888e2ab343a01bc8f61e48c6c8571cdf8026f75d7e00a669264b8cd2aab1d84890bcf36069f4fd7b4ef122e39b7bfe857fbb27003e965bfa2b0a8e66b1206831e70e6a44648290a06459728d2d8e1f0f530178431113ef5bbe0e21dc7b5073dedfcf0f9a5d6bf9438853e52304a6d119a28697ed6472d72f2ef035170f5f892a1fe0b048589a2b5df788edfdbd6ff01b6b2432ff97c87038d650240d9b1705793e63a671172f542a04be6bc3b46209c5d9f7a1b98142af850be5d4982ab47b6d90e1b0f9d17183975ddc8c13aa7eb167feb2a325c4f2ea67d1d50a82e2aa69e28e31dddec6ae61c16793013576ce4660c9e3ee5ffaf05f47adbc93b40a4bffd8d191c43ce6a1d931f4da0ccbe26b8408f604b0b7ed948eaa6f5391207f855f0bf24b9ddd084450b5f953e709f10720da19a871fc7298ed5fd48763f091669687741cdb50bfab4ab02f38c985f1065a335eb172218d53d30807ecfce3d7b76948804861db7afd3a0c52b87340a6c095c181e0bbbd4044e521bfeed020db731c18e02a46d77f6ca5bcfa2f9c89e614c3d0cba5b9e38ed86d8050181923f18c6a2970868155158995d21cbb2d4e032c1ce69426f878217c342b600c1869344f6970b5b52a6d14b63349e2285efab30c9f385c18199a04ac804590004220a0dfc26749bf3efa886585792109d03a2564c850fb989c3fd723cf0d45638c92aae04f370e6bb15581afbe2e2bbba91e69a64ad3eb7995769b2fff319d5bf73432602146fcb6dee003d4dbfc5d507a4041aaa293d49e67669fc60203c91b0d2f5d7074af20a6d05d1f9135d08af256fedf2164fbe632171d6f16d260fed9e1a8ec136ba62bd9f0983e29eb7161a9315dfc568d47e113b19245aa3b6c3eb2580cce01e3222a76fdd5b80431dc75aa6695755fd78b2982c28dd0dbd51423334bbf804b6d67d44cfb2bc14a9d3cb101f507bc1ed85cbb4b20704f269320eafb234f1972980b3defbaaafec860dab58a3c6425ec90c3fd3d3ebc0e1cd3af195c735a0ab0f4441db4fc3ca24f175edd45a312420e7dfc8a058eda060c684c90d005ee0b4c63ba94c078a6491b579954b8be37ba92ad9449e6b61706063e761c1ba37fd5a51348508e347cd71763f6bdd83da35ccb664ddef1bd86438a5f73d3c561ca12d0a8247f9955b4694ba4c725d414fc00d633662ad10a7eb1a4337049c5b9c7bbca1eea8018a89a17d8172ddec543c9eda722e36c30f136a824c1c7716796be1c3bedd955fad5dcec4eb528ffb707dc08a17641b58a760ef479ecc702ee9021a3138e2b2117e439cf029d40863964c12f72610d43a980ca090f716730320d111defe710b51cc9d500e0d4136108005aa3ae123f53a8bad67852e5aba87e21483bdfa1814dba4c20dc29beb3cbddfca02679f6063832ab31ab1de41d4be6cfd6b14f1cfb47b572602e9fef65469761cbcdabcd1b66ef1b7d2fe247acf2c3b026b1cc2fccc39c67ac20a5607b89c924f48f78d0dac9f845d23754e3c8e97559f7a7871f3f711735c1083f75fd2e66648ab13fe230611b11208584f6556fc1593b002464f664d6a2e5e77c9b87130bad428685a1a9c2a3975bdd3dde3384d0fae1cdf8d311da8ca2a7a587da3636f3fbf5b0288dcbed52f6d148197cdda436ef3547f9ed21a83c259f613438c49287f2efb1e60c3a5173000007a6a4aeb6a82ba76df27e7d09651c056cb589ddb14804a5b3dd10c8101ba52f1f7d1b122834800f3be97d2b723dc55ca5e3f5ee546cb5d1a97e694f92af58fbeab339e29b14a87f5ce7c3e21e464bc22671afdad682f08d27add6bfcb12f796291afc40d3e5bd075d91b4bba0913e963db3741420bda88b63a9d59bdfbdeba5b1006cd60935e938ddb8c57e0b1b59263d971b48e41ce7ff8b582518b14ab35fc0d5921ef0da11929e6a27fcec72eb3ba3dbd81e3f75ba8d0b1f2be4e9f6ca9d13871e6c7e4d451b2387a66fedf3fb2233b8dd72db04e9a2dcb7901bb0ed8eae70c1c50a2d6f832137b50ff1995272658119802604cc8c1ee8b5e0092aade74f3192325e65c884db163aae6ec9b3de16f446128961f1e5352916fdda9565cbcd5a8afa5f06d495b0cea120a25cb1d642500a92112500ed7101af1852fba42f161cf8a19dfa3a271080131b981efa56277e2882f31e3f983caab80833994de21e84c7898ccd93bda503be0d2114e8d1f5a0979f4fd67b0a912068818419c00f0551d6fbe5fdf2814d8490e74a3e696aac6fc6cddd82e31d767ec0a8c4ef13ec0a342232ec5edd81e758ff00cb3a47b9c6f2ff88e66236bfa68d11508edb623a1cb2f2a459b0a84bd01d028bba1df653cfa302ad628e2e318d4b591855cd1a47c79b23a0cf857a4f819f29ba11897e16eebf360941b72be288fd847179586a603e9f6ab2140f7ac99426d411c33a7ff562e0001e75050e06d7ec1a1d6f6cebb9e11bcc92f6d0d8506317794315eb5aacb61f0a7994e04270c0f8093888bf26656ffd9d6b36c6c283857f0a6340d603dd2eb4526b6021a860e849c7b58730b9750c6e0c95c9ecf2317967861705154c8c5dbd4ddf1dfef5e8deb8e06897e835e774293fac596b70c8797d555d023d398a86c14be8d33ff4659b2393c490fba2a1cfdc6253e32ab6bc7d5f3c5ecc057248c18088eb244cfc8b639fbe2a67377a3bf074797d906471b8bf1e02e8515fa07cb2e0463104c0cbae648933d20c4f9a42e58774ae4deb461c24956e8cb174586b13dc18d8e5fd94afe7b8e9ee77551779e9f55f93fbd5017cb330849f56619cccfea8bccfe7ea865e2f38ad21d2fdddcdcae02d470fbf5fe1ad2316acb4ce58514397e5121c0bc86ade6568d4ceea5c2c2e07f63d3380604ac0490e5c9f910109e49f18d53b194ee2d7046f9d97cdb37a8392e312147f4cee53bc39b84a30525998dcc79e2b8dfda5699527953d805e74449cbb7be415eb3029c21005a1faa768798d40125db28031deb1de0806a204ff7b9a132332ee5e29db5d94a43041a298b742fb110ba1adf3961a3fd25df818fd4cb036bb2d1b2a74c893df8ccaba05914911323f8eab0d23061b1f4f5aa6f0e758a5f21f43ba88c38b08e1cc1c2550205d328b78f0dfe627d7d7fdcfc2a61dbf9d767bf07274f190acdd4eb89e6e86ab4aa4ad73b207e37c2c3fc5b6b77d6b508dcdd412e3bd28079f386115cb8f8778a9ea5b490883a4749ce85fa5194c4301d8a9cdc57a63a99b71d04616a0bbae459f7a0a3df8697f7316b8ec9258b01b94aca451c92a01e11a75d01cd59c93545c24af48ded15a4b540f5a4ec24e11879b22674a58f604397f163570d7b658dda4a78248585ea9273270ce75dccc847ff11462daa73e3e23a0f4a50d7a8d2ae79f1b7896dfa7195d9994ebfe50e9ebb263722a7a08b4a7e4e2ff86378746690b27106f3a4a14a0d97f66b281ec0fc7375ff3b5df269049970c0806fd7656843865f926596fc13975432ac3c8e4a87f7027d3849181b52cfdc626e756b569ceade6bad39783097646150bead3d7cc2c1bed3574121bedb8f8c0a9d32c5068f7c5adf81bd5d81c2c325799e4900c90fc833fa5a0773d085ed463a4bd3d4ec3181373d99315d15da687c0934f5cb94a7ee5ba3336e4f1cf58a4715540342b4610d513bcbab7c2d4e6fb9dde6238de30af5b8ac903291bd32cc84a162ae522afb7478caf1839cf2faab9a3ebc458f4c4fdcb3cc343ccd5599c133e34343c00ebb3f1fd3c4b33bf04b158ed972bf773156f7515ee9ffa245a61106f0460bfc0b9ba86e4220b8f66b01e6613938cf93467fa29cc1ef9b7103e24b49967286a952475cd615dc83d15ccca8c69d9d2056c79eadb5bacc0df46491b4dcc313bca519cdaaba0b979e4fbe9dcbdf8e0350e35d56405ef220d6828ce7e0191094d251975dde91d9a82a0f6c49f4eeda680e1c3394b1cff45d55f3538537a230cbd3868867adfdc4331426ceeaef9c674b050a598c316e391786a7ba5c054b481abd6640d1ffb637766b51134ce5af66ea43d861578c37c7548cfa096caf0d658b1d1d8dd1eb955e546596e454f91bd4e06c2d705a1d95344488ab047c08a26e5bceba93a7313a6155318fa2650dc59c34a2b332ff083d56956d7f05140fb8507fc04707365740563020003646913f5f5d725f3aef6086e0e967210f6157f1540da096300da672b3f86ce5798b13d0bafdf1144c67baae6b169c87f2b9f6f276b8e906ec59a2b96a57d2dc7c2a3e01f88c637e48e56aa7514c6ba7aaab3dcbae99502e32e2bf5578661c3e207fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc0470736574072102c4bdaa0e5ec994336d9a2260e14b6e0b8fceacc9c4f14284f12a8dc34cbdf00707fc047073657408040000000007fc0470736574094920000000000dade26c85eee06b409c43d01e34275f66f5655e64ba8492338aaade1c4cbf9a922f80b64c8e4a7cea9b2c860ae6420c9e670d6b0ce6229cbcef324e317a893f0fac433307fc04707365741043010001512b8805d822f01e115b906d50613a24b7723c9980a747edc77cfd4f1564575cc6370d93a1e3c7d1f42a3594c7d3a320f6e70e5ed5c7d0fc4f877720d0b8e6eb00010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000001030840420f000000000007fc0470736574012109509217e1b071fbec4cb5fa3cdfb5c860197b889067967e06503b2391f40ae3ad07fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210af17cabef5f73bfab19c674c1bcb64bde67017100e7d2b1d3b877c3503aff67c7010422002037831b3ee29fc96f8e61ccb98fbe2dcb03e189dd29cfecc691b5a7442d8548e807fc047073657404fd4e10603300000000000000014fb15d00b63989058441e4842b16c6bb22c9a696efbd85b29fab7a9b3d53e8aa8465745f9724a942187af247fe6cfb7f3022b6e71316143eed2856014c7be59f599d4073a2ec1865d2845e012bbbf7383866639d3db07fb201862a50769bc3b54842151e02be410ce11b7ac9c0274a0cdcfa576c371c34a0c5e24bdd28f0657dbd7d21a927329868e30bbcbb23efd6d4bfb11d7ad35469ee052372534201265908c920bc7d914c7bba26e7375c2074993118679e1e0fe984b6f4146df255d8894140156e507c10543e641470743669c9f8969d8f38beb2d31b0619cbb531b54e3982d89e9bf66016ede12a9d7393853dfb607bcbfaa232371fc2c9730e834b669ec63d14d57e819abcb4fd2d726d0152ef897ca78a5d6016ff98de8a34d12ab84e5d0005d64548d63ed26747f4d2413215cba58504dcac6f35c57d9a5e0e7819f6cb22c76b1fe48082726de5f178ab9b7a0dcf749a559f0911cd1b33b71ec3153b634540c8587915605c94d852d744270a2068825be5b08cd968d41867c98fcdc9ea6a23eceefbdd1e15e3a7bacce90624a1e95dfd8b11b331054d7f74c57088c33bab33fe5511dd1561e086ff8f3e4a1fb355772bd406ea1d86242a796924821ce70c831ad4a1a057a173bd9d82bedb3dee0dd565c542dce7ea55947c0c48a1d10cc87c9b0d8e41bfb0f85f95c3abfe329cf127ad76c53eeaeee336086b056318d45c91555873e7daa6d88f13856b6fa0629991f86ce192a10acdcb370f33d98e5d810ca81270542a10b71312f3d179f7051e982f62aff114f15d107ea9082b577a0afeaa56274546c058ed7f345f21222c4dea005bf39ef6d458740dcab3dbbca367eb42a75c3b632d3f3e2b6fd25edfd496b966f2d22912bade07416de9c44168928e1e8ba694a09ba078cad5301cf4f6f675638c6e281e43b5f43ea9ecd42ecc3dec9bd9037bd67492cf6999dc4ba074a7d19e3c554464721aac74934f0d4631b9f1d32367f271249e8b1684ead35f9cac9bd121a13261e58aa19e65df246425a279297c93548de0893f23ec291e3240e8f9c30b80777c53f066a29d6581e0b3413246a8e5bb3eb1f99cce0e859d5366864ab9e8234b7ab311663967fdc179ad9de44d8f35b6d146a8301a7f7dadd0fa79887907abc6f28de2406d83ee50f3c55fa49369e4c178505b4c85f975e3e684463062d2e3bd8493dd28bcf693453d22b29843c9c91a2c32ef7e4719b0e073eaf129be36d575520d9e37ac1b81a1f96ffda0e7748380ee0b3399c63ef23cde17aef220f142ebe2552c425b2d39e206a42829f6d1d31b1b393bb31eb3f9835d5e1108b0a8fb6788b26abfc93ff3f368e321c62e205028038625f3ebd51872e75a528af725c55b285ec840a7bf2bff4a9f52d076310a027baa0415c032c2ff246d7942dd08019a448b1834f1d36857c0a3af27c782bc06ae568643b320ce292b4c0f6bbf44fc9760ca1b9f4e36b52cc115367fcfdd2777055dc6e98e42cef8414e12a7d300e6b60b2e3d4d244e558461536f748319d6bf1bc0f605bf7eb06d81e5768f46c05c4b666d45705f53d792ac0086c990d103d1a8e5b405347851a072c287fe9333be36cbe92ac9e16cdc08f0e7b65da846981e00256fe627d3fd74b0da87a58f1daabe54fb15389e44e8d79bf5befe1f319c882d33d1d34db9d812fed2b172326175fa082f1359599c7bb2fb9a3b81a1ecb9f9f81dfd08e26c28ac46a6a47431d5806b9e001691da3a7bccf0521051473e57597723733ccc14621e2c7fb5a0562a4804342629f9866ad71dec71e34b8f618c6b6c12fd5c227edfd3ebc77f62c8f6d82f039f95547db5226e4073d4fc8d91e4fdea73ea398763edd21e502fe6b2c01ddad50d3ef35ba50adf34a6f232d98de5564f7ef0bc16b65beeab8da4c2181205563532042e859b257ccd500c9dbfc26f9a4ca20a10ee1b94e3cfff12680f78ce7cc134a4f5cf2efcd88b99b8fcf52e2cfae220af7a7ae84362220c2e6bc3bf5e0db4d5cde8cc81948f07941a67348da2ad2c28b790551241bf6bde70f4bbd366cbbe8e0ce1a3f72e2d06224d11e41bafdf24f0f390ae129d2cf2e5228337390cb1571b747532b76aa1a6934fb680ccdf24da86cdc22a9c5db3be198a58913746d1701c9c19224da613598403d56aaf4753c8d0c74ce502034f72b81ed5d3aaeff0ee3ea0b36b7abc30a5876b3bc0ca0d8c7705fb2533b1f934cad358fb473d71fa4008e1c65f070e535a2b9ec54b123123f09759c9a16a7b2c0be92d5112b77954d89dfd0a23882250228a83435365587c49ca0c389210b39934c212ead258cddc4226a820f949f698a18388961df4246f47d12b164f298895d295cf3041aef15cf4c9e40e6a737ddff8a95bd5baf6a19fc254d4fd502d074df2035eadbe0b257ea3bce05945143decffde78ea44297a7966abae6504f760d6f1dcefc239ff98cc84cd1fbc8c9df2d1338847ada737eab43912688a127f1ae0d667beb5518974c77d35b96f00d0bb7e60d1bebc35afb99d786e764af0abe6c83b061fe09e059ceae787473771ab6c7892dc4506d25f46b3e721401d573abeb5a915452797c587de7ef97b68b330baa7b2779fa15bd64a6b9a51afa61dc579cbbb55d4ea6adc7eebbf1f0bd88162163cc467b5b7ff637dbebacf873bf9631e1311deaae65ac2f14d24c62b04d8a0c3cd6aba6d69bd65975a895b71055b98bdc560e327cce9eb05944a3251cdfdaed48fbe5d1183ba00699080b7fe549a4ace7f3091558cfb3fc20f8b444823abb0243100cca662b5fe049b89b1f1708b2ece7fced45358a8eb4fc2151b8d57099593c22a0c31d695369214fd2134df49179a31f82565bcda5711f36fb8ec735e41c42076ee6b018e10803e1c74807e4751d9510dcb43a8cfcd75341568e216e7fd3f6eb2872b9dc03a2a05fd4d7ac7fbe7e638e9119c7eb13544cfea6887c0fa0bfc9339658b66130079ea4c950a8294d51f6af904a371c0c22a1f1a7599d4ea540af8ff2c6249ef9a44da29439ff23187182a184663320e4b2e0ebef0c3453f28758dd258b1e23133b666cbbc30f85fd2cc64626e26a30125cce0d8e1f87cea15ff97b5c2f07bcbd4c9d47b80a80121c65df06186fecbf5340c915fc8d2e7e36ca1389d825f105a555e171f596e5d1e303a4c5224cfb877a48096f63c31be2af62b75d73300a31a6c30f64b0bf140af539c637cfaa566d97aac9c84f234e737fe9066170f75b53c461a7cd68ede00a9191decd786c6424f10036f2746a12ba754ee2d89a1f52568ba3ae87381f44919fce988b1cf4b2d7ba62e0b285768a42ea948bba1eb0244c098fe7417d9a5783e560359039080f888bef6063da915b2a1ce8150deca746dbd185cb38b6614e5a5669845a42db9593a6738fa8607f5a2153da62090550905623df524542ddbf104994c946204f395d5ba61bd5a436cf68bcb80a070abb7d792a3fdd2d0f6a6ff87ccd4e73aff3990d3ecd0d1bdeabd4dfb4900c4122d985e33fff0b4d208d842cdbe951f1b6f4362de838c7acd4cd2ba5c2a8e17d67b5220d9241b0851df436916a2a725da8b1c76ae745d0fff3da0c1b069f9163676a78e8aaaeea7419ed7c7e92953028797639eeb4dc25f36f94356e49af627e4945a169fa4a668cb888c106fc5c25b53648acd976caa28c8e8a8412326397650e4a5aebbdfda667184cb7f3b7d2b832e89cbdbf2e07d59d386a9eb62f145962a5e28b02cd443c8adc4f3f8a033e449451e1bd02872da37f66ada25a8985599a623cf12f50448c652a47dab1a3315a596f2c7ed3a60abd1ef19da223043159ec8e4b26af5885dc625b68397b55e5dcd7d9ad750ad35954efc0f86237b7b28dec267046eb921626f6df35323a4420760b98a43a361dea3c3c879ed787114fd0c58ac5eed564cfaee4074d5980a966c6863249ea7bd5a2bf272a4558344a01612347a2588e1501a4848c4e0f42d02f11fe40ab00acbd4f51d4717ac2c1493d860b7805631ea64b4fc4b2a127ce9d960ba435b86dc3b9cd0fbadc736245012c47e1273650c85bff9a464c3c0bc21dc4820b1a5d551a8f725df754fe7205245b4561ccca3bc68773edb377d507f2f84dda7e83bff96e2b0df81a1c972d56f4b914ad8cf9b76e47b47816230c6da79cae4018557495456db3b6be2f30a4040ef3d1bc24aab6a2cfcd3104ea3cdbc941ba91fbc9481c26cf9a8f88e0bdc92a6d705fd5dac916e293b408592ca682c292d2ff86fda40c63906e10abcc7fe0dbfaf03d93b3116a80e46b9523a3996085673964c9e206d839c9eaca4a8eb249be46dbfa237c8e54b2cfb5fbff8bc07863e1636192baa381a94067a07cc3e7ec645da22658af620a84e32276e2ce71eeabf3c4ae39c7108ee533fd070acf4616424676293f2b5377874ac6eacf474066dc7b19c06d217d7bb68bb7323d0e05d6bd8c437a0bdd3785485b513c5bee4503235a688f5ab27c330248678be9785b02e2d6c04a7601fd17ee2387e385a624a5d047671de8f439dcaa7e9bbb60a8b1ca23b6ad378b55b65fb64f1b4a2df25a073a23e08cb49f58e1f528d19c2850ffe29deee6376568af2a6ec919413bafef949c0bb4d5d7650df62dc6bc042ca7ffa4d7e064ecce7b7cbaa1b3f519080b608b33d9ff9a0a552e89655bafd7151a8cdcbc517708896932554a85dab5569defd15b593d4dd7c5d086c9ae3b719c36fd0a4d44c5971848eb7dced3b1b96ae9956fe0c177f649884d0335625e8ebffb3bead27d622a154d8a596e7a94537eae5cb487edcffbae5a48dbddb7e909e6b24b0aa14319eee52f51d2ef8c7ef1abf69ea5f023269b6562c6927e3606ef29d751615f51796ab9bf36a1d5b0e245b6ce01df75d3af3d3d5af57691bb7b7089718df8c3ddb9593b0d28d8c4f85ca44a95c9f4a707a500e779a4e5eed45a0769818ba60ad1e7d89be2bb584b2a50c3fadada806a685a5b701b7c1b7810b88e44efcf762d7f45972bc1cacf4fda5b61525d916e3e0feaf525cdc1276a9ae187a1b0234d98b835f4c79aef141160e58bf8f5cca950dbc372a2f9ebbe2e25e97af0c39440303771604924d00aeac4bdb4003bda5a36f4ef911cab7f0018bfd07e09cab255b1c74b144174ba9f5c5b307c7c4be504ad0cd1d51fedd9de1e0949ddde6fbd84f9b60319fdbe5c6d6c7d19677bfb257765d4cc471d567c2dac942bbae5756bdf6461f197a0e8ee8221fa438f9de69c64d896f2ed5e28854c03d8e816ff76d00453c7a5069c6404655463694e0b1f87579382eb95b60bafca557f5a14da4518a3997d76dee43b573449fd12cd8d2893bc6679e53fb5154256f792b4f98e712e414c6761db503689d1d9459b8d5899b656082afe0691a7408d5ed7518a29833e3376941a5ca0c70f611fb7b804c55b61db4493c91f8d1dd703d06996688843ff9112c99c66b05e52921f0e2a46ab1531e5e55f46143446eca09bf7a58b72f5caed68ddcdacb7f410aab0724e7037f2df6ffe7449092eb767ee4398f3abe6a48706349f1708b18a99f1c0ae82465dae5681f529d8e839f782c54e216f24eecfa79c68200d049b132644fca362b6a0e800926c058569561da9b75d18e067f79b9d7e39c1236e818902875ce2a515be8bcf5779229f87ca9116525bc9b6b2b759120197008be7e61b1553263a54a9d06928acf54479ffa2789e3606f7307aa5d9689ba497c2aa42995caae76caea723e18edc3d1fc6ded3a3e0c577f67fdadb943bb2b0d9d3d27b54a21125cf391c96e3e49d6279547c1954cfbe510bfe405dd3ec1729c2c74fb955923df0a8d5b3c372a8d90d0520a20be2bfd6949b3bd96b1ebce44db4acd8a188c361c07fc04707365740563020003b281b811efef8fdab596b2f9f5f9551c18e3abb759de33e0918371cb66dba3bc47d87cdde6c9702f0f0376109b3453dfa4665bd3f34fb1781c4fb87024eac601b4561d5b1d31ad1a0d43b9c34ff29c861c25da23928a05ca39033cfe6f03051e07fc0470736574062103d559d2a5a4180f418a69c4bed5508971cda9313722fff71e053d3d82fee9d7bd07fc04707365740721027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac07fc047073657408040100000007fc047073657409492000000000000f4240ebd01380d359eac86844d694bb283ffa28201397c492fb532ded8c459440f52de3f8424ef929f29132cdeb4f6d5df6089ec15c4fbaba614d4fcdbef511ae320507fc047073657410430100015227273ad2e3e885820f517805e3f88f2412f610c71515496d43ef5b57b3bcdc438c6430eea4858f66ec5547b9c4e196d2574423df87a9aa58cf393837f75e2a00010308c0878b3b0000000007fc04707365740121080d40d0e9edf648b2dc42a95dc78fb9914ea1caa7f5527dfa3bae14e1b596d51b07fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210b3c73bf89a27c01a24c6c1ef51dd63d8dcd83d589f7c2c5fd6557c365fdf0101c0104220020e7da55d19cc85b0420c539a90b667d4d85f59ee0ed417493a947c3a2256cc0aa07fc047073657404fd4e10603300000000000000012acc3a015e9d9f4debaea69c0602d611e4b10d80e7d8a80cf67061b3e07ca1841709d0eca32a81a9c0fd15bd122851d8b72cce053501e95944345c243ce86613a52c6187bdb1d1365aaa3a1dac0e91168ab05d392cc7ab56eff07afbfc6a1bfec8ade02f723dbd7f83809371994df8b49f6bcb5a92f4481991b8918ec599f32fd2a5d15c79c3cdc8e4e109c32304f255bf227b849394f368900f3af5edc1ed9366a1210d5fbf7200fc21343f547f2065e33493bd98c241b1005a55a091e2b57c0ea4e0874092b59c1e39d490e923821689f6119874f1073f7f4a615573bff6612161327b52b961113271ec5e2a76f1efe539ec450bf3915f961c4bc29cb352a9eeb6edce8a3aa869dbd1110b94ef9c7efafa601164fab10771ba30fd7c8e1558e6cd47a08eb5fa1aa62c2b805a65aa0a40268d01ea709d8cb8a62558da3595220908e8318768c6df25ce0478502cef56d2b06911aa5054013a5d1097ee01dd25aa81c8db1c0f13a9f90fddd0c2d558a7e2021f0cef8059265baea2375694f040fe122e8caa55c2f2904114094ebf219597096d613e3b338a44a834a97d5e65e111001835f44aa7a5ab911e6e35cf9b893c955a648c72f32ed033c1c74bcacffa013e5404d478b61ec0c6590233b2469fa2ec5ba9b8db23b594ef8f918cacf1039804afd2e595fb0cb20fc0ce1e190ff42e93e3128447d0b68fc515ec29a3e0fcd6e9b19f3c3b376dc78095cccb7f198ee76373498327fb3f381b46a87023f54952a0782a7fd31c2c44cbef1e6e90e80d83d2d6cbeb33538f74b8a25ab754e400e52649b6c5c8ba9a2621a44dea5b4df8811525bd3acb2ea229c6f8b5eccf431bd68c4badb7590fcf2092332aa1fee30b2395fefd0f3cdfe9a40c0e94afff8bc14dd51d8b8f001c0e5bd82fa1899b07d2690d180dfc7d5983e1b530ff7f6024db1dcd0f98eff1a12517951c0e0deabf1867cdc9532ace02fde535803df81d2aad3685c74033309717173efa32a5a40c330b79488a4974033f16e4b8a3e040b25b5337ae4cd0ad98ce72b21035d207321ef531675cb2546337ae7672b0a54b810ec359b9874e7b184b58867f095bcd9decb3e852021416b09ca3f2daa0c52fdf4220cc947a6eee29ec5d56f65e53b4a02a3b58603ba08b2584fe74c0daeb61f7cdd031de947bb7799ba5ec3039a488b591223625a88dcd466d99396052b310ca74cdc2e30dd0945d5e663b2afeebfa0a5572d24a76b570494c73ab194178368a2f12331ada92cb9a84f49eae898bee93d3e55ae7e90db1c27a2096a5132c34fc20378a83feccc707d52812f55fde0ff2e1c6932f16a9b18aab25dca5403c2c5ef94c54b996008b59b63044272a2b28d5be7a653cf50d5ddaf43722e8f691b5b409d38f61dc96ee65b3fcfafb2c0e15c14a047b5201deb971c6d6036541fb23dad3beedac3d007347a074e6cbf3acb7c7b01111eed3c29b61b8c28f3db45963504a220bee6e11873821471e021f9c870e44a50637e0460b09424f89161afcefc637933ba008d148d32eb1533e49712dae6e55c49440c1bb538dcb829d22c1a12503e7b6f2e04883d9f2a0f55566fe633a7975cace2c690e5ac873f359d7bb48c9e98dafefa4649c4cb79d6f062a5664c1ab0bd7e04a712de2f40eb68fa9992f4d072eb5fef52d29cc8e2fab7ff9ec75d3a823fcca43ad7313d6ee0496509f43970e5b887f5c8e59ac9bdb12921b1316d348b67a5d7c56b314fe2edb26571c22ed9f532228a5d2267fec066e41fde5e69e5ab177df2fc3b2f0e0b243a4aa4b3f125f65c4a01714016b6925c88d1c2878e1092f4a8f9e9461d5fcac6d1b125f049af6a47d19a4e01fbf5d6da2a304fd7c28784123ba4a3be552da363a5ea3b40b80418451a609e1311587a5d0ea16d74c52e44c611c87da8c70906f0c9e2c3321b5e4707e0abd5191038763e2806c94cc2a3968554fb39af2e8b686476ccb9c4eebe6f53c94f29036af6277d674d16fb17931ec6cc2012913fd8d3a09348627b5f553ba176e86596034208b1b512c02187b284054f1f606239dac1014a294fc6c09947a514144f2297a2d9779e711c51b2ccd846ba2e1ae3bcbfa6fc74affa83f9a4b975cf26493d761d3294619432a384ae0fcf6de9bf3615c086339006be226cc44346f712a50e5c3380f7415d3b084d42e556a1ac629bf8af4af6efa8f9ffc5cbb4e240f46438c31f63117056ac040fb777348e43ba90599d05d3216b97e9d0733510779e3debd7b595838e42dad7ea7e637afaf3d09749b29f1687a004d5570824b1c1e38af99204e667cd5dfa92fcd9d67639d3eb793a8b531a3d72b94e5fdaa225b3b9da6226e2a807dc33ed220dff6edd7e31d734c86aac2db8a886672034d73b9139c7b6a6e7b867ef0ea634f482882ba94da1f1f1f06d568ea07df16c4014b5ce60387c37a7d164f6c7900eb6ea98e885dab0c420d0812034ac6ed0df56794684bc0aec44291e83092adf6764ed85004f2d49df49e98cc6d270dd80701cc2f1eda12cd03cbe5f480591f16ff211dafa69fce7d2451edad3d341159c32f9109e8c2d25c4ad3b81a70f5147c083444ff1ba0eb603e14102706892dd3fa92097e18f81ce46f8804271f4a6c74129c733c155174d88479cff8a7d3643b85bb43d14b9098fca82528b463bebdcdc36e937009428cbf6c5e4f4ba2ef740ff2ab1674ad47efb90cd0d4e65d4d276d9a1b699aaa981840df38758a8a16a79fa1165e1974f2d9603188fe20efae0e7eafc571017a283fe15e43458e7fe8ff92e8c8f4e6a048dd9cdb801c2a6d77049839d9828ae973e0edf534b84bbb6bb03190cf996842873b42a152e338562b8c7f9c0f5d5ae58c1a5ad407c1ee61e367ca33234bfe5270a73bb44719854cb7bf4dea61be5ee990823a00f45cd4db6ea36ab2581e5c8cb1c629106e8f37e9f3f7c2ed2acad99d622447c420fe691f9c9a1cc3bc98d4c464c1a63ba92f0ea4184eed55ca47648cbbb4158a2e884faa922157dd271daf6263193721bac97753777e07905b296e9ceaa2d900cf21f04216e0281a9a30a0b1231d5ab2d6443f4f9d51e3757a050309ff6bc97ead7010b93fea6a65134f7a0339044b8e9391cd1025a5bd17885eac4d7a24dde6a579cfa1901c4040c778ae2194674728ee28dd65106d05e7e4011034a887a7730f6d226932b4fd502ce8caaed47ce21178772adf9014ba33cfb443b1ae85de686901877fb6ca52fa5875bc6ce3c32cb979e7f132fe7e9d9ab40b7e6a6a0391a56c5fc2801691b4b99f37549995690382f6d0f245134cf947e8376941303af9071fe13859a4270ca4cbdd0dafbd00c369011687443b59f696ff41c0500d3a7be4d3bff6a66d4d5e1a50c4f490343c3e50cb03195c88b95e64cc0f295350f22c3b8fa8b7d25cf2c5675df228ddd696637ef8ac4e01137dfecd9edfccc31d83dd34902bb2a54943b8ec8e45b5bc67ce207241ea557ae3edd509bc80095e502a18588a2b97d9fd03b63cc2251ce924031726ff40dc6933f9f2b66875145a80a0e896c9716aead406b369333f88485cf1368cc90aa6d1ce2f68f82cbbbf3efa8ee5696f8b2b326d41bfc0ed85e0ac9865b13c2386c5e20288e1dc58448a3b49454af33caf60c5a86c41d23dc26e7c2828ff23917ad0be9b8ec8a8cf78c6c6612f5518c8330fcff344ce7c7a21b1f89953b698cfc7a4ff263f9ccbf5db65f07e62c472f0d1419c396487c460fda06b2a002bd65f04242089e7d50b6870e57265e11b5d49a071ad1b4b458a09500d48144a8c62ee50e8d686c2acfe473301d7d0124e836e4cee32e32ac7caf6c14eefcadca6a7026caf566c0023284bf64ad701bd646e76af7664a86a228d4589eb88a5cd28aa059d91217f3a937b6e1f32afa270b53f4b1795b3f191421358aed91b942ad0ad5858bafa748646c6b9f65e401547acb20e8c6f113363f680a077720dfa730e3dfc704218861ca0fbdd928b226494b69beffc257334fd4e08f58b038c15751f9b1d41b4ccc3290b4dafb2c8ef235833f76fdc8863429276ab1dd720bb139db46bffa11219dcbd284ebd2fad0a1040d63fa37a91b2f80902f2b035f8786671a19796467bde9023c6994516424cbd04281d25c3dde5a0ef1d4575e4a00ae782df2282f9db9c544f5cc45a685fbfd89d0458c112a17cdec4e2e039183e4f630fb81ad1f2a0e33f746c49b38f298387a3ebdc226674b2735aa2b58ca60b599c4770800a796d39ba8d24d86b3efd166a069a8047836c351d829eb62f25671e82574faa1ca1bf68219d5042f3afc59081c8f16e5b4b0d00c0c388d3fa4e7ddb0b0f863c0a24c8cdbca08ec279c0086c352b12f4e386ddd8a01338f7268ba7f63489fc401df63fb742de254d85c6693e03b4d150e1478797a20de9b409a0b4e5ecf177fd68540244f5bed875a9d32eb265252d079c7a9e17f6e4416161388a4e332218d4ff3621c1b924119bac9293de73163066f1c73f0c1295b3eed9643cc966590adce42aae3578d382e621b3b1ca433c3bcfd5e9d0a27085c278ef0f187dd4e8590cafc3262164b9b5d89affc33a2387bc168d4e099f3c12ca8545de8bdcccc593a91c71abe9398cd42b4d73055350857cbad1929c2c29de1600cf5a8d83c00ab24a600075e5a0ccca035161fd8d484bb4fdfbfad3091077f17a2e3e802d8c5b36376d693c7ee06476b388e3e9478cd15a4661999670ebe4c21cc663f33892ed489849ae4f4b4b54c320636d8e3a8b3be2ab86d0854aff856da8c8e1aa7dbabeda7b56549d44f14aefc0f083d57055521177a98940afe74ded11720866a46a5134644c3530a5fc675008a08e590b4daa999c3bae8caa1b9cfecc005e7a5f98274d866adfa86f76dd947ea5d09481787c48929fddece7441b3e85ed6bfb846aec0c8fd9b4b0db2daec03a885f938afea3222f0a88343bcad7aeffb3e95310f8181c6a73d60c14dd1d8318ba7afe05a405c77c8f42cca43ab7b1f1f91a5ffdddef6929475aa15df3d23a2470be8b4c87d230c9fbb88ea348d8a8483167da2f9289c6e698177d6dc4e8a2b70b2348b0f3589a0cc508d725c7ebc0caffac14d04b2d5fb262aef8708e80dc42c9e6aaec31c12fe140afe6e404dfebc94e9e04db94c3b15b2c4d2f101de1f91240b5abe79804abcba7efe7bb57794f793a74aff82fbfd6d86bbd29bd52ab4bb7693115f3351a84d6d4ee8f097e838848623063badee9eae1650a77c9065095b195177c6651398a08944e2014a6ae7a5d61b7bd533a2240846679a68eb2863a57a9bc05dd940f0ad27df68c1db7576aae6245a04abdfc59bb66d15f75e010c99b43e478170747a4df410c0eaf779de363b9363353aa2d7452ab9cc353c38d7f78f7804fbcc1acf385da9a56cd81b3780bb9fa34c25d365912232f4cd655b3854f0bcfd88aeb7c2f8f732f0abae59bf488801d8842972dfae0fa1036e2e66f00f1cfa5beea48173a64915d8ca9ba74497a5beee3af229fda8caeb70959508d817c0f6640015caf812263de88f5a0c31d65b9022dcacd6b8bcee9f14235f9d92675eb569b5e86150246f6d11c613576cc80e11ae8447813ad313263186c42b677acc23ac701f950831c468887a126b908351dfaeb9be1ae83e013e47eedf6553c6b75d569d7489c0c827c8334df58e0f1546715740b1776545bf08e6283108050f82a9ffd98a8dc8eae42a68b58ebfad9f4d721c0b8863f57bfacf1169e2da6d37df8f0c20ab85ee16de943293e6c12d119f7404310e29360bd517de96f81b8ac30c86f60f9c59795c7a5d069985f623511bcd8d07e15d391f2f894bff809bf2f718751483db6aca5ebe658071d36fa6a5303b5a533209a07fc0470736574056302000334ce7af071342163a2d94f1d8fb109e22745c62ef02ffe6aa559067a7f2e9c0d7eb6badcd1dad503c57eea0fff44d75ef3fe4ce4060284bea46f107e62cd17245969a3b0f08613acbf3193def7940ded98c44a672c29a374a253b8e7117844c107fc04707365740621029e5980b4f9b9a9fd568c1c4b48631a800c310405ae8b2ac41ddaf87add3062f107fc04707365740721024b1909e1c80e8ac00cc4954cd2cdcb0cd1d4a38d2c16f0774480c0b50af89c7807fc047073657408040100000007fc0470736574094920000000003b8b87c05549a8cfd6b34dff0d58b5f8c6d8bdcb6e660a3ca320e377a4eb56df63e9b3c37251b3b779c3a433c57ac05905cf1782771ac4435dceb17d10be290d1e6d94ba07fc04707365741043010001e9a50ad3e77b73d6ca67913e649819d9f9f34a58392c6a4c6471ac9d95edf2f3763f7635ac35a3d8120d59a8a8d48583299540c82be7f8dc74b986a859d7483d00")); + tests.insert(String::from("pset_coinjoined_B_blinded") , include_str!("test_vector/pset_blind_coinjoin/pset_coinjoined_B_blinded.hex").to_string()); + tests.insert(String::from("pset_coinjoined_blinded") , include_str!("test_vector/pset_blind_coinjoin/pset_coinjoined_blinded.hex").to_string()); tests } @@ -138,8 +140,10 @@ fn main() { let tests = test_data(); // Initially secp context and rng global state let secp = secp256k1_zkp::Secp256k1::new(); - #[allow(deprecated)] - let mut rng = rand::ChaChaRng::seed_from_u64(0); + + // NOTE: Zero is not a reasonable seed for production code. + // It is used here so that we can match test vectors. + let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); let txouts = txout_data(); let (btc_txout, btc_txout_secrets, btc_inp) = txouts[0].clone(); @@ -164,7 +168,7 @@ fn main() { let dest_btc_txout = TxOut { asset: confidential::Asset::Explicit(btc_txout_secrets.sec.asset), value: confidential::Value::Explicit(dest_btc_amt), - nonce: confidential::Nonce::Confidential(dest_btc_blind_pk.key), + nonce: confidential::Nonce::Confidential(dest_btc_blind_pk.inner), script_pubkey: Script::new_v0_wsh(&dest_btc_wsh), witness: TxOutWitness::default(), }; @@ -181,7 +185,7 @@ fn main() { let change_btc_txout = TxOut { asset: confidential::Asset::Explicit(btc_txout_secrets.sec.asset), value: confidential::Value::Explicit(change_amt), - nonce: confidential::Nonce::Confidential(change_btc_blind_pk.key), + nonce: confidential::Nonce::Confidential(change_btc_blind_pk.inner), script_pubkey: Script::new_v0_wsh(&change_btc_wsh), witness: TxOutWitness::default(), }; @@ -226,7 +230,7 @@ fn main() { let dest_asset_txout = TxOut { asset: confidential::Asset::Explicit(asset_txout_secrets.sec.asset), value: confidential::Value::Explicit(dest_asset_amt), - nonce: confidential::Nonce::Confidential(dest_asset_blind_pk.key), + nonce: confidential::Nonce::Confidential(dest_asset_blind_pk.inner), script_pubkey: Script::new_v0_wsh(&dest_asset_wsh), witness: TxOutWitness::default(), }; @@ -242,7 +246,7 @@ fn main() { let change_asset_txout = TxOut { asset: confidential::Asset::Explicit(asset_txout_secrets.sec.asset), value: confidential::Value::Explicit(change_asset_amt), - nonce: confidential::Nonce::Confidential(change_asset_blind_pk.key), + nonce: confidential::Nonce::Confidential(change_asset_blind_pk.inner), script_pubkey: Script::new_v0_wsh(&change_asset_wsh), witness: TxOutWitness::default(), }; @@ -264,20 +268,16 @@ fn main() { // ---------------------------------------------------------- // B Adds it's own outputs. Step 2 completed // ----- Step 3: B to blind it's own outputs - let inp_txout_sec = [ - None, - Some(&asset_txout_secrets.sec), - ]; + let mut inp_txout_sec = HashMap::new(); + inp_txout_sec.insert(1, asset_txout_secrets.sec); pset.blind_non_last(&mut rng, &secp, &inp_txout_sec).unwrap(); assert_eq!(pset, deser_pset(&tests["pset_coinjoined_B_blinded"])); // Step 4: A blinds it's own inputs - let inp_txout_sec = [ - Some(&btc_txout_secrets.sec), - None, - ]; - pset.blind_last(&mut rng, &secp, &inp_txout_sec).unwrap(); + let mut inp_txout_sec_a = HashMap::new(); + inp_txout_sec_a.insert(0, btc_txout_secrets.sec); + pset.blind_last(&mut rng, &secp, &inp_txout_sec_a).unwrap(); assert_eq!(pset, deser_pset(&tests["pset_coinjoined_blinded"])); // check whether the blinding was correct diff --git a/examples/raw_blind.rs b/examples/raw_blind.rs index dbedb78a..18a0e974 100644 --- a/examples/raw_blind.rs +++ b/examples/raw_blind.rs @@ -5,17 +5,19 @@ 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}; +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: /// Simple transaction spending a confidential asset /// with external signer and blinding done by rust-elements using raw APIs @@ -25,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 { @@ -34,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) { @@ -43,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() - .as_sat(), - asset: AssetId::from_hex(&v["asset"].as_str().unwrap()).unwrap(), + .to_sat(), + 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, )); @@ -124,12 +126,12 @@ fn test_data() -> HashMap { ); tests.insert(String::from("one_inp_zero_out") , String::from("70736574ff01020402000000010401010105010001fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f040000000000")); tests.insert(String::from("two_inp_zero_out") , String::from("70736574ff01020402000000010401020105010001fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f040100000000")); - tests.insert(String::from("two_inp_two_out") , String::from("70736574ff01020402000000010401020105010201fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc0470736574012108a337e7e0ecf24c121a17193623254c277a306e9fd39cd5aaf8b7d374f4011c6507fc047073657403210b8e11e3b8904ac80caeb16af1c93053f8a11a963269bcefa96823d75b8640ae940104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e1060330000000000000001efdfbc0022a6437ec10c672d76c513868f403f6b9706e09733d6624e3cda831c2c199dd4c5763054a1c219e079e8cf3ce0c00dc2f16516972e1e64d576adfd9f5d778675c8a3172450a4cc82d6e6c59f2b16dffddc902d0aaa647b750d6224cafca7239a4fa81219cf2ee89741ffc5b7dd7e47d332ca931cffb1d1d432935a6013207629118c965b7a5102e62c43ca9d06bca191307a476738548536809ff6b01c6b5b25d76ff2f67d99e20fdf7d2eff6fc248186d21d054196023c5e4f572ccf0f3aad8728c46f2ff6756ea39de46028610a3d26cd42978b09e0e29e0a8aa46e4fd39d28d028592560264cf1a794c27f6c95d382f486dfe900a81d9d92935c7e0e6306549b3e49b1f60182512ccb994338c3541a2956139b2ccb3dd156853105abf5fb394cb2c45915dfd4106c7472dc5d360ab5bca408203a3fef58b4dd33b0c11c367dde2f19c8af7682be067244bf49a2b8cd4685f5481cc31ba27fe2f3d7b7a353be9b41e4eee2342fd70b8408c91951c71c75dde8fbc03e3f28e6d3d3b41e0e963d3ba0c25b2eb50560c21221950b0699d2615f0128e6cb7fa1b04ac1a046e569a7e87df98c14360b8ffc43db5e17548c7ea5056f84ffd14f4cdfc68a4f10e9b391cfa63eef2c2d623e7cdcdfae2fc4d63496d81462174ff360809b7e3b4305979d9cb8e9ad5b0f012494d31ce51ee6489555b09dddb16641ed9e2534fc34db99d2a4fa736eeabacf2f8cdca97f9e84c964277c6f30f1af7fe2b51b39b487d56ebaf593a3f98e811cb09849c5b445d5b7c9ba37807bd0189c8bdb2709fa70c230f9aba41dd3c62384aea6e1ca098ffec26367aa65a09459fca074d1da0365cf7fc2d8310ae099b838ca78e62cee10f95ec549faac1ff0f8236fb8cf2c0f6654e471d3950ef45e0159c44f9e343d05b3af59b939cf76090d040376407c41661eeda7d2cb61cad0088a286948787dae0cc5abdcb97f7f42026c65a13e1df1357c25d376955942adc858e73876e1d8812969055d55decac9a689dcd11dffe5cf6e06088b93a11e153ffed104266bba472cebffb2b0cfc8ef132309bd7836071d3b6ed459a5950c64cccf230015c98f9210f2d57b7f3a07c382f3df09f055c88e1f312db0d60d471afdc0b780d319a6229babd8f45edecff8d1073fa850f755219a3ea14e7234cbc7590c60eba0ba0cc1afde6ce91c8e8835b1ca809926b3e7d8d7a0941425e5f08e884f12693eeea1b3651f53da90972aaa37d426f37db3edae4285db114cbad5964c269e03b15358ad2a7242e0af538a594fc779ec3c43c3d94fab2028310c6d0acf3efdf0acf028ee757c5c02bb5b8b691aa5eed1a62acbabd0d61faa478cdcd54c6db2cec6144d8d1185115097a7da79c16ad3118d12e36dbcb7a70b0ddb27bddfcb1c6e5426b7e411f607d22c3ac2b8d3e41f55faf5e2105bf3b943846cc4c33edc64f902c1eeda09c8110d4f3ed0a5e511156a3e368f02161b92126abf649341ddb8ed03d1d41b91fc34548c6d94dfb47c088ef27a3cbfe1c9cb05f2ab2f18b8746394c8080c4cd92e818e46f861614ab870cb7ae3446e376793f3a6568a2ccfc2ab0ed0365567671436fee6cf427b6410a046d80b9d88f094924ad370da363e8eb70355b711687e92d88a08ed811ff241c7a6dbbad9dcfc18e6a42493483b938e36c1edd2a1c6e078a17c5d145c9c058b4dd69afc44c345f1c88afb95c1deb5c4994161ba25783165d43b9e50a2d8333a8037cec2ae809a3dbe026d6ba40d60badd05bce73b0f9f36966c30b9c0cb5776544c1182024a96e746a3b01f9db10b45aaffd3b055b02b40bccd41e57c10719bedb0fba99a0f6b0868b186fca0397ab8c219f33190f81e4cce2fbcbc0975c394919c98fdfd7e25a33e5f31fefd06c8dc409cbd3e743f0f48dc90abe45b2e68948436caa37fe9932a77b7e0fe0819d8283964b0eee2249d9190f3eb0bb8178e10a287be1059f35cc1a153dda14def65f3c49cdc5186da86bd3e965446f914e3c9b4cbfcfd2c379f306c5ef8844c4fd398b3c6f96601e90d2dc8810875663939f63abe3ee2e1c8a9c2c2010a01d0dcaebb556a7c98421f8e6465cb0434c07dcea9db1a142f9684e50b06c545785f0dee4def1257e4bb22d87a2b37ca9eb53081eb8f1ea0439c4575abac435868a36552df569ccc63477594ccf7eadfed6adbb8e81800a2e2fdd7effdd1e2f09cf76c9e780f6f8eb8408a3fcc06cb8bcd28db7a37edb0232a6f0e509c684318f179d0c91a97718ce3956c266790361ea3a1bf70cdb8a2f2a59b06dee18075745c7302db9b13a452c188c5624964af2d5d4bbb1138dec59df5dfb077a0f62ac4db3de81f54365f2a4a6dee63a6092b4660d3f5dca3cc8de3bb5350f5dcebabd515c72c9114bc58d96e2e863106f0982c2632bbacf2fe5cf6a8df880c550f7008fd09227baff82d4eb031802fbe7d50f6174860c70fcd9f0356e34c0d45df66492dc309b260b7158adf678e2da66348ca84de3e721c6196e0c717f59cb802c6866defdd9032a4b7da82b816d9681e5eb9115fc2a572fbbe105f479ab339bfa5961aa1920346c9ae4185a74aa828da78a71e55876c657249e83f5812cfb055400da1db8bbb5ed3ce2ca4655b0c39b698ed7d235fb0ed4f29a7e8925cb873176efcd1c6981dd23865468c6e01ddf61fa3e40d6fed18d8e3dbd97a08c68bfb092e441e512d44089cf563509785dd58203949a1ca9b66a700db14060a760aa404e5e9f31eaac015f1527f6d760d8714c88040b87fc8a4d183230cfae35326947e28a7a37eefe1d77070f5232a0d67e278a45d649709a7398cbd43094c5001263671517f83e62e79fb75e6f9bc592fb3bbececa3f597dee71dda0fc909079ea49d81554d2fd79d1cf3cf25d1186efb83cf972b7426600d7d6eb6c48a5f0e26af640c733f3f771e0926e6b38b6f39d7882b0538dcf281d92a6bd361bb32e16f3988d6790fa0a45f549e983f4eb68ce5ff11647b37f8e4c444aae8bc0f7c49ce7215545a29215b55f37dc42aa6add6fd1fb45d9ae580434097e7a8686e23cdbdbf6f8b6b1e5579a7908bafd8878b004c7c94e045fdf2b94f8c1a75ccee7bfabd9bb6d0ea8a60dca61053636160c19f8f3fff3d0330fc95d20a1393629f33f281e5da80a5ff66aaa5eefcf495f8bf7e24744778841d7c633f01af2305a122ee093837998e87f060105ffbc083c0d71f68c2c63820d7a547d9af5618544efb9736af56736e73cb696f191c68970ea1deb587231c889672b3b5399b9b5e915e3c567474c3905ec5b6468da826f1a6438ec335da847db540a091ab311c6846f96a2f17befa2f29ea491a41d7630e42583ad1212e6c606dc258a49f756e2480f90775c04c5c533300e37f8bc7afe7b155fb95877252ce4a53f78491ebf9d8a4aa41da1848633816542901d56e66f126316c80efdaf4b457f9ce771edb012a0b3c27c717f5cb3cc99ffcc959a02289e30d5b1ff936320579c469bd55cba6e79a0c5f1bda59981b71840a8c1ca56863e91eb21fbaf84525d5f04e0f282d03bb56d6dc2352f163d8357b86bc6e4e621bb693db3565eb9ef5629af537874c1cb3459582463362dab3c6fed7e574ce9bffcc685b8eea61599292cc69860c4f9584818182f94d719dcc463e9a6f854405ca2deb3de29ffa1826f795b7e7ac2555c0ee576c75494cb832c59de8d9620927167bc136549b731ef79a39fbf8789831cc003a772fa00d4d699d089d47037e12e1c6eb8c20c5535225a33a1a787ba866ad481e9a4d689c83d75f1986405c82154e312ce8b3494ac5721b96193c025bb75b2cc974f9171297da058d2ed4f00d94e60737af29da660fc51fb9bb4d241e78c9d1815d7e3ea90aa541c4c512ab423ece93f9ff2f479f464b953b4171217b758b280c5d8acda32cced7bb0c92cd9b405afb3a8405602da914e1831fec5b6a291f90635afe82b9389a8b41d957f81be125de2f943b7cda9e873fcd74b446bacc00bdfcbe643053f6cc8162c0a98343242b489f59f7ebac016a7fbf620dafc15c8ecfe20b954153dba19eb81087673a1e847238864036e9a300239a00a5a03d8b39aae2bc201c04c477b1f2a37552670c06ea65551c5c88a42476d0d797af494b077737cb48050bd35f4980964b8b9b98bde84615e8fdca407230f5d15e97ae65d63a96f3d88518fde12d6a82881db5c8d4cd79e724312e88b394129de3569c5c99bf842a90f193c91fa55a82116e237c877dec2da6dc652cb33e33df47420c5238fb6ba5fd96cb77f0cf6c8a12be1a95186680d16a064d9b1a7c2461fdf5e0a512a5a3ed2f8dc0eeadb6c95a658aa1c2713fea473de51f65b13a40a2fe64e5227bc9248f59c2bd60dd2fc14918e333faab6e792ce2f0597be47cee70f5a8b788a50c9cd153bf24786aa80f631ad21926e3285f41c5125a15c12cc889112df0b1857020160122c2595ab3359bcab184ecb32f7cf8f38eea23f4f9135104629d8f1e8273c44f8afa129d3a74cf58ce494214629cd53b99a8489ddc5d339e317ce75dd6c5dbd098d811c56ec5f19c00fa00b5465e39f8f2022c71ab3ed0d5a24058c197719551985ab30a851390ab5e11420c521b1398bff0038d904e5879db37d60c623a0a82e191cd0b6bd20a9e956d41d5daef703bdedd6bd0e20fb096423dd39706e939e796aa8bdf071e44791d2e1ed4a81ffd018cb79c5b6bdfd6297cc6cd379845fd7ddfdb0b83d7b26c21e3edb58185e71472a79cb65322579bd5e0e7b1fe081dcae5447834d70459f8a96b341a26d8922a2b1412f95f3533e04b5f65be112cca03ca45ee0eca10adc7593c78c041281c880d879125e585e68f5c7fbf8686f245cd736ab5aadc57c032c637b753b49575acf4bbff46883382f408417438d7817097accae3fc54afc6015b84b3b05627d7c83c4627c87727bd24c4b4d96a08c64164ab8d4abe3fff5c3b09e8fd12498e3cc3627e799e4ef72a870cd89598d51ba396655b4de66e48997faa51d05d581c52f52852cffc9f89252f26d314d2fb8fcbce197f3a8f330339c2ec3c710710b74f0915da3bbb638b1f1a0cb0c176adc151da0ab52796d466b23e8a269c5b22b76fe7586cbc217621acc955a43571dc04f69deb50066cc587595cf0ca4e9a00a261ff2791ada8de04bcbbea249d2a7840bd8fd3ef98a248eacb7fea5318b2e207668e13ab4e5578c03315d44b478df5247d8afd0624d2a57adf85fd16d86ae54cca21f6c91d8cafb3b31e3f9c4e903c04f39d67c90560dd10b2d28c6ee937cae01e921c98ddd833a9b9247053110c0f29d2f35f8ea322a738636f17648b627db622a9cc891fc1d066d5fcabc168f1b492479b0578d0bd8de0298d4bb96a4351714a10a39de49d57ad66bd6b458ae0d799a19da54bf620c0e6979a95b61a83cd06e46d7d263d53d66f77557232983268f5fd01d10442af0c840ab09c6cc4ad77add6379eeaf98f7cdbd70fffcb035abae4efecdb7e6f63ed8acebd8449b3a910e5eb06b0ee6d80dd45026c9e6c2f978f813b2800386a4d7ad9d127d76c4286c1b2facccfd16e6a263ba3e2893ae5e42049d421e08a62ee9e9574a1f70f0add7b68344256592513cc15f4bc64c5a029f60d617c245c1c34d06c095b2d0a9647e65dadb33ea79e3e0ea62ce55347d24eaab7cf9fc33fd70c07363771f78a13d92941daf7970c97b43d2fbe3e789a52ae7bed64736e6ce59f95322d20f9d6dc00b5824a0ce9072dd051c66ac20d39bd953f567a831d75fa91fb4d49b200fc893489e60df95c2810f7fc57b7f6b7725ae600e1c2def6a943b8d111ff3e9a5ac80aa981fad8709e98ee1550005bb8f65dedffbfe3793abb407fc0470736574056302000359a33e8f3d0439eb7d60aa79255858269bc1011e4cd0d4aa753bc2793f9638fe70dda4a99191f08700047a8b66d9cf7a53bf70756013c8630419e3a0bf428158dd02ab6e16822f239df0a6f1b67b6e1335509a286b30c4b11a22dcea9f95d51307fc04707365740721027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac0007fc04707365740121081271c10f0caf52a40e015e7d35966dbad39525a6c0691d4beeb3bfb22af5304d07fc047073657403210b85bd6dc21b4919f6ebda7ab86ac8122c793be3fad19e44455945ddec8b59e9f6010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001cb6f1101e2b960c9a2fe480eb88fc76c63e58b57445c78d88e04f740580a36d6956006ef12ba606aaca89796dad5ff7cb4c4c7f4e65a07fea8a2ce3b58b265d4b3220013a09ecf4e01023aabca5586d61ecd5d7f2bedceb8b18a8e41451c97a192098b196add06a8a6b619e1e731fd9ae2973741aff6358c1ad45cc873a2d497271e3a20873a6f701d4c44ef670c8c7a9793079d9bdfe956bdc02a52d1de8b2de58b0f7d309240999f13985fa8c98e57fe1bb75219ae6330e9495b523e891dba7885a5302817d68a9891ae0c2597d4d7833b91b073c4f9b27be1cf81ca1c2c1014c57a4affe7b402c0e0c1862bd17bc1d43428671a106c26cab61d5e4ffd1c01f6af1c6e97f7a25ae8aaa8d2c91c57881df52bd5f242b127ec7aed5880101caa01783884dfe943ec9764473323f59b27a204f34aa1fe7fd4433fa606608b0f253247d149e3e269ae5445e917390be83d5650c05859e3a817a6c7e561e2b12baf6d4ca4221df0b7f29fa134269139afd9cee5c7d60149dfa23031705c6a8b72d00d659d7a085de8330de2e5c168ee3eba2180a9319f5e8e0806dadaf0fffbc2df839571360b8cd32300504a527cf914da7a1e788261c9cca2872ebec5a17cad33b15a7a7427c200b8746761359194520f0da1d7ae03f26190d07cf92d515e04815f514c66a98997320a028ad24e83182f213d991accf19a8a59003a2ca666ca505c0d8349e2cb22887efb7db9e4a4e3e2d8da0d5f032603e03f48fb967fd018fc99c6eb72acee4178edeb48703903a89e9462a2d1f447234c3373fff8b205848c7a9d6e45ccc31eeeb0105ed3af1188fb847cf6f03a7f0a646fa325b672796168dbbd9d1d19b2f41685b18c3c96f1cfb7ecb1dea132e8815b62647820ac4b12597d8e737a01a970704b03ba5ae68f8d64a1810db10427fbff1d74cfac91561484cfc8d3b23f74ba0f37e7db2f942c58fb1dd30f6a50c57b5eae720c6ed69b0fb81a6e0260c53028c732a544391e319987a24548230378f8f1ddf65a2d654c7b26951d5c6546cc8fda6003228e8913c9e3c5adf7b7b72f4ff5c4aedba6096097f9a4b58ea1e060609e8d1f0fb0e5dd905b29fec72b118d40c5f599a63f9a37041742a7f1af3ef951c31833621842ec212f9fc5cd3e8c08278ea192f8032ade447d2f35ab59810aff371f598b7dd57b3cde2ad854ecad0c786150748ac241ecd67cb868663a6fcbe9d68c3dc221d4fcec9b0ad89c337c1e10b91a38ab1357f260ca3a084da536e56262973e6c6cca838601fb2f335811375aed78295c4b17d3798d39d4cbf6254ce57680a102907e5c08b5c7ec0a1e73bf3d7b8babdc6156c6ca3f09c0423d6716ff4e6ee0338cfd778158202b5ffb60b74558dc6d0be9b5aed6892c4e0ad3e20de5b604b2b010af09ceaba7c0fba5a7fed39d31cb2769909a251ba5b0668330f734728f5c6786a8f1a35df77f3d739eec71d9268fb2a494a14bf7ab91c075023e76856d1745f5813d83878822934d0b4cc0538048c3f96cad7aef2bbc49bab139915dd4998ed3c6701973eb7913530b14c688f0b344ca9cb990de2a9b12fa7ad8b292234a33428b4d16813693964365bd412fb936c1461fdf8dfb0bba31202685f29addf95fc65f841e97042bc5d82f103e41017c6ac91d3d266e12db2dc195ac4f6d4353924270c7144cd96dcd07b9612e2bf1e0f7fc46ef83690648395c575a13c2b2df0fa04f740340e6b116b0866a401de22f68fa3f621d75b9f5f625cd5d5d7e12a5b0ff795749d742eb2648ddf068a451059390da566b3de84f9d528b2663de7dcc562eb014071e8277a054783d322fa67b9e5261b87e9c354ff58fc52bf9df404139311d6bf9650658fc94835a6220ca02a4bfbf46b2af4f30b1ba681c0de7c134d98dd52eb1e937c191cc9557aa73a343b99ae0b8954a3e929174984b574da75e90a9b71af1215317b04e4090770ab596091fc05c82235a324f86280f7fa081360ac758c1980c6a6763453e6b344611e46b05760941f9517ceb298ed7a2d6f96b11f5719ed0dec803c511dadb8d056d4ca06ca0ad090558a4fbf3a886f3ea9c33e1bb4bb5ddef4798cdfb384bea75a3794c42a53af60f12c95fe9b38d7f6a5d914ef100508091fc84756b596b9ba08d1dc42a7e253435b5481d1470ef00a1172cb036b5bdfaccaee02b15d099da17f9753846226325232e71ed9b48f57697bb3d1db75ed214310e6e3608ded6f188e37b21b96eefaa396db0df05a86416fe64aa950eea87e7af4407c828a8822b17be661269c9f9e365cdc79d20503e2d5e575c5387fa961206118dd379834aade7727a1b2477a24f05f6c2ba3156fe7f5b5ba6d7487b1a124f38e3ed3cda92a8edf05f28e3d699f0c12de9cfeb30ec8d4cd253287a6d3f596db0c06006a531e80083f5b41ee26c05d529e0a59ec151b4a9260a7ea8c1a175ca41b59f464adc122f448fc43e90dfa7b88f81caa7a84e3d6d4dbb85e5828b6ae7455dc2c87c8d9c706af2e62763675851b4ca449c9c0f95d953ce53c5327ba8981321a1cbfb76bafac7fa17c99567725d8e0ac288f3ccd3bad666e515156517281c97a3dbfbd5f345f8eb80bb3c4230301a38b7c920d74eb910d380f884a683f7d3e2738fabf9646138e8a32c35e43786e67925508e4cc7b1e7a36abdf6a497077704e216f1e730c20ecfcddfbdf2484d8eb0ef9b7478f77063fa90b8afbcd019d1a584df58c537b40b193f246598ae4c53958a947dd3916b14a5a4227f01bf543a8c5d6ab8eaefdfb959c8715b756a9c797fe39229dc56a86364cb367fa37e16e96cf8dca1fdc8b6a46ce88e3d29c75b6cc718ca8c28e9e1ae991b28c286107c1dced150814d6369cc8003b2976018941df7fb8eeb68be57adcf8a0e813d2fcd16dc390ce17dd0a91dd674d766d21b5de7f4678a548d75cc3de7d099b418fbdb66b2a2b12a56b1dd8b3f84a7b45e36db9bb63b3ef9fdadebc21c6a75364173925ffc69d481024e49076dca105795e7a64a3c82011889aacf0114a1bd31414e288cbb63eb47d399d0e025a54f11670c8ad4a793d0e7ebd3b0d8eb30b87cf62b131c61a4c55d2e56e47ae99fc543c57a1877ce44dac4c6f9fd99552701ade43858e0b4020230cb4d42d1990bfb509471a5c1c5557c29c00072c5cbbc056de6d465ea2ba2f4a1a5dd604dbf998e834b9200c59fdf90bec38af9218e47ea5ed5512cf8b0146126a15d49bf915eb893ad1ef83d8faf6c93f944ebce3704f930b6f69a1afe767ac2c9fdf092c804c7372ae1a64bca3a3b5a31f2ad430ba9e61dab3daa3be90a538cf0fba00796de511cddb5a594f14aa6d215bd0be19c0ca5f043403b7dc56c87b092d9436eb79509c3dbe367c2fcbc2b09f9818cf5a55d5ee0e477888166bbfece661b62812ffed3e4167ecc81dba148f89a8f2e7c27424a2ee091ac487e68fbbf9798e8caf46b35de8dde66a7cad6dc98a426ea7b8b930cf2d7d71fe71066dbe51e8a1c7151e97fecbeedef3d6f8e01cd5576dd58097b46153f9c3bcecf30bc82b4cdfb1bbfa826c7c5b217a023e4da02a35d0947b2c4d24d50a292e4c70fe854a420c54490d4850ee8c372756671fe130532f89878a64977ead60bf604b73859ae64ed62536423bb51b30c6fedbc819fdae05cb64b87b153f4252b832d6b866100a7b307d1e705643f96f0724858e6211802b2ce706d5424b99315db90c7d74fac66951251f6cb739ea008f839c7f62318f795337ca4ff05886a2640819873ae00b933081b64da1101d1b17451357921a2f4d4dc86c92a5eb346fd6cd3a63315768b94b90a15162aec91cea706b9221330db154d1fd71acac90be47055c725bce38710f1b4b4144f331cfa9fe15ebf6535747669e4aa1f0d72092ef395f16a4ec9014029b5c9d1b4bed9ec81a7568f8f1c87e0ce0f4ef9cf49e3fe4960b0b7904614772c63147f3c5463fffed182919c4973a8256346116be40b10a38cdea5bad5878760daaeda57476e86ec65e58030bc8ff99ebf017e9bf6383f981edc83b538e2bf4bb573637e6f4717d74a3ac71f5e151259b29c1b7226e3a8ab176baa2f27e3e385cd48c6f072bb6e8e9c92e35d7ff51177f4e3553555d967c04befc1847003a8592738c82eef231ce3d372372a19a01b27dc7916f986ff3f3ea8872d4986bc1ca649a92924c8f582ae2b44028e711e955d2563c33db3b7734aa246de553dab2c5268538f7c2b6dbe9a70657e2a26e52f5d216dfa9aa8e197a227c86a7d3b468880a721b804a0c51af1592043eee7fe3657b40c1832a858cfb4e037d208ee69c4fd697b929383a971b2a0864544505af2267e334db5f3f3f4ed88e2f6f2b3d7b96016ff92a120556e4d1e50440943893b00508c6242bcc41b6d06dc29ba20564b42257b9fa1b5f4f453e94e72501d31e8947e09a38261020400db4b8cf675b1854639a962a57905cbbee14ea0daa9bd281fd635c8f900fcabdcbcfb140f4f24b47731782ca899284fadfc9d0d6eff77045792e139afad34c7672bacfa22d85fcc05652178cc169bcf52ee3cef281d99935abf9c9910caa7994cfe33b9319d9854aa6e2590a86297f23b0cada7251a6ee1b2f3d2cb5a193cae8768d92f556994e416d4bcf5bba3a295d9e2215291237df74e3c493189bc5a27b3fec3e1a1329c6c6c75e0802b40fc74776476dd2d9e3453ec1709a8a2d162a936d9fe7dd4b6f7abdadfa429aed4cbd5896cfb24a2d91f1cee781d0ed73cf7306d6f9545fd3346c2232b8799553357a5a531d6085d04af5f183019dfd3483baf23b0ba5a6b58bba4b7a1eb0a32626bebb5cc0c4dad45c55d135be8b0a4dee787ed48b90a2a3fc120a753b52290c27ad0eb32b668a9540986ca0256c1676debc4d19b1b80bedd81e505154264a199ef1a54e1fff1b3589fbb6b2d87a1e04e67066571b68fe04f030d473f8d135585f8b7be91f0ae1a4fa2f44008c0f727f6aba9c5c60c3a9ff22911e267ffa2a5d1c71ff25f96ad257be29a2ec641e2055d785765cadd0c2fe513c44154e74f4b8cc4b24d9ee2f6cfe245549713f9dada1935247a2ac7ccc13c4a5c00c6ffe7ca33c0cde4c75c4fe2ea041a965e4c7385ebfdbc348da8c4216a02fa829892c827adbcbb87745120667cdc5a4aef28b190a54b422df45b8547c447765838dbf9f05de338ab672706b59b2f0644d6735d9316e64a1a15d3dfad893bb1bbcbe1b574a0b8ef5265e195a929148cc5e97c8bfb032d4528b523b6a6b29209428f38115cd102e3b4e268333e490140964278238e7ea9638ac34559e05300dad0c3f18e9228474cd3a7186102967c33fc14f086b4f1ec3957d87f2609d140dbb9e7ff793101d1cb90d004565c6a32924715b1bcab483e6389e2e824054edef350f328870f16412aca0fee39edcbd02a6a667aaaeabb6654c4762c639a26286a3c1730ab75defcd9451b4377f6acdc3b905684658c0844d904c47d313d85eb54963fd71a9a76dc8e345e479004f00e446b886ec41cf7bbb9311463e877aa4e357a0dd0b185a5cf12244296f5f8568e84cf6ac3b6125db6897e4e7c183cafc04f61b3263456f61a7880b446e3cffe986c3bf027c7deba490ec8544d2627e6596ef8f8a5cb577796e7c36ffc4f4346263b5d86ae1d7b8d04709367349d590047a8bfb34fbf6481adce60eddfdd1e148ab83874d70b5303fa0ce60c0ba7a047a477dc4c513d2218b0bb2bdf9b60be9a39e928976464e333ce0cb19aa21fda1c6851c6db9d5bbc6552e080f8e448637af711fed92e72c8d8530187de94603377dc387041a9eb42ac70b3923b9828de33bb337d55409cb419bf4089cb2bec79ffc3afc6dcc3cb82bc903d956d28e307fc0470736574056302000318eb22887126df9062243038440ef3fca2aa024fff8739db4930b314822b00cab172e83c33b7e5b959b0c3f27c714e447120833ef69bd634d1e011fc3c2585beaa882a29c2dabddfd31a48c0255d1ab57c6796aa4625a546a02cef51a9ee2e8007fc047073657407210302e71fc2fff37aeb6d2e2a7b7f2308d4ab7d4bf0a4cf9be7c06e89a66442b48300")); - tests.insert(String::from("blinded_unsigned") , String::from("70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc0470736574012108a337e7e0ecf24c121a17193623254c277a306e9fd39cd5aaf8b7d374f4011c6507fc047073657403210b8e11e3b8904ac80caeb16af1c93053f8a11a963269bcefa96823d75b8640ae940104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e1060330000000000000001efdfbc0022a6437ec10c672d76c513868f403f6b9706e09733d6624e3cda831c2c199dd4c5763054a1c219e079e8cf3ce0c00dc2f16516972e1e64d576adfd9f5d778675c8a3172450a4cc82d6e6c59f2b16dffddc902d0aaa647b750d6224cafca7239a4fa81219cf2ee89741ffc5b7dd7e47d332ca931cffb1d1d432935a6013207629118c965b7a5102e62c43ca9d06bca191307a476738548536809ff6b01c6b5b25d76ff2f67d99e20fdf7d2eff6fc248186d21d054196023c5e4f572ccf0f3aad8728c46f2ff6756ea39de46028610a3d26cd42978b09e0e29e0a8aa46e4fd39d28d028592560264cf1a794c27f6c95d382f486dfe900a81d9d92935c7e0e6306549b3e49b1f60182512ccb994338c3541a2956139b2ccb3dd156853105abf5fb394cb2c45915dfd4106c7472dc5d360ab5bca408203a3fef58b4dd33b0c11c367dde2f19c8af7682be067244bf49a2b8cd4685f5481cc31ba27fe2f3d7b7a353be9b41e4eee2342fd70b8408c91951c71c75dde8fbc03e3f28e6d3d3b41e0e963d3ba0c25b2eb50560c21221950b0699d2615f0128e6cb7fa1b04ac1a046e569a7e87df98c14360b8ffc43db5e17548c7ea5056f84ffd14f4cdfc68a4f10e9b391cfa63eef2c2d623e7cdcdfae2fc4d63496d81462174ff360809b7e3b4305979d9cb8e9ad5b0f012494d31ce51ee6489555b09dddb16641ed9e2534fc34db99d2a4fa736eeabacf2f8cdca97f9e84c964277c6f30f1af7fe2b51b39b487d56ebaf593a3f98e811cb09849c5b445d5b7c9ba37807bd0189c8bdb2709fa70c230f9aba41dd3c62384aea6e1ca098ffec26367aa65a09459fca074d1da0365cf7fc2d8310ae099b838ca78e62cee10f95ec549faac1ff0f8236fb8cf2c0f6654e471d3950ef45e0159c44f9e343d05b3af59b939cf76090d040376407c41661eeda7d2cb61cad0088a286948787dae0cc5abdcb97f7f42026c65a13e1df1357c25d376955942adc858e73876e1d8812969055d55decac9a689dcd11dffe5cf6e06088b93a11e153ffed104266bba472cebffb2b0cfc8ef132309bd7836071d3b6ed459a5950c64cccf230015c98f9210f2d57b7f3a07c382f3df09f055c88e1f312db0d60d471afdc0b780d319a6229babd8f45edecff8d1073fa850f755219a3ea14e7234cbc7590c60eba0ba0cc1afde6ce91c8e8835b1ca809926b3e7d8d7a0941425e5f08e884f12693eeea1b3651f53da90972aaa37d426f37db3edae4285db114cbad5964c269e03b15358ad2a7242e0af538a594fc779ec3c43c3d94fab2028310c6d0acf3efdf0acf028ee757c5c02bb5b8b691aa5eed1a62acbabd0d61faa478cdcd54c6db2cec6144d8d1185115097a7da79c16ad3118d12e36dbcb7a70b0ddb27bddfcb1c6e5426b7e411f607d22c3ac2b8d3e41f55faf5e2105bf3b943846cc4c33edc64f902c1eeda09c8110d4f3ed0a5e511156a3e368f02161b92126abf649341ddb8ed03d1d41b91fc34548c6d94dfb47c088ef27a3cbfe1c9cb05f2ab2f18b8746394c8080c4cd92e818e46f861614ab870cb7ae3446e376793f3a6568a2ccfc2ab0ed0365567671436fee6cf427b6410a046d80b9d88f094924ad370da363e8eb70355b711687e92d88a08ed811ff241c7a6dbbad9dcfc18e6a42493483b938e36c1edd2a1c6e078a17c5d145c9c058b4dd69afc44c345f1c88afb95c1deb5c4994161ba25783165d43b9e50a2d8333a8037cec2ae809a3dbe026d6ba40d60badd05bce73b0f9f36966c30b9c0cb5776544c1182024a96e746a3b01f9db10b45aaffd3b055b02b40bccd41e57c10719bedb0fba99a0f6b0868b186fca0397ab8c219f33190f81e4cce2fbcbc0975c394919c98fdfd7e25a33e5f31fefd06c8dc409cbd3e743f0f48dc90abe45b2e68948436caa37fe9932a77b7e0fe0819d8283964b0eee2249d9190f3eb0bb8178e10a287be1059f35cc1a153dda14def65f3c49cdc5186da86bd3e965446f914e3c9b4cbfcfd2c379f306c5ef8844c4fd398b3c6f96601e90d2dc8810875663939f63abe3ee2e1c8a9c2c2010a01d0dcaebb556a7c98421f8e6465cb0434c07dcea9db1a142f9684e50b06c545785f0dee4def1257e4bb22d87a2b37ca9eb53081eb8f1ea0439c4575abac435868a36552df569ccc63477594ccf7eadfed6adbb8e81800a2e2fdd7effdd1e2f09cf76c9e780f6f8eb8408a3fcc06cb8bcd28db7a37edb0232a6f0e509c684318f179d0c91a97718ce3956c266790361ea3a1bf70cdb8a2f2a59b06dee18075745c7302db9b13a452c188c5624964af2d5d4bbb1138dec59df5dfb077a0f62ac4db3de81f54365f2a4a6dee63a6092b4660d3f5dca3cc8de3bb5350f5dcebabd515c72c9114bc58d96e2e863106f0982c2632bbacf2fe5cf6a8df880c550f7008fd09227baff82d4eb031802fbe7d50f6174860c70fcd9f0356e34c0d45df66492dc309b260b7158adf678e2da66348ca84de3e721c6196e0c717f59cb802c6866defdd9032a4b7da82b816d9681e5eb9115fc2a572fbbe105f479ab339bfa5961aa1920346c9ae4185a74aa828da78a71e55876c657249e83f5812cfb055400da1db8bbb5ed3ce2ca4655b0c39b698ed7d235fb0ed4f29a7e8925cb873176efcd1c6981dd23865468c6e01ddf61fa3e40d6fed18d8e3dbd97a08c68bfb092e441e512d44089cf563509785dd58203949a1ca9b66a700db14060a760aa404e5e9f31eaac015f1527f6d760d8714c88040b87fc8a4d183230cfae35326947e28a7a37eefe1d77070f5232a0d67e278a45d649709a7398cbd43094c5001263671517f83e62e79fb75e6f9bc592fb3bbececa3f597dee71dda0fc909079ea49d81554d2fd79d1cf3cf25d1186efb83cf972b7426600d7d6eb6c48a5f0e26af640c733f3f771e0926e6b38b6f39d7882b0538dcf281d92a6bd361bb32e16f3988d6790fa0a45f549e983f4eb68ce5ff11647b37f8e4c444aae8bc0f7c49ce7215545a29215b55f37dc42aa6add6fd1fb45d9ae580434097e7a8686e23cdbdbf6f8b6b1e5579a7908bafd8878b004c7c94e045fdf2b94f8c1a75ccee7bfabd9bb6d0ea8a60dca61053636160c19f8f3fff3d0330fc95d20a1393629f33f281e5da80a5ff66aaa5eefcf495f8bf7e24744778841d7c633f01af2305a122ee093837998e87f060105ffbc083c0d71f68c2c63820d7a547d9af5618544efb9736af56736e73cb696f191c68970ea1deb587231c889672b3b5399b9b5e915e3c567474c3905ec5b6468da826f1a6438ec335da847db540a091ab311c6846f96a2f17befa2f29ea491a41d7630e42583ad1212e6c606dc258a49f756e2480f90775c04c5c533300e37f8bc7afe7b155fb95877252ce4a53f78491ebf9d8a4aa41da1848633816542901d56e66f126316c80efdaf4b457f9ce771edb012a0b3c27c717f5cb3cc99ffcc959a02289e30d5b1ff936320579c469bd55cba6e79a0c5f1bda59981b71840a8c1ca56863e91eb21fbaf84525d5f04e0f282d03bb56d6dc2352f163d8357b86bc6e4e621bb693db3565eb9ef5629af537874c1cb3459582463362dab3c6fed7e574ce9bffcc685b8eea61599292cc69860c4f9584818182f94d719dcc463e9a6f854405ca2deb3de29ffa1826f795b7e7ac2555c0ee576c75494cb832c59de8d9620927167bc136549b731ef79a39fbf8789831cc003a772fa00d4d699d089d47037e12e1c6eb8c20c5535225a33a1a787ba866ad481e9a4d689c83d75f1986405c82154e312ce8b3494ac5721b96193c025bb75b2cc974f9171297da058d2ed4f00d94e60737af29da660fc51fb9bb4d241e78c9d1815d7e3ea90aa541c4c512ab423ece93f9ff2f479f464b953b4171217b758b280c5d8acda32cced7bb0c92cd9b405afb3a8405602da914e1831fec5b6a291f90635afe82b9389a8b41d957f81be125de2f943b7cda9e873fcd74b446bacc00bdfcbe643053f6cc8162c0a98343242b489f59f7ebac016a7fbf620dafc15c8ecfe20b954153dba19eb81087673a1e847238864036e9a300239a00a5a03d8b39aae2bc201c04c477b1f2a37552670c06ea65551c5c88a42476d0d797af494b077737cb48050bd35f4980964b8b9b98bde84615e8fdca407230f5d15e97ae65d63a96f3d88518fde12d6a82881db5c8d4cd79e724312e88b394129de3569c5c99bf842a90f193c91fa55a82116e237c877dec2da6dc652cb33e33df47420c5238fb6ba5fd96cb77f0cf6c8a12be1a95186680d16a064d9b1a7c2461fdf5e0a512a5a3ed2f8dc0eeadb6c95a658aa1c2713fea473de51f65b13a40a2fe64e5227bc9248f59c2bd60dd2fc14918e333faab6e792ce2f0597be47cee70f5a8b788a50c9cd153bf24786aa80f631ad21926e3285f41c5125a15c12cc889112df0b1857020160122c2595ab3359bcab184ecb32f7cf8f38eea23f4f9135104629d8f1e8273c44f8afa129d3a74cf58ce494214629cd53b99a8489ddc5d339e317ce75dd6c5dbd098d811c56ec5f19c00fa00b5465e39f8f2022c71ab3ed0d5a24058c197719551985ab30a851390ab5e11420c521b1398bff0038d904e5879db37d60c623a0a82e191cd0b6bd20a9e956d41d5daef703bdedd6bd0e20fb096423dd39706e939e796aa8bdf071e44791d2e1ed4a81ffd018cb79c5b6bdfd6297cc6cd379845fd7ddfdb0b83d7b26c21e3edb58185e71472a79cb65322579bd5e0e7b1fe081dcae5447834d70459f8a96b341a26d8922a2b1412f95f3533e04b5f65be112cca03ca45ee0eca10adc7593c78c041281c880d879125e585e68f5c7fbf8686f245cd736ab5aadc57c032c637b753b49575acf4bbff46883382f408417438d7817097accae3fc54afc6015b84b3b05627d7c83c4627c87727bd24c4b4d96a08c64164ab8d4abe3fff5c3b09e8fd12498e3cc3627e799e4ef72a870cd89598d51ba396655b4de66e48997faa51d05d581c52f52852cffc9f89252f26d314d2fb8fcbce197f3a8f330339c2ec3c710710b74f0915da3bbb638b1f1a0cb0c176adc151da0ab52796d466b23e8a269c5b22b76fe7586cbc217621acc955a43571dc04f69deb50066cc587595cf0ca4e9a00a261ff2791ada8de04bcbbea249d2a7840bd8fd3ef98a248eacb7fea5318b2e207668e13ab4e5578c03315d44b478df5247d8afd0624d2a57adf85fd16d86ae54cca21f6c91d8cafb3b31e3f9c4e903c04f39d67c90560dd10b2d28c6ee937cae01e921c98ddd833a9b9247053110c0f29d2f35f8ea322a738636f17648b627db622a9cc891fc1d066d5fcabc168f1b492479b0578d0bd8de0298d4bb96a4351714a10a39de49d57ad66bd6b458ae0d799a19da54bf620c0e6979a95b61a83cd06e46d7d263d53d66f77557232983268f5fd01d10442af0c840ab09c6cc4ad77add6379eeaf98f7cdbd70fffcb035abae4efecdb7e6f63ed8acebd8449b3a910e5eb06b0ee6d80dd45026c9e6c2f978f813b2800386a4d7ad9d127d76c4286c1b2facccfd16e6a263ba3e2893ae5e42049d421e08a62ee9e9574a1f70f0add7b68344256592513cc15f4bc64c5a029f60d617c245c1c34d06c095b2d0a9647e65dadb33ea79e3e0ea62ce55347d24eaab7cf9fc33fd70c07363771f78a13d92941daf7970c97b43d2fbe3e789a52ae7bed64736e6ce59f95322d20f9d6dc00b5824a0ce9072dd051c66ac20d39bd953f567a831d75fa91fb4d49b200fc893489e60df95c2810f7fc57b7f6b7725ae600e1c2def6a943b8d111ff3e9a5ac80aa981fad8709e98ee1550005bb8f65dedffbfe3793abb407fc0470736574056302000359a33e8f3d0439eb7d60aa79255858269bc1011e4cd0d4aa753bc2793f9638fe70dda4a99191f08700047a8b66d9cf7a53bf70756013c8630419e3a0bf428158dd02ab6e16822f239df0a6f1b67b6e1335509a286b30c4b11a22dcea9f95d51307fc04707365740721027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac0007fc04707365740121081271c10f0caf52a40e015e7d35966dbad39525a6c0691d4beeb3bfb22af5304d07fc047073657403210b85bd6dc21b4919f6ebda7ab86ac8122c793be3fad19e44455945ddec8b59e9f6010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001cb6f1101e2b960c9a2fe480eb88fc76c63e58b57445c78d88e04f740580a36d6956006ef12ba606aaca89796dad5ff7cb4c4c7f4e65a07fea8a2ce3b58b265d4b3220013a09ecf4e01023aabca5586d61ecd5d7f2bedceb8b18a8e41451c97a192098b196add06a8a6b619e1e731fd9ae2973741aff6358c1ad45cc873a2d497271e3a20873a6f701d4c44ef670c8c7a9793079d9bdfe956bdc02a52d1de8b2de58b0f7d309240999f13985fa8c98e57fe1bb75219ae6330e9495b523e891dba7885a5302817d68a9891ae0c2597d4d7833b91b073c4f9b27be1cf81ca1c2c1014c57a4affe7b402c0e0c1862bd17bc1d43428671a106c26cab61d5e4ffd1c01f6af1c6e97f7a25ae8aaa8d2c91c57881df52bd5f242b127ec7aed5880101caa01783884dfe943ec9764473323f59b27a204f34aa1fe7fd4433fa606608b0f253247d149e3e269ae5445e917390be83d5650c05859e3a817a6c7e561e2b12baf6d4ca4221df0b7f29fa134269139afd9cee5c7d60149dfa23031705c6a8b72d00d659d7a085de8330de2e5c168ee3eba2180a9319f5e8e0806dadaf0fffbc2df839571360b8cd32300504a527cf914da7a1e788261c9cca2872ebec5a17cad33b15a7a7427c200b8746761359194520f0da1d7ae03f26190d07cf92d515e04815f514c66a98997320a028ad24e83182f213d991accf19a8a59003a2ca666ca505c0d8349e2cb22887efb7db9e4a4e3e2d8da0d5f032603e03f48fb967fd018fc99c6eb72acee4178edeb48703903a89e9462a2d1f447234c3373fff8b205848c7a9d6e45ccc31eeeb0105ed3af1188fb847cf6f03a7f0a646fa325b672796168dbbd9d1d19b2f41685b18c3c96f1cfb7ecb1dea132e8815b62647820ac4b12597d8e737a01a970704b03ba5ae68f8d64a1810db10427fbff1d74cfac91561484cfc8d3b23f74ba0f37e7db2f942c58fb1dd30f6a50c57b5eae720c6ed69b0fb81a6e0260c53028c732a544391e319987a24548230378f8f1ddf65a2d654c7b26951d5c6546cc8fda6003228e8913c9e3c5adf7b7b72f4ff5c4aedba6096097f9a4b58ea1e060609e8d1f0fb0e5dd905b29fec72b118d40c5f599a63f9a37041742a7f1af3ef951c31833621842ec212f9fc5cd3e8c08278ea192f8032ade447d2f35ab59810aff371f598b7dd57b3cde2ad854ecad0c786150748ac241ecd67cb868663a6fcbe9d68c3dc221d4fcec9b0ad89c337c1e10b91a38ab1357f260ca3a084da536e56262973e6c6cca838601fb2f335811375aed78295c4b17d3798d39d4cbf6254ce57680a102907e5c08b5c7ec0a1e73bf3d7b8babdc6156c6ca3f09c0423d6716ff4e6ee0338cfd778158202b5ffb60b74558dc6d0be9b5aed6892c4e0ad3e20de5b604b2b010af09ceaba7c0fba5a7fed39d31cb2769909a251ba5b0668330f734728f5c6786a8f1a35df77f3d739eec71d9268fb2a494a14bf7ab91c075023e76856d1745f5813d83878822934d0b4cc0538048c3f96cad7aef2bbc49bab139915dd4998ed3c6701973eb7913530b14c688f0b344ca9cb990de2a9b12fa7ad8b292234a33428b4d16813693964365bd412fb936c1461fdf8dfb0bba31202685f29addf95fc65f841e97042bc5d82f103e41017c6ac91d3d266e12db2dc195ac4f6d4353924270c7144cd96dcd07b9612e2bf1e0f7fc46ef83690648395c575a13c2b2df0fa04f740340e6b116b0866a401de22f68fa3f621d75b9f5f625cd5d5d7e12a5b0ff795749d742eb2648ddf068a451059390da566b3de84f9d528b2663de7dcc562eb014071e8277a054783d322fa67b9e5261b87e9c354ff58fc52bf9df404139311d6bf9650658fc94835a6220ca02a4bfbf46b2af4f30b1ba681c0de7c134d98dd52eb1e937c191cc9557aa73a343b99ae0b8954a3e929174984b574da75e90a9b71af1215317b04e4090770ab596091fc05c82235a324f86280f7fa081360ac758c1980c6a6763453e6b344611e46b05760941f9517ceb298ed7a2d6f96b11f5719ed0dec803c511dadb8d056d4ca06ca0ad090558a4fbf3a886f3ea9c33e1bb4bb5ddef4798cdfb384bea75a3794c42a53af60f12c95fe9b38d7f6a5d914ef100508091fc84756b596b9ba08d1dc42a7e253435b5481d1470ef00a1172cb036b5bdfaccaee02b15d099da17f9753846226325232e71ed9b48f57697bb3d1db75ed214310e6e3608ded6f188e37b21b96eefaa396db0df05a86416fe64aa950eea87e7af4407c828a8822b17be661269c9f9e365cdc79d20503e2d5e575c5387fa961206118dd379834aade7727a1b2477a24f05f6c2ba3156fe7f5b5ba6d7487b1a124f38e3ed3cda92a8edf05f28e3d699f0c12de9cfeb30ec8d4cd253287a6d3f596db0c06006a531e80083f5b41ee26c05d529e0a59ec151b4a9260a7ea8c1a175ca41b59f464adc122f448fc43e90dfa7b88f81caa7a84e3d6d4dbb85e5828b6ae7455dc2c87c8d9c706af2e62763675851b4ca449c9c0f95d953ce53c5327ba8981321a1cbfb76bafac7fa17c99567725d8e0ac288f3ccd3bad666e515156517281c97a3dbfbd5f345f8eb80bb3c4230301a38b7c920d74eb910d380f884a683f7d3e2738fabf9646138e8a32c35e43786e67925508e4cc7b1e7a36abdf6a497077704e216f1e730c20ecfcddfbdf2484d8eb0ef9b7478f77063fa90b8afbcd019d1a584df58c537b40b193f246598ae4c53958a947dd3916b14a5a4227f01bf543a8c5d6ab8eaefdfb959c8715b756a9c797fe39229dc56a86364cb367fa37e16e96cf8dca1fdc8b6a46ce88e3d29c75b6cc718ca8c28e9e1ae991b28c286107c1dced150814d6369cc8003b2976018941df7fb8eeb68be57adcf8a0e813d2fcd16dc390ce17dd0a91dd674d766d21b5de7f4678a548d75cc3de7d099b418fbdb66b2a2b12a56b1dd8b3f84a7b45e36db9bb63b3ef9fdadebc21c6a75364173925ffc69d481024e49076dca105795e7a64a3c82011889aacf0114a1bd31414e288cbb63eb47d399d0e025a54f11670c8ad4a793d0e7ebd3b0d8eb30b87cf62b131c61a4c55d2e56e47ae99fc543c57a1877ce44dac4c6f9fd99552701ade43858e0b4020230cb4d42d1990bfb509471a5c1c5557c29c00072c5cbbc056de6d465ea2ba2f4a1a5dd604dbf998e834b9200c59fdf90bec38af9218e47ea5ed5512cf8b0146126a15d49bf915eb893ad1ef83d8faf6c93f944ebce3704f930b6f69a1afe767ac2c9fdf092c804c7372ae1a64bca3a3b5a31f2ad430ba9e61dab3daa3be90a538cf0fba00796de511cddb5a594f14aa6d215bd0be19c0ca5f043403b7dc56c87b092d9436eb79509c3dbe367c2fcbc2b09f9818cf5a55d5ee0e477888166bbfece661b62812ffed3e4167ecc81dba148f89a8f2e7c27424a2ee091ac487e68fbbf9798e8caf46b35de8dde66a7cad6dc98a426ea7b8b930cf2d7d71fe71066dbe51e8a1c7151e97fecbeedef3d6f8e01cd5576dd58097b46153f9c3bcecf30bc82b4cdfb1bbfa826c7c5b217a023e4da02a35d0947b2c4d24d50a292e4c70fe854a420c54490d4850ee8c372756671fe130532f89878a64977ead60bf604b73859ae64ed62536423bb51b30c6fedbc819fdae05cb64b87b153f4252b832d6b866100a7b307d1e705643f96f0724858e6211802b2ce706d5424b99315db90c7d74fac66951251f6cb739ea008f839c7f62318f795337ca4ff05886a2640819873ae00b933081b64da1101d1b17451357921a2f4d4dc86c92a5eb346fd6cd3a63315768b94b90a15162aec91cea706b9221330db154d1fd71acac90be47055c725bce38710f1b4b4144f331cfa9fe15ebf6535747669e4aa1f0d72092ef395f16a4ec9014029b5c9d1b4bed9ec81a7568f8f1c87e0ce0f4ef9cf49e3fe4960b0b7904614772c63147f3c5463fffed182919c4973a8256346116be40b10a38cdea5bad5878760daaeda57476e86ec65e58030bc8ff99ebf017e9bf6383f981edc83b538e2bf4bb573637e6f4717d74a3ac71f5e151259b29c1b7226e3a8ab176baa2f27e3e385cd48c6f072bb6e8e9c92e35d7ff51177f4e3553555d967c04befc1847003a8592738c82eef231ce3d372372a19a01b27dc7916f986ff3f3ea8872d4986bc1ca649a92924c8f582ae2b44028e711e955d2563c33db3b7734aa246de553dab2c5268538f7c2b6dbe9a70657e2a26e52f5d216dfa9aa8e197a227c86a7d3b468880a721b804a0c51af1592043eee7fe3657b40c1832a858cfb4e037d208ee69c4fd697b929383a971b2a0864544505af2267e334db5f3f3f4ed88e2f6f2b3d7b96016ff92a120556e4d1e50440943893b00508c6242bcc41b6d06dc29ba20564b42257b9fa1b5f4f453e94e72501d31e8947e09a38261020400db4b8cf675b1854639a962a57905cbbee14ea0daa9bd281fd635c8f900fcabdcbcfb140f4f24b47731782ca899284fadfc9d0d6eff77045792e139afad34c7672bacfa22d85fcc05652178cc169bcf52ee3cef281d99935abf9c9910caa7994cfe33b9319d9854aa6e2590a86297f23b0cada7251a6ee1b2f3d2cb5a193cae8768d92f556994e416d4bcf5bba3a295d9e2215291237df74e3c493189bc5a27b3fec3e1a1329c6c6c75e0802b40fc74776476dd2d9e3453ec1709a8a2d162a936d9fe7dd4b6f7abdadfa429aed4cbd5896cfb24a2d91f1cee781d0ed73cf7306d6f9545fd3346c2232b8799553357a5a531d6085d04af5f183019dfd3483baf23b0ba5a6b58bba4b7a1eb0a32626bebb5cc0c4dad45c55d135be8b0a4dee787ed48b90a2a3fc120a753b52290c27ad0eb32b668a9540986ca0256c1676debc4d19b1b80bedd81e505154264a199ef1a54e1fff1b3589fbb6b2d87a1e04e67066571b68fe04f030d473f8d135585f8b7be91f0ae1a4fa2f44008c0f727f6aba9c5c60c3a9ff22911e267ffa2a5d1c71ff25f96ad257be29a2ec641e2055d785765cadd0c2fe513c44154e74f4b8cc4b24d9ee2f6cfe245549713f9dada1935247a2ac7ccc13c4a5c00c6ffe7ca33c0cde4c75c4fe2ea041a965e4c7385ebfdbc348da8c4216a02fa829892c827adbcbb87745120667cdc5a4aef28b190a54b422df45b8547c447765838dbf9f05de338ab672706b59b2f0644d6735d9316e64a1a15d3dfad893bb1bbcbe1b574a0b8ef5265e195a929148cc5e97c8bfb032d4528b523b6a6b29209428f38115cd102e3b4e268333e490140964278238e7ea9638ac34559e05300dad0c3f18e9228474cd3a7186102967c33fc14f086b4f1ec3957d87f2609d140dbb9e7ff793101d1cb90d004565c6a32924715b1bcab483e6389e2e824054edef350f328870f16412aca0fee39edcbd02a6a667aaaeabb6654c4762c639a26286a3c1730ab75defcd9451b4377f6acdc3b905684658c0844d904c47d313d85eb54963fd71a9a76dc8e345e479004f00e446b886ec41cf7bbb9311463e877aa4e357a0dd0b185a5cf12244296f5f8568e84cf6ac3b6125db6897e4e7c183cafc04f61b3263456f61a7880b446e3cffe986c3bf027c7deba490ec8544d2627e6596ef8f8a5cb577796e7c36ffc4f4346263b5d86ae1d7b8d04709367349d590047a8bfb34fbf6481adce60eddfdd1e148ab83874d70b5303fa0ce60c0ba7a047a477dc4c513d2218b0bb2bdf9b60be9a39e928976464e333ce0cb19aa21fda1c6851c6db9d5bbc6552e080f8e448637af711fed92e72c8d8530187de94603377dc387041a9eb42ac70b3923b9828de33bb337d55409cb419bf4089cb2bec79ffc3afc6dcc3cb82bc903d956d28e307fc0470736574056302000318eb22887126df9062243038440ef3fca2aa024fff8739db4930b314822b00cab172e83c33b7e5b959b0c3f27c714e447120833ef69bd634d1e011fc3c2585beaa882a29c2dabddfd31a48c0255d1ab57c6796aa4625a546a02cef51a9ee2e8007fc047073657407210302e71fc2fff37aeb6d2e2a7b7f2308d4ab7d4bf0a4cf9be7c06e89a66442b48300010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc047073657401210970cb1dff96101101e24bed1a66fb0794d2fcb26aba11e7f2393edc1534df8a9607fc047073657403210b9991881df2ec384ff5616f6c03d45cf033ee64536297c3557f9fffec716c1fbe0104220020cd4ed5f0db8c450e8b0be595b6410c1759c19f1f6926279095c83dade9862b6d07fc047073657404fd4e10603300000000000000018b6d2d00790f618171a9e8eb6051fc14dc27c502a54f5ccecacb4bcb350581a8f4b1a887a1e58865a21f51ef5098400c115cca84f489637718d8c3ca4350ad677374d40d2b64fe949ca34bd4ba845a87c7693421b70aa8d852e021aee329762883b9820bc0a47ea2fc6a8411ef773703632849094e51c73997c67f3e922d2801b6abc445375cd963a62c2bb28d733bf9274fe503081b51a56023a94200743e7d38865c190331449d19bf3d4eb7bf0b2a9b90696484aab906b9470e10946744838fc428f88461864d6601e93f10909e049a315fcc0ea7f4d71507fe5ed19b9ac2f55b0d3902b153ee27da9c55ab66debfb5ca440dfbb1a600cd8875049c4c13bbf242d26b10035002c7859f9c3ea631d955914adfa0817e4801e85b3f250cd9e7523db62c4ecb17a49028a02a1b61eb96a410643f1fec0ac664d96f99a1db4ac335c2bfec2946707c3c779a56373db197039af9a903e833af6f51de5cf8fc667dbf5416845d415dc98641e0984ebdfab88bbcdf305f605d36f7a472a516206d7ec722251e269800a00e8adb8f2ce161395c1c6ad4b406e1fda77bc7f0af07ea075011cee6cc5e7fd1b6e96e321171ca7d28a35595ddf0b0c831d28a88d30c7391c5832d90f906539aa49185fc6c0864e2d10b21160991fc19a1d084006d03fe3fb6f571f1ffd9f06ffbc0c076689373f9c2f8655758f7f44618345a6d5f8edae46e73a09c38157d5c39dd51a47756ea761e6320d6a91a1bb22d8dd35e4e7806b0242bcc4cae8720a953c6f933c0ee3216513cce6742a2cb4bf44553d8eaf175471dbc0f71c7c245fe99ab4240ccac637a16c82f0c60586f00d83ff852af48a239289d8b70a2638b0aff02572d6d2d68e22b87ba93f3db51bb27825265607ba4b6e9a194c7351f220b9e715a23237c40024fb7cfedaf7c8e0a1c4c6aebef8841fcf2609a3dd7f44dfd8895077df41f10790372eac8adff5ea7eeb28e371b4700d5044b41fd358dc4ba29158213e0f93711026fac8abfbe7cd80a939477ce545bc91cd1d32e8f2268563b0fe3e80855777d51cdf4835050a557d3c7b7f6905ba744ae5450d22837d2c2fa0c0755934654a78601a1fe236f11b6d72f368880ed938ff4a2e8d82c118493a1bb9a9ee95cf329f2d175d467aacc2c6cd73ed59ce64d508604db77942f9b7f3b0f86d913d3e1b489d2190a6da1ed997ab1eee356c885270e4e0192acc9c2b8702107c96aed654318bbb7724b913808dd2cfbbf6ace8db9c4cbf26f2c90a76eb967e365c58855b83550819cb5b28ea5e6ec1ea153ad3ee5e6292a174f7cab1b39e852194a6926bdc4d42326e17998cc057f614e91f6acab5a5bbedd93d9a13dc61977aa2accaf35370a3f1d3819c43c9430d0e767e92de96bb225007a7b6576ed7c4f05c984ce2e437d3b7f7f50c270caf90f8e5dbecb992be2972ef8d79c7674e025ee06b1302bcbd57fe5be9d9b4e38f17d9dca898ef3d71918550fd77a15c7aa2b66ee1a2f38b81040f3d6ec6693f54cc3fe39515275849e9e24b0af3e81705dea95e8f1bb1b4665cfdeb0630a5542a2857f2b58c36761d743299d77872d6894302123f4347702572f04d9876191d771e87655c071fbd29338eef0f9acb0a8ca3f0327ead8e875bc2e7ba38f249e6eda1fb74162e972ff523d20c6638b93297d406e9b2264187c06d0a78203d771f2525227d029b27b23192f747492b9774b12b039729ef75d11ce8d701e56fbb202f3e4bd68686f096f13680456529e5f132915595e0e0b4061b3014219f1fbef4d7403dbfcac01c701b7727c2463cb838d427e07706400be4fdd9140a800e47e7ac15a7a8a897187a38a0080726349d8b7566252076b1ae1366496b50e9a6d236837ffa0c4c3e61bb409a7d9faf8662109ef7213007a76a6d94cb79ad07183a33a4cdedd78896278a3b5531ec0efda9d77076d85a63536b426a6b58c3d0e8a46faf0fdf678e4964272e8410b346cc753edb94a61a2f309f8e753ff332f5b15215e0a20ef32e6a6faa044a431764e008e66b4af820bb6afe4d8dc1679219fe7614482517fdc523d4d7ce2bad9d59f7b9aed89b813e5b36b58a7c668e53181c0e1da45e4ba3851b46de8e056d63e440c471fbe23fbffc31ece7cd20658784f2d2fca3f730a1f8a90ea6995ccb490fca475b1441e38468433d1423c1d83a20b04f1216aff13c8e035c2ed389695c2bb0bedadc38c8a6222d687f0235bda3566c0865774bb6098acd674ff9091db1a6c4f0c2955c1759730b361f310d956f892089f500eae0084022e610ca4e758ce2c680d213bd1b5e00a6c991976e93189159bb98a1e4eff14f681d47694060126c0852ad52489c15d7302d82c66aa830208a9fd13da87a2aac3e44dddae632f2e4d56dc768ee6b3622c61e59aee27fee5aa0bfa9c56402ae3525f00634b11381d0b9637ac699e4c4b225c32bef9dc563e87cfc33bf07450c2cdd015bbd94df63697cdd9b6cf4169cfe1f626c126dbb588c57542662d02eeed561946ce94b46571f72ca3cf7fe52a8a6ea24fa216f01655dadbeb0f74e383e522da69db64b368cb155c2ba4c4fc8aea8745b1081223317003f0cd7ae620fc7d9a6c9c4f39a237d5d9d7f3e756c77daeccbd5d06ed28ac2f089db5449340872bc444aa4aecf6552621b84e4fabfd6baf0e1b61cab00e5c2050bfa38899e9840e2dac55c7d5e7105f65dc3afe3c35dd3e8ebb1a013d65fb0b9d76665f3c4eff5b766fbb0c03cc10c411987ef516405296437a91a67d7398d788e30f55bc1e6236b317784504c53800cc07f4eaf78bac03a025a7943573edcc67db3d5d5281f85ee993238fee3580cb846efae33b45ee2ee1ad640b1d068302504521269a31a166435e8fd964d2e15048f299a95ec1cbeb0c8d5d119e24670112d61d56ea120d7bc30fcd924fc7b812404ac45798af3fb590570a4d7a3f41639b879d1bdf2c72c979105460892fb41570cffee716679eb7b4124be188e28ca4c68d206c1cc9d49cd7489d63372853dbe83f874579238ecdd28c0dc265871d6dccf782b571f1004702121e8b15c9d8af9e6812dbbe742812c7e7cd1dafbdbe3d189f618936e270855700be8f6a7b528f271a936a2cae204b44d909ae3f6a8bcbfcda44b430397b1c96187e5a8362afe92bb9afb0a0a482cb6c8cac39a0fb1fe0034f8a965a3be425b4b34f67ff38a8ab30f8219a8933539c1d06d9b2997b6de743c568caea8b269642b8503b546c9e136d0e47775c8fef45d4489e6b4747c6accd6fad3d34fdd6cbafe07264e7ae1e025306c97d77a130093cc8d5ab8e4abe095787183a9084c569168011766b47dade4e3e1353a78f9f3464d8237b7de02da10c7a7b8951bafbaa898003ad9de997c89d8507393c6fb782b8f41aa99d5c92d5ed93e02a2e7550d42f178739898406badb6da850e305c10063b1a687a2b321a1f867ddfc7949d9764493af4f9681137101f87ec3c3733c0ee23a758a69dd5dbfccd20e0e7cc8543c213688ef9cc85bf9c539d3b6758e24befb3c1b617b7d59a17e915bb985df8229429430dd3057a8225d22afc2775e352a2d7c2f2f46786d4e6e984fb35ac6d7e1b57da82a6d1470895e56521c88c166f8124a4f0c5ada7d5e43174e319c21b9416735858ab50958def63c9a38e853d28a0f47b8e601beb0cdfb30d87070003f957ae347d5b03e80890311ded8018de0227c430f29ff20f0d0d1331dd5f22cff547847c0a26c1fdc272cd234b668823635f68e0797bf68fb1e531a423b1fae9b056fb8c0589908cd2774a7bea8da465248a438a22919d35e0ed8c5020f06824aa856c1a75e0d50d8e9c2dca471d249a97e8e8055d0927432577ef07e6658aa6ab3e9f5f4f50848b12dcdcd9594dae732864f9c6253cccf39922b5f1f8e16458a0c11db397017821de73a331400a76c2a5a29c7661405e9f244a92862a8ca13bc86a78a36744e52019c067573574f75e01b21c36e19a714e9cc0860e9df8e659cefab57002dc2b3d1a2918dc20ff25bc0d87023813abcbbe29defa514bd71fedf314a140508c7e8dd4ace82d56cfd6307fc8299a65c8b3b0719881dc06070d670729902ebfa1dc0b88050e5824cc186454ec2727d0fb2f95eb677f949375a0a31661c7f2bc452426a052b160b0654bffae35766a5317b845582176f1ec52e5bb7d814651781e268cfaefcf6da560810d84343dab8a8b2f2f7976334043498f529d75f9dec893bafdde0ebf003ea7d43f22d8e4eb3afea7be8bd22a9adb9334db2d6b05b8bfe72a96ca2c5ecc925333e4076bf5b7d9ed519ac3eb87c73d0c8121b1034424991561eccb669807a722bcd43141915c9db90f7a8a8e732054970645da1845b988f21cd5d2a89abd8e14c0e6532a3db89e43571b795d6f475b11c16cd7253a4538f4e73d0e3791e0c8f3e51ce3d9645062f249549d9165185fe6920659fe72ae01f69c6d5cef5a01cbbce0fcfd671e1a0aac35d541210a723d2fde91f37c130657d8825662bc46ca7780e07422ce2e7e5cbc1b15ad29b8473ffb832f45c480e960d620267561b3e3112c54359459c3f0235eb6eb2720dd5cf65f88267e099ae87ec7e35b3a915f18a4ab67583ccb4d907637812d797dfa51acc7b5cb13ab58934217a562b00a4a3d030bd9d3565a47c95396034bd65d39d39a6e7795f13e5031d148db16eddba996e1e70730eee02c47bd8b5c42812a12a2db5490fc3ad50d7d03c72c0e933ed8b43f8e34124e272cb39e06e628f18acfe4488dcced67a2c47008801e8d2db5dc7f7556d38020d53512993f3efeb4b07fd1e7b631deeeda74983011ca401ec7b86eb2a7a4d341f97337d37ada0dbbe124fe481ecac3e9b8e8f3b8b90ccf81ec028f2ee4e62934abcfcc2647966f6eea6563a0fc8fc75348f5fb3d2390db24d49858bbca822217fa81d240cacb7b2f6c2e4bad8a7a112b83a5d35ad68ec1cd599afee41fd4e860097f33afbc2cd790b2933f1194549c5aa3fe5c651a456c8ba7c8fa5ee5492233c21a80bd9216fcfccc03ecb76af3849711a0f69d5a261ce94484c30f4b74ca6cb4d7d5c4dcc0be1f0390c208e1e7e863700c6dce7ea5ab3831c00e586094a6a1f3b32dfbbf99a7581f90001dd558738804d0663a59d77407b6ab1ef3a82c156bc58d5a694a365b300a90ebbcddc49bbe5ba63c88e7a6e0faf5364ea17cabc602ddbf87a4c2f55b068ef296be5bb66befc1e37ad77f8e97c08f02d17dcdeb47959b0f46c2ae2c3d99a948f2a52c5793d53fe57876c38336a74786042abafb63c2154096eaed98598aed76da5a17ae179e79f848b237bdd6db63e5fbe77ee28b2bdcae462905ce1827800407317c2a42fc0dc234b2183db9097c0fae23da91aca6b1b7894537b0dc8524536573a34ef68596021cba863ece0de4a068f806d888aa03ed88ad92f375860f8885c5533c637a7a330c24aa0357f007ac3678e59cb1f6aa7c6979e15fd107a98ab42a635a3c3e70b58a19ec73b4bdc482000c3dfb4af84012ea3957d3d30b80857d9e06a2e677c84e5d9041ad4bf22afbdf0351f6d5c7a7a2ada3dd5ab4e48e37d51e5fad56cf9963715438c5f04b9772773893a59e2e46c6b7e19e9001650b66b469f4b1c4d188b286d5ce8b21269179e772147c7cb09eaee9b9de684ffc33068e5d346e79b710d82fd1278aee64ddd4ed37a944e6c745217c6a626444aa95b565c1f68a12cd2b86a61927896884bd5a76a9df4f6fe548bc16444272ec1e856334d64e0f32ba90daa77085b0683e3e010abf571dc564fc703a12998c72b6712386cfa4ccc802c850a3604bb0b2d5cd2eb6e788d475ff198f94a220c925a32ab6d69b037f1b9f0f4ce1d1b89cc0d07fc047073657405630200033fb971149dc9881cf31b7bd90de445f0ada69611016c5fde4af5bffe2b40a1a27d651a1d2127b0a95539b18fbb835f5145c1c4c7492866c6bd08ba260abb300720d2d42b729b72c6166f9ae0e4ac09a0f6851ca65444bed00b72b7f12befbbd007fc047073657407210252a4b012198c2131d70498b5939c401c01eb1178dfd58123b55766b03f008f7b00")); - tests.insert(String::from("blinded_one_inp_signed") , String::from("70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c22020334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe7473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc0470736574012108a337e7e0ecf24c121a17193623254c277a306e9fd39cd5aaf8b7d374f4011c6507fc047073657403210b8e11e3b8904ac80caeb16af1c93053f8a11a963269bcefa96823d75b8640ae940104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e1060330000000000000001efdfbc0022a6437ec10c672d76c513868f403f6b9706e09733d6624e3cda831c2c199dd4c5763054a1c219e079e8cf3ce0c00dc2f16516972e1e64d576adfd9f5d778675c8a3172450a4cc82d6e6c59f2b16dffddc902d0aaa647b750d6224cafca7239a4fa81219cf2ee89741ffc5b7dd7e47d332ca931cffb1d1d432935a6013207629118c965b7a5102e62c43ca9d06bca191307a476738548536809ff6b01c6b5b25d76ff2f67d99e20fdf7d2eff6fc248186d21d054196023c5e4f572ccf0f3aad8728c46f2ff6756ea39de46028610a3d26cd42978b09e0e29e0a8aa46e4fd39d28d028592560264cf1a794c27f6c95d382f486dfe900a81d9d92935c7e0e6306549b3e49b1f60182512ccb994338c3541a2956139b2ccb3dd156853105abf5fb394cb2c45915dfd4106c7472dc5d360ab5bca408203a3fef58b4dd33b0c11c367dde2f19c8af7682be067244bf49a2b8cd4685f5481cc31ba27fe2f3d7b7a353be9b41e4eee2342fd70b8408c91951c71c75dde8fbc03e3f28e6d3d3b41e0e963d3ba0c25b2eb50560c21221950b0699d2615f0128e6cb7fa1b04ac1a046e569a7e87df98c14360b8ffc43db5e17548c7ea5056f84ffd14f4cdfc68a4f10e9b391cfa63eef2c2d623e7cdcdfae2fc4d63496d81462174ff360809b7e3b4305979d9cb8e9ad5b0f012494d31ce51ee6489555b09dddb16641ed9e2534fc34db99d2a4fa736eeabacf2f8cdca97f9e84c964277c6f30f1af7fe2b51b39b487d56ebaf593a3f98e811cb09849c5b445d5b7c9ba37807bd0189c8bdb2709fa70c230f9aba41dd3c62384aea6e1ca098ffec26367aa65a09459fca074d1da0365cf7fc2d8310ae099b838ca78e62cee10f95ec549faac1ff0f8236fb8cf2c0f6654e471d3950ef45e0159c44f9e343d05b3af59b939cf76090d040376407c41661eeda7d2cb61cad0088a286948787dae0cc5abdcb97f7f42026c65a13e1df1357c25d376955942adc858e73876e1d8812969055d55decac9a689dcd11dffe5cf6e06088b93a11e153ffed104266bba472cebffb2b0cfc8ef132309bd7836071d3b6ed459a5950c64cccf230015c98f9210f2d57b7f3a07c382f3df09f055c88e1f312db0d60d471afdc0b780d319a6229babd8f45edecff8d1073fa850f755219a3ea14e7234cbc7590c60eba0ba0cc1afde6ce91c8e8835b1ca809926b3e7d8d7a0941425e5f08e884f12693eeea1b3651f53da90972aaa37d426f37db3edae4285db114cbad5964c269e03b15358ad2a7242e0af538a594fc779ec3c43c3d94fab2028310c6d0acf3efdf0acf028ee757c5c02bb5b8b691aa5eed1a62acbabd0d61faa478cdcd54c6db2cec6144d8d1185115097a7da79c16ad3118d12e36dbcb7a70b0ddb27bddfcb1c6e5426b7e411f607d22c3ac2b8d3e41f55faf5e2105bf3b943846cc4c33edc64f902c1eeda09c8110d4f3ed0a5e511156a3e368f02161b92126abf649341ddb8ed03d1d41b91fc34548c6d94dfb47c088ef27a3cbfe1c9cb05f2ab2f18b8746394c8080c4cd92e818e46f861614ab870cb7ae3446e376793f3a6568a2ccfc2ab0ed0365567671436fee6cf427b6410a046d80b9d88f094924ad370da363e8eb70355b711687e92d88a08ed811ff241c7a6dbbad9dcfc18e6a42493483b938e36c1edd2a1c6e078a17c5d145c9c058b4dd69afc44c345f1c88afb95c1deb5c4994161ba25783165d43b9e50a2d8333a8037cec2ae809a3dbe026d6ba40d60badd05bce73b0f9f36966c30b9c0cb5776544c1182024a96e746a3b01f9db10b45aaffd3b055b02b40bccd41e57c10719bedb0fba99a0f6b0868b186fca0397ab8c219f33190f81e4cce2fbcbc0975c394919c98fdfd7e25a33e5f31fefd06c8dc409cbd3e743f0f48dc90abe45b2e68948436caa37fe9932a77b7e0fe0819d8283964b0eee2249d9190f3eb0bb8178e10a287be1059f35cc1a153dda14def65f3c49cdc5186da86bd3e965446f914e3c9b4cbfcfd2c379f306c5ef8844c4fd398b3c6f96601e90d2dc8810875663939f63abe3ee2e1c8a9c2c2010a01d0dcaebb556a7c98421f8e6465cb0434c07dcea9db1a142f9684e50b06c545785f0dee4def1257e4bb22d87a2b37ca9eb53081eb8f1ea0439c4575abac435868a36552df569ccc63477594ccf7eadfed6adbb8e81800a2e2fdd7effdd1e2f09cf76c9e780f6f8eb8408a3fcc06cb8bcd28db7a37edb0232a6f0e509c684318f179d0c91a97718ce3956c266790361ea3a1bf70cdb8a2f2a59b06dee18075745c7302db9b13a452c188c5624964af2d5d4bbb1138dec59df5dfb077a0f62ac4db3de81f54365f2a4a6dee63a6092b4660d3f5dca3cc8de3bb5350f5dcebabd515c72c9114bc58d96e2e863106f0982c2632bbacf2fe5cf6a8df880c550f7008fd09227baff82d4eb031802fbe7d50f6174860c70fcd9f0356e34c0d45df66492dc309b260b7158adf678e2da66348ca84de3e721c6196e0c717f59cb802c6866defdd9032a4b7da82b816d9681e5eb9115fc2a572fbbe105f479ab339bfa5961aa1920346c9ae4185a74aa828da78a71e55876c657249e83f5812cfb055400da1db8bbb5ed3ce2ca4655b0c39b698ed7d235fb0ed4f29a7e8925cb873176efcd1c6981dd23865468c6e01ddf61fa3e40d6fed18d8e3dbd97a08c68bfb092e441e512d44089cf563509785dd58203949a1ca9b66a700db14060a760aa404e5e9f31eaac015f1527f6d760d8714c88040b87fc8a4d183230cfae35326947e28a7a37eefe1d77070f5232a0d67e278a45d649709a7398cbd43094c5001263671517f83e62e79fb75e6f9bc592fb3bbececa3f597dee71dda0fc909079ea49d81554d2fd79d1cf3cf25d1186efb83cf972b7426600d7d6eb6c48a5f0e26af640c733f3f771e0926e6b38b6f39d7882b0538dcf281d92a6bd361bb32e16f3988d6790fa0a45f549e983f4eb68ce5ff11647b37f8e4c444aae8bc0f7c49ce7215545a29215b55f37dc42aa6add6fd1fb45d9ae580434097e7a8686e23cdbdbf6f8b6b1e5579a7908bafd8878b004c7c94e045fdf2b94f8c1a75ccee7bfabd9bb6d0ea8a60dca61053636160c19f8f3fff3d0330fc95d20a1393629f33f281e5da80a5ff66aaa5eefcf495f8bf7e24744778841d7c633f01af2305a122ee093837998e87f060105ffbc083c0d71f68c2c63820d7a547d9af5618544efb9736af56736e73cb696f191c68970ea1deb587231c889672b3b5399b9b5e915e3c567474c3905ec5b6468da826f1a6438ec335da847db540a091ab311c6846f96a2f17befa2f29ea491a41d7630e42583ad1212e6c606dc258a49f756e2480f90775c04c5c533300e37f8bc7afe7b155fb95877252ce4a53f78491ebf9d8a4aa41da1848633816542901d56e66f126316c80efdaf4b457f9ce771edb012a0b3c27c717f5cb3cc99ffcc959a02289e30d5b1ff936320579c469bd55cba6e79a0c5f1bda59981b71840a8c1ca56863e91eb21fbaf84525d5f04e0f282d03bb56d6dc2352f163d8357b86bc6e4e621bb693db3565eb9ef5629af537874c1cb3459582463362dab3c6fed7e574ce9bffcc685b8eea61599292cc69860c4f9584818182f94d719dcc463e9a6f854405ca2deb3de29ffa1826f795b7e7ac2555c0ee576c75494cb832c59de8d9620927167bc136549b731ef79a39fbf8789831cc003a772fa00d4d699d089d47037e12e1c6eb8c20c5535225a33a1a787ba866ad481e9a4d689c83d75f1986405c82154e312ce8b3494ac5721b96193c025bb75b2cc974f9171297da058d2ed4f00d94e60737af29da660fc51fb9bb4d241e78c9d1815d7e3ea90aa541c4c512ab423ece93f9ff2f479f464b953b4171217b758b280c5d8acda32cced7bb0c92cd9b405afb3a8405602da914e1831fec5b6a291f90635afe82b9389a8b41d957f81be125de2f943b7cda9e873fcd74b446bacc00bdfcbe643053f6cc8162c0a98343242b489f59f7ebac016a7fbf620dafc15c8ecfe20b954153dba19eb81087673a1e847238864036e9a300239a00a5a03d8b39aae2bc201c04c477b1f2a37552670c06ea65551c5c88a42476d0d797af494b077737cb48050bd35f4980964b8b9b98bde84615e8fdca407230f5d15e97ae65d63a96f3d88518fde12d6a82881db5c8d4cd79e724312e88b394129de3569c5c99bf842a90f193c91fa55a82116e237c877dec2da6dc652cb33e33df47420c5238fb6ba5fd96cb77f0cf6c8a12be1a95186680d16a064d9b1a7c2461fdf5e0a512a5a3ed2f8dc0eeadb6c95a658aa1c2713fea473de51f65b13a40a2fe64e5227bc9248f59c2bd60dd2fc14918e333faab6e792ce2f0597be47cee70f5a8b788a50c9cd153bf24786aa80f631ad21926e3285f41c5125a15c12cc889112df0b1857020160122c2595ab3359bcab184ecb32f7cf8f38eea23f4f9135104629d8f1e8273c44f8afa129d3a74cf58ce494214629cd53b99a8489ddc5d339e317ce75dd6c5dbd098d811c56ec5f19c00fa00b5465e39f8f2022c71ab3ed0d5a24058c197719551985ab30a851390ab5e11420c521b1398bff0038d904e5879db37d60c623a0a82e191cd0b6bd20a9e956d41d5daef703bdedd6bd0e20fb096423dd39706e939e796aa8bdf071e44791d2e1ed4a81ffd018cb79c5b6bdfd6297cc6cd379845fd7ddfdb0b83d7b26c21e3edb58185e71472a79cb65322579bd5e0e7b1fe081dcae5447834d70459f8a96b341a26d8922a2b1412f95f3533e04b5f65be112cca03ca45ee0eca10adc7593c78c041281c880d879125e585e68f5c7fbf8686f245cd736ab5aadc57c032c637b753b49575acf4bbff46883382f408417438d7817097accae3fc54afc6015b84b3b05627d7c83c4627c87727bd24c4b4d96a08c64164ab8d4abe3fff5c3b09e8fd12498e3cc3627e799e4ef72a870cd89598d51ba396655b4de66e48997faa51d05d581c52f52852cffc9f89252f26d314d2fb8fcbce197f3a8f330339c2ec3c710710b74f0915da3bbb638b1f1a0cb0c176adc151da0ab52796d466b23e8a269c5b22b76fe7586cbc217621acc955a43571dc04f69deb50066cc587595cf0ca4e9a00a261ff2791ada8de04bcbbea249d2a7840bd8fd3ef98a248eacb7fea5318b2e207668e13ab4e5578c03315d44b478df5247d8afd0624d2a57adf85fd16d86ae54cca21f6c91d8cafb3b31e3f9c4e903c04f39d67c90560dd10b2d28c6ee937cae01e921c98ddd833a9b9247053110c0f29d2f35f8ea322a738636f17648b627db622a9cc891fc1d066d5fcabc168f1b492479b0578d0bd8de0298d4bb96a4351714a10a39de49d57ad66bd6b458ae0d799a19da54bf620c0e6979a95b61a83cd06e46d7d263d53d66f77557232983268f5fd01d10442af0c840ab09c6cc4ad77add6379eeaf98f7cdbd70fffcb035abae4efecdb7e6f63ed8acebd8449b3a910e5eb06b0ee6d80dd45026c9e6c2f978f813b2800386a4d7ad9d127d76c4286c1b2facccfd16e6a263ba3e2893ae5e42049d421e08a62ee9e9574a1f70f0add7b68344256592513cc15f4bc64c5a029f60d617c245c1c34d06c095b2d0a9647e65dadb33ea79e3e0ea62ce55347d24eaab7cf9fc33fd70c07363771f78a13d92941daf7970c97b43d2fbe3e789a52ae7bed64736e6ce59f95322d20f9d6dc00b5824a0ce9072dd051c66ac20d39bd953f567a831d75fa91fb4d49b200fc893489e60df95c2810f7fc57b7f6b7725ae600e1c2def6a943b8d111ff3e9a5ac80aa981fad8709e98ee1550005bb8f65dedffbfe3793abb407fc0470736574056302000359a33e8f3d0439eb7d60aa79255858269bc1011e4cd0d4aa753bc2793f9638fe70dda4a99191f08700047a8b66d9cf7a53bf70756013c8630419e3a0bf428158dd02ab6e16822f239df0a6f1b67b6e1335509a286b30c4b11a22dcea9f95d51307fc04707365740721027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac0007fc04707365740121081271c10f0caf52a40e015e7d35966dbad39525a6c0691d4beeb3bfb22af5304d07fc047073657403210b85bd6dc21b4919f6ebda7ab86ac8122c793be3fad19e44455945ddec8b59e9f6010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001cb6f1101e2b960c9a2fe480eb88fc76c63e58b57445c78d88e04f740580a36d6956006ef12ba606aaca89796dad5ff7cb4c4c7f4e65a07fea8a2ce3b58b265d4b3220013a09ecf4e01023aabca5586d61ecd5d7f2bedceb8b18a8e41451c97a192098b196add06a8a6b619e1e731fd9ae2973741aff6358c1ad45cc873a2d497271e3a20873a6f701d4c44ef670c8c7a9793079d9bdfe956bdc02a52d1de8b2de58b0f7d309240999f13985fa8c98e57fe1bb75219ae6330e9495b523e891dba7885a5302817d68a9891ae0c2597d4d7833b91b073c4f9b27be1cf81ca1c2c1014c57a4affe7b402c0e0c1862bd17bc1d43428671a106c26cab61d5e4ffd1c01f6af1c6e97f7a25ae8aaa8d2c91c57881df52bd5f242b127ec7aed5880101caa01783884dfe943ec9764473323f59b27a204f34aa1fe7fd4433fa606608b0f253247d149e3e269ae5445e917390be83d5650c05859e3a817a6c7e561e2b12baf6d4ca4221df0b7f29fa134269139afd9cee5c7d60149dfa23031705c6a8b72d00d659d7a085de8330de2e5c168ee3eba2180a9319f5e8e0806dadaf0fffbc2df839571360b8cd32300504a527cf914da7a1e788261c9cca2872ebec5a17cad33b15a7a7427c200b8746761359194520f0da1d7ae03f26190d07cf92d515e04815f514c66a98997320a028ad24e83182f213d991accf19a8a59003a2ca666ca505c0d8349e2cb22887efb7db9e4a4e3e2d8da0d5f032603e03f48fb967fd018fc99c6eb72acee4178edeb48703903a89e9462a2d1f447234c3373fff8b205848c7a9d6e45ccc31eeeb0105ed3af1188fb847cf6f03a7f0a646fa325b672796168dbbd9d1d19b2f41685b18c3c96f1cfb7ecb1dea132e8815b62647820ac4b12597d8e737a01a970704b03ba5ae68f8d64a1810db10427fbff1d74cfac91561484cfc8d3b23f74ba0f37e7db2f942c58fb1dd30f6a50c57b5eae720c6ed69b0fb81a6e0260c53028c732a544391e319987a24548230378f8f1ddf65a2d654c7b26951d5c6546cc8fda6003228e8913c9e3c5adf7b7b72f4ff5c4aedba6096097f9a4b58ea1e060609e8d1f0fb0e5dd905b29fec72b118d40c5f599a63f9a37041742a7f1af3ef951c31833621842ec212f9fc5cd3e8c08278ea192f8032ade447d2f35ab59810aff371f598b7dd57b3cde2ad854ecad0c786150748ac241ecd67cb868663a6fcbe9d68c3dc221d4fcec9b0ad89c337c1e10b91a38ab1357f260ca3a084da536e56262973e6c6cca838601fb2f335811375aed78295c4b17d3798d39d4cbf6254ce57680a102907e5c08b5c7ec0a1e73bf3d7b8babdc6156c6ca3f09c0423d6716ff4e6ee0338cfd778158202b5ffb60b74558dc6d0be9b5aed6892c4e0ad3e20de5b604b2b010af09ceaba7c0fba5a7fed39d31cb2769909a251ba5b0668330f734728f5c6786a8f1a35df77f3d739eec71d9268fb2a494a14bf7ab91c075023e76856d1745f5813d83878822934d0b4cc0538048c3f96cad7aef2bbc49bab139915dd4998ed3c6701973eb7913530b14c688f0b344ca9cb990de2a9b12fa7ad8b292234a33428b4d16813693964365bd412fb936c1461fdf8dfb0bba31202685f29addf95fc65f841e97042bc5d82f103e41017c6ac91d3d266e12db2dc195ac4f6d4353924270c7144cd96dcd07b9612e2bf1e0f7fc46ef83690648395c575a13c2b2df0fa04f740340e6b116b0866a401de22f68fa3f621d75b9f5f625cd5d5d7e12a5b0ff795749d742eb2648ddf068a451059390da566b3de84f9d528b2663de7dcc562eb014071e8277a054783d322fa67b9e5261b87e9c354ff58fc52bf9df404139311d6bf9650658fc94835a6220ca02a4bfbf46b2af4f30b1ba681c0de7c134d98dd52eb1e937c191cc9557aa73a343b99ae0b8954a3e929174984b574da75e90a9b71af1215317b04e4090770ab596091fc05c82235a324f86280f7fa081360ac758c1980c6a6763453e6b344611e46b05760941f9517ceb298ed7a2d6f96b11f5719ed0dec803c511dadb8d056d4ca06ca0ad090558a4fbf3a886f3ea9c33e1bb4bb5ddef4798cdfb384bea75a3794c42a53af60f12c95fe9b38d7f6a5d914ef100508091fc84756b596b9ba08d1dc42a7e253435b5481d1470ef00a1172cb036b5bdfaccaee02b15d099da17f9753846226325232e71ed9b48f57697bb3d1db75ed214310e6e3608ded6f188e37b21b96eefaa396db0df05a86416fe64aa950eea87e7af4407c828a8822b17be661269c9f9e365cdc79d20503e2d5e575c5387fa961206118dd379834aade7727a1b2477a24f05f6c2ba3156fe7f5b5ba6d7487b1a124f38e3ed3cda92a8edf05f28e3d699f0c12de9cfeb30ec8d4cd253287a6d3f596db0c06006a531e80083f5b41ee26c05d529e0a59ec151b4a9260a7ea8c1a175ca41b59f464adc122f448fc43e90dfa7b88f81caa7a84e3d6d4dbb85e5828b6ae7455dc2c87c8d9c706af2e62763675851b4ca449c9c0f95d953ce53c5327ba8981321a1cbfb76bafac7fa17c99567725d8e0ac288f3ccd3bad666e515156517281c97a3dbfbd5f345f8eb80bb3c4230301a38b7c920d74eb910d380f884a683f7d3e2738fabf9646138e8a32c35e43786e67925508e4cc7b1e7a36abdf6a497077704e216f1e730c20ecfcddfbdf2484d8eb0ef9b7478f77063fa90b8afbcd019d1a584df58c537b40b193f246598ae4c53958a947dd3916b14a5a4227f01bf543a8c5d6ab8eaefdfb959c8715b756a9c797fe39229dc56a86364cb367fa37e16e96cf8dca1fdc8b6a46ce88e3d29c75b6cc718ca8c28e9e1ae991b28c286107c1dced150814d6369cc8003b2976018941df7fb8eeb68be57adcf8a0e813d2fcd16dc390ce17dd0a91dd674d766d21b5de7f4678a548d75cc3de7d099b418fbdb66b2a2b12a56b1dd8b3f84a7b45e36db9bb63b3ef9fdadebc21c6a75364173925ffc69d481024e49076dca105795e7a64a3c82011889aacf0114a1bd31414e288cbb63eb47d399d0e025a54f11670c8ad4a793d0e7ebd3b0d8eb30b87cf62b131c61a4c55d2e56e47ae99fc543c57a1877ce44dac4c6f9fd99552701ade43858e0b4020230cb4d42d1990bfb509471a5c1c5557c29c00072c5cbbc056de6d465ea2ba2f4a1a5dd604dbf998e834b9200c59fdf90bec38af9218e47ea5ed5512cf8b0146126a15d49bf915eb893ad1ef83d8faf6c93f944ebce3704f930b6f69a1afe767ac2c9fdf092c804c7372ae1a64bca3a3b5a31f2ad430ba9e61dab3daa3be90a538cf0fba00796de511cddb5a594f14aa6d215bd0be19c0ca5f043403b7dc56c87b092d9436eb79509c3dbe367c2fcbc2b09f9818cf5a55d5ee0e477888166bbfece661b62812ffed3e4167ecc81dba148f89a8f2e7c27424a2ee091ac487e68fbbf9798e8caf46b35de8dde66a7cad6dc98a426ea7b8b930cf2d7d71fe71066dbe51e8a1c7151e97fecbeedef3d6f8e01cd5576dd58097b46153f9c3bcecf30bc82b4cdfb1bbfa826c7c5b217a023e4da02a35d0947b2c4d24d50a292e4c70fe854a420c54490d4850ee8c372756671fe130532f89878a64977ead60bf604b73859ae64ed62536423bb51b30c6fedbc819fdae05cb64b87b153f4252b832d6b866100a7b307d1e705643f96f0724858e6211802b2ce706d5424b99315db90c7d74fac66951251f6cb739ea008f839c7f62318f795337ca4ff05886a2640819873ae00b933081b64da1101d1b17451357921a2f4d4dc86c92a5eb346fd6cd3a63315768b94b90a15162aec91cea706b9221330db154d1fd71acac90be47055c725bce38710f1b4b4144f331cfa9fe15ebf6535747669e4aa1f0d72092ef395f16a4ec9014029b5c9d1b4bed9ec81a7568f8f1c87e0ce0f4ef9cf49e3fe4960b0b7904614772c63147f3c5463fffed182919c4973a8256346116be40b10a38cdea5bad5878760daaeda57476e86ec65e58030bc8ff99ebf017e9bf6383f981edc83b538e2bf4bb573637e6f4717d74a3ac71f5e151259b29c1b7226e3a8ab176baa2f27e3e385cd48c6f072bb6e8e9c92e35d7ff51177f4e3553555d967c04befc1847003a8592738c82eef231ce3d372372a19a01b27dc7916f986ff3f3ea8872d4986bc1ca649a92924c8f582ae2b44028e711e955d2563c33db3b7734aa246de553dab2c5268538f7c2b6dbe9a70657e2a26e52f5d216dfa9aa8e197a227c86a7d3b468880a721b804a0c51af1592043eee7fe3657b40c1832a858cfb4e037d208ee69c4fd697b929383a971b2a0864544505af2267e334db5f3f3f4ed88e2f6f2b3d7b96016ff92a120556e4d1e50440943893b00508c6242bcc41b6d06dc29ba20564b42257b9fa1b5f4f453e94e72501d31e8947e09a38261020400db4b8cf675b1854639a962a57905cbbee14ea0daa9bd281fd635c8f900fcabdcbcfb140f4f24b47731782ca899284fadfc9d0d6eff77045792e139afad34c7672bacfa22d85fcc05652178cc169bcf52ee3cef281d99935abf9c9910caa7994cfe33b9319d9854aa6e2590a86297f23b0cada7251a6ee1b2f3d2cb5a193cae8768d92f556994e416d4bcf5bba3a295d9e2215291237df74e3c493189bc5a27b3fec3e1a1329c6c6c75e0802b40fc74776476dd2d9e3453ec1709a8a2d162a936d9fe7dd4b6f7abdadfa429aed4cbd5896cfb24a2d91f1cee781d0ed73cf7306d6f9545fd3346c2232b8799553357a5a531d6085d04af5f183019dfd3483baf23b0ba5a6b58bba4b7a1eb0a32626bebb5cc0c4dad45c55d135be8b0a4dee787ed48b90a2a3fc120a753b52290c27ad0eb32b668a9540986ca0256c1676debc4d19b1b80bedd81e505154264a199ef1a54e1fff1b3589fbb6b2d87a1e04e67066571b68fe04f030d473f8d135585f8b7be91f0ae1a4fa2f44008c0f727f6aba9c5c60c3a9ff22911e267ffa2a5d1c71ff25f96ad257be29a2ec641e2055d785765cadd0c2fe513c44154e74f4b8cc4b24d9ee2f6cfe245549713f9dada1935247a2ac7ccc13c4a5c00c6ffe7ca33c0cde4c75c4fe2ea041a965e4c7385ebfdbc348da8c4216a02fa829892c827adbcbb87745120667cdc5a4aef28b190a54b422df45b8547c447765838dbf9f05de338ab672706b59b2f0644d6735d9316e64a1a15d3dfad893bb1bbcbe1b574a0b8ef5265e195a929148cc5e97c8bfb032d4528b523b6a6b29209428f38115cd102e3b4e268333e490140964278238e7ea9638ac34559e05300dad0c3f18e9228474cd3a7186102967c33fc14f086b4f1ec3957d87f2609d140dbb9e7ff793101d1cb90d004565c6a32924715b1bcab483e6389e2e824054edef350f328870f16412aca0fee39edcbd02a6a667aaaeabb6654c4762c639a26286a3c1730ab75defcd9451b4377f6acdc3b905684658c0844d904c47d313d85eb54963fd71a9a76dc8e345e479004f00e446b886ec41cf7bbb9311463e877aa4e357a0dd0b185a5cf12244296f5f8568e84cf6ac3b6125db6897e4e7c183cafc04f61b3263456f61a7880b446e3cffe986c3bf027c7deba490ec8544d2627e6596ef8f8a5cb577796e7c36ffc4f4346263b5d86ae1d7b8d04709367349d590047a8bfb34fbf6481adce60eddfdd1e148ab83874d70b5303fa0ce60c0ba7a047a477dc4c513d2218b0bb2bdf9b60be9a39e928976464e333ce0cb19aa21fda1c6851c6db9d5bbc6552e080f8e448637af711fed92e72c8d8530187de94603377dc387041a9eb42ac70b3923b9828de33bb337d55409cb419bf4089cb2bec79ffc3afc6dcc3cb82bc903d956d28e307fc0470736574056302000318eb22887126df9062243038440ef3fca2aa024fff8739db4930b314822b00cab172e83c33b7e5b959b0c3f27c714e447120833ef69bd634d1e011fc3c2585beaa882a29c2dabddfd31a48c0255d1ab57c6796aa4625a546a02cef51a9ee2e8007fc047073657407210302e71fc2fff37aeb6d2e2a7b7f2308d4ab7d4bf0a4cf9be7c06e89a66442b48300010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc047073657401210970cb1dff96101101e24bed1a66fb0794d2fcb26aba11e7f2393edc1534df8a9607fc047073657403210b9991881df2ec384ff5616f6c03d45cf033ee64536297c3557f9fffec716c1fbe0104220020cd4ed5f0db8c450e8b0be595b6410c1759c19f1f6926279095c83dade9862b6d07fc047073657404fd4e10603300000000000000018b6d2d00790f618171a9e8eb6051fc14dc27c502a54f5ccecacb4bcb350581a8f4b1a887a1e58865a21f51ef5098400c115cca84f489637718d8c3ca4350ad677374d40d2b64fe949ca34bd4ba845a87c7693421b70aa8d852e021aee329762883b9820bc0a47ea2fc6a8411ef773703632849094e51c73997c67f3e922d2801b6abc445375cd963a62c2bb28d733bf9274fe503081b51a56023a94200743e7d38865c190331449d19bf3d4eb7bf0b2a9b90696484aab906b9470e10946744838fc428f88461864d6601e93f10909e049a315fcc0ea7f4d71507fe5ed19b9ac2f55b0d3902b153ee27da9c55ab66debfb5ca440dfbb1a600cd8875049c4c13bbf242d26b10035002c7859f9c3ea631d955914adfa0817e4801e85b3f250cd9e7523db62c4ecb17a49028a02a1b61eb96a410643f1fec0ac664d96f99a1db4ac335c2bfec2946707c3c779a56373db197039af9a903e833af6f51de5cf8fc667dbf5416845d415dc98641e0984ebdfab88bbcdf305f605d36f7a472a516206d7ec722251e269800a00e8adb8f2ce161395c1c6ad4b406e1fda77bc7f0af07ea075011cee6cc5e7fd1b6e96e321171ca7d28a35595ddf0b0c831d28a88d30c7391c5832d90f906539aa49185fc6c0864e2d10b21160991fc19a1d084006d03fe3fb6f571f1ffd9f06ffbc0c076689373f9c2f8655758f7f44618345a6d5f8edae46e73a09c38157d5c39dd51a47756ea761e6320d6a91a1bb22d8dd35e4e7806b0242bcc4cae8720a953c6f933c0ee3216513cce6742a2cb4bf44553d8eaf175471dbc0f71c7c245fe99ab4240ccac637a16c82f0c60586f00d83ff852af48a239289d8b70a2638b0aff02572d6d2d68e22b87ba93f3db51bb27825265607ba4b6e9a194c7351f220b9e715a23237c40024fb7cfedaf7c8e0a1c4c6aebef8841fcf2609a3dd7f44dfd8895077df41f10790372eac8adff5ea7eeb28e371b4700d5044b41fd358dc4ba29158213e0f93711026fac8abfbe7cd80a939477ce545bc91cd1d32e8f2268563b0fe3e80855777d51cdf4835050a557d3c7b7f6905ba744ae5450d22837d2c2fa0c0755934654a78601a1fe236f11b6d72f368880ed938ff4a2e8d82c118493a1bb9a9ee95cf329f2d175d467aacc2c6cd73ed59ce64d508604db77942f9b7f3b0f86d913d3e1b489d2190a6da1ed997ab1eee356c885270e4e0192acc9c2b8702107c96aed654318bbb7724b913808dd2cfbbf6ace8db9c4cbf26f2c90a76eb967e365c58855b83550819cb5b28ea5e6ec1ea153ad3ee5e6292a174f7cab1b39e852194a6926bdc4d42326e17998cc057f614e91f6acab5a5bbedd93d9a13dc61977aa2accaf35370a3f1d3819c43c9430d0e767e92de96bb225007a7b6576ed7c4f05c984ce2e437d3b7f7f50c270caf90f8e5dbecb992be2972ef8d79c7674e025ee06b1302bcbd57fe5be9d9b4e38f17d9dca898ef3d71918550fd77a15c7aa2b66ee1a2f38b81040f3d6ec6693f54cc3fe39515275849e9e24b0af3e81705dea95e8f1bb1b4665cfdeb0630a5542a2857f2b58c36761d743299d77872d6894302123f4347702572f04d9876191d771e87655c071fbd29338eef0f9acb0a8ca3f0327ead8e875bc2e7ba38f249e6eda1fb74162e972ff523d20c6638b93297d406e9b2264187c06d0a78203d771f2525227d029b27b23192f747492b9774b12b039729ef75d11ce8d701e56fbb202f3e4bd68686f096f13680456529e5f132915595e0e0b4061b3014219f1fbef4d7403dbfcac01c701b7727c2463cb838d427e07706400be4fdd9140a800e47e7ac15a7a8a897187a38a0080726349d8b7566252076b1ae1366496b50e9a6d236837ffa0c4c3e61bb409a7d9faf8662109ef7213007a76a6d94cb79ad07183a33a4cdedd78896278a3b5531ec0efda9d77076d85a63536b426a6b58c3d0e8a46faf0fdf678e4964272e8410b346cc753edb94a61a2f309f8e753ff332f5b15215e0a20ef32e6a6faa044a431764e008e66b4af820bb6afe4d8dc1679219fe7614482517fdc523d4d7ce2bad9d59f7b9aed89b813e5b36b58a7c668e53181c0e1da45e4ba3851b46de8e056d63e440c471fbe23fbffc31ece7cd20658784f2d2fca3f730a1f8a90ea6995ccb490fca475b1441e38468433d1423c1d83a20b04f1216aff13c8e035c2ed389695c2bb0bedadc38c8a6222d687f0235bda3566c0865774bb6098acd674ff9091db1a6c4f0c2955c1759730b361f310d956f892089f500eae0084022e610ca4e758ce2c680d213bd1b5e00a6c991976e93189159bb98a1e4eff14f681d47694060126c0852ad52489c15d7302d82c66aa830208a9fd13da87a2aac3e44dddae632f2e4d56dc768ee6b3622c61e59aee27fee5aa0bfa9c56402ae3525f00634b11381d0b9637ac699e4c4b225c32bef9dc563e87cfc33bf07450c2cdd015bbd94df63697cdd9b6cf4169cfe1f626c126dbb588c57542662d02eeed561946ce94b46571f72ca3cf7fe52a8a6ea24fa216f01655dadbeb0f74e383e522da69db64b368cb155c2ba4c4fc8aea8745b1081223317003f0cd7ae620fc7d9a6c9c4f39a237d5d9d7f3e756c77daeccbd5d06ed28ac2f089db5449340872bc444aa4aecf6552621b84e4fabfd6baf0e1b61cab00e5c2050bfa38899e9840e2dac55c7d5e7105f65dc3afe3c35dd3e8ebb1a013d65fb0b9d76665f3c4eff5b766fbb0c03cc10c411987ef516405296437a91a67d7398d788e30f55bc1e6236b317784504c53800cc07f4eaf78bac03a025a7943573edcc67db3d5d5281f85ee993238fee3580cb846efae33b45ee2ee1ad640b1d068302504521269a31a166435e8fd964d2e15048f299a95ec1cbeb0c8d5d119e24670112d61d56ea120d7bc30fcd924fc7b812404ac45798af3fb590570a4d7a3f41639b879d1bdf2c72c979105460892fb41570cffee716679eb7b4124be188e28ca4c68d206c1cc9d49cd7489d63372853dbe83f874579238ecdd28c0dc265871d6dccf782b571f1004702121e8b15c9d8af9e6812dbbe742812c7e7cd1dafbdbe3d189f618936e270855700be8f6a7b528f271a936a2cae204b44d909ae3f6a8bcbfcda44b430397b1c96187e5a8362afe92bb9afb0a0a482cb6c8cac39a0fb1fe0034f8a965a3be425b4b34f67ff38a8ab30f8219a8933539c1d06d9b2997b6de743c568caea8b269642b8503b546c9e136d0e47775c8fef45d4489e6b4747c6accd6fad3d34fdd6cbafe07264e7ae1e025306c97d77a130093cc8d5ab8e4abe095787183a9084c569168011766b47dade4e3e1353a78f9f3464d8237b7de02da10c7a7b8951bafbaa898003ad9de997c89d8507393c6fb782b8f41aa99d5c92d5ed93e02a2e7550d42f178739898406badb6da850e305c10063b1a687a2b321a1f867ddfc7949d9764493af4f9681137101f87ec3c3733c0ee23a758a69dd5dbfccd20e0e7cc8543c213688ef9cc85bf9c539d3b6758e24befb3c1b617b7d59a17e915bb985df8229429430dd3057a8225d22afc2775e352a2d7c2f2f46786d4e6e984fb35ac6d7e1b57da82a6d1470895e56521c88c166f8124a4f0c5ada7d5e43174e319c21b9416735858ab50958def63c9a38e853d28a0f47b8e601beb0cdfb30d87070003f957ae347d5b03e80890311ded8018de0227c430f29ff20f0d0d1331dd5f22cff547847c0a26c1fdc272cd234b668823635f68e0797bf68fb1e531a423b1fae9b056fb8c0589908cd2774a7bea8da465248a438a22919d35e0ed8c5020f06824aa856c1a75e0d50d8e9c2dca471d249a97e8e8055d0927432577ef07e6658aa6ab3e9f5f4f50848b12dcdcd9594dae732864f9c6253cccf39922b5f1f8e16458a0c11db397017821de73a331400a76c2a5a29c7661405e9f244a92862a8ca13bc86a78a36744e52019c067573574f75e01b21c36e19a714e9cc0860e9df8e659cefab57002dc2b3d1a2918dc20ff25bc0d87023813abcbbe29defa514bd71fedf314a140508c7e8dd4ace82d56cfd6307fc8299a65c8b3b0719881dc06070d670729902ebfa1dc0b88050e5824cc186454ec2727d0fb2f95eb677f949375a0a31661c7f2bc452426a052b160b0654bffae35766a5317b845582176f1ec52e5bb7d814651781e268cfaefcf6da560810d84343dab8a8b2f2f7976334043498f529d75f9dec893bafdde0ebf003ea7d43f22d8e4eb3afea7be8bd22a9adb9334db2d6b05b8bfe72a96ca2c5ecc925333e4076bf5b7d9ed519ac3eb87c73d0c8121b1034424991561eccb669807a722bcd43141915c9db90f7a8a8e732054970645da1845b988f21cd5d2a89abd8e14c0e6532a3db89e43571b795d6f475b11c16cd7253a4538f4e73d0e3791e0c8f3e51ce3d9645062f249549d9165185fe6920659fe72ae01f69c6d5cef5a01cbbce0fcfd671e1a0aac35d541210a723d2fde91f37c130657d8825662bc46ca7780e07422ce2e7e5cbc1b15ad29b8473ffb832f45c480e960d620267561b3e3112c54359459c3f0235eb6eb2720dd5cf65f88267e099ae87ec7e35b3a915f18a4ab67583ccb4d907637812d797dfa51acc7b5cb13ab58934217a562b00a4a3d030bd9d3565a47c95396034bd65d39d39a6e7795f13e5031d148db16eddba996e1e70730eee02c47bd8b5c42812a12a2db5490fc3ad50d7d03c72c0e933ed8b43f8e34124e272cb39e06e628f18acfe4488dcced67a2c47008801e8d2db5dc7f7556d38020d53512993f3efeb4b07fd1e7b631deeeda74983011ca401ec7b86eb2a7a4d341f97337d37ada0dbbe124fe481ecac3e9b8e8f3b8b90ccf81ec028f2ee4e62934abcfcc2647966f6eea6563a0fc8fc75348f5fb3d2390db24d49858bbca822217fa81d240cacb7b2f6c2e4bad8a7a112b83a5d35ad68ec1cd599afee41fd4e860097f33afbc2cd790b2933f1194549c5aa3fe5c651a456c8ba7c8fa5ee5492233c21a80bd9216fcfccc03ecb76af3849711a0f69d5a261ce94484c30f4b74ca6cb4d7d5c4dcc0be1f0390c208e1e7e863700c6dce7ea5ab3831c00e586094a6a1f3b32dfbbf99a7581f90001dd558738804d0663a59d77407b6ab1ef3a82c156bc58d5a694a365b300a90ebbcddc49bbe5ba63c88e7a6e0faf5364ea17cabc602ddbf87a4c2f55b068ef296be5bb66befc1e37ad77f8e97c08f02d17dcdeb47959b0f46c2ae2c3d99a948f2a52c5793d53fe57876c38336a74786042abafb63c2154096eaed98598aed76da5a17ae179e79f848b237bdd6db63e5fbe77ee28b2bdcae462905ce1827800407317c2a42fc0dc234b2183db9097c0fae23da91aca6b1b7894537b0dc8524536573a34ef68596021cba863ece0de4a068f806d888aa03ed88ad92f375860f8885c5533c637a7a330c24aa0357f007ac3678e59cb1f6aa7c6979e15fd107a98ab42a635a3c3e70b58a19ec73b4bdc482000c3dfb4af84012ea3957d3d30b80857d9e06a2e677c84e5d9041ad4bf22afbdf0351f6d5c7a7a2ada3dd5ab4e48e37d51e5fad56cf9963715438c5f04b9772773893a59e2e46c6b7e19e9001650b66b469f4b1c4d188b286d5ce8b21269179e772147c7cb09eaee9b9de684ffc33068e5d346e79b710d82fd1278aee64ddd4ed37a944e6c745217c6a626444aa95b565c1f68a12cd2b86a61927896884bd5a76a9df4f6fe548bc16444272ec1e856334d64e0f32ba90daa77085b0683e3e010abf571dc564fc703a12998c72b6712386cfa4ccc802c850a3604bb0b2d5cd2eb6e788d475ff198f94a220c925a32ab6d69b037f1b9f0f4ce1d1b89cc0d07fc047073657405630200033fb971149dc9881cf31b7bd90de445f0ada69611016c5fde4af5bffe2b40a1a27d651a1d2127b0a95539b18fbb835f5145c1c4c7492866c6bd08ba260abb300720d2d42b729b72c6166f9ae0e4ac09a0f6851ca65444bed00b72b7f12befbbd007fc047073657407210252a4b012198c2131d70498b5939c401c01eb1178dfd58123b55766b03f008f7b00")); - tests.insert(String::from("blinded_signed") , String::from("70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c22020334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe7473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b220203df8f51c053ba0dfb443cce9793b6dc3339ffb0ce97af4792dade3aae1eb890f6473044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa01010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc0470736574012108a337e7e0ecf24c121a17193623254c277a306e9fd39cd5aaf8b7d374f4011c6507fc047073657403210b8e11e3b8904ac80caeb16af1c93053f8a11a963269bcefa96823d75b8640ae940104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e1060330000000000000001efdfbc0022a6437ec10c672d76c513868f403f6b9706e09733d6624e3cda831c2c199dd4c5763054a1c219e079e8cf3ce0c00dc2f16516972e1e64d576adfd9f5d778675c8a3172450a4cc82d6e6c59f2b16dffddc902d0aaa647b750d6224cafca7239a4fa81219cf2ee89741ffc5b7dd7e47d332ca931cffb1d1d432935a6013207629118c965b7a5102e62c43ca9d06bca191307a476738548536809ff6b01c6b5b25d76ff2f67d99e20fdf7d2eff6fc248186d21d054196023c5e4f572ccf0f3aad8728c46f2ff6756ea39de46028610a3d26cd42978b09e0e29e0a8aa46e4fd39d28d028592560264cf1a794c27f6c95d382f486dfe900a81d9d92935c7e0e6306549b3e49b1f60182512ccb994338c3541a2956139b2ccb3dd156853105abf5fb394cb2c45915dfd4106c7472dc5d360ab5bca408203a3fef58b4dd33b0c11c367dde2f19c8af7682be067244bf49a2b8cd4685f5481cc31ba27fe2f3d7b7a353be9b41e4eee2342fd70b8408c91951c71c75dde8fbc03e3f28e6d3d3b41e0e963d3ba0c25b2eb50560c21221950b0699d2615f0128e6cb7fa1b04ac1a046e569a7e87df98c14360b8ffc43db5e17548c7ea5056f84ffd14f4cdfc68a4f10e9b391cfa63eef2c2d623e7cdcdfae2fc4d63496d81462174ff360809b7e3b4305979d9cb8e9ad5b0f012494d31ce51ee6489555b09dddb16641ed9e2534fc34db99d2a4fa736eeabacf2f8cdca97f9e84c964277c6f30f1af7fe2b51b39b487d56ebaf593a3f98e811cb09849c5b445d5b7c9ba37807bd0189c8bdb2709fa70c230f9aba41dd3c62384aea6e1ca098ffec26367aa65a09459fca074d1da0365cf7fc2d8310ae099b838ca78e62cee10f95ec549faac1ff0f8236fb8cf2c0f6654e471d3950ef45e0159c44f9e343d05b3af59b939cf76090d040376407c41661eeda7d2cb61cad0088a286948787dae0cc5abdcb97f7f42026c65a13e1df1357c25d376955942adc858e73876e1d8812969055d55decac9a689dcd11dffe5cf6e06088b93a11e153ffed104266bba472cebffb2b0cfc8ef132309bd7836071d3b6ed459a5950c64cccf230015c98f9210f2d57b7f3a07c382f3df09f055c88e1f312db0d60d471afdc0b780d319a6229babd8f45edecff8d1073fa850f755219a3ea14e7234cbc7590c60eba0ba0cc1afde6ce91c8e8835b1ca809926b3e7d8d7a0941425e5f08e884f12693eeea1b3651f53da90972aaa37d426f37db3edae4285db114cbad5964c269e03b15358ad2a7242e0af538a594fc779ec3c43c3d94fab2028310c6d0acf3efdf0acf028ee757c5c02bb5b8b691aa5eed1a62acbabd0d61faa478cdcd54c6db2cec6144d8d1185115097a7da79c16ad3118d12e36dbcb7a70b0ddb27bddfcb1c6e5426b7e411f607d22c3ac2b8d3e41f55faf5e2105bf3b943846cc4c33edc64f902c1eeda09c8110d4f3ed0a5e511156a3e368f02161b92126abf649341ddb8ed03d1d41b91fc34548c6d94dfb47c088ef27a3cbfe1c9cb05f2ab2f18b8746394c8080c4cd92e818e46f861614ab870cb7ae3446e376793f3a6568a2ccfc2ab0ed0365567671436fee6cf427b6410a046d80b9d88f094924ad370da363e8eb70355b711687e92d88a08ed811ff241c7a6dbbad9dcfc18e6a42493483b938e36c1edd2a1c6e078a17c5d145c9c058b4dd69afc44c345f1c88afb95c1deb5c4994161ba25783165d43b9e50a2d8333a8037cec2ae809a3dbe026d6ba40d60badd05bce73b0f9f36966c30b9c0cb5776544c1182024a96e746a3b01f9db10b45aaffd3b055b02b40bccd41e57c10719bedb0fba99a0f6b0868b186fca0397ab8c219f33190f81e4cce2fbcbc0975c394919c98fdfd7e25a33e5f31fefd06c8dc409cbd3e743f0f48dc90abe45b2e68948436caa37fe9932a77b7e0fe0819d8283964b0eee2249d9190f3eb0bb8178e10a287be1059f35cc1a153dda14def65f3c49cdc5186da86bd3e965446f914e3c9b4cbfcfd2c379f306c5ef8844c4fd398b3c6f96601e90d2dc8810875663939f63abe3ee2e1c8a9c2c2010a01d0dcaebb556a7c98421f8e6465cb0434c07dcea9db1a142f9684e50b06c545785f0dee4def1257e4bb22d87a2b37ca9eb53081eb8f1ea0439c4575abac435868a36552df569ccc63477594ccf7eadfed6adbb8e81800a2e2fdd7effdd1e2f09cf76c9e780f6f8eb8408a3fcc06cb8bcd28db7a37edb0232a6f0e509c684318f179d0c91a97718ce3956c266790361ea3a1bf70cdb8a2f2a59b06dee18075745c7302db9b13a452c188c5624964af2d5d4bbb1138dec59df5dfb077a0f62ac4db3de81f54365f2a4a6dee63a6092b4660d3f5dca3cc8de3bb5350f5dcebabd515c72c9114bc58d96e2e863106f0982c2632bbacf2fe5cf6a8df880c550f7008fd09227baff82d4eb031802fbe7d50f6174860c70fcd9f0356e34c0d45df66492dc309b260b7158adf678e2da66348ca84de3e721c6196e0c717f59cb802c6866defdd9032a4b7da82b816d9681e5eb9115fc2a572fbbe105f479ab339bfa5961aa1920346c9ae4185a74aa828da78a71e55876c657249e83f5812cfb055400da1db8bbb5ed3ce2ca4655b0c39b698ed7d235fb0ed4f29a7e8925cb873176efcd1c6981dd23865468c6e01ddf61fa3e40d6fed18d8e3dbd97a08c68bfb092e441e512d44089cf563509785dd58203949a1ca9b66a700db14060a760aa404e5e9f31eaac015f1527f6d760d8714c88040b87fc8a4d183230cfae35326947e28a7a37eefe1d77070f5232a0d67e278a45d649709a7398cbd43094c5001263671517f83e62e79fb75e6f9bc592fb3bbececa3f597dee71dda0fc909079ea49d81554d2fd79d1cf3cf25d1186efb83cf972b7426600d7d6eb6c48a5f0e26af640c733f3f771e0926e6b38b6f39d7882b0538dcf281d92a6bd361bb32e16f3988d6790fa0a45f549e983f4eb68ce5ff11647b37f8e4c444aae8bc0f7c49ce7215545a29215b55f37dc42aa6add6fd1fb45d9ae580434097e7a8686e23cdbdbf6f8b6b1e5579a7908bafd8878b004c7c94e045fdf2b94f8c1a75ccee7bfabd9bb6d0ea8a60dca61053636160c19f8f3fff3d0330fc95d20a1393629f33f281e5da80a5ff66aaa5eefcf495f8bf7e24744778841d7c633f01af2305a122ee093837998e87f060105ffbc083c0d71f68c2c63820d7a547d9af5618544efb9736af56736e73cb696f191c68970ea1deb587231c889672b3b5399b9b5e915e3c567474c3905ec5b6468da826f1a6438ec335da847db540a091ab311c6846f96a2f17befa2f29ea491a41d7630e42583ad1212e6c606dc258a49f756e2480f90775c04c5c533300e37f8bc7afe7b155fb95877252ce4a53f78491ebf9d8a4aa41da1848633816542901d56e66f126316c80efdaf4b457f9ce771edb012a0b3c27c717f5cb3cc99ffcc959a02289e30d5b1ff936320579c469bd55cba6e79a0c5f1bda59981b71840a8c1ca56863e91eb21fbaf84525d5f04e0f282d03bb56d6dc2352f163d8357b86bc6e4e621bb693db3565eb9ef5629af537874c1cb3459582463362dab3c6fed7e574ce9bffcc685b8eea61599292cc69860c4f9584818182f94d719dcc463e9a6f854405ca2deb3de29ffa1826f795b7e7ac2555c0ee576c75494cb832c59de8d9620927167bc136549b731ef79a39fbf8789831cc003a772fa00d4d699d089d47037e12e1c6eb8c20c5535225a33a1a787ba866ad481e9a4d689c83d75f1986405c82154e312ce8b3494ac5721b96193c025bb75b2cc974f9171297da058d2ed4f00d94e60737af29da660fc51fb9bb4d241e78c9d1815d7e3ea90aa541c4c512ab423ece93f9ff2f479f464b953b4171217b758b280c5d8acda32cced7bb0c92cd9b405afb3a8405602da914e1831fec5b6a291f90635afe82b9389a8b41d957f81be125de2f943b7cda9e873fcd74b446bacc00bdfcbe643053f6cc8162c0a98343242b489f59f7ebac016a7fbf620dafc15c8ecfe20b954153dba19eb81087673a1e847238864036e9a300239a00a5a03d8b39aae2bc201c04c477b1f2a37552670c06ea65551c5c88a42476d0d797af494b077737cb48050bd35f4980964b8b9b98bde84615e8fdca407230f5d15e97ae65d63a96f3d88518fde12d6a82881db5c8d4cd79e724312e88b394129de3569c5c99bf842a90f193c91fa55a82116e237c877dec2da6dc652cb33e33df47420c5238fb6ba5fd96cb77f0cf6c8a12be1a95186680d16a064d9b1a7c2461fdf5e0a512a5a3ed2f8dc0eeadb6c95a658aa1c2713fea473de51f65b13a40a2fe64e5227bc9248f59c2bd60dd2fc14918e333faab6e792ce2f0597be47cee70f5a8b788a50c9cd153bf24786aa80f631ad21926e3285f41c5125a15c12cc889112df0b1857020160122c2595ab3359bcab184ecb32f7cf8f38eea23f4f9135104629d8f1e8273c44f8afa129d3a74cf58ce494214629cd53b99a8489ddc5d339e317ce75dd6c5dbd098d811c56ec5f19c00fa00b5465e39f8f2022c71ab3ed0d5a24058c197719551985ab30a851390ab5e11420c521b1398bff0038d904e5879db37d60c623a0a82e191cd0b6bd20a9e956d41d5daef703bdedd6bd0e20fb096423dd39706e939e796aa8bdf071e44791d2e1ed4a81ffd018cb79c5b6bdfd6297cc6cd379845fd7ddfdb0b83d7b26c21e3edb58185e71472a79cb65322579bd5e0e7b1fe081dcae5447834d70459f8a96b341a26d8922a2b1412f95f3533e04b5f65be112cca03ca45ee0eca10adc7593c78c041281c880d879125e585e68f5c7fbf8686f245cd736ab5aadc57c032c637b753b49575acf4bbff46883382f408417438d7817097accae3fc54afc6015b84b3b05627d7c83c4627c87727bd24c4b4d96a08c64164ab8d4abe3fff5c3b09e8fd12498e3cc3627e799e4ef72a870cd89598d51ba396655b4de66e48997faa51d05d581c52f52852cffc9f89252f26d314d2fb8fcbce197f3a8f330339c2ec3c710710b74f0915da3bbb638b1f1a0cb0c176adc151da0ab52796d466b23e8a269c5b22b76fe7586cbc217621acc955a43571dc04f69deb50066cc587595cf0ca4e9a00a261ff2791ada8de04bcbbea249d2a7840bd8fd3ef98a248eacb7fea5318b2e207668e13ab4e5578c03315d44b478df5247d8afd0624d2a57adf85fd16d86ae54cca21f6c91d8cafb3b31e3f9c4e903c04f39d67c90560dd10b2d28c6ee937cae01e921c98ddd833a9b9247053110c0f29d2f35f8ea322a738636f17648b627db622a9cc891fc1d066d5fcabc168f1b492479b0578d0bd8de0298d4bb96a4351714a10a39de49d57ad66bd6b458ae0d799a19da54bf620c0e6979a95b61a83cd06e46d7d263d53d66f77557232983268f5fd01d10442af0c840ab09c6cc4ad77add6379eeaf98f7cdbd70fffcb035abae4efecdb7e6f63ed8acebd8449b3a910e5eb06b0ee6d80dd45026c9e6c2f978f813b2800386a4d7ad9d127d76c4286c1b2facccfd16e6a263ba3e2893ae5e42049d421e08a62ee9e9574a1f70f0add7b68344256592513cc15f4bc64c5a029f60d617c245c1c34d06c095b2d0a9647e65dadb33ea79e3e0ea62ce55347d24eaab7cf9fc33fd70c07363771f78a13d92941daf7970c97b43d2fbe3e789a52ae7bed64736e6ce59f95322d20f9d6dc00b5824a0ce9072dd051c66ac20d39bd953f567a831d75fa91fb4d49b200fc893489e60df95c2810f7fc57b7f6b7725ae600e1c2def6a943b8d111ff3e9a5ac80aa981fad8709e98ee1550005bb8f65dedffbfe3793abb407fc0470736574056302000359a33e8f3d0439eb7d60aa79255858269bc1011e4cd0d4aa753bc2793f9638fe70dda4a99191f08700047a8b66d9cf7a53bf70756013c8630419e3a0bf428158dd02ab6e16822f239df0a6f1b67b6e1335509a286b30c4b11a22dcea9f95d51307fc04707365740721027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac0007fc04707365740121081271c10f0caf52a40e015e7d35966dbad39525a6c0691d4beeb3bfb22af5304d07fc047073657403210b85bd6dc21b4919f6ebda7ab86ac8122c793be3fad19e44455945ddec8b59e9f6010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001cb6f1101e2b960c9a2fe480eb88fc76c63e58b57445c78d88e04f740580a36d6956006ef12ba606aaca89796dad5ff7cb4c4c7f4e65a07fea8a2ce3b58b265d4b3220013a09ecf4e01023aabca5586d61ecd5d7f2bedceb8b18a8e41451c97a192098b196add06a8a6b619e1e731fd9ae2973741aff6358c1ad45cc873a2d497271e3a20873a6f701d4c44ef670c8c7a9793079d9bdfe956bdc02a52d1de8b2de58b0f7d309240999f13985fa8c98e57fe1bb75219ae6330e9495b523e891dba7885a5302817d68a9891ae0c2597d4d7833b91b073c4f9b27be1cf81ca1c2c1014c57a4affe7b402c0e0c1862bd17bc1d43428671a106c26cab61d5e4ffd1c01f6af1c6e97f7a25ae8aaa8d2c91c57881df52bd5f242b127ec7aed5880101caa01783884dfe943ec9764473323f59b27a204f34aa1fe7fd4433fa606608b0f253247d149e3e269ae5445e917390be83d5650c05859e3a817a6c7e561e2b12baf6d4ca4221df0b7f29fa134269139afd9cee5c7d60149dfa23031705c6a8b72d00d659d7a085de8330de2e5c168ee3eba2180a9319f5e8e0806dadaf0fffbc2df839571360b8cd32300504a527cf914da7a1e788261c9cca2872ebec5a17cad33b15a7a7427c200b8746761359194520f0da1d7ae03f26190d07cf92d515e04815f514c66a98997320a028ad24e83182f213d991accf19a8a59003a2ca666ca505c0d8349e2cb22887efb7db9e4a4e3e2d8da0d5f032603e03f48fb967fd018fc99c6eb72acee4178edeb48703903a89e9462a2d1f447234c3373fff8b205848c7a9d6e45ccc31eeeb0105ed3af1188fb847cf6f03a7f0a646fa325b672796168dbbd9d1d19b2f41685b18c3c96f1cfb7ecb1dea132e8815b62647820ac4b12597d8e737a01a970704b03ba5ae68f8d64a1810db10427fbff1d74cfac91561484cfc8d3b23f74ba0f37e7db2f942c58fb1dd30f6a50c57b5eae720c6ed69b0fb81a6e0260c53028c732a544391e319987a24548230378f8f1ddf65a2d654c7b26951d5c6546cc8fda6003228e8913c9e3c5adf7b7b72f4ff5c4aedba6096097f9a4b58ea1e060609e8d1f0fb0e5dd905b29fec72b118d40c5f599a63f9a37041742a7f1af3ef951c31833621842ec212f9fc5cd3e8c08278ea192f8032ade447d2f35ab59810aff371f598b7dd57b3cde2ad854ecad0c786150748ac241ecd67cb868663a6fcbe9d68c3dc221d4fcec9b0ad89c337c1e10b91a38ab1357f260ca3a084da536e56262973e6c6cca838601fb2f335811375aed78295c4b17d3798d39d4cbf6254ce57680a102907e5c08b5c7ec0a1e73bf3d7b8babdc6156c6ca3f09c0423d6716ff4e6ee0338cfd778158202b5ffb60b74558dc6d0be9b5aed6892c4e0ad3e20de5b604b2b010af09ceaba7c0fba5a7fed39d31cb2769909a251ba5b0668330f734728f5c6786a8f1a35df77f3d739eec71d9268fb2a494a14bf7ab91c075023e76856d1745f5813d83878822934d0b4cc0538048c3f96cad7aef2bbc49bab139915dd4998ed3c6701973eb7913530b14c688f0b344ca9cb990de2a9b12fa7ad8b292234a33428b4d16813693964365bd412fb936c1461fdf8dfb0bba31202685f29addf95fc65f841e97042bc5d82f103e41017c6ac91d3d266e12db2dc195ac4f6d4353924270c7144cd96dcd07b9612e2bf1e0f7fc46ef83690648395c575a13c2b2df0fa04f740340e6b116b0866a401de22f68fa3f621d75b9f5f625cd5d5d7e12a5b0ff795749d742eb2648ddf068a451059390da566b3de84f9d528b2663de7dcc562eb014071e8277a054783d322fa67b9e5261b87e9c354ff58fc52bf9df404139311d6bf9650658fc94835a6220ca02a4bfbf46b2af4f30b1ba681c0de7c134d98dd52eb1e937c191cc9557aa73a343b99ae0b8954a3e929174984b574da75e90a9b71af1215317b04e4090770ab596091fc05c82235a324f86280f7fa081360ac758c1980c6a6763453e6b344611e46b05760941f9517ceb298ed7a2d6f96b11f5719ed0dec803c511dadb8d056d4ca06ca0ad090558a4fbf3a886f3ea9c33e1bb4bb5ddef4798cdfb384bea75a3794c42a53af60f12c95fe9b38d7f6a5d914ef100508091fc84756b596b9ba08d1dc42a7e253435b5481d1470ef00a1172cb036b5bdfaccaee02b15d099da17f9753846226325232e71ed9b48f57697bb3d1db75ed214310e6e3608ded6f188e37b21b96eefaa396db0df05a86416fe64aa950eea87e7af4407c828a8822b17be661269c9f9e365cdc79d20503e2d5e575c5387fa961206118dd379834aade7727a1b2477a24f05f6c2ba3156fe7f5b5ba6d7487b1a124f38e3ed3cda92a8edf05f28e3d699f0c12de9cfeb30ec8d4cd253287a6d3f596db0c06006a531e80083f5b41ee26c05d529e0a59ec151b4a9260a7ea8c1a175ca41b59f464adc122f448fc43e90dfa7b88f81caa7a84e3d6d4dbb85e5828b6ae7455dc2c87c8d9c706af2e62763675851b4ca449c9c0f95d953ce53c5327ba8981321a1cbfb76bafac7fa17c99567725d8e0ac288f3ccd3bad666e515156517281c97a3dbfbd5f345f8eb80bb3c4230301a38b7c920d74eb910d380f884a683f7d3e2738fabf9646138e8a32c35e43786e67925508e4cc7b1e7a36abdf6a497077704e216f1e730c20ecfcddfbdf2484d8eb0ef9b7478f77063fa90b8afbcd019d1a584df58c537b40b193f246598ae4c53958a947dd3916b14a5a4227f01bf543a8c5d6ab8eaefdfb959c8715b756a9c797fe39229dc56a86364cb367fa37e16e96cf8dca1fdc8b6a46ce88e3d29c75b6cc718ca8c28e9e1ae991b28c286107c1dced150814d6369cc8003b2976018941df7fb8eeb68be57adcf8a0e813d2fcd16dc390ce17dd0a91dd674d766d21b5de7f4678a548d75cc3de7d099b418fbdb66b2a2b12a56b1dd8b3f84a7b45e36db9bb63b3ef9fdadebc21c6a75364173925ffc69d481024e49076dca105795e7a64a3c82011889aacf0114a1bd31414e288cbb63eb47d399d0e025a54f11670c8ad4a793d0e7ebd3b0d8eb30b87cf62b131c61a4c55d2e56e47ae99fc543c57a1877ce44dac4c6f9fd99552701ade43858e0b4020230cb4d42d1990bfb509471a5c1c5557c29c00072c5cbbc056de6d465ea2ba2f4a1a5dd604dbf998e834b9200c59fdf90bec38af9218e47ea5ed5512cf8b0146126a15d49bf915eb893ad1ef83d8faf6c93f944ebce3704f930b6f69a1afe767ac2c9fdf092c804c7372ae1a64bca3a3b5a31f2ad430ba9e61dab3daa3be90a538cf0fba00796de511cddb5a594f14aa6d215bd0be19c0ca5f043403b7dc56c87b092d9436eb79509c3dbe367c2fcbc2b09f9818cf5a55d5ee0e477888166bbfece661b62812ffed3e4167ecc81dba148f89a8f2e7c27424a2ee091ac487e68fbbf9798e8caf46b35de8dde66a7cad6dc98a426ea7b8b930cf2d7d71fe71066dbe51e8a1c7151e97fecbeedef3d6f8e01cd5576dd58097b46153f9c3bcecf30bc82b4cdfb1bbfa826c7c5b217a023e4da02a35d0947b2c4d24d50a292e4c70fe854a420c54490d4850ee8c372756671fe130532f89878a64977ead60bf604b73859ae64ed62536423bb51b30c6fedbc819fdae05cb64b87b153f4252b832d6b866100a7b307d1e705643f96f0724858e6211802b2ce706d5424b99315db90c7d74fac66951251f6cb739ea008f839c7f62318f795337ca4ff05886a2640819873ae00b933081b64da1101d1b17451357921a2f4d4dc86c92a5eb346fd6cd3a63315768b94b90a15162aec91cea706b9221330db154d1fd71acac90be47055c725bce38710f1b4b4144f331cfa9fe15ebf6535747669e4aa1f0d72092ef395f16a4ec9014029b5c9d1b4bed9ec81a7568f8f1c87e0ce0f4ef9cf49e3fe4960b0b7904614772c63147f3c5463fffed182919c4973a8256346116be40b10a38cdea5bad5878760daaeda57476e86ec65e58030bc8ff99ebf017e9bf6383f981edc83b538e2bf4bb573637e6f4717d74a3ac71f5e151259b29c1b7226e3a8ab176baa2f27e3e385cd48c6f072bb6e8e9c92e35d7ff51177f4e3553555d967c04befc1847003a8592738c82eef231ce3d372372a19a01b27dc7916f986ff3f3ea8872d4986bc1ca649a92924c8f582ae2b44028e711e955d2563c33db3b7734aa246de553dab2c5268538f7c2b6dbe9a70657e2a26e52f5d216dfa9aa8e197a227c86a7d3b468880a721b804a0c51af1592043eee7fe3657b40c1832a858cfb4e037d208ee69c4fd697b929383a971b2a0864544505af2267e334db5f3f3f4ed88e2f6f2b3d7b96016ff92a120556e4d1e50440943893b00508c6242bcc41b6d06dc29ba20564b42257b9fa1b5f4f453e94e72501d31e8947e09a38261020400db4b8cf675b1854639a962a57905cbbee14ea0daa9bd281fd635c8f900fcabdcbcfb140f4f24b47731782ca899284fadfc9d0d6eff77045792e139afad34c7672bacfa22d85fcc05652178cc169bcf52ee3cef281d99935abf9c9910caa7994cfe33b9319d9854aa6e2590a86297f23b0cada7251a6ee1b2f3d2cb5a193cae8768d92f556994e416d4bcf5bba3a295d9e2215291237df74e3c493189bc5a27b3fec3e1a1329c6c6c75e0802b40fc74776476dd2d9e3453ec1709a8a2d162a936d9fe7dd4b6f7abdadfa429aed4cbd5896cfb24a2d91f1cee781d0ed73cf7306d6f9545fd3346c2232b8799553357a5a531d6085d04af5f183019dfd3483baf23b0ba5a6b58bba4b7a1eb0a32626bebb5cc0c4dad45c55d135be8b0a4dee787ed48b90a2a3fc120a753b52290c27ad0eb32b668a9540986ca0256c1676debc4d19b1b80bedd81e505154264a199ef1a54e1fff1b3589fbb6b2d87a1e04e67066571b68fe04f030d473f8d135585f8b7be91f0ae1a4fa2f44008c0f727f6aba9c5c60c3a9ff22911e267ffa2a5d1c71ff25f96ad257be29a2ec641e2055d785765cadd0c2fe513c44154e74f4b8cc4b24d9ee2f6cfe245549713f9dada1935247a2ac7ccc13c4a5c00c6ffe7ca33c0cde4c75c4fe2ea041a965e4c7385ebfdbc348da8c4216a02fa829892c827adbcbb87745120667cdc5a4aef28b190a54b422df45b8547c447765838dbf9f05de338ab672706b59b2f0644d6735d9316e64a1a15d3dfad893bb1bbcbe1b574a0b8ef5265e195a929148cc5e97c8bfb032d4528b523b6a6b29209428f38115cd102e3b4e268333e490140964278238e7ea9638ac34559e05300dad0c3f18e9228474cd3a7186102967c33fc14f086b4f1ec3957d87f2609d140dbb9e7ff793101d1cb90d004565c6a32924715b1bcab483e6389e2e824054edef350f328870f16412aca0fee39edcbd02a6a667aaaeabb6654c4762c639a26286a3c1730ab75defcd9451b4377f6acdc3b905684658c0844d904c47d313d85eb54963fd71a9a76dc8e345e479004f00e446b886ec41cf7bbb9311463e877aa4e357a0dd0b185a5cf12244296f5f8568e84cf6ac3b6125db6897e4e7c183cafc04f61b3263456f61a7880b446e3cffe986c3bf027c7deba490ec8544d2627e6596ef8f8a5cb577796e7c36ffc4f4346263b5d86ae1d7b8d04709367349d590047a8bfb34fbf6481adce60eddfdd1e148ab83874d70b5303fa0ce60c0ba7a047a477dc4c513d2218b0bb2bdf9b60be9a39e928976464e333ce0cb19aa21fda1c6851c6db9d5bbc6552e080f8e448637af711fed92e72c8d8530187de94603377dc387041a9eb42ac70b3923b9828de33bb337d55409cb419bf4089cb2bec79ffc3afc6dcc3cb82bc903d956d28e307fc0470736574056302000318eb22887126df9062243038440ef3fca2aa024fff8739db4930b314822b00cab172e83c33b7e5b959b0c3f27c714e447120833ef69bd634d1e011fc3c2585beaa882a29c2dabddfd31a48c0255d1ab57c6796aa4625a546a02cef51a9ee2e8007fc047073657407210302e71fc2fff37aeb6d2e2a7b7f2308d4ab7d4bf0a4cf9be7c06e89a66442b48300010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc047073657401210970cb1dff96101101e24bed1a66fb0794d2fcb26aba11e7f2393edc1534df8a9607fc047073657403210b9991881df2ec384ff5616f6c03d45cf033ee64536297c3557f9fffec716c1fbe0104220020cd4ed5f0db8c450e8b0be595b6410c1759c19f1f6926279095c83dade9862b6d07fc047073657404fd4e10603300000000000000018b6d2d00790f618171a9e8eb6051fc14dc27c502a54f5ccecacb4bcb350581a8f4b1a887a1e58865a21f51ef5098400c115cca84f489637718d8c3ca4350ad677374d40d2b64fe949ca34bd4ba845a87c7693421b70aa8d852e021aee329762883b9820bc0a47ea2fc6a8411ef773703632849094e51c73997c67f3e922d2801b6abc445375cd963a62c2bb28d733bf9274fe503081b51a56023a94200743e7d38865c190331449d19bf3d4eb7bf0b2a9b90696484aab906b9470e10946744838fc428f88461864d6601e93f10909e049a315fcc0ea7f4d71507fe5ed19b9ac2f55b0d3902b153ee27da9c55ab66debfb5ca440dfbb1a600cd8875049c4c13bbf242d26b10035002c7859f9c3ea631d955914adfa0817e4801e85b3f250cd9e7523db62c4ecb17a49028a02a1b61eb96a410643f1fec0ac664d96f99a1db4ac335c2bfec2946707c3c779a56373db197039af9a903e833af6f51de5cf8fc667dbf5416845d415dc98641e0984ebdfab88bbcdf305f605d36f7a472a516206d7ec722251e269800a00e8adb8f2ce161395c1c6ad4b406e1fda77bc7f0af07ea075011cee6cc5e7fd1b6e96e321171ca7d28a35595ddf0b0c831d28a88d30c7391c5832d90f906539aa49185fc6c0864e2d10b21160991fc19a1d084006d03fe3fb6f571f1ffd9f06ffbc0c076689373f9c2f8655758f7f44618345a6d5f8edae46e73a09c38157d5c39dd51a47756ea761e6320d6a91a1bb22d8dd35e4e7806b0242bcc4cae8720a953c6f933c0ee3216513cce6742a2cb4bf44553d8eaf175471dbc0f71c7c245fe99ab4240ccac637a16c82f0c60586f00d83ff852af48a239289d8b70a2638b0aff02572d6d2d68e22b87ba93f3db51bb27825265607ba4b6e9a194c7351f220b9e715a23237c40024fb7cfedaf7c8e0a1c4c6aebef8841fcf2609a3dd7f44dfd8895077df41f10790372eac8adff5ea7eeb28e371b4700d5044b41fd358dc4ba29158213e0f93711026fac8abfbe7cd80a939477ce545bc91cd1d32e8f2268563b0fe3e80855777d51cdf4835050a557d3c7b7f6905ba744ae5450d22837d2c2fa0c0755934654a78601a1fe236f11b6d72f368880ed938ff4a2e8d82c118493a1bb9a9ee95cf329f2d175d467aacc2c6cd73ed59ce64d508604db77942f9b7f3b0f86d913d3e1b489d2190a6da1ed997ab1eee356c885270e4e0192acc9c2b8702107c96aed654318bbb7724b913808dd2cfbbf6ace8db9c4cbf26f2c90a76eb967e365c58855b83550819cb5b28ea5e6ec1ea153ad3ee5e6292a174f7cab1b39e852194a6926bdc4d42326e17998cc057f614e91f6acab5a5bbedd93d9a13dc61977aa2accaf35370a3f1d3819c43c9430d0e767e92de96bb225007a7b6576ed7c4f05c984ce2e437d3b7f7f50c270caf90f8e5dbecb992be2972ef8d79c7674e025ee06b1302bcbd57fe5be9d9b4e38f17d9dca898ef3d71918550fd77a15c7aa2b66ee1a2f38b81040f3d6ec6693f54cc3fe39515275849e9e24b0af3e81705dea95e8f1bb1b4665cfdeb0630a5542a2857f2b58c36761d743299d77872d6894302123f4347702572f04d9876191d771e87655c071fbd29338eef0f9acb0a8ca3f0327ead8e875bc2e7ba38f249e6eda1fb74162e972ff523d20c6638b93297d406e9b2264187c06d0a78203d771f2525227d029b27b23192f747492b9774b12b039729ef75d11ce8d701e56fbb202f3e4bd68686f096f13680456529e5f132915595e0e0b4061b3014219f1fbef4d7403dbfcac01c701b7727c2463cb838d427e07706400be4fdd9140a800e47e7ac15a7a8a897187a38a0080726349d8b7566252076b1ae1366496b50e9a6d236837ffa0c4c3e61bb409a7d9faf8662109ef7213007a76a6d94cb79ad07183a33a4cdedd78896278a3b5531ec0efda9d77076d85a63536b426a6b58c3d0e8a46faf0fdf678e4964272e8410b346cc753edb94a61a2f309f8e753ff332f5b15215e0a20ef32e6a6faa044a431764e008e66b4af820bb6afe4d8dc1679219fe7614482517fdc523d4d7ce2bad9d59f7b9aed89b813e5b36b58a7c668e53181c0e1da45e4ba3851b46de8e056d63e440c471fbe23fbffc31ece7cd20658784f2d2fca3f730a1f8a90ea6995ccb490fca475b1441e38468433d1423c1d83a20b04f1216aff13c8e035c2ed389695c2bb0bedadc38c8a6222d687f0235bda3566c0865774bb6098acd674ff9091db1a6c4f0c2955c1759730b361f310d956f892089f500eae0084022e610ca4e758ce2c680d213bd1b5e00a6c991976e93189159bb98a1e4eff14f681d47694060126c0852ad52489c15d7302d82c66aa830208a9fd13da87a2aac3e44dddae632f2e4d56dc768ee6b3622c61e59aee27fee5aa0bfa9c56402ae3525f00634b11381d0b9637ac699e4c4b225c32bef9dc563e87cfc33bf07450c2cdd015bbd94df63697cdd9b6cf4169cfe1f626c126dbb588c57542662d02eeed561946ce94b46571f72ca3cf7fe52a8a6ea24fa216f01655dadbeb0f74e383e522da69db64b368cb155c2ba4c4fc8aea8745b1081223317003f0cd7ae620fc7d9a6c9c4f39a237d5d9d7f3e756c77daeccbd5d06ed28ac2f089db5449340872bc444aa4aecf6552621b84e4fabfd6baf0e1b61cab00e5c2050bfa38899e9840e2dac55c7d5e7105f65dc3afe3c35dd3e8ebb1a013d65fb0b9d76665f3c4eff5b766fbb0c03cc10c411987ef516405296437a91a67d7398d788e30f55bc1e6236b317784504c53800cc07f4eaf78bac03a025a7943573edcc67db3d5d5281f85ee993238fee3580cb846efae33b45ee2ee1ad640b1d068302504521269a31a166435e8fd964d2e15048f299a95ec1cbeb0c8d5d119e24670112d61d56ea120d7bc30fcd924fc7b812404ac45798af3fb590570a4d7a3f41639b879d1bdf2c72c979105460892fb41570cffee716679eb7b4124be188e28ca4c68d206c1cc9d49cd7489d63372853dbe83f874579238ecdd28c0dc265871d6dccf782b571f1004702121e8b15c9d8af9e6812dbbe742812c7e7cd1dafbdbe3d189f618936e270855700be8f6a7b528f271a936a2cae204b44d909ae3f6a8bcbfcda44b430397b1c96187e5a8362afe92bb9afb0a0a482cb6c8cac39a0fb1fe0034f8a965a3be425b4b34f67ff38a8ab30f8219a8933539c1d06d9b2997b6de743c568caea8b269642b8503b546c9e136d0e47775c8fef45d4489e6b4747c6accd6fad3d34fdd6cbafe07264e7ae1e025306c97d77a130093cc8d5ab8e4abe095787183a9084c569168011766b47dade4e3e1353a78f9f3464d8237b7de02da10c7a7b8951bafbaa898003ad9de997c89d8507393c6fb782b8f41aa99d5c92d5ed93e02a2e7550d42f178739898406badb6da850e305c10063b1a687a2b321a1f867ddfc7949d9764493af4f9681137101f87ec3c3733c0ee23a758a69dd5dbfccd20e0e7cc8543c213688ef9cc85bf9c539d3b6758e24befb3c1b617b7d59a17e915bb985df8229429430dd3057a8225d22afc2775e352a2d7c2f2f46786d4e6e984fb35ac6d7e1b57da82a6d1470895e56521c88c166f8124a4f0c5ada7d5e43174e319c21b9416735858ab50958def63c9a38e853d28a0f47b8e601beb0cdfb30d87070003f957ae347d5b03e80890311ded8018de0227c430f29ff20f0d0d1331dd5f22cff547847c0a26c1fdc272cd234b668823635f68e0797bf68fb1e531a423b1fae9b056fb8c0589908cd2774a7bea8da465248a438a22919d35e0ed8c5020f06824aa856c1a75e0d50d8e9c2dca471d249a97e8e8055d0927432577ef07e6658aa6ab3e9f5f4f50848b12dcdcd9594dae732864f9c6253cccf39922b5f1f8e16458a0c11db397017821de73a331400a76c2a5a29c7661405e9f244a92862a8ca13bc86a78a36744e52019c067573574f75e01b21c36e19a714e9cc0860e9df8e659cefab57002dc2b3d1a2918dc20ff25bc0d87023813abcbbe29defa514bd71fedf314a140508c7e8dd4ace82d56cfd6307fc8299a65c8b3b0719881dc06070d670729902ebfa1dc0b88050e5824cc186454ec2727d0fb2f95eb677f949375a0a31661c7f2bc452426a052b160b0654bffae35766a5317b845582176f1ec52e5bb7d814651781e268cfaefcf6da560810d84343dab8a8b2f2f7976334043498f529d75f9dec893bafdde0ebf003ea7d43f22d8e4eb3afea7be8bd22a9adb9334db2d6b05b8bfe72a96ca2c5ecc925333e4076bf5b7d9ed519ac3eb87c73d0c8121b1034424991561eccb669807a722bcd43141915c9db90f7a8a8e732054970645da1845b988f21cd5d2a89abd8e14c0e6532a3db89e43571b795d6f475b11c16cd7253a4538f4e73d0e3791e0c8f3e51ce3d9645062f249549d9165185fe6920659fe72ae01f69c6d5cef5a01cbbce0fcfd671e1a0aac35d541210a723d2fde91f37c130657d8825662bc46ca7780e07422ce2e7e5cbc1b15ad29b8473ffb832f45c480e960d620267561b3e3112c54359459c3f0235eb6eb2720dd5cf65f88267e099ae87ec7e35b3a915f18a4ab67583ccb4d907637812d797dfa51acc7b5cb13ab58934217a562b00a4a3d030bd9d3565a47c95396034bd65d39d39a6e7795f13e5031d148db16eddba996e1e70730eee02c47bd8b5c42812a12a2db5490fc3ad50d7d03c72c0e933ed8b43f8e34124e272cb39e06e628f18acfe4488dcced67a2c47008801e8d2db5dc7f7556d38020d53512993f3efeb4b07fd1e7b631deeeda74983011ca401ec7b86eb2a7a4d341f97337d37ada0dbbe124fe481ecac3e9b8e8f3b8b90ccf81ec028f2ee4e62934abcfcc2647966f6eea6563a0fc8fc75348f5fb3d2390db24d49858bbca822217fa81d240cacb7b2f6c2e4bad8a7a112b83a5d35ad68ec1cd599afee41fd4e860097f33afbc2cd790b2933f1194549c5aa3fe5c651a456c8ba7c8fa5ee5492233c21a80bd9216fcfccc03ecb76af3849711a0f69d5a261ce94484c30f4b74ca6cb4d7d5c4dcc0be1f0390c208e1e7e863700c6dce7ea5ab3831c00e586094a6a1f3b32dfbbf99a7581f90001dd558738804d0663a59d77407b6ab1ef3a82c156bc58d5a694a365b300a90ebbcddc49bbe5ba63c88e7a6e0faf5364ea17cabc602ddbf87a4c2f55b068ef296be5bb66befc1e37ad77f8e97c08f02d17dcdeb47959b0f46c2ae2c3d99a948f2a52c5793d53fe57876c38336a74786042abafb63c2154096eaed98598aed76da5a17ae179e79f848b237bdd6db63e5fbe77ee28b2bdcae462905ce1827800407317c2a42fc0dc234b2183db9097c0fae23da91aca6b1b7894537b0dc8524536573a34ef68596021cba863ece0de4a068f806d888aa03ed88ad92f375860f8885c5533c637a7a330c24aa0357f007ac3678e59cb1f6aa7c6979e15fd107a98ab42a635a3c3e70b58a19ec73b4bdc482000c3dfb4af84012ea3957d3d30b80857d9e06a2e677c84e5d9041ad4bf22afbdf0351f6d5c7a7a2ada3dd5ab4e48e37d51e5fad56cf9963715438c5f04b9772773893a59e2e46c6b7e19e9001650b66b469f4b1c4d188b286d5ce8b21269179e772147c7cb09eaee9b9de684ffc33068e5d346e79b710d82fd1278aee64ddd4ed37a944e6c745217c6a626444aa95b565c1f68a12cd2b86a61927896884bd5a76a9df4f6fe548bc16444272ec1e856334d64e0f32ba90daa77085b0683e3e010abf571dc564fc703a12998c72b6712386cfa4ccc802c850a3604bb0b2d5cd2eb6e788d475ff198f94a220c925a32ab6d69b037f1b9f0f4ce1d1b89cc0d07fc047073657405630200033fb971149dc9881cf31b7bd90de445f0ada69611016c5fde4af5bffe2b40a1a27d651a1d2127b0a95539b18fbb835f5145c1c4c7492866c6bd08ba260abb300720d2d42b729b72c6166f9ae0e4ac09a0f6851ca65444bed00b72b7f12befbbd007fc047073657407210252a4b012198c2131d70498b5939c401c01eb1178dfd58123b55766b03f008f7b00")); - tests.insert(String::from("finalized") , String::from("70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c01086b02473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801210334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe7010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b01086b02473044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa012103df8f51c053ba0dfb443cce9793b6dc3339ffb0ce97af4792dade3aae1eb890f6010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc0470736574012108a337e7e0ecf24c121a17193623254c277a306e9fd39cd5aaf8b7d374f4011c6507fc047073657403210b8e11e3b8904ac80caeb16af1c93053f8a11a963269bcefa96823d75b8640ae940104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e1060330000000000000001efdfbc0022a6437ec10c672d76c513868f403f6b9706e09733d6624e3cda831c2c199dd4c5763054a1c219e079e8cf3ce0c00dc2f16516972e1e64d576adfd9f5d778675c8a3172450a4cc82d6e6c59f2b16dffddc902d0aaa647b750d6224cafca7239a4fa81219cf2ee89741ffc5b7dd7e47d332ca931cffb1d1d432935a6013207629118c965b7a5102e62c43ca9d06bca191307a476738548536809ff6b01c6b5b25d76ff2f67d99e20fdf7d2eff6fc248186d21d054196023c5e4f572ccf0f3aad8728c46f2ff6756ea39de46028610a3d26cd42978b09e0e29e0a8aa46e4fd39d28d028592560264cf1a794c27f6c95d382f486dfe900a81d9d92935c7e0e6306549b3e49b1f60182512ccb994338c3541a2956139b2ccb3dd156853105abf5fb394cb2c45915dfd4106c7472dc5d360ab5bca408203a3fef58b4dd33b0c11c367dde2f19c8af7682be067244bf49a2b8cd4685f5481cc31ba27fe2f3d7b7a353be9b41e4eee2342fd70b8408c91951c71c75dde8fbc03e3f28e6d3d3b41e0e963d3ba0c25b2eb50560c21221950b0699d2615f0128e6cb7fa1b04ac1a046e569a7e87df98c14360b8ffc43db5e17548c7ea5056f84ffd14f4cdfc68a4f10e9b391cfa63eef2c2d623e7cdcdfae2fc4d63496d81462174ff360809b7e3b4305979d9cb8e9ad5b0f012494d31ce51ee6489555b09dddb16641ed9e2534fc34db99d2a4fa736eeabacf2f8cdca97f9e84c964277c6f30f1af7fe2b51b39b487d56ebaf593a3f98e811cb09849c5b445d5b7c9ba37807bd0189c8bdb2709fa70c230f9aba41dd3c62384aea6e1ca098ffec26367aa65a09459fca074d1da0365cf7fc2d8310ae099b838ca78e62cee10f95ec549faac1ff0f8236fb8cf2c0f6654e471d3950ef45e0159c44f9e343d05b3af59b939cf76090d040376407c41661eeda7d2cb61cad0088a286948787dae0cc5abdcb97f7f42026c65a13e1df1357c25d376955942adc858e73876e1d8812969055d55decac9a689dcd11dffe5cf6e06088b93a11e153ffed104266bba472cebffb2b0cfc8ef132309bd7836071d3b6ed459a5950c64cccf230015c98f9210f2d57b7f3a07c382f3df09f055c88e1f312db0d60d471afdc0b780d319a6229babd8f45edecff8d1073fa850f755219a3ea14e7234cbc7590c60eba0ba0cc1afde6ce91c8e8835b1ca809926b3e7d8d7a0941425e5f08e884f12693eeea1b3651f53da90972aaa37d426f37db3edae4285db114cbad5964c269e03b15358ad2a7242e0af538a594fc779ec3c43c3d94fab2028310c6d0acf3efdf0acf028ee757c5c02bb5b8b691aa5eed1a62acbabd0d61faa478cdcd54c6db2cec6144d8d1185115097a7da79c16ad3118d12e36dbcb7a70b0ddb27bddfcb1c6e5426b7e411f607d22c3ac2b8d3e41f55faf5e2105bf3b943846cc4c33edc64f902c1eeda09c8110d4f3ed0a5e511156a3e368f02161b92126abf649341ddb8ed03d1d41b91fc34548c6d94dfb47c088ef27a3cbfe1c9cb05f2ab2f18b8746394c8080c4cd92e818e46f861614ab870cb7ae3446e376793f3a6568a2ccfc2ab0ed0365567671436fee6cf427b6410a046d80b9d88f094924ad370da363e8eb70355b711687e92d88a08ed811ff241c7a6dbbad9dcfc18e6a42493483b938e36c1edd2a1c6e078a17c5d145c9c058b4dd69afc44c345f1c88afb95c1deb5c4994161ba25783165d43b9e50a2d8333a8037cec2ae809a3dbe026d6ba40d60badd05bce73b0f9f36966c30b9c0cb5776544c1182024a96e746a3b01f9db10b45aaffd3b055b02b40bccd41e57c10719bedb0fba99a0f6b0868b186fca0397ab8c219f33190f81e4cce2fbcbc0975c394919c98fdfd7e25a33e5f31fefd06c8dc409cbd3e743f0f48dc90abe45b2e68948436caa37fe9932a77b7e0fe0819d8283964b0eee2249d9190f3eb0bb8178e10a287be1059f35cc1a153dda14def65f3c49cdc5186da86bd3e965446f914e3c9b4cbfcfd2c379f306c5ef8844c4fd398b3c6f96601e90d2dc8810875663939f63abe3ee2e1c8a9c2c2010a01d0dcaebb556a7c98421f8e6465cb0434c07dcea9db1a142f9684e50b06c545785f0dee4def1257e4bb22d87a2b37ca9eb53081eb8f1ea0439c4575abac435868a36552df569ccc63477594ccf7eadfed6adbb8e81800a2e2fdd7effdd1e2f09cf76c9e780f6f8eb8408a3fcc06cb8bcd28db7a37edb0232a6f0e509c684318f179d0c91a97718ce3956c266790361ea3a1bf70cdb8a2f2a59b06dee18075745c7302db9b13a452c188c5624964af2d5d4bbb1138dec59df5dfb077a0f62ac4db3de81f54365f2a4a6dee63a6092b4660d3f5dca3cc8de3bb5350f5dcebabd515c72c9114bc58d96e2e863106f0982c2632bbacf2fe5cf6a8df880c550f7008fd09227baff82d4eb031802fbe7d50f6174860c70fcd9f0356e34c0d45df66492dc309b260b7158adf678e2da66348ca84de3e721c6196e0c717f59cb802c6866defdd9032a4b7da82b816d9681e5eb9115fc2a572fbbe105f479ab339bfa5961aa1920346c9ae4185a74aa828da78a71e55876c657249e83f5812cfb055400da1db8bbb5ed3ce2ca4655b0c39b698ed7d235fb0ed4f29a7e8925cb873176efcd1c6981dd23865468c6e01ddf61fa3e40d6fed18d8e3dbd97a08c68bfb092e441e512d44089cf563509785dd58203949a1ca9b66a700db14060a760aa404e5e9f31eaac015f1527f6d760d8714c88040b87fc8a4d183230cfae35326947e28a7a37eefe1d77070f5232a0d67e278a45d649709a7398cbd43094c5001263671517f83e62e79fb75e6f9bc592fb3bbececa3f597dee71dda0fc909079ea49d81554d2fd79d1cf3cf25d1186efb83cf972b7426600d7d6eb6c48a5f0e26af640c733f3f771e0926e6b38b6f39d7882b0538dcf281d92a6bd361bb32e16f3988d6790fa0a45f549e983f4eb68ce5ff11647b37f8e4c444aae8bc0f7c49ce7215545a29215b55f37dc42aa6add6fd1fb45d9ae580434097e7a8686e23cdbdbf6f8b6b1e5579a7908bafd8878b004c7c94e045fdf2b94f8c1a75ccee7bfabd9bb6d0ea8a60dca61053636160c19f8f3fff3d0330fc95d20a1393629f33f281e5da80a5ff66aaa5eefcf495f8bf7e24744778841d7c633f01af2305a122ee093837998e87f060105ffbc083c0d71f68c2c63820d7a547d9af5618544efb9736af56736e73cb696f191c68970ea1deb587231c889672b3b5399b9b5e915e3c567474c3905ec5b6468da826f1a6438ec335da847db540a091ab311c6846f96a2f17befa2f29ea491a41d7630e42583ad1212e6c606dc258a49f756e2480f90775c04c5c533300e37f8bc7afe7b155fb95877252ce4a53f78491ebf9d8a4aa41da1848633816542901d56e66f126316c80efdaf4b457f9ce771edb012a0b3c27c717f5cb3cc99ffcc959a02289e30d5b1ff936320579c469bd55cba6e79a0c5f1bda59981b71840a8c1ca56863e91eb21fbaf84525d5f04e0f282d03bb56d6dc2352f163d8357b86bc6e4e621bb693db3565eb9ef5629af537874c1cb3459582463362dab3c6fed7e574ce9bffcc685b8eea61599292cc69860c4f9584818182f94d719dcc463e9a6f854405ca2deb3de29ffa1826f795b7e7ac2555c0ee576c75494cb832c59de8d9620927167bc136549b731ef79a39fbf8789831cc003a772fa00d4d699d089d47037e12e1c6eb8c20c5535225a33a1a787ba866ad481e9a4d689c83d75f1986405c82154e312ce8b3494ac5721b96193c025bb75b2cc974f9171297da058d2ed4f00d94e60737af29da660fc51fb9bb4d241e78c9d1815d7e3ea90aa541c4c512ab423ece93f9ff2f479f464b953b4171217b758b280c5d8acda32cced7bb0c92cd9b405afb3a8405602da914e1831fec5b6a291f90635afe82b9389a8b41d957f81be125de2f943b7cda9e873fcd74b446bacc00bdfcbe643053f6cc8162c0a98343242b489f59f7ebac016a7fbf620dafc15c8ecfe20b954153dba19eb81087673a1e847238864036e9a300239a00a5a03d8b39aae2bc201c04c477b1f2a37552670c06ea65551c5c88a42476d0d797af494b077737cb48050bd35f4980964b8b9b98bde84615e8fdca407230f5d15e97ae65d63a96f3d88518fde12d6a82881db5c8d4cd79e724312e88b394129de3569c5c99bf842a90f193c91fa55a82116e237c877dec2da6dc652cb33e33df47420c5238fb6ba5fd96cb77f0cf6c8a12be1a95186680d16a064d9b1a7c2461fdf5e0a512a5a3ed2f8dc0eeadb6c95a658aa1c2713fea473de51f65b13a40a2fe64e5227bc9248f59c2bd60dd2fc14918e333faab6e792ce2f0597be47cee70f5a8b788a50c9cd153bf24786aa80f631ad21926e3285f41c5125a15c12cc889112df0b1857020160122c2595ab3359bcab184ecb32f7cf8f38eea23f4f9135104629d8f1e8273c44f8afa129d3a74cf58ce494214629cd53b99a8489ddc5d339e317ce75dd6c5dbd098d811c56ec5f19c00fa00b5465e39f8f2022c71ab3ed0d5a24058c197719551985ab30a851390ab5e11420c521b1398bff0038d904e5879db37d60c623a0a82e191cd0b6bd20a9e956d41d5daef703bdedd6bd0e20fb096423dd39706e939e796aa8bdf071e44791d2e1ed4a81ffd018cb79c5b6bdfd6297cc6cd379845fd7ddfdb0b83d7b26c21e3edb58185e71472a79cb65322579bd5e0e7b1fe081dcae5447834d70459f8a96b341a26d8922a2b1412f95f3533e04b5f65be112cca03ca45ee0eca10adc7593c78c041281c880d879125e585e68f5c7fbf8686f245cd736ab5aadc57c032c637b753b49575acf4bbff46883382f408417438d7817097accae3fc54afc6015b84b3b05627d7c83c4627c87727bd24c4b4d96a08c64164ab8d4abe3fff5c3b09e8fd12498e3cc3627e799e4ef72a870cd89598d51ba396655b4de66e48997faa51d05d581c52f52852cffc9f89252f26d314d2fb8fcbce197f3a8f330339c2ec3c710710b74f0915da3bbb638b1f1a0cb0c176adc151da0ab52796d466b23e8a269c5b22b76fe7586cbc217621acc955a43571dc04f69deb50066cc587595cf0ca4e9a00a261ff2791ada8de04bcbbea249d2a7840bd8fd3ef98a248eacb7fea5318b2e207668e13ab4e5578c03315d44b478df5247d8afd0624d2a57adf85fd16d86ae54cca21f6c91d8cafb3b31e3f9c4e903c04f39d67c90560dd10b2d28c6ee937cae01e921c98ddd833a9b9247053110c0f29d2f35f8ea322a738636f17648b627db622a9cc891fc1d066d5fcabc168f1b492479b0578d0bd8de0298d4bb96a4351714a10a39de49d57ad66bd6b458ae0d799a19da54bf620c0e6979a95b61a83cd06e46d7d263d53d66f77557232983268f5fd01d10442af0c840ab09c6cc4ad77add6379eeaf98f7cdbd70fffcb035abae4efecdb7e6f63ed8acebd8449b3a910e5eb06b0ee6d80dd45026c9e6c2f978f813b2800386a4d7ad9d127d76c4286c1b2facccfd16e6a263ba3e2893ae5e42049d421e08a62ee9e9574a1f70f0add7b68344256592513cc15f4bc64c5a029f60d617c245c1c34d06c095b2d0a9647e65dadb33ea79e3e0ea62ce55347d24eaab7cf9fc33fd70c07363771f78a13d92941daf7970c97b43d2fbe3e789a52ae7bed64736e6ce59f95322d20f9d6dc00b5824a0ce9072dd051c66ac20d39bd953f567a831d75fa91fb4d49b200fc893489e60df95c2810f7fc57b7f6b7725ae600e1c2def6a943b8d111ff3e9a5ac80aa981fad8709e98ee1550005bb8f65dedffbfe3793abb407fc0470736574056302000359a33e8f3d0439eb7d60aa79255858269bc1011e4cd0d4aa753bc2793f9638fe70dda4a99191f08700047a8b66d9cf7a53bf70756013c8630419e3a0bf428158dd02ab6e16822f239df0a6f1b67b6e1335509a286b30c4b11a22dcea9f95d51307fc04707365740721027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac0007fc04707365740121081271c10f0caf52a40e015e7d35966dbad39525a6c0691d4beeb3bfb22af5304d07fc047073657403210b85bd6dc21b4919f6ebda7ab86ac8122c793be3fad19e44455945ddec8b59e9f6010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001cb6f1101e2b960c9a2fe480eb88fc76c63e58b57445c78d88e04f740580a36d6956006ef12ba606aaca89796dad5ff7cb4c4c7f4e65a07fea8a2ce3b58b265d4b3220013a09ecf4e01023aabca5586d61ecd5d7f2bedceb8b18a8e41451c97a192098b196add06a8a6b619e1e731fd9ae2973741aff6358c1ad45cc873a2d497271e3a20873a6f701d4c44ef670c8c7a9793079d9bdfe956bdc02a52d1de8b2de58b0f7d309240999f13985fa8c98e57fe1bb75219ae6330e9495b523e891dba7885a5302817d68a9891ae0c2597d4d7833b91b073c4f9b27be1cf81ca1c2c1014c57a4affe7b402c0e0c1862bd17bc1d43428671a106c26cab61d5e4ffd1c01f6af1c6e97f7a25ae8aaa8d2c91c57881df52bd5f242b127ec7aed5880101caa01783884dfe943ec9764473323f59b27a204f34aa1fe7fd4433fa606608b0f253247d149e3e269ae5445e917390be83d5650c05859e3a817a6c7e561e2b12baf6d4ca4221df0b7f29fa134269139afd9cee5c7d60149dfa23031705c6a8b72d00d659d7a085de8330de2e5c168ee3eba2180a9319f5e8e0806dadaf0fffbc2df839571360b8cd32300504a527cf914da7a1e788261c9cca2872ebec5a17cad33b15a7a7427c200b8746761359194520f0da1d7ae03f26190d07cf92d515e04815f514c66a98997320a028ad24e83182f213d991accf19a8a59003a2ca666ca505c0d8349e2cb22887efb7db9e4a4e3e2d8da0d5f032603e03f48fb967fd018fc99c6eb72acee4178edeb48703903a89e9462a2d1f447234c3373fff8b205848c7a9d6e45ccc31eeeb0105ed3af1188fb847cf6f03a7f0a646fa325b672796168dbbd9d1d19b2f41685b18c3c96f1cfb7ecb1dea132e8815b62647820ac4b12597d8e737a01a970704b03ba5ae68f8d64a1810db10427fbff1d74cfac91561484cfc8d3b23f74ba0f37e7db2f942c58fb1dd30f6a50c57b5eae720c6ed69b0fb81a6e0260c53028c732a544391e319987a24548230378f8f1ddf65a2d654c7b26951d5c6546cc8fda6003228e8913c9e3c5adf7b7b72f4ff5c4aedba6096097f9a4b58ea1e060609e8d1f0fb0e5dd905b29fec72b118d40c5f599a63f9a37041742a7f1af3ef951c31833621842ec212f9fc5cd3e8c08278ea192f8032ade447d2f35ab59810aff371f598b7dd57b3cde2ad854ecad0c786150748ac241ecd67cb868663a6fcbe9d68c3dc221d4fcec9b0ad89c337c1e10b91a38ab1357f260ca3a084da536e56262973e6c6cca838601fb2f335811375aed78295c4b17d3798d39d4cbf6254ce57680a102907e5c08b5c7ec0a1e73bf3d7b8babdc6156c6ca3f09c0423d6716ff4e6ee0338cfd778158202b5ffb60b74558dc6d0be9b5aed6892c4e0ad3e20de5b604b2b010af09ceaba7c0fba5a7fed39d31cb2769909a251ba5b0668330f734728f5c6786a8f1a35df77f3d739eec71d9268fb2a494a14bf7ab91c075023e76856d1745f5813d83878822934d0b4cc0538048c3f96cad7aef2bbc49bab139915dd4998ed3c6701973eb7913530b14c688f0b344ca9cb990de2a9b12fa7ad8b292234a33428b4d16813693964365bd412fb936c1461fdf8dfb0bba31202685f29addf95fc65f841e97042bc5d82f103e41017c6ac91d3d266e12db2dc195ac4f6d4353924270c7144cd96dcd07b9612e2bf1e0f7fc46ef83690648395c575a13c2b2df0fa04f740340e6b116b0866a401de22f68fa3f621d75b9f5f625cd5d5d7e12a5b0ff795749d742eb2648ddf068a451059390da566b3de84f9d528b2663de7dcc562eb014071e8277a054783d322fa67b9e5261b87e9c354ff58fc52bf9df404139311d6bf9650658fc94835a6220ca02a4bfbf46b2af4f30b1ba681c0de7c134d98dd52eb1e937c191cc9557aa73a343b99ae0b8954a3e929174984b574da75e90a9b71af1215317b04e4090770ab596091fc05c82235a324f86280f7fa081360ac758c1980c6a6763453e6b344611e46b05760941f9517ceb298ed7a2d6f96b11f5719ed0dec803c511dadb8d056d4ca06ca0ad090558a4fbf3a886f3ea9c33e1bb4bb5ddef4798cdfb384bea75a3794c42a53af60f12c95fe9b38d7f6a5d914ef100508091fc84756b596b9ba08d1dc42a7e253435b5481d1470ef00a1172cb036b5bdfaccaee02b15d099da17f9753846226325232e71ed9b48f57697bb3d1db75ed214310e6e3608ded6f188e37b21b96eefaa396db0df05a86416fe64aa950eea87e7af4407c828a8822b17be661269c9f9e365cdc79d20503e2d5e575c5387fa961206118dd379834aade7727a1b2477a24f05f6c2ba3156fe7f5b5ba6d7487b1a124f38e3ed3cda92a8edf05f28e3d699f0c12de9cfeb30ec8d4cd253287a6d3f596db0c06006a531e80083f5b41ee26c05d529e0a59ec151b4a9260a7ea8c1a175ca41b59f464adc122f448fc43e90dfa7b88f81caa7a84e3d6d4dbb85e5828b6ae7455dc2c87c8d9c706af2e62763675851b4ca449c9c0f95d953ce53c5327ba8981321a1cbfb76bafac7fa17c99567725d8e0ac288f3ccd3bad666e515156517281c97a3dbfbd5f345f8eb80bb3c4230301a38b7c920d74eb910d380f884a683f7d3e2738fabf9646138e8a32c35e43786e67925508e4cc7b1e7a36abdf6a497077704e216f1e730c20ecfcddfbdf2484d8eb0ef9b7478f77063fa90b8afbcd019d1a584df58c537b40b193f246598ae4c53958a947dd3916b14a5a4227f01bf543a8c5d6ab8eaefdfb959c8715b756a9c797fe39229dc56a86364cb367fa37e16e96cf8dca1fdc8b6a46ce88e3d29c75b6cc718ca8c28e9e1ae991b28c286107c1dced150814d6369cc8003b2976018941df7fb8eeb68be57adcf8a0e813d2fcd16dc390ce17dd0a91dd674d766d21b5de7f4678a548d75cc3de7d099b418fbdb66b2a2b12a56b1dd8b3f84a7b45e36db9bb63b3ef9fdadebc21c6a75364173925ffc69d481024e49076dca105795e7a64a3c82011889aacf0114a1bd31414e288cbb63eb47d399d0e025a54f11670c8ad4a793d0e7ebd3b0d8eb30b87cf62b131c61a4c55d2e56e47ae99fc543c57a1877ce44dac4c6f9fd99552701ade43858e0b4020230cb4d42d1990bfb509471a5c1c5557c29c00072c5cbbc056de6d465ea2ba2f4a1a5dd604dbf998e834b9200c59fdf90bec38af9218e47ea5ed5512cf8b0146126a15d49bf915eb893ad1ef83d8faf6c93f944ebce3704f930b6f69a1afe767ac2c9fdf092c804c7372ae1a64bca3a3b5a31f2ad430ba9e61dab3daa3be90a538cf0fba00796de511cddb5a594f14aa6d215bd0be19c0ca5f043403b7dc56c87b092d9436eb79509c3dbe367c2fcbc2b09f9818cf5a55d5ee0e477888166bbfece661b62812ffed3e4167ecc81dba148f89a8f2e7c27424a2ee091ac487e68fbbf9798e8caf46b35de8dde66a7cad6dc98a426ea7b8b930cf2d7d71fe71066dbe51e8a1c7151e97fecbeedef3d6f8e01cd5576dd58097b46153f9c3bcecf30bc82b4cdfb1bbfa826c7c5b217a023e4da02a35d0947b2c4d24d50a292e4c70fe854a420c54490d4850ee8c372756671fe130532f89878a64977ead60bf604b73859ae64ed62536423bb51b30c6fedbc819fdae05cb64b87b153f4252b832d6b866100a7b307d1e705643f96f0724858e6211802b2ce706d5424b99315db90c7d74fac66951251f6cb739ea008f839c7f62318f795337ca4ff05886a2640819873ae00b933081b64da1101d1b17451357921a2f4d4dc86c92a5eb346fd6cd3a63315768b94b90a15162aec91cea706b9221330db154d1fd71acac90be47055c725bce38710f1b4b4144f331cfa9fe15ebf6535747669e4aa1f0d72092ef395f16a4ec9014029b5c9d1b4bed9ec81a7568f8f1c87e0ce0f4ef9cf49e3fe4960b0b7904614772c63147f3c5463fffed182919c4973a8256346116be40b10a38cdea5bad5878760daaeda57476e86ec65e58030bc8ff99ebf017e9bf6383f981edc83b538e2bf4bb573637e6f4717d74a3ac71f5e151259b29c1b7226e3a8ab176baa2f27e3e385cd48c6f072bb6e8e9c92e35d7ff51177f4e3553555d967c04befc1847003a8592738c82eef231ce3d372372a19a01b27dc7916f986ff3f3ea8872d4986bc1ca649a92924c8f582ae2b44028e711e955d2563c33db3b7734aa246de553dab2c5268538f7c2b6dbe9a70657e2a26e52f5d216dfa9aa8e197a227c86a7d3b468880a721b804a0c51af1592043eee7fe3657b40c1832a858cfb4e037d208ee69c4fd697b929383a971b2a0864544505af2267e334db5f3f3f4ed88e2f6f2b3d7b96016ff92a120556e4d1e50440943893b00508c6242bcc41b6d06dc29ba20564b42257b9fa1b5f4f453e94e72501d31e8947e09a38261020400db4b8cf675b1854639a962a57905cbbee14ea0daa9bd281fd635c8f900fcabdcbcfb140f4f24b47731782ca899284fadfc9d0d6eff77045792e139afad34c7672bacfa22d85fcc05652178cc169bcf52ee3cef281d99935abf9c9910caa7994cfe33b9319d9854aa6e2590a86297f23b0cada7251a6ee1b2f3d2cb5a193cae8768d92f556994e416d4bcf5bba3a295d9e2215291237df74e3c493189bc5a27b3fec3e1a1329c6c6c75e0802b40fc74776476dd2d9e3453ec1709a8a2d162a936d9fe7dd4b6f7abdadfa429aed4cbd5896cfb24a2d91f1cee781d0ed73cf7306d6f9545fd3346c2232b8799553357a5a531d6085d04af5f183019dfd3483baf23b0ba5a6b58bba4b7a1eb0a32626bebb5cc0c4dad45c55d135be8b0a4dee787ed48b90a2a3fc120a753b52290c27ad0eb32b668a9540986ca0256c1676debc4d19b1b80bedd81e505154264a199ef1a54e1fff1b3589fbb6b2d87a1e04e67066571b68fe04f030d473f8d135585f8b7be91f0ae1a4fa2f44008c0f727f6aba9c5c60c3a9ff22911e267ffa2a5d1c71ff25f96ad257be29a2ec641e2055d785765cadd0c2fe513c44154e74f4b8cc4b24d9ee2f6cfe245549713f9dada1935247a2ac7ccc13c4a5c00c6ffe7ca33c0cde4c75c4fe2ea041a965e4c7385ebfdbc348da8c4216a02fa829892c827adbcbb87745120667cdc5a4aef28b190a54b422df45b8547c447765838dbf9f05de338ab672706b59b2f0644d6735d9316e64a1a15d3dfad893bb1bbcbe1b574a0b8ef5265e195a929148cc5e97c8bfb032d4528b523b6a6b29209428f38115cd102e3b4e268333e490140964278238e7ea9638ac34559e05300dad0c3f18e9228474cd3a7186102967c33fc14f086b4f1ec3957d87f2609d140dbb9e7ff793101d1cb90d004565c6a32924715b1bcab483e6389e2e824054edef350f328870f16412aca0fee39edcbd02a6a667aaaeabb6654c4762c639a26286a3c1730ab75defcd9451b4377f6acdc3b905684658c0844d904c47d313d85eb54963fd71a9a76dc8e345e479004f00e446b886ec41cf7bbb9311463e877aa4e357a0dd0b185a5cf12244296f5f8568e84cf6ac3b6125db6897e4e7c183cafc04f61b3263456f61a7880b446e3cffe986c3bf027c7deba490ec8544d2627e6596ef8f8a5cb577796e7c36ffc4f4346263b5d86ae1d7b8d04709367349d590047a8bfb34fbf6481adce60eddfdd1e148ab83874d70b5303fa0ce60c0ba7a047a477dc4c513d2218b0bb2bdf9b60be9a39e928976464e333ce0cb19aa21fda1c6851c6db9d5bbc6552e080f8e448637af711fed92e72c8d8530187de94603377dc387041a9eb42ac70b3923b9828de33bb337d55409cb419bf4089cb2bec79ffc3afc6dcc3cb82bc903d956d28e307fc0470736574056302000318eb22887126df9062243038440ef3fca2aa024fff8739db4930b314822b00cab172e83c33b7e5b959b0c3f27c714e447120833ef69bd634d1e011fc3c2585beaa882a29c2dabddfd31a48c0255d1ab57c6796aa4625a546a02cef51a9ee2e8007fc047073657407210302e71fc2fff37aeb6d2e2a7b7f2308d4ab7d4bf0a4cf9be7c06e89a66442b48300010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc047073657401210970cb1dff96101101e24bed1a66fb0794d2fcb26aba11e7f2393edc1534df8a9607fc047073657403210b9991881df2ec384ff5616f6c03d45cf033ee64536297c3557f9fffec716c1fbe0104220020cd4ed5f0db8c450e8b0be595b6410c1759c19f1f6926279095c83dade9862b6d07fc047073657404fd4e10603300000000000000018b6d2d00790f618171a9e8eb6051fc14dc27c502a54f5ccecacb4bcb350581a8f4b1a887a1e58865a21f51ef5098400c115cca84f489637718d8c3ca4350ad677374d40d2b64fe949ca34bd4ba845a87c7693421b70aa8d852e021aee329762883b9820bc0a47ea2fc6a8411ef773703632849094e51c73997c67f3e922d2801b6abc445375cd963a62c2bb28d733bf9274fe503081b51a56023a94200743e7d38865c190331449d19bf3d4eb7bf0b2a9b90696484aab906b9470e10946744838fc428f88461864d6601e93f10909e049a315fcc0ea7f4d71507fe5ed19b9ac2f55b0d3902b153ee27da9c55ab66debfb5ca440dfbb1a600cd8875049c4c13bbf242d26b10035002c7859f9c3ea631d955914adfa0817e4801e85b3f250cd9e7523db62c4ecb17a49028a02a1b61eb96a410643f1fec0ac664d96f99a1db4ac335c2bfec2946707c3c779a56373db197039af9a903e833af6f51de5cf8fc667dbf5416845d415dc98641e0984ebdfab88bbcdf305f605d36f7a472a516206d7ec722251e269800a00e8adb8f2ce161395c1c6ad4b406e1fda77bc7f0af07ea075011cee6cc5e7fd1b6e96e321171ca7d28a35595ddf0b0c831d28a88d30c7391c5832d90f906539aa49185fc6c0864e2d10b21160991fc19a1d084006d03fe3fb6f571f1ffd9f06ffbc0c076689373f9c2f8655758f7f44618345a6d5f8edae46e73a09c38157d5c39dd51a47756ea761e6320d6a91a1bb22d8dd35e4e7806b0242bcc4cae8720a953c6f933c0ee3216513cce6742a2cb4bf44553d8eaf175471dbc0f71c7c245fe99ab4240ccac637a16c82f0c60586f00d83ff852af48a239289d8b70a2638b0aff02572d6d2d68e22b87ba93f3db51bb27825265607ba4b6e9a194c7351f220b9e715a23237c40024fb7cfedaf7c8e0a1c4c6aebef8841fcf2609a3dd7f44dfd8895077df41f10790372eac8adff5ea7eeb28e371b4700d5044b41fd358dc4ba29158213e0f93711026fac8abfbe7cd80a939477ce545bc91cd1d32e8f2268563b0fe3e80855777d51cdf4835050a557d3c7b7f6905ba744ae5450d22837d2c2fa0c0755934654a78601a1fe236f11b6d72f368880ed938ff4a2e8d82c118493a1bb9a9ee95cf329f2d175d467aacc2c6cd73ed59ce64d508604db77942f9b7f3b0f86d913d3e1b489d2190a6da1ed997ab1eee356c885270e4e0192acc9c2b8702107c96aed654318bbb7724b913808dd2cfbbf6ace8db9c4cbf26f2c90a76eb967e365c58855b83550819cb5b28ea5e6ec1ea153ad3ee5e6292a174f7cab1b39e852194a6926bdc4d42326e17998cc057f614e91f6acab5a5bbedd93d9a13dc61977aa2accaf35370a3f1d3819c43c9430d0e767e92de96bb225007a7b6576ed7c4f05c984ce2e437d3b7f7f50c270caf90f8e5dbecb992be2972ef8d79c7674e025ee06b1302bcbd57fe5be9d9b4e38f17d9dca898ef3d71918550fd77a15c7aa2b66ee1a2f38b81040f3d6ec6693f54cc3fe39515275849e9e24b0af3e81705dea95e8f1bb1b4665cfdeb0630a5542a2857f2b58c36761d743299d77872d6894302123f4347702572f04d9876191d771e87655c071fbd29338eef0f9acb0a8ca3f0327ead8e875bc2e7ba38f249e6eda1fb74162e972ff523d20c6638b93297d406e9b2264187c06d0a78203d771f2525227d029b27b23192f747492b9774b12b039729ef75d11ce8d701e56fbb202f3e4bd68686f096f13680456529e5f132915595e0e0b4061b3014219f1fbef4d7403dbfcac01c701b7727c2463cb838d427e07706400be4fdd9140a800e47e7ac15a7a8a897187a38a0080726349d8b7566252076b1ae1366496b50e9a6d236837ffa0c4c3e61bb409a7d9faf8662109ef7213007a76a6d94cb79ad07183a33a4cdedd78896278a3b5531ec0efda9d77076d85a63536b426a6b58c3d0e8a46faf0fdf678e4964272e8410b346cc753edb94a61a2f309f8e753ff332f5b15215e0a20ef32e6a6faa044a431764e008e66b4af820bb6afe4d8dc1679219fe7614482517fdc523d4d7ce2bad9d59f7b9aed89b813e5b36b58a7c668e53181c0e1da45e4ba3851b46de8e056d63e440c471fbe23fbffc31ece7cd20658784f2d2fca3f730a1f8a90ea6995ccb490fca475b1441e38468433d1423c1d83a20b04f1216aff13c8e035c2ed389695c2bb0bedadc38c8a6222d687f0235bda3566c0865774bb6098acd674ff9091db1a6c4f0c2955c1759730b361f310d956f892089f500eae0084022e610ca4e758ce2c680d213bd1b5e00a6c991976e93189159bb98a1e4eff14f681d47694060126c0852ad52489c15d7302d82c66aa830208a9fd13da87a2aac3e44dddae632f2e4d56dc768ee6b3622c61e59aee27fee5aa0bfa9c56402ae3525f00634b11381d0b9637ac699e4c4b225c32bef9dc563e87cfc33bf07450c2cdd015bbd94df63697cdd9b6cf4169cfe1f626c126dbb588c57542662d02eeed561946ce94b46571f72ca3cf7fe52a8a6ea24fa216f01655dadbeb0f74e383e522da69db64b368cb155c2ba4c4fc8aea8745b1081223317003f0cd7ae620fc7d9a6c9c4f39a237d5d9d7f3e756c77daeccbd5d06ed28ac2f089db5449340872bc444aa4aecf6552621b84e4fabfd6baf0e1b61cab00e5c2050bfa38899e9840e2dac55c7d5e7105f65dc3afe3c35dd3e8ebb1a013d65fb0b9d76665f3c4eff5b766fbb0c03cc10c411987ef516405296437a91a67d7398d788e30f55bc1e6236b317784504c53800cc07f4eaf78bac03a025a7943573edcc67db3d5d5281f85ee993238fee3580cb846efae33b45ee2ee1ad640b1d068302504521269a31a166435e8fd964d2e15048f299a95ec1cbeb0c8d5d119e24670112d61d56ea120d7bc30fcd924fc7b812404ac45798af3fb590570a4d7a3f41639b879d1bdf2c72c979105460892fb41570cffee716679eb7b4124be188e28ca4c68d206c1cc9d49cd7489d63372853dbe83f874579238ecdd28c0dc265871d6dccf782b571f1004702121e8b15c9d8af9e6812dbbe742812c7e7cd1dafbdbe3d189f618936e270855700be8f6a7b528f271a936a2cae204b44d909ae3f6a8bcbfcda44b430397b1c96187e5a8362afe92bb9afb0a0a482cb6c8cac39a0fb1fe0034f8a965a3be425b4b34f67ff38a8ab30f8219a8933539c1d06d9b2997b6de743c568caea8b269642b8503b546c9e136d0e47775c8fef45d4489e6b4747c6accd6fad3d34fdd6cbafe07264e7ae1e025306c97d77a130093cc8d5ab8e4abe095787183a9084c569168011766b47dade4e3e1353a78f9f3464d8237b7de02da10c7a7b8951bafbaa898003ad9de997c89d8507393c6fb782b8f41aa99d5c92d5ed93e02a2e7550d42f178739898406badb6da850e305c10063b1a687a2b321a1f867ddfc7949d9764493af4f9681137101f87ec3c3733c0ee23a758a69dd5dbfccd20e0e7cc8543c213688ef9cc85bf9c539d3b6758e24befb3c1b617b7d59a17e915bb985df8229429430dd3057a8225d22afc2775e352a2d7c2f2f46786d4e6e984fb35ac6d7e1b57da82a6d1470895e56521c88c166f8124a4f0c5ada7d5e43174e319c21b9416735858ab50958def63c9a38e853d28a0f47b8e601beb0cdfb30d87070003f957ae347d5b03e80890311ded8018de0227c430f29ff20f0d0d1331dd5f22cff547847c0a26c1fdc272cd234b668823635f68e0797bf68fb1e531a423b1fae9b056fb8c0589908cd2774a7bea8da465248a438a22919d35e0ed8c5020f06824aa856c1a75e0d50d8e9c2dca471d249a97e8e8055d0927432577ef07e6658aa6ab3e9f5f4f50848b12dcdcd9594dae732864f9c6253cccf39922b5f1f8e16458a0c11db397017821de73a331400a76c2a5a29c7661405e9f244a92862a8ca13bc86a78a36744e52019c067573574f75e01b21c36e19a714e9cc0860e9df8e659cefab57002dc2b3d1a2918dc20ff25bc0d87023813abcbbe29defa514bd71fedf314a140508c7e8dd4ace82d56cfd6307fc8299a65c8b3b0719881dc06070d670729902ebfa1dc0b88050e5824cc186454ec2727d0fb2f95eb677f949375a0a31661c7f2bc452426a052b160b0654bffae35766a5317b845582176f1ec52e5bb7d814651781e268cfaefcf6da560810d84343dab8a8b2f2f7976334043498f529d75f9dec893bafdde0ebf003ea7d43f22d8e4eb3afea7be8bd22a9adb9334db2d6b05b8bfe72a96ca2c5ecc925333e4076bf5b7d9ed519ac3eb87c73d0c8121b1034424991561eccb669807a722bcd43141915c9db90f7a8a8e732054970645da1845b988f21cd5d2a89abd8e14c0e6532a3db89e43571b795d6f475b11c16cd7253a4538f4e73d0e3791e0c8f3e51ce3d9645062f249549d9165185fe6920659fe72ae01f69c6d5cef5a01cbbce0fcfd671e1a0aac35d541210a723d2fde91f37c130657d8825662bc46ca7780e07422ce2e7e5cbc1b15ad29b8473ffb832f45c480e960d620267561b3e3112c54359459c3f0235eb6eb2720dd5cf65f88267e099ae87ec7e35b3a915f18a4ab67583ccb4d907637812d797dfa51acc7b5cb13ab58934217a562b00a4a3d030bd9d3565a47c95396034bd65d39d39a6e7795f13e5031d148db16eddba996e1e70730eee02c47bd8b5c42812a12a2db5490fc3ad50d7d03c72c0e933ed8b43f8e34124e272cb39e06e628f18acfe4488dcced67a2c47008801e8d2db5dc7f7556d38020d53512993f3efeb4b07fd1e7b631deeeda74983011ca401ec7b86eb2a7a4d341f97337d37ada0dbbe124fe481ecac3e9b8e8f3b8b90ccf81ec028f2ee4e62934abcfcc2647966f6eea6563a0fc8fc75348f5fb3d2390db24d49858bbca822217fa81d240cacb7b2f6c2e4bad8a7a112b83a5d35ad68ec1cd599afee41fd4e860097f33afbc2cd790b2933f1194549c5aa3fe5c651a456c8ba7c8fa5ee5492233c21a80bd9216fcfccc03ecb76af3849711a0f69d5a261ce94484c30f4b74ca6cb4d7d5c4dcc0be1f0390c208e1e7e863700c6dce7ea5ab3831c00e586094a6a1f3b32dfbbf99a7581f90001dd558738804d0663a59d77407b6ab1ef3a82c156bc58d5a694a365b300a90ebbcddc49bbe5ba63c88e7a6e0faf5364ea17cabc602ddbf87a4c2f55b068ef296be5bb66befc1e37ad77f8e97c08f02d17dcdeb47959b0f46c2ae2c3d99a948f2a52c5793d53fe57876c38336a74786042abafb63c2154096eaed98598aed76da5a17ae179e79f848b237bdd6db63e5fbe77ee28b2bdcae462905ce1827800407317c2a42fc0dc234b2183db9097c0fae23da91aca6b1b7894537b0dc8524536573a34ef68596021cba863ece0de4a068f806d888aa03ed88ad92f375860f8885c5533c637a7a330c24aa0357f007ac3678e59cb1f6aa7c6979e15fd107a98ab42a635a3c3e70b58a19ec73b4bdc482000c3dfb4af84012ea3957d3d30b80857d9e06a2e677c84e5d9041ad4bf22afbdf0351f6d5c7a7a2ada3dd5ab4e48e37d51e5fad56cf9963715438c5f04b9772773893a59e2e46c6b7e19e9001650b66b469f4b1c4d188b286d5ce8b21269179e772147c7cb09eaee9b9de684ffc33068e5d346e79b710d82fd1278aee64ddd4ed37a944e6c745217c6a626444aa95b565c1f68a12cd2b86a61927896884bd5a76a9df4f6fe548bc16444272ec1e856334d64e0f32ba90daa77085b0683e3e010abf571dc564fc703a12998c72b6712386cfa4ccc802c850a3604bb0b2d5cd2eb6e788d475ff198f94a220c925a32ab6d69b037f1b9f0f4ce1d1b89cc0d07fc047073657405630200033fb971149dc9881cf31b7bd90de445f0ada69611016c5fde4af5bffe2b40a1a27d651a1d2127b0a95539b18fbb835f5145c1c4c7492866c6bd08ba260abb300720d2d42b729b72c6166f9ae0e4ac09a0f6851ca65444bed00b72b7f12befbbd007fc047073657407210252a4b012198c2131d70498b5939c401c01eb1178dfd58123b55766b03f008f7b00")); - tests.insert(String::from("extracted_tx") , String::from("020000000102cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae0000000000ffffffffcbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae0100000000ffffffff040b8e11e3b8904ac80caeb16af1c93053f8a11a963269bcefa96823d75b8640ae9408a337e7e0ecf24c121a17193623254c277a306e9fd39cd5aaf8b7d374f4011c65027fa518c54bb326d5ce3da5c2eb8a6521ab99df86b511477d0f5ebcd616f063ac220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d930b85bd6dc21b4919f6ebda7ab86ac8122c793be3fad19e44455945ddec8b59e9f6081271c10f0caf52a40e015e7d35966dbad39525a6c0691d4beeb3bfb22af5304d0302e71fc2fff37aeb6d2e2a7b7f2308d4ab7d4bf0a4cf9be7c06e89a66442b48322002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84501230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000001f400000b9991881df2ec384ff5616f6c03d45cf033ee64536297c3557f9fffec716c1fbe0970cb1dff96101101e24bed1a66fb0794d2fcb26aba11e7f2393edc1534df8a960252a4b012198c2131d70498b5939c401c01eb1178dfd58123b55766b03f008f7b220020cd4ed5f0db8c450e8b0be595b6410c1759c19f1f6926279095c83dade9862b6d00000000000002473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801210334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe700000002473044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa012103df8f51c053ba0dfb443cce9793b6dc3339ffb0ce97af4792dade3aae1eb890f6006302000359a33e8f3d0439eb7d60aa79255858269bc1011e4cd0d4aa753bc2793f9638fe70dda4a99191f08700047a8b66d9cf7a53bf70756013c8630419e3a0bf428158dd02ab6e16822f239df0a6f1b67b6e1335509a286b30c4b11a22dcea9f95d513fd4e1060330000000000000001efdfbc0022a6437ec10c672d76c513868f403f6b9706e09733d6624e3cda831c2c199dd4c5763054a1c219e079e8cf3ce0c00dc2f16516972e1e64d576adfd9f5d778675c8a3172450a4cc82d6e6c59f2b16dffddc902d0aaa647b750d6224cafca7239a4fa81219cf2ee89741ffc5b7dd7e47d332ca931cffb1d1d432935a6013207629118c965b7a5102e62c43ca9d06bca191307a476738548536809ff6b01c6b5b25d76ff2f67d99e20fdf7d2eff6fc248186d21d054196023c5e4f572ccf0f3aad8728c46f2ff6756ea39de46028610a3d26cd42978b09e0e29e0a8aa46e4fd39d28d028592560264cf1a794c27f6c95d382f486dfe900a81d9d92935c7e0e6306549b3e49b1f60182512ccb994338c3541a2956139b2ccb3dd156853105abf5fb394cb2c45915dfd4106c7472dc5d360ab5bca408203a3fef58b4dd33b0c11c367dde2f19c8af7682be067244bf49a2b8cd4685f5481cc31ba27fe2f3d7b7a353be9b41e4eee2342fd70b8408c91951c71c75dde8fbc03e3f28e6d3d3b41e0e963d3ba0c25b2eb50560c21221950b0699d2615f0128e6cb7fa1b04ac1a046e569a7e87df98c14360b8ffc43db5e17548c7ea5056f84ffd14f4cdfc68a4f10e9b391cfa63eef2c2d623e7cdcdfae2fc4d63496d81462174ff360809b7e3b4305979d9cb8e9ad5b0f012494d31ce51ee6489555b09dddb16641ed9e2534fc34db99d2a4fa736eeabacf2f8cdca97f9e84c964277c6f30f1af7fe2b51b39b487d56ebaf593a3f98e811cb09849c5b445d5b7c9ba37807bd0189c8bdb2709fa70c230f9aba41dd3c62384aea6e1ca098ffec26367aa65a09459fca074d1da0365cf7fc2d8310ae099b838ca78e62cee10f95ec549faac1ff0f8236fb8cf2c0f6654e471d3950ef45e0159c44f9e343d05b3af59b939cf76090d040376407c41661eeda7d2cb61cad0088a286948787dae0cc5abdcb97f7f42026c65a13e1df1357c25d376955942adc858e73876e1d8812969055d55decac9a689dcd11dffe5cf6e06088b93a11e153ffed104266bba472cebffb2b0cfc8ef132309bd7836071d3b6ed459a5950c64cccf230015c98f9210f2d57b7f3a07c382f3df09f055c88e1f312db0d60d471afdc0b780d319a6229babd8f45edecff8d1073fa850f755219a3ea14e7234cbc7590c60eba0ba0cc1afde6ce91c8e8835b1ca809926b3e7d8d7a0941425e5f08e884f12693eeea1b3651f53da90972aaa37d426f37db3edae4285db114cbad5964c269e03b15358ad2a7242e0af538a594fc779ec3c43c3d94fab2028310c6d0acf3efdf0acf028ee757c5c02bb5b8b691aa5eed1a62acbabd0d61faa478cdcd54c6db2cec6144d8d1185115097a7da79c16ad3118d12e36dbcb7a70b0ddb27bddfcb1c6e5426b7e411f607d22c3ac2b8d3e41f55faf5e2105bf3b943846cc4c33edc64f902c1eeda09c8110d4f3ed0a5e511156a3e368f02161b92126abf649341ddb8ed03d1d41b91fc34548c6d94dfb47c088ef27a3cbfe1c9cb05f2ab2f18b8746394c8080c4cd92e818e46f861614ab870cb7ae3446e376793f3a6568a2ccfc2ab0ed0365567671436fee6cf427b6410a046d80b9d88f094924ad370da363e8eb70355b711687e92d88a08ed811ff241c7a6dbbad9dcfc18e6a42493483b938e36c1edd2a1c6e078a17c5d145c9c058b4dd69afc44c345f1c88afb95c1deb5c4994161ba25783165d43b9e50a2d8333a8037cec2ae809a3dbe026d6ba40d60badd05bce73b0f9f36966c30b9c0cb5776544c1182024a96e746a3b01f9db10b45aaffd3b055b02b40bccd41e57c10719bedb0fba99a0f6b0868b186fca0397ab8c219f33190f81e4cce2fbcbc0975c394919c98fdfd7e25a33e5f31fefd06c8dc409cbd3e743f0f48dc90abe45b2e68948436caa37fe9932a77b7e0fe0819d8283964b0eee2249d9190f3eb0bb8178e10a287be1059f35cc1a153dda14def65f3c49cdc5186da86bd3e965446f914e3c9b4cbfcfd2c379f306c5ef8844c4fd398b3c6f96601e90d2dc8810875663939f63abe3ee2e1c8a9c2c2010a01d0dcaebb556a7c98421f8e6465cb0434c07dcea9db1a142f9684e50b06c545785f0dee4def1257e4bb22d87a2b37ca9eb53081eb8f1ea0439c4575abac435868a36552df569ccc63477594ccf7eadfed6adbb8e81800a2e2fdd7effdd1e2f09cf76c9e780f6f8eb8408a3fcc06cb8bcd28db7a37edb0232a6f0e509c684318f179d0c91a97718ce3956c266790361ea3a1bf70cdb8a2f2a59b06dee18075745c7302db9b13a452c188c5624964af2d5d4bbb1138dec59df5dfb077a0f62ac4db3de81f54365f2a4a6dee63a6092b4660d3f5dca3cc8de3bb5350f5dcebabd515c72c9114bc58d96e2e863106f0982c2632bbacf2fe5cf6a8df880c550f7008fd09227baff82d4eb031802fbe7d50f6174860c70fcd9f0356e34c0d45df66492dc309b260b7158adf678e2da66348ca84de3e721c6196e0c717f59cb802c6866defdd9032a4b7da82b816d9681e5eb9115fc2a572fbbe105f479ab339bfa5961aa1920346c9ae4185a74aa828da78a71e55876c657249e83f5812cfb055400da1db8bbb5ed3ce2ca4655b0c39b698ed7d235fb0ed4f29a7e8925cb873176efcd1c6981dd23865468c6e01ddf61fa3e40d6fed18d8e3dbd97a08c68bfb092e441e512d44089cf563509785dd58203949a1ca9b66a700db14060a760aa404e5e9f31eaac015f1527f6d760d8714c88040b87fc8a4d183230cfae35326947e28a7a37eefe1d77070f5232a0d67e278a45d649709a7398cbd43094c5001263671517f83e62e79fb75e6f9bc592fb3bbececa3f597dee71dda0fc909079ea49d81554d2fd79d1cf3cf25d1186efb83cf972b7426600d7d6eb6c48a5f0e26af640c733f3f771e0926e6b38b6f39d7882b0538dcf281d92a6bd361bb32e16f3988d6790fa0a45f549e983f4eb68ce5ff11647b37f8e4c444aae8bc0f7c49ce7215545a29215b55f37dc42aa6add6fd1fb45d9ae580434097e7a8686e23cdbdbf6f8b6b1e5579a7908bafd8878b004c7c94e045fdf2b94f8c1a75ccee7bfabd9bb6d0ea8a60dca61053636160c19f8f3fff3d0330fc95d20a1393629f33f281e5da80a5ff66aaa5eefcf495f8bf7e24744778841d7c633f01af2305a122ee093837998e87f060105ffbc083c0d71f68c2c63820d7a547d9af5618544efb9736af56736e73cb696f191c68970ea1deb587231c889672b3b5399b9b5e915e3c567474c3905ec5b6468da826f1a6438ec335da847db540a091ab311c6846f96a2f17befa2f29ea491a41d7630e42583ad1212e6c606dc258a49f756e2480f90775c04c5c533300e37f8bc7afe7b155fb95877252ce4a53f78491ebf9d8a4aa41da1848633816542901d56e66f126316c80efdaf4b457f9ce771edb012a0b3c27c717f5cb3cc99ffcc959a02289e30d5b1ff936320579c469bd55cba6e79a0c5f1bda59981b71840a8c1ca56863e91eb21fbaf84525d5f04e0f282d03bb56d6dc2352f163d8357b86bc6e4e621bb693db3565eb9ef5629af537874c1cb3459582463362dab3c6fed7e574ce9bffcc685b8eea61599292cc69860c4f9584818182f94d719dcc463e9a6f854405ca2deb3de29ffa1826f795b7e7ac2555c0ee576c75494cb832c59de8d9620927167bc136549b731ef79a39fbf8789831cc003a772fa00d4d699d089d47037e12e1c6eb8c20c5535225a33a1a787ba866ad481e9a4d689c83d75f1986405c82154e312ce8b3494ac5721b96193c025bb75b2cc974f9171297da058d2ed4f00d94e60737af29da660fc51fb9bb4d241e78c9d1815d7e3ea90aa541c4c512ab423ece93f9ff2f479f464b953b4171217b758b280c5d8acda32cced7bb0c92cd9b405afb3a8405602da914e1831fec5b6a291f90635afe82b9389a8b41d957f81be125de2f943b7cda9e873fcd74b446bacc00bdfcbe643053f6cc8162c0a98343242b489f59f7ebac016a7fbf620dafc15c8ecfe20b954153dba19eb81087673a1e847238864036e9a300239a00a5a03d8b39aae2bc201c04c477b1f2a37552670c06ea65551c5c88a42476d0d797af494b077737cb48050bd35f4980964b8b9b98bde84615e8fdca407230f5d15e97ae65d63a96f3d88518fde12d6a82881db5c8d4cd79e724312e88b394129de3569c5c99bf842a90f193c91fa55a82116e237c877dec2da6dc652cb33e33df47420c5238fb6ba5fd96cb77f0cf6c8a12be1a95186680d16a064d9b1a7c2461fdf5e0a512a5a3ed2f8dc0eeadb6c95a658aa1c2713fea473de51f65b13a40a2fe64e5227bc9248f59c2bd60dd2fc14918e333faab6e792ce2f0597be47cee70f5a8b788a50c9cd153bf24786aa80f631ad21926e3285f41c5125a15c12cc889112df0b1857020160122c2595ab3359bcab184ecb32f7cf8f38eea23f4f9135104629d8f1e8273c44f8afa129d3a74cf58ce494214629cd53b99a8489ddc5d339e317ce75dd6c5dbd098d811c56ec5f19c00fa00b5465e39f8f2022c71ab3ed0d5a24058c197719551985ab30a851390ab5e11420c521b1398bff0038d904e5879db37d60c623a0a82e191cd0b6bd20a9e956d41d5daef703bdedd6bd0e20fb096423dd39706e939e796aa8bdf071e44791d2e1ed4a81ffd018cb79c5b6bdfd6297cc6cd379845fd7ddfdb0b83d7b26c21e3edb58185e71472a79cb65322579bd5e0e7b1fe081dcae5447834d70459f8a96b341a26d8922a2b1412f95f3533e04b5f65be112cca03ca45ee0eca10adc7593c78c041281c880d879125e585e68f5c7fbf8686f245cd736ab5aadc57c032c637b753b49575acf4bbff46883382f408417438d7817097accae3fc54afc6015b84b3b05627d7c83c4627c87727bd24c4b4d96a08c64164ab8d4abe3fff5c3b09e8fd12498e3cc3627e799e4ef72a870cd89598d51ba396655b4de66e48997faa51d05d581c52f52852cffc9f89252f26d314d2fb8fcbce197f3a8f330339c2ec3c710710b74f0915da3bbb638b1f1a0cb0c176adc151da0ab52796d466b23e8a269c5b22b76fe7586cbc217621acc955a43571dc04f69deb50066cc587595cf0ca4e9a00a261ff2791ada8de04bcbbea249d2a7840bd8fd3ef98a248eacb7fea5318b2e207668e13ab4e5578c03315d44b478df5247d8afd0624d2a57adf85fd16d86ae54cca21f6c91d8cafb3b31e3f9c4e903c04f39d67c90560dd10b2d28c6ee937cae01e921c98ddd833a9b9247053110c0f29d2f35f8ea322a738636f17648b627db622a9cc891fc1d066d5fcabc168f1b492479b0578d0bd8de0298d4bb96a4351714a10a39de49d57ad66bd6b458ae0d799a19da54bf620c0e6979a95b61a83cd06e46d7d263d53d66f77557232983268f5fd01d10442af0c840ab09c6cc4ad77add6379eeaf98f7cdbd70fffcb035abae4efecdb7e6f63ed8acebd8449b3a910e5eb06b0ee6d80dd45026c9e6c2f978f813b2800386a4d7ad9d127d76c4286c1b2facccfd16e6a263ba3e2893ae5e42049d421e08a62ee9e9574a1f70f0add7b68344256592513cc15f4bc64c5a029f60d617c245c1c34d06c095b2d0a9647e65dadb33ea79e3e0ea62ce55347d24eaab7cf9fc33fd70c07363771f78a13d92941daf7970c97b43d2fbe3e789a52ae7bed64736e6ce59f95322d20f9d6dc00b5824a0ce9072dd051c66ac20d39bd953f567a831d75fa91fb4d49b200fc893489e60df95c2810f7fc57b7f6b7725ae600e1c2def6a943b8d111ff3e9a5ac80aa981fad8709e98ee1550005bb8f65dedffbfe3793abb46302000318eb22887126df9062243038440ef3fca2aa024fff8739db4930b314822b00cab172e83c33b7e5b959b0c3f27c714e447120833ef69bd634d1e011fc3c2585beaa882a29c2dabddfd31a48c0255d1ab57c6796aa4625a546a02cef51a9ee2e80fd4e1060330000000000000001cb6f1101e2b960c9a2fe480eb88fc76c63e58b57445c78d88e04f740580a36d6956006ef12ba606aaca89796dad5ff7cb4c4c7f4e65a07fea8a2ce3b58b265d4b3220013a09ecf4e01023aabca5586d61ecd5d7f2bedceb8b18a8e41451c97a192098b196add06a8a6b619e1e731fd9ae2973741aff6358c1ad45cc873a2d497271e3a20873a6f701d4c44ef670c8c7a9793079d9bdfe956bdc02a52d1de8b2de58b0f7d309240999f13985fa8c98e57fe1bb75219ae6330e9495b523e891dba7885a5302817d68a9891ae0c2597d4d7833b91b073c4f9b27be1cf81ca1c2c1014c57a4affe7b402c0e0c1862bd17bc1d43428671a106c26cab61d5e4ffd1c01f6af1c6e97f7a25ae8aaa8d2c91c57881df52bd5f242b127ec7aed5880101caa01783884dfe943ec9764473323f59b27a204f34aa1fe7fd4433fa606608b0f253247d149e3e269ae5445e917390be83d5650c05859e3a817a6c7e561e2b12baf6d4ca4221df0b7f29fa134269139afd9cee5c7d60149dfa23031705c6a8b72d00d659d7a085de8330de2e5c168ee3eba2180a9319f5e8e0806dadaf0fffbc2df839571360b8cd32300504a527cf914da7a1e788261c9cca2872ebec5a17cad33b15a7a7427c200b8746761359194520f0da1d7ae03f26190d07cf92d515e04815f514c66a98997320a028ad24e83182f213d991accf19a8a59003a2ca666ca505c0d8349e2cb22887efb7db9e4a4e3e2d8da0d5f032603e03f48fb967fd018fc99c6eb72acee4178edeb48703903a89e9462a2d1f447234c3373fff8b205848c7a9d6e45ccc31eeeb0105ed3af1188fb847cf6f03a7f0a646fa325b672796168dbbd9d1d19b2f41685b18c3c96f1cfb7ecb1dea132e8815b62647820ac4b12597d8e737a01a970704b03ba5ae68f8d64a1810db10427fbff1d74cfac91561484cfc8d3b23f74ba0f37e7db2f942c58fb1dd30f6a50c57b5eae720c6ed69b0fb81a6e0260c53028c732a544391e319987a24548230378f8f1ddf65a2d654c7b26951d5c6546cc8fda6003228e8913c9e3c5adf7b7b72f4ff5c4aedba6096097f9a4b58ea1e060609e8d1f0fb0e5dd905b29fec72b118d40c5f599a63f9a37041742a7f1af3ef951c31833621842ec212f9fc5cd3e8c08278ea192f8032ade447d2f35ab59810aff371f598b7dd57b3cde2ad854ecad0c786150748ac241ecd67cb868663a6fcbe9d68c3dc221d4fcec9b0ad89c337c1e10b91a38ab1357f260ca3a084da536e56262973e6c6cca838601fb2f335811375aed78295c4b17d3798d39d4cbf6254ce57680a102907e5c08b5c7ec0a1e73bf3d7b8babdc6156c6ca3f09c0423d6716ff4e6ee0338cfd778158202b5ffb60b74558dc6d0be9b5aed6892c4e0ad3e20de5b604b2b010af09ceaba7c0fba5a7fed39d31cb2769909a251ba5b0668330f734728f5c6786a8f1a35df77f3d739eec71d9268fb2a494a14bf7ab91c075023e76856d1745f5813d83878822934d0b4cc0538048c3f96cad7aef2bbc49bab139915dd4998ed3c6701973eb7913530b14c688f0b344ca9cb990de2a9b12fa7ad8b292234a33428b4d16813693964365bd412fb936c1461fdf8dfb0bba31202685f29addf95fc65f841e97042bc5d82f103e41017c6ac91d3d266e12db2dc195ac4f6d4353924270c7144cd96dcd07b9612e2bf1e0f7fc46ef83690648395c575a13c2b2df0fa04f740340e6b116b0866a401de22f68fa3f621d75b9f5f625cd5d5d7e12a5b0ff795749d742eb2648ddf068a451059390da566b3de84f9d528b2663de7dcc562eb014071e8277a054783d322fa67b9e5261b87e9c354ff58fc52bf9df404139311d6bf9650658fc94835a6220ca02a4bfbf46b2af4f30b1ba681c0de7c134d98dd52eb1e937c191cc9557aa73a343b99ae0b8954a3e929174984b574da75e90a9b71af1215317b04e4090770ab596091fc05c82235a324f86280f7fa081360ac758c1980c6a6763453e6b344611e46b05760941f9517ceb298ed7a2d6f96b11f5719ed0dec803c511dadb8d056d4ca06ca0ad090558a4fbf3a886f3ea9c33e1bb4bb5ddef4798cdfb384bea75a3794c42a53af60f12c95fe9b38d7f6a5d914ef100508091fc84756b596b9ba08d1dc42a7e253435b5481d1470ef00a1172cb036b5bdfaccaee02b15d099da17f9753846226325232e71ed9b48f57697bb3d1db75ed214310e6e3608ded6f188e37b21b96eefaa396db0df05a86416fe64aa950eea87e7af4407c828a8822b17be661269c9f9e365cdc79d20503e2d5e575c5387fa961206118dd379834aade7727a1b2477a24f05f6c2ba3156fe7f5b5ba6d7487b1a124f38e3ed3cda92a8edf05f28e3d699f0c12de9cfeb30ec8d4cd253287a6d3f596db0c06006a531e80083f5b41ee26c05d529e0a59ec151b4a9260a7ea8c1a175ca41b59f464adc122f448fc43e90dfa7b88f81caa7a84e3d6d4dbb85e5828b6ae7455dc2c87c8d9c706af2e62763675851b4ca449c9c0f95d953ce53c5327ba8981321a1cbfb76bafac7fa17c99567725d8e0ac288f3ccd3bad666e515156517281c97a3dbfbd5f345f8eb80bb3c4230301a38b7c920d74eb910d380f884a683f7d3e2738fabf9646138e8a32c35e43786e67925508e4cc7b1e7a36abdf6a497077704e216f1e730c20ecfcddfbdf2484d8eb0ef9b7478f77063fa90b8afbcd019d1a584df58c537b40b193f246598ae4c53958a947dd3916b14a5a4227f01bf543a8c5d6ab8eaefdfb959c8715b756a9c797fe39229dc56a86364cb367fa37e16e96cf8dca1fdc8b6a46ce88e3d29c75b6cc718ca8c28e9e1ae991b28c286107c1dced150814d6369cc8003b2976018941df7fb8eeb68be57adcf8a0e813d2fcd16dc390ce17dd0a91dd674d766d21b5de7f4678a548d75cc3de7d099b418fbdb66b2a2b12a56b1dd8b3f84a7b45e36db9bb63b3ef9fdadebc21c6a75364173925ffc69d481024e49076dca105795e7a64a3c82011889aacf0114a1bd31414e288cbb63eb47d399d0e025a54f11670c8ad4a793d0e7ebd3b0d8eb30b87cf62b131c61a4c55d2e56e47ae99fc543c57a1877ce44dac4c6f9fd99552701ade43858e0b4020230cb4d42d1990bfb509471a5c1c5557c29c00072c5cbbc056de6d465ea2ba2f4a1a5dd604dbf998e834b9200c59fdf90bec38af9218e47ea5ed5512cf8b0146126a15d49bf915eb893ad1ef83d8faf6c93f944ebce3704f930b6f69a1afe767ac2c9fdf092c804c7372ae1a64bca3a3b5a31f2ad430ba9e61dab3daa3be90a538cf0fba00796de511cddb5a594f14aa6d215bd0be19c0ca5f043403b7dc56c87b092d9436eb79509c3dbe367c2fcbc2b09f9818cf5a55d5ee0e477888166bbfece661b62812ffed3e4167ecc81dba148f89a8f2e7c27424a2ee091ac487e68fbbf9798e8caf46b35de8dde66a7cad6dc98a426ea7b8b930cf2d7d71fe71066dbe51e8a1c7151e97fecbeedef3d6f8e01cd5576dd58097b46153f9c3bcecf30bc82b4cdfb1bbfa826c7c5b217a023e4da02a35d0947b2c4d24d50a292e4c70fe854a420c54490d4850ee8c372756671fe130532f89878a64977ead60bf604b73859ae64ed62536423bb51b30c6fedbc819fdae05cb64b87b153f4252b832d6b866100a7b307d1e705643f96f0724858e6211802b2ce706d5424b99315db90c7d74fac66951251f6cb739ea008f839c7f62318f795337ca4ff05886a2640819873ae00b933081b64da1101d1b17451357921a2f4d4dc86c92a5eb346fd6cd3a63315768b94b90a15162aec91cea706b9221330db154d1fd71acac90be47055c725bce38710f1b4b4144f331cfa9fe15ebf6535747669e4aa1f0d72092ef395f16a4ec9014029b5c9d1b4bed9ec81a7568f8f1c87e0ce0f4ef9cf49e3fe4960b0b7904614772c63147f3c5463fffed182919c4973a8256346116be40b10a38cdea5bad5878760daaeda57476e86ec65e58030bc8ff99ebf017e9bf6383f981edc83b538e2bf4bb573637e6f4717d74a3ac71f5e151259b29c1b7226e3a8ab176baa2f27e3e385cd48c6f072bb6e8e9c92e35d7ff51177f4e3553555d967c04befc1847003a8592738c82eef231ce3d372372a19a01b27dc7916f986ff3f3ea8872d4986bc1ca649a92924c8f582ae2b44028e711e955d2563c33db3b7734aa246de553dab2c5268538f7c2b6dbe9a70657e2a26e52f5d216dfa9aa8e197a227c86a7d3b468880a721b804a0c51af1592043eee7fe3657b40c1832a858cfb4e037d208ee69c4fd697b929383a971b2a0864544505af2267e334db5f3f3f4ed88e2f6f2b3d7b96016ff92a120556e4d1e50440943893b00508c6242bcc41b6d06dc29ba20564b42257b9fa1b5f4f453e94e72501d31e8947e09a38261020400db4b8cf675b1854639a962a57905cbbee14ea0daa9bd281fd635c8f900fcabdcbcfb140f4f24b47731782ca899284fadfc9d0d6eff77045792e139afad34c7672bacfa22d85fcc05652178cc169bcf52ee3cef281d99935abf9c9910caa7994cfe33b9319d9854aa6e2590a86297f23b0cada7251a6ee1b2f3d2cb5a193cae8768d92f556994e416d4bcf5bba3a295d9e2215291237df74e3c493189bc5a27b3fec3e1a1329c6c6c75e0802b40fc74776476dd2d9e3453ec1709a8a2d162a936d9fe7dd4b6f7abdadfa429aed4cbd5896cfb24a2d91f1cee781d0ed73cf7306d6f9545fd3346c2232b8799553357a5a531d6085d04af5f183019dfd3483baf23b0ba5a6b58bba4b7a1eb0a32626bebb5cc0c4dad45c55d135be8b0a4dee787ed48b90a2a3fc120a753b52290c27ad0eb32b668a9540986ca0256c1676debc4d19b1b80bedd81e505154264a199ef1a54e1fff1b3589fbb6b2d87a1e04e67066571b68fe04f030d473f8d135585f8b7be91f0ae1a4fa2f44008c0f727f6aba9c5c60c3a9ff22911e267ffa2a5d1c71ff25f96ad257be29a2ec641e2055d785765cadd0c2fe513c44154e74f4b8cc4b24d9ee2f6cfe245549713f9dada1935247a2ac7ccc13c4a5c00c6ffe7ca33c0cde4c75c4fe2ea041a965e4c7385ebfdbc348da8c4216a02fa829892c827adbcbb87745120667cdc5a4aef28b190a54b422df45b8547c447765838dbf9f05de338ab672706b59b2f0644d6735d9316e64a1a15d3dfad893bb1bbcbe1b574a0b8ef5265e195a929148cc5e97c8bfb032d4528b523b6a6b29209428f38115cd102e3b4e268333e490140964278238e7ea9638ac34559e05300dad0c3f18e9228474cd3a7186102967c33fc14f086b4f1ec3957d87f2609d140dbb9e7ff793101d1cb90d004565c6a32924715b1bcab483e6389e2e824054edef350f328870f16412aca0fee39edcbd02a6a667aaaeabb6654c4762c639a26286a3c1730ab75defcd9451b4377f6acdc3b905684658c0844d904c47d313d85eb54963fd71a9a76dc8e345e479004f00e446b886ec41cf7bbb9311463e877aa4e357a0dd0b185a5cf12244296f5f8568e84cf6ac3b6125db6897e4e7c183cafc04f61b3263456f61a7880b446e3cffe986c3bf027c7deba490ec8544d2627e6596ef8f8a5cb577796e7c36ffc4f4346263b5d86ae1d7b8d04709367349d590047a8bfb34fbf6481adce60eddfdd1e148ab83874d70b5303fa0ce60c0ba7a047a477dc4c513d2218b0bb2bdf9b60be9a39e928976464e333ce0cb19aa21fda1c6851c6db9d5bbc6552e080f8e448637af711fed92e72c8d8530187de94603377dc387041a9eb42ac70b3923b9828de33bb337d55409cb419bf4089cb2bec79ffc3afc6dcc3cb82bc903d956d28e30000630200033fb971149dc9881cf31b7bd90de445f0ada69611016c5fde4af5bffe2b40a1a27d651a1d2127b0a95539b18fbb835f5145c1c4c7492866c6bd08ba260abb300720d2d42b729b72c6166f9ae0e4ac09a0f6851ca65444bed00b72b7f12befbbd0fd4e10603300000000000000018b6d2d00790f618171a9e8eb6051fc14dc27c502a54f5ccecacb4bcb350581a8f4b1a887a1e58865a21f51ef5098400c115cca84f489637718d8c3ca4350ad677374d40d2b64fe949ca34bd4ba845a87c7693421b70aa8d852e021aee329762883b9820bc0a47ea2fc6a8411ef773703632849094e51c73997c67f3e922d2801b6abc445375cd963a62c2bb28d733bf9274fe503081b51a56023a94200743e7d38865c190331449d19bf3d4eb7bf0b2a9b90696484aab906b9470e10946744838fc428f88461864d6601e93f10909e049a315fcc0ea7f4d71507fe5ed19b9ac2f55b0d3902b153ee27da9c55ab66debfb5ca440dfbb1a600cd8875049c4c13bbf242d26b10035002c7859f9c3ea631d955914adfa0817e4801e85b3f250cd9e7523db62c4ecb17a49028a02a1b61eb96a410643f1fec0ac664d96f99a1db4ac335c2bfec2946707c3c779a56373db197039af9a903e833af6f51de5cf8fc667dbf5416845d415dc98641e0984ebdfab88bbcdf305f605d36f7a472a516206d7ec722251e269800a00e8adb8f2ce161395c1c6ad4b406e1fda77bc7f0af07ea075011cee6cc5e7fd1b6e96e321171ca7d28a35595ddf0b0c831d28a88d30c7391c5832d90f906539aa49185fc6c0864e2d10b21160991fc19a1d084006d03fe3fb6f571f1ffd9f06ffbc0c076689373f9c2f8655758f7f44618345a6d5f8edae46e73a09c38157d5c39dd51a47756ea761e6320d6a91a1bb22d8dd35e4e7806b0242bcc4cae8720a953c6f933c0ee3216513cce6742a2cb4bf44553d8eaf175471dbc0f71c7c245fe99ab4240ccac637a16c82f0c60586f00d83ff852af48a239289d8b70a2638b0aff02572d6d2d68e22b87ba93f3db51bb27825265607ba4b6e9a194c7351f220b9e715a23237c40024fb7cfedaf7c8e0a1c4c6aebef8841fcf2609a3dd7f44dfd8895077df41f10790372eac8adff5ea7eeb28e371b4700d5044b41fd358dc4ba29158213e0f93711026fac8abfbe7cd80a939477ce545bc91cd1d32e8f2268563b0fe3e80855777d51cdf4835050a557d3c7b7f6905ba744ae5450d22837d2c2fa0c0755934654a78601a1fe236f11b6d72f368880ed938ff4a2e8d82c118493a1bb9a9ee95cf329f2d175d467aacc2c6cd73ed59ce64d508604db77942f9b7f3b0f86d913d3e1b489d2190a6da1ed997ab1eee356c885270e4e0192acc9c2b8702107c96aed654318bbb7724b913808dd2cfbbf6ace8db9c4cbf26f2c90a76eb967e365c58855b83550819cb5b28ea5e6ec1ea153ad3ee5e6292a174f7cab1b39e852194a6926bdc4d42326e17998cc057f614e91f6acab5a5bbedd93d9a13dc61977aa2accaf35370a3f1d3819c43c9430d0e767e92de96bb225007a7b6576ed7c4f05c984ce2e437d3b7f7f50c270caf90f8e5dbecb992be2972ef8d79c7674e025ee06b1302bcbd57fe5be9d9b4e38f17d9dca898ef3d71918550fd77a15c7aa2b66ee1a2f38b81040f3d6ec6693f54cc3fe39515275849e9e24b0af3e81705dea95e8f1bb1b4665cfdeb0630a5542a2857f2b58c36761d743299d77872d6894302123f4347702572f04d9876191d771e87655c071fbd29338eef0f9acb0a8ca3f0327ead8e875bc2e7ba38f249e6eda1fb74162e972ff523d20c6638b93297d406e9b2264187c06d0a78203d771f2525227d029b27b23192f747492b9774b12b039729ef75d11ce8d701e56fbb202f3e4bd68686f096f13680456529e5f132915595e0e0b4061b3014219f1fbef4d7403dbfcac01c701b7727c2463cb838d427e07706400be4fdd9140a800e47e7ac15a7a8a897187a38a0080726349d8b7566252076b1ae1366496b50e9a6d236837ffa0c4c3e61bb409a7d9faf8662109ef7213007a76a6d94cb79ad07183a33a4cdedd78896278a3b5531ec0efda9d77076d85a63536b426a6b58c3d0e8a46faf0fdf678e4964272e8410b346cc753edb94a61a2f309f8e753ff332f5b15215e0a20ef32e6a6faa044a431764e008e66b4af820bb6afe4d8dc1679219fe7614482517fdc523d4d7ce2bad9d59f7b9aed89b813e5b36b58a7c668e53181c0e1da45e4ba3851b46de8e056d63e440c471fbe23fbffc31ece7cd20658784f2d2fca3f730a1f8a90ea6995ccb490fca475b1441e38468433d1423c1d83a20b04f1216aff13c8e035c2ed389695c2bb0bedadc38c8a6222d687f0235bda3566c0865774bb6098acd674ff9091db1a6c4f0c2955c1759730b361f310d956f892089f500eae0084022e610ca4e758ce2c680d213bd1b5e00a6c991976e93189159bb98a1e4eff14f681d47694060126c0852ad52489c15d7302d82c66aa830208a9fd13da87a2aac3e44dddae632f2e4d56dc768ee6b3622c61e59aee27fee5aa0bfa9c56402ae3525f00634b11381d0b9637ac699e4c4b225c32bef9dc563e87cfc33bf07450c2cdd015bbd94df63697cdd9b6cf4169cfe1f626c126dbb588c57542662d02eeed561946ce94b46571f72ca3cf7fe52a8a6ea24fa216f01655dadbeb0f74e383e522da69db64b368cb155c2ba4c4fc8aea8745b1081223317003f0cd7ae620fc7d9a6c9c4f39a237d5d9d7f3e756c77daeccbd5d06ed28ac2f089db5449340872bc444aa4aecf6552621b84e4fabfd6baf0e1b61cab00e5c2050bfa38899e9840e2dac55c7d5e7105f65dc3afe3c35dd3e8ebb1a013d65fb0b9d76665f3c4eff5b766fbb0c03cc10c411987ef516405296437a91a67d7398d788e30f55bc1e6236b317784504c53800cc07f4eaf78bac03a025a7943573edcc67db3d5d5281f85ee993238fee3580cb846efae33b45ee2ee1ad640b1d068302504521269a31a166435e8fd964d2e15048f299a95ec1cbeb0c8d5d119e24670112d61d56ea120d7bc30fcd924fc7b812404ac45798af3fb590570a4d7a3f41639b879d1bdf2c72c979105460892fb41570cffee716679eb7b4124be188e28ca4c68d206c1cc9d49cd7489d63372853dbe83f874579238ecdd28c0dc265871d6dccf782b571f1004702121e8b15c9d8af9e6812dbbe742812c7e7cd1dafbdbe3d189f618936e270855700be8f6a7b528f271a936a2cae204b44d909ae3f6a8bcbfcda44b430397b1c96187e5a8362afe92bb9afb0a0a482cb6c8cac39a0fb1fe0034f8a965a3be425b4b34f67ff38a8ab30f8219a8933539c1d06d9b2997b6de743c568caea8b269642b8503b546c9e136d0e47775c8fef45d4489e6b4747c6accd6fad3d34fdd6cbafe07264e7ae1e025306c97d77a130093cc8d5ab8e4abe095787183a9084c569168011766b47dade4e3e1353a78f9f3464d8237b7de02da10c7a7b8951bafbaa898003ad9de997c89d8507393c6fb782b8f41aa99d5c92d5ed93e02a2e7550d42f178739898406badb6da850e305c10063b1a687a2b321a1f867ddfc7949d9764493af4f9681137101f87ec3c3733c0ee23a758a69dd5dbfccd20e0e7cc8543c213688ef9cc85bf9c539d3b6758e24befb3c1b617b7d59a17e915bb985df8229429430dd3057a8225d22afc2775e352a2d7c2f2f46786d4e6e984fb35ac6d7e1b57da82a6d1470895e56521c88c166f8124a4f0c5ada7d5e43174e319c21b9416735858ab50958def63c9a38e853d28a0f47b8e601beb0cdfb30d87070003f957ae347d5b03e80890311ded8018de0227c430f29ff20f0d0d1331dd5f22cff547847c0a26c1fdc272cd234b668823635f68e0797bf68fb1e531a423b1fae9b056fb8c0589908cd2774a7bea8da465248a438a22919d35e0ed8c5020f06824aa856c1a75e0d50d8e9c2dca471d249a97e8e8055d0927432577ef07e6658aa6ab3e9f5f4f50848b12dcdcd9594dae732864f9c6253cccf39922b5f1f8e16458a0c11db397017821de73a331400a76c2a5a29c7661405e9f244a92862a8ca13bc86a78a36744e52019c067573574f75e01b21c36e19a714e9cc0860e9df8e659cefab57002dc2b3d1a2918dc20ff25bc0d87023813abcbbe29defa514bd71fedf314a140508c7e8dd4ace82d56cfd6307fc8299a65c8b3b0719881dc06070d670729902ebfa1dc0b88050e5824cc186454ec2727d0fb2f95eb677f949375a0a31661c7f2bc452426a052b160b0654bffae35766a5317b845582176f1ec52e5bb7d814651781e268cfaefcf6da560810d84343dab8a8b2f2f7976334043498f529d75f9dec893bafdde0ebf003ea7d43f22d8e4eb3afea7be8bd22a9adb9334db2d6b05b8bfe72a96ca2c5ecc925333e4076bf5b7d9ed519ac3eb87c73d0c8121b1034424991561eccb669807a722bcd43141915c9db90f7a8a8e732054970645da1845b988f21cd5d2a89abd8e14c0e6532a3db89e43571b795d6f475b11c16cd7253a4538f4e73d0e3791e0c8f3e51ce3d9645062f249549d9165185fe6920659fe72ae01f69c6d5cef5a01cbbce0fcfd671e1a0aac35d541210a723d2fde91f37c130657d8825662bc46ca7780e07422ce2e7e5cbc1b15ad29b8473ffb832f45c480e960d620267561b3e3112c54359459c3f0235eb6eb2720dd5cf65f88267e099ae87ec7e35b3a915f18a4ab67583ccb4d907637812d797dfa51acc7b5cb13ab58934217a562b00a4a3d030bd9d3565a47c95396034bd65d39d39a6e7795f13e5031d148db16eddba996e1e70730eee02c47bd8b5c42812a12a2db5490fc3ad50d7d03c72c0e933ed8b43f8e34124e272cb39e06e628f18acfe4488dcced67a2c47008801e8d2db5dc7f7556d38020d53512993f3efeb4b07fd1e7b631deeeda74983011ca401ec7b86eb2a7a4d341f97337d37ada0dbbe124fe481ecac3e9b8e8f3b8b90ccf81ec028f2ee4e62934abcfcc2647966f6eea6563a0fc8fc75348f5fb3d2390db24d49858bbca822217fa81d240cacb7b2f6c2e4bad8a7a112b83a5d35ad68ec1cd599afee41fd4e860097f33afbc2cd790b2933f1194549c5aa3fe5c651a456c8ba7c8fa5ee5492233c21a80bd9216fcfccc03ecb76af3849711a0f69d5a261ce94484c30f4b74ca6cb4d7d5c4dcc0be1f0390c208e1e7e863700c6dce7ea5ab3831c00e586094a6a1f3b32dfbbf99a7581f90001dd558738804d0663a59d77407b6ab1ef3a82c156bc58d5a694a365b300a90ebbcddc49bbe5ba63c88e7a6e0faf5364ea17cabc602ddbf87a4c2f55b068ef296be5bb66befc1e37ad77f8e97c08f02d17dcdeb47959b0f46c2ae2c3d99a948f2a52c5793d53fe57876c38336a74786042abafb63c2154096eaed98598aed76da5a17ae179e79f848b237bdd6db63e5fbe77ee28b2bdcae462905ce1827800407317c2a42fc0dc234b2183db9097c0fae23da91aca6b1b7894537b0dc8524536573a34ef68596021cba863ece0de4a068f806d888aa03ed88ad92f375860f8885c5533c637a7a330c24aa0357f007ac3678e59cb1f6aa7c6979e15fd107a98ab42a635a3c3e70b58a19ec73b4bdc482000c3dfb4af84012ea3957d3d30b80857d9e06a2e677c84e5d9041ad4bf22afbdf0351f6d5c7a7a2ada3dd5ab4e48e37d51e5fad56cf9963715438c5f04b9772773893a59e2e46c6b7e19e9001650b66b469f4b1c4d188b286d5ce8b21269179e772147c7cb09eaee9b9de684ffc33068e5d346e79b710d82fd1278aee64ddd4ed37a944e6c745217c6a626444aa95b565c1f68a12cd2b86a61927896884bd5a76a9df4f6fe548bc16444272ec1e856334d64e0f32ba90daa77085b0683e3e010abf571dc564fc703a12998c72b6712386cfa4ccc802c850a3604bb0b2d5cd2eb6e788d475ff198f94a220c925a32ab6d69b037f1b9f0f4ce1d1b89cc0d")); + tests.insert(String::from("two_inp_two_out") , include_str!("test_vector/raw_blind/two_inp_two_out.hex").to_string()); + tests.insert(String::from("blinded_unsigned") , include_str!("test_vector/raw_blind/blinded_unsigned.hex").to_string()); + tests.insert(String::from("blinded_one_inp_signed") , include_str!("test_vector/raw_blind/blinded_one_inp_signed.hex").to_string()); + tests.insert(String::from("blinded_signed") , include_str!("test_vector/raw_blind/blinded_signed.hex").to_string()); + tests.insert(String::from("finalized") , include_str!("test_vector/raw_blind/finalized.hex").to_string()); + tests.insert(String::from("extracted_tx") , include_str!("test_vector/raw_blind/extracted_tx.hex").to_string()); tests } @@ -138,8 +140,10 @@ fn main() { let tests = test_data(); // Initially secp context and rng global state let secp = secp256k1_zkp::Secp256k1::new(); - #[allow(deprecated)] - let mut rng = rand::ChaChaRng::seed_from_u64(0); + + // NOTE: Zero is not a reasonable seed for production code. + // It is used here so that we can match test vectors. + let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]); let txouts = txout_data(); let (btc_txout, btc_txout_secrets, btc_inp) = txouts[0].clone(); @@ -165,8 +169,8 @@ fn main() { // Add outputs // Send 5_000 worth of asset units to new address let inputs = [ - (btc_txout.asset, Some(&btc_txout_secrets.sec)), - (asset_txout.asset, Some(&asset_txout_secrets.sec)), + (SurjectionInput::from_txout_secrets(btc_txout_secrets.sec)), + (SurjectionInput::from_txout_secrets(asset_txout_secrets.sec)), ]; let dest_wsh = @@ -176,13 +180,13 @@ fn main() { let dest_blind_pk = PublicKey::from_str("0212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea") .unwrap(); - let (dest_asset_txout, dest_abf, dest_vbf) = TxOut::new_not_last_confidential( + let (dest_asset_txout, dest_abf, dest_vbf, _) = TxOut::new_not_last_confidential( &mut rng, &secp, dest_amt, - Address::p2wsh( + &Address::p2wsh( &Script::new_v0_wsh(&dest_wsh), - Some(dest_blind_pk.key), + Some(dest_blind_pk.inner), &PARAMS, ), asset_txout_secrets.sec.asset, @@ -197,14 +201,14 @@ fn main() { let change_wsh = WScriptHash::from_str("f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e2") .unwrap(); - let (change_asset_txout, asset_change_abf, asset_change_vbf) = + let (change_asset_txout, asset_change_abf, asset_change_vbf, _) = TxOut::new_not_last_confidential( &mut rng, &secp, change_amt, - Address::p2wsh( + &Address::p2wsh( &Script::new_v0_wsh(&change_wsh), - Some(change_blind_pk.key), + Some(change_blind_pk.inner), &PARAMS, ), asset_txout_secrets.sec.asset, @@ -251,19 +255,16 @@ fn main() { // For the last output we require all secrets. let inputs = [ - (btc_txout.asset, &btc_txout_secrets.sec), - (asset_txout.asset, &asset_txout_secrets.sec), + btc_txout_secrets.sec, + asset_txout_secrets.sec, ]; - let (btc_change_txout, _abf, _vbf) = TxOut::new_last_confidential( + let (btc_change_txout, _abf, _vbf, _) = TxOut::new_last_confidential( &mut rng, &secp, change_amt, - Address::p2wsh( - &Script::new_v0_wsh(&change_wsh), - Some(change_blind_pk.key), - &PARAMS, - ), btc_txout_secrets.sec.asset, + Script::new_v0_wsh(&change_wsh), + change_blind_pk.inner, &inputs, &output_secrets, ) @@ -271,7 +272,6 @@ fn main() { // Add both pset outputs to btc transaction pset.add_output(pset::Output::from_txout(btc_fees_txout)); pset.add_output(pset::Output::from_txout(btc_change_txout)); - assert_eq!(pset, deser_pset(&tests["blinded_unsigned"])); // Verify the balance checks @@ -280,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(); @@ -305,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/test_vector/pset_blind_coinjoin/pset_coinjoined_B_blinded.hex b/examples/test_vector/pset_blind_coinjoin/pset_coinjoined_B_blinded.hex new file mode 100644 index 00000000..a46ac8d6 --- /dev/null +++ b/examples/test_vector/pset_blind_coinjoin/pset_coinjoined_B_blinded.hex @@ -0,0 +1 @@ +70736574ff01020402000000010401020105010501fb040200000027fc047073657400855d023b6dc94a584252509f6d5214ec6b511771fc0e597c43c357c73804d2d7000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001017a0ac55f449ddb6853f2508766d5afb9f3b45e41a8ef5368cad75fb88e5e249395d1097d88c92ca814a207f73441c56cee943f0bb2556da194c14a4b912b078c2238ae025341cb5e4e2d8cb69e694cb20e5ea4cc8ddf2801180096fd071addfcd8bc4445160014011d384302576b408aa3686db874e2b17cc2b01b010e207aff956e6c2a379543b1ec82b06958f062bd9f29aa1ac1c98d1f6398b65a8555010f04010000000001030820a107000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc0470736574080400000000000103086ce2ad0d0000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc047073657408040000000000010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000001030840420f000000000007fc0470736574012109c5ba9abdcf24dcd65cd842bb47f78d6cf84f178efc80216d94ff8a2585826d6007fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210a29206a6e6974d903b467480e654c2180efbdbda5f69e8a87d0529501734cd158010422002037831b3ee29fc96f8e61ccb98fbe2dcb03e189dd29cfecc691b5a7442d8548e807fc047073657404fd4e1060330000000000000001736c1f018f94d3c1aeecfc5e79481642ae10aa575e15948ecb3a54b85b2274822d874cf225334a0603c3701fc52635fd65e8a946895af1092760785855a1c67b846e206246bb2eb1c7a660e6213aedab1ff06645630fe6b84c78ab6b74615866ba3cfe138e553d325b0ebdd542cf4a04a9276faf2e25594936cbc56aa6d70db453ad0868e47992d0ecd1227aba88a66b0d3844e26d586630c23fd6d8c162fceebab92e9d5500acf05a81fdf0b6d41db6f6513e8f25234380fb0561c2765480b6fe83d9e74d6913e1925916a3a90f9eef648a8f1caf476244191dc98cf2c39a172a0240b36034c31c9f8782aaccad02a0928d92970cf443f0331e5f580d52a6b9e6caad1c51a30e5fa0503c795fb40b0bcf047fcd873f922bd831c115b7627f77fed8fa76aad6d1250cd84184595f5163142bf77f385b47c49b529b7dda32fee16cf081631bfcb948c2426eb17b0c3bae41b16d5abc688afa285bc99579a30a05f57d0cd7bf777dd172ae03f3982ad912e0177f9cbebec46d6e57741b83289959bf6f68d816cf5ebabfc09676bcfb7766aaf6f5f04179a22d75273b82c94b7a9095eefa90574ba83aa85ea3ec3599fdd72a4fa267f7c2b944b92b690776b97b3cd4692fcd0079760d6a820ae51e1b0e6ef405d751414face0fdad0994cbf553c60017ea379d035e8ae184df1cf7fb28bfd746adef7df476213509924690e6d284ec2e3cc3d735b072f6582d913bbec5977d21f20bdd596b10eaf9bdada349dd63d99e0c28c2006f6945f09ca0d244d434f86841afa621649c4c1704252ac1a676d9131594297fde37194b901014a176abda3e2a5103b8b73b586382b3f469da738714e2d59b36cf4eee345a7faa4666c33a0d74469e39ca152ce7510f1abbf7cf24d37054b272691780309bd69916a964f2d535082d4403fca3210cbdb357ce06704f0f3aa6d8189badf11539d9e6f867cd4dbb2e3b4e13d6f9bedb459edcf1b520488c9334bec854794d3e5d943e7837e557c3131c085307ef712e122199d245bffbe6520f7af80cd73f6547057bc2b896e5430a27d5cc922ffbb5b4e012861061caf149d42b05b342f061fc5cab91be0645ba78f334c46f64615e60f37c84d641348800c5349f34cfda388fd58e48553722b7b53474475a6b6efc9753883a2b41e4678247a363998c59c37cd6c4706c56f5d5fe7596fb195c11748b66b2001accb18fd56fcb137f059973988f981c166b04c8e8ff2a52d705cbc61ee69c26f271e80995009608334dcef363a0f4eb5cc24fcb2160313836cb1707ec294c1ea357ebb70a478b84dd760a10284dd551ee7c8b086a201b900f7e3aa1e491926d74005fb2c989d32a3809ec1cca10bcad7b68c7be99d865361038b2b3023f421fff764a9ab759faf86c0a0042945969c6ca5f096cc1a592850622d6f60ba2b56287fdb6214d761dfd118ab8806a8557af5dd1d31f3d551e6b749bd37ad351184646d26360f150167a34685e24bd8127ec4970c73231e0ee4659bedbdf0b0fd64895cf13396f52417032f8bb221ea9216166d0a324c0d03603302e8234e9d98444153e91790567554056afa2f1d49d0277d757fd5211fe66a7f61624aed4e5dfea2cbbb8dabc4ea3037f4c6e236f1ee048b499cda952c7c9086f756adeb75022c1925ac0f64e4d7da8ec583e3326792bf13136d6e3eca27c56cb4be411d6b03ba8c29a4f17a860d1d27b93242e345cf7228bc6ce11aa876e7a30159a8dfead13520347ff5e949748ffc4ea51b75a7135456ca0a3b40a39bb082a9f02da862f19c651284ea28779ec034b05c07cbe85588f56b4a5432144ca7c1dd30b9783f231b1fc1ec05869de856a20a784c05f33055379658ec2a2ad24995e00c47ffc7bfea72e91df9d5e9a8bacd3ae3e12df323c0bc759857ff6e7891ef49b0176d0959f8fb2494e0fd85e68f0b8069bfbdea365850c15ca98d8e858936cce10a0d8a7d26877f260673ec3d1bb5f3ec4565726c935593e6b34c48f73e551b908f0a9c632166096efd4887e168831b0eb66c3e0e9e01a31f354e76d5f584273ad8f8ac2af7e4310b3273d106c171892ff3bd3c92f2727920d88b82b4cab6517eac2ac6a808c62da14c35536e47b33f028ead9d5486d872332fb61dc53c8281117f517b0b6d08626a4bf5d9ceef0eeb7828b56cf42aad8cc3cdaaf2c8f8970c6c20e1fa4c8dde0d822de374fad8fc898518129f7251fd9ced6b8f32364a1987cc0969892d1ab885dc9bbfa2a08a3d831a51755a6aadfe1bf78a2dd5372eacdb0e6a9668d2c7d1b720cb911005bae7f6d5d4e112aa4256171b6dd5652f95a4bd7f624b45432a99f14bddbfada9af1ee6dadae06f8392adf959979f46dc9a55439b5515ff57cb5f4934cb1259ed9ecd114014b99b3fa0f325eefa2ab8738e05c5ded5885d7430aa31b3878a10dd0a9ff95668e01477b0ebcc904e4b4f27840de17442661cf246f47d542f85a168187669820e6642cf58b23557c109b11133106fb043781a2da51fed9b2c5fdc426b4c1ee1ec47d2b9e992a2cb3485029d8c9d6dd44d104f0caa06a44b9f0470af5f905d155326e8156155903b9706cf6ff62c6c565ad9d1d25a0e82b1d8164268d83b8c8d4fce0ff202992738237d73dd242cca368fd3d3ca71d6acf49ae0d749a33eef07157adc94a049d2c6de65d2ce6a4c5957853fc89dca92da3f014739b24459f24de757abe4a127ec8f6b774beebfe44bf1eddd84a14f85c9d57ed3a9d8dbfe616db906c2d4b482b0d79f8165cd4c78af23c98cd715b406244bb5353edea403d54b8aa46206c5e8e551d1ee3a1310f7b7ce0e28392eac7e2546867d00b34fa47ae8f2c74ac9db78565bd7c685108de0ff3c0af495fcd31120e5497025416017ba47e6f32d3ad05f2290b471eb21aba1f60878f71e3102c59fe5ec96012f68e73c9bff4f35c25e65d66aa930286c31bd6a59e0d26b33cbedf4e5e43ec5ab3a5822ab352d61bfb45a2d9929dc72c5179acbb217e760683d079002b856bfe25e1951ee428475a5fa9447193778beaf49236a1a67905a55d541e69dc27eafcba25dc6ac6110f8e6528c5a3206cbeb36833544ca08db02c91ce03c1ec97b309014e52c546186d2bf62098b708054fce5cd5bc7e042f7da37780fd1ee3ccce6b8abe4c586f46b0488117d8cc2b42679ddb89dd5e4ff7f5e4a5c2e9c3101dc1d5c6d0415f255164eb38486f78bce5a0dbfc54aef8adfb647804a8358c4e5de06804539bb4ea7f7487f158527cd91081477a5a020152aaf49b4f8077296820d254d13e3c8fdd21906a801e665a4043b21de0edc08c9664be88f3834b0246b6264ffc081846cc200d629cf1ad0db881aded247a8174b4f43970d5680332dba5061f5055a2e69334cf4c8dda7302a9e37091e4f440608dea5399f1a8d748a59b009299a79f9da3c5f2b2648846c64a893bfbe0bd187b7b7767bf42e351a8392ecc744318a9871515d435ee05be0e5c800e0d1cfa6ea10335b48a2f00c17344afcc6d08ddb426031a35d6daf07f48c7980f56bb9fd2588f9a1f119610d70c8af3da487964a170b63ed3ba6c097e8ae750aeeaad733aa300d87a15e558fa6330680cfa4cfad9101ca85d012858b3ce6d944c9bd9ff072a151920018cd52314a595dddb0337a6af4e1aa7905ff54bec93f50fcf5625cb86d5ba0a39bd757139dbdd63b96c940ec14303b6473c48ec00bf1cb5db2ea7cdbcf4f0c4ee6a6336d7a321e57fa1f288b4b635addf70b724364084f05cbba5d52bdfe7b708cf25f7f1aae574c49758e7587eb15bde51dd3343269812c5030e9398a31487dfd86e2e8c9ab8e8ab0713e955f9981b7d8f1aff0424d01499555af7a1a17d041f3eead79e2a4f9528a6356081c550326b1bb4a6610302590430aead818e2195199742dd13fdd928a9ab23b15aa67c41758b2762078b2e3d3957ab202d0ffe80595cd51ff54ca213e21734d475b7b4b924a60580b5ae4c475a71949622806ef418273349811ac08a912880079ea2dacbd0e0a4092af04fe20ee0b2bbe1addcbf4289b5e3dba3125ef6cc09a8787e29930cb27e987eb599669349ac70e8fcc66f5c27aed363e28888b1b2a48e432ad2987606cc5496d76beabe66d1bb6fad2d16116a4d0d98509756e4d641f57d65cdd71ef99262d6047800dc6fd0b2a0d167b8d242cf53c41b8411b26d48b6a95e511d316178d34aa3d6852de377725d9a253717dda8238bbb2ece522f60ff4535f1bae8a71e52d904e5c849d13cb91765cfb20f5f74c9067e24de2d70006e384883e676b33549e708f3c90a0ae967fd54ba1364dd90f00f9660a27ed880dcb1067a4904a3c649d024cf9cb3a8184706c74d461b0efb02ecc4e93a709527eff8753dc3a515d7ccec22c9d08751a74a522f9fe99cc193a5fbe2b0a6cdff1d4e8e3654d4d74ae57fbd8e2582cced7c52051797b02e0637ae4d541cc206a754e960bb8983b341808b783032236a3ba305d2a5460a04da0da20f09a4d4f6c6312435e6c4c6b45a8d17998f969964284fa49cfa49c760dabcec76fb87768369f98153bc3ad86a3bca1acccc12ca8cf61b14d00a1de4623a372364bcc893983fca31b111b9458d809ff8ec7716190987297326bda2bcdef2faf880fcf8cd999d32ea77ec98958cf30e60cc0fcc3055d978fe7037e1328ca6c4c4134afde8d8ae5d71811ecd682a48230273bb9264eaf971717fb815eef06ac881f3d0f4d859b87043d7698ce28668bac8524b1d5763caf11c0dc6534c62c0dbc2291bd62b5d145fd10553b6eaadb1f6bed46b96458fba4bcec7785861955f68552335f36c687735425b9f9d17f2514a734e3fc385901219d20976df1045aa5023d76750a5cb10e70ea07f1c197526f5e71396177f66443999abc0a008d400da3d08c9740cf6662adf93dd1735f8c262f58241fc6cdedc9ef7cc4590b8aac248a831fc7476c0e26f0445f9ed723cfee1fbc2c8ac6315e14bb2f49c2fbbd593d870b8b98cee16f8adc6f841f236e4b499571094e07ad82e44e57f16f728a7187d6c1baf3cd1315b1f9ac4b7dae69e39efb6a03176430f69720e0ef879284f59ffee4e6720d31c51753d666681dd15e7ee5340a8629e8863d5e86b4088a32de13e13e0f92e15511e9bc4d57a25c71743e9b39af867dc17ad3ebe41b9c8f64d1c38573db395382b55a50ec279030ce8f59ba8a13525681efe3862f3c46eddcd3dca4eb3a191771723d3a2875fb2074ee9867632eda4ffadda4c64283202cbed717d3ebcb2030ead311b9c2ca56169f7ce6deb9fec78a76cb9f78ab8251b83bcda6dcdb0e916ba12daac78a151eb76d040507fa29eec866225f87c694233ed07362b097aec619f5df3dc5ad0a58f0cc94eacaf252f1f9dd59bce58904ff7460052a5bd0650111c105aeb34485c4730ed6e3e1c927e2cd956d1b9404fbaf0440afad135a3f320005d666abd4c69e98edcda3691b7e69b2c3c88ef5e1ab9062b0b3f08b8ca38e8f6f7b4d04f3daa358986a3f8bc5f6a13115c31e454c0dfff61613d0dde9dcf4516bad111328b9e88f262988e8c05e5ffe1fa6ba708f5fd51c34e4757814ad0ea5e6fd33cf51b45b84c85882406cd3eb107b96fe546364a9972d19022be3b5121ec64c6454f3a4f9809f49e11611b2131d5af0aa5e65c156f37a6062aa395e763522fe6b1451c0452c6c56443087202605ec9ddac8635e7fc086e330a7d22b15851671565726b2cce451d6288755a04862bc7f934b99de6809533c05b851ed07faf408f83fa9c18f3ed65a94e9788f830f5bb9ec744b71f59d6821899f8640441d47746497dbaf8e9ddbd252e04a5e4b313e2580a1a858e4550a1e5c09a04eae54307fc047073657405630200037f019cde9197e7270a1c7d726ced68dcabcbad740497313348e7ce6ad7d29f0bdf616a31c760921e4f9796161da146c841e86ee758cfe053b88447152770fa72c1d2b4d17de0698a19a7f1cd72c95a4b3851905c2a7a76d894a2885cc28addf607fc0470736574062103d559d2a5a4180f418a69c4bed5508971cda9313722fff71e053d3d82fee9d7bd07fc0470736574072103701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d07fc047073657408040100000007fc047073657409492000000000000f424056773886ab9ae39b4056b656fb255a3af50694cda3ff4d8d4d450f15ffbbbd9043f90fa6f45a7e8563f8d305ab4b991a2c33e51b65f94230c9bdd457a3b31bc007fc04707365740a430100017f113b550d4432ae02cc0d943900d25f6833c26bc1df867fa17b36d215044f889bcf7e3dc63814f6d7894608232902f7cf313b28194f104be657b44bf54471c300010308c0878b3b0000000007fc04707365740121080b9276ef17348a17b4d25e1d429594891f5bbb07fc8e33e4bd35bdf8296b7de607fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210bb668bf543a321a7cbad607ba5714cfbdff67e8d04c9a4dc539ff7672b84b19cb0104220020e7da55d19cc85b0420c539a90b667d4d85f59ee0ed417493a947c3a2256cc0aa07fc047073657404fd4e1060330000000000000001a000d4005894c852815a527aeb01259da98708628a2f2657826496e2711f7355a941f50a31be4e1df7d245f27e22dfcbb737e28008eee8322b6ee9755cf564411c5f360cd8ebd076db3f857f2a89a6dc3c84a200a3895a0a68b8dd14ca3c294eec37cd45d1cc69ba3e5a114c8a4d8e32eb79cdf984ca018052e503a5a6a71b49c78112909d2f1b1fe97ba13cb6804156d6202dfe1275a223c3b99f77964f3eefe61f87d483f5d8273edf67f4ba970c9abc27627bdac0fbfccdb6582e30429d2d82ee4658e2284927f66b3fcbd59b8282ea88e37198b3e6809a79e9be849ae2080195239e82395ea60e837e3e03459c4a635ae8626d29939026c1b69d6ac6a32a6a8c8a3686d29c636841fe4ad37402bd3c94a58b46a74898b614dd318211822e2927db89bc8d121373ee1aed057436ea6bc084e7a38d612fb9322265c7b04ba2a4187cd70856fd276971057ab4aec79d4e9f1ca9bad675c62c481a621f23ecb7b78b72a90448f40620152a603d4a150d1fb87c37cb281195045dd4730899f58f4813c1c99569828f377e6973bdaeff1bec942f98038d6599a73de84ce4c350ec161074fc30b151949f22687b9c50d05e8339c048beea2e944f77f601797f7fe64cde986746644b4c5f0baa42d6738add8f9773e7ff9199cbfa3bf03cda1e8be64163a3d8cff0875ea6b495e372107b5f6e9602fb6b0654e5fe1bc8a2626e4c9ca3a33bb0d2755055547dbbd2b1548de0b9fd7193ac0d3bf7d4ecc929c3ed0b1112aebe26188476f63dfb75b12187242436f8bcab54de04510dc31f3f3f14204e8b50bf3594432edebf93b26903f87c1dbea50dc9af089c4092654250e1505301daa5b0534645e52f2d3c36e7af6e9e04f7b54111282044afe94411854aa73be65f18fc868f39f79e312f62c4250e61a2511718808980fa0ce8089437569c149f4f9381db1e12e4d2d1ff682f47d63c8cae57eadf9cefd30e52128dceadcee043c84d90555b10095765a0d1f898399cb734477bc068fd2b37f47847332d38182cb23da3800c56dcb2a074aad8ef25d741d569c355fd4a0d806cfb698151873d1e48e24fe359b2182c8a26a1ca1c21ab609fe19dadbf5f8e3ffed3fa7679363ac7e303eb917554fda9d2bbaf6c053e658dfeeb88448f5fb017687f3eecaa3e7ffb6188c18e55678cbe2328a05263a987fc502280d070f181129902cc0f5e415e13f76b3f58dd257136ce579c4a999b127374e2bff94f694c456247c502239e301d21aa870ac1021b97b11a7d1ba0095696a50fe16e888616548d7eef96efa59cff978962a7c6f38aca24282091616f6acdb4bebedf236c043b36a6f12eb82c2c589c6a71080a46cf27e6e663060788c2871ff04d26ecdfe534ee254dce0328134af51228c908c5184eace62cebf5ff692097fc2d272a7275b965ef8b5953fb1ffe8d713cf92bf4b3f6a428bfc3aac0e67aa93e9571902dda5b1f66ce523109092aca555f007ee5529597ec185981f45602be0a8a0f9e9b75c477e0af5d27e546adda7c4a2776b97b9a301f0b6fc15495197cfa6f2975d19bbeb70db18dbad8d0ed06176d97c066ac19d4061d7f7cccedb352788ce5d15c26fd970bb68b879b5571ec5bf2917d7d58249fc8f9b2f5a5efd5e3596451a7be38dd75525fd1c2b0444ef9aab646869322c23e52b1e0b2b420d2220a4c150f0c6b8a4bb245282f546abb74f264b6c75286d05fc7bb2c266b5e9864bc6e9b45ffacfa2352ba898178856db19b41e7e6d90ba0e14226e698ebeb02b3fa349e8ee09d7bfee08a54763ac1db861aa0683b78f6bf07263e3cf0fff0b91225e5e009231155bea60b2e661e7b04d8803cf0c89192cb8f70cf16a6d256bf31a28d2203da20d7d33c7a6f841169a845ac35c8fca9253e807f72e96355c296323f6e0a1d48bff8c43f55a594fa251f69b907d9f97e7b2feb5296d12d331b34ce9107df9da5671effe1120e078956b85abd24c8cc1276d648a2c515791fd40ab74a5c46be36a6426b2c992c22cd3d96d61ddf089ca711c15ec60538057230a2e96a92ccfd605b09a4ead233440b7fed99abc47c50263728b18b2bbf79d814c30b5e96a72393d828446bc3ff14bcfa0c4530b59750c28d7c9639c961ba47320401566962b9fd69df8e87719e0bcfc5e9987d1cd9ff4407dec489ec6e1c0a82037f7f0f94d3a06d131876d541f3885906168181c21a395a51fcd3e2efd3d116948742f8911cb892fa7d4d4d5f83e86c9334732191c632a2a6a74d7f760ccb95e04ff59f8e207ca2491d0a6f48df78254a0019fad13c0251e107639d5669bbe90cd0188042c02eb726d3d33f69e5ca06ac96c733811098c5f9a09210b762e5fc94c493552e060058e811cd302abe0d60be900fb0158126a4d3c188a65ea6c2620aad9c73b0d452413bb26c5e6c3b2d885731bbac7abbd42895d5542bcacd1a93c405daa46bea191af513b0bfc3a1f992700daf4967cbe498bf1df5bbdecbdef4614af954060b7cb7de6833dc0d721eaa570659400f452664ea7c88e410ec5c35e300465ce779d76b0fcf8ecd375f727347bc46e069ac8ad816d35d08f4da02da77a75447a3da203d8ce3a0be9093dbe938dbb62936c7602f9bebd69261fa9fde170c9fb22a345183ae119af5b9a47c1cb0d740ff9771a27493c9bcb0ffeca14c376e0512014dfacbfbcc9d0956db6d642cc892dd7d8a5070b054f26e977ee6e3c121b16163d8ac151df29a5b8c5601d3cba799ceb10f04141c3f6081d7d1b6ea22202b3aed1f932f8cec9c65eea07befa558159499b1033e235078fa7e16be66be6f2ded84e8b47708a59c0c707fd79061945818132b2f2e58242238fb3c3410d2080cc8ff7b9a689d259b2bb63fb40f1af64f19e4dc53ebe3bce60977776328ebf6dcf24cac233e04c5df29d5ffed25609e3b8d96accff140f9f6acf21d9951ccff9f5c9bb27b7c8991b5bc8c2dbed6ff31b2b6e75f6f620ddf45cf6f67b900678567745e607b42d0bdb9d86d80eed1a1923c2386f371e0d2f11306ded8684325e46bfd979059ece5fdda6ee9b5ffd0697a54a64abd7599292509c3a82bb926b743a91affe05b7dfb350b605521dedda17617dec004a378b01682b2998941f02dbbc804c3553ace603226369bfca98ab4aeee767abc892e00f56d79438d075692a547da501c50289e91c3a891646daf82de39c7abdb48b3663bd870eaba0393b66dc7723ba77c588f5246fcf7b877e296fb784834e2cd679b54141860f4696b1b1ac330edcab74b59aff6fe2e36b7e3a459bac93e5068785f455d12c51b486f2063341113b6e196def028901f106b9993e4147ec924af8e6d86155d8dc2aff8b1f9268d9c75e94e253ad38ae65a8cd3cd19161dd5258588f8e74f3da7bc7df4bc6b7d23de75a02c87e8648a7bbd55210110239ed6e6214189825dee3bad6a028c70a2bf249b2294234798311c5695fabbfde68637ab9d066fca327472ca460468d34f74922aac4f1a64ddfdf7ccd4fe12d83ef033b901384ab8149ab50288fcdf087a50c0271046e7824c6c69c33ce1179c9e87dbbca60692027c1c54481a8d4909fcb1f2565c47d50218995fccd8cd64f51874180fd930256880aa1f696c321790cc5c32c49d146f6cd0128b18c52d00417adfc06aa6e279c03f5b21daea9a33390a91dd3c5281444d023362578aa83eb3a81d57ad360d337ab374c1bb3b251a51fef636418e1966cea1b2135bf444a03c39f4619c7acaf3c7a5cdfdf8a3893696e5fc076e915af6ef7817a32f5c815803e92810abc3ca09e421f8f039206e524a47ed746d3ece4c67e1dab74477abe25707b92598e71f6011ac9e3ea85417a6e4f43c55f158c2c73776c63c3af8fa472016ccae79a2b9e99d8c42fa1cd72ce8c9cb3c535719e075a0adf26fbb559ef129512833698d6892ca1a37f176cf6a363cbeaacf40813114285b032412f6d98854bdd758465604eb2ef4756064a16cbc02763323f29722e625ce895315bc6eb1ef3aba39543df3668991b3d7ce33c68ca975c18823cf37e8bf38b87ded0527d61ba10aa43db45b82e23b1582da992b9650f7d146eb420fdf679eddc26eaa3639a6aa41a2149d1b89057a05552b772504cc29132b7f6b3eeb5732a9830fca236fd41645f4da9b38b2207d931944a98ad45585eb659b7e1e43fed797c41705265bd155174f52e45a89ae79e5c1dd264a39758ab5ba42f6b721b926a06d45ceb114a0ad592ec71276bd7fe1cfc592324e58b37500b106355dc6ed962e1830ce33f8a18a0ca236c3301b0bd9029c6dcee2dc37057635fceb494a8352d74963fb2e3f75b91b19bbdaa49abbf11351f88b4d2d0550320a8df123289cb16e1a4de9e71974d920d2b28e59037e3a941e0f7c6b2265ee281d0f9da960d2b707fdbd0a58f0a19ff8003ccec3d1be962eb47f57923ee2a1df81cd2e77430eeb8e0825e8cc93ae0466a19c80dde5b8872491326f877b27db9543c495ce3c5965bbd48f216390115c7c31bacb20bd9f11267d2aedd4586ffaa27664991a100235bc197ea189840540fe0b29a07f24c400ffa2bd81eea1d15496bc14a65fea7e05604f987cd847b0814439e09ef47bdb3f273cf0a56e339e7105542a2ea1fdda77a2dcb792e474cd086341b510cd5b424caaa98497744303dc149759640302337f8d4038396f776dd79fc41fba310214a60ac5e047311bcdb5daeb1d8a601368fd3d84f981aa6fe7fc43d244beabf0a386414c569c8599918f8250487c546f1dd49a7793061a0107ae626beb6bda487f633b1abfa389e182f4f1aa99309ffc7c0b69344d0e9c5c2feb35cfe38f33c6f8b668af1df1bdcbdb5822c6c1931c7ca25258556881584dae524b35c0fc1dedf8604298014dcdfa39c3d38908364b01f55062bf7ad3ce8f85bb765afed8f365b1996a6600d39105203dd47023b5de1e2c2ff78a2e4b1fd6329afd0e86777471721fdf55e7189b18718b9c7d6f7d764b6bba82ea9eb9cd9dd9c09b9ff5ef25b3aa8ab309e6d85f43d6dca7681a49975372a9ee196f917e4e6b390103537d24a35a084c5eb5b378dd1654025fbee39935f599bd4a15741ce19479a93dc9489035158fa0812b6ac13c6f247432cf8ed3a92f86b372cf624e5b7b2a419184ab9b333a461d5fa9df9573f67212435ee96a59a020ab3c1dc0c61440905516a8b6f341e4d9cceac3129b6b7b6b3ba2ebbabef7857d8d7594f778163a5f95f9b443067a94f9e0fe9db323e25ccae107e960ce163d62fa0c6268d99cdf208705b57e3869a8e0687fb95d9244a3fcb447add7b7dfa850e9af704e2619821754988858693e27fce4b0b3be89e42def5bdc8213e02489179150764fc0a542e382f52e8d7cdb80e4aa78ac9b3d1d56ab23c8b885f0f173c4020d2330058dd37a34c0c1fde5142b3176254a6899d47514d9c2e68b040104b134f331dc4d9036505210d4944ab4b7762bbfa364fa3d96c7f87e53bf71d5a5c3c40cad698031e95ff17922952b5d1f9337c93bef79c09d075ac5d73b37a317f6c4d767a183283d1bbc97560d4930e47628d489a2c28267ed8bf0922b519eb3f72c62d710f81f07dffdf6d41b5ed95190ce20e2b94f8cf525be8352568b59a9a9db7840636d2b76494fb3f791bbdfacaf5541ddf8d7c104462dd1c6d3006bc386ac1f55fb8400589bcd895274de80092f69d1c759fdc3c31f2e1681ae75689e587d9b11a5673e069a98c1f77b538829c59e64bf65c1417abb2d8f52b8ac6c4bed00ddbb1a1d04d05d874315f55ed580c6fe3cfc967c15bdfda8be76eb1c77e3a720450dcbce40f772c28097a0925aa159858f38058ace779e4d8467de433e39518554630649613da5d20707fc04707365740563020003891394633c5f31c9ee5ca072b289c5be81c8d6237fb8290032940c0f535b9f72b4a4b8e967bc5c791a60b0567bd7c2fdc5ec71eb54c8f0770c92f5488c50d1416d636dad9c610e29d32d95c6ba93f6ce39a14fd1e6a11c2934090f494dd860d307fc04707365740621029e5980b4f9b9a9fd568c1c4b48631a800c310405ae8b2ac41ddaf87add3062f107fc0470736574072103504bd87c31711ff592af95f3c9c4f78e63f2f30805bfa95bc2d21e5a2db5c4da07fc047073657408040100000007fc0470736574094920000000003b8b87c003c8a469312a60ecdf33a36e22b37eb9c3e380ddab84491cd12ef7c80a6565124063d8d889bbc3a6f2e0d2fb66f78adb6d5a43d5fbb716a5f09a0db9788282a307fc04707365740a430100018474c718fb7fef3e0585e56e19e551b357042f0195930751ce4d22721d93302dd78d263aa3c247113a0958851c182ad93f59216c95ad62b808bad00d4127fdf100 \ No newline at end of file diff --git a/examples/test_vector/pset_blind_coinjoin/pset_coinjoined_blinded.hex b/examples/test_vector/pset_blind_coinjoin/pset_coinjoined_blinded.hex new file mode 100644 index 00000000..5b08a2f0 --- /dev/null +++ b/examples/test_vector/pset_blind_coinjoin/pset_coinjoined_blinded.hex @@ -0,0 +1 @@ +70736574ff01020402000000010401020105010501fb04020000000001017a0bb9325c276764451bbc2eb82a4c8c4bb6f4007ba803e5a5ba72d0cd7c09848e1a091622d935953bf06e0b7393239c68c6f810a00fe19d11c6ae343cffd3037077da02535fe4ad0fcd675cd0f62bf73b60a554dc1569b80f1f76a2bbfc9f00d439bf4b160014d2cbec8783bd01c9f178348b08500a830a89a7f9010e20805131ba6b37165c026eed9325ac56059ba872fd569e3ed462734098688b4770010f04000000000001017a0ac55f449ddb6853f2508766d5afb9f3b45e41a8ef5368cad75fb88e5e249395d1097d88c92ca814a207f73441c56cee943f0bb2556da194c14a4b912b078c2238ae025341cb5e4e2d8cb69e694cb20e5ea4cc8ddf2801180096fd071addfcd8bc4445160014011d384302576b408aa3686db874e2b17cc2b01b010e207aff956e6c2a379543b1ec82b06958f062bd9f29aa1ac1c98d1f6398b65a8555010f04010000000001030820a107000000000007fc04707365740121091517eb0e8ff9154ed835c45bed0856ff6b385b12f7aa224a1d91a532dce77d3f07fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b207fc047073657403210aaeb20261582b6a470b523b53d6969fb878704689a6ad0b0a690ccfc39b2b03d70104220020e5793ad956ee91ebf3543b37d110701118ed4078ffa0d477eacb8885e486ad8507fc047073657404fd4e1060330000000000000001c49a6101a4a3a122e2316e466cd2d4eaf9ec9029024390d41ab38146cb008e124d94df5c0827b57353b88df660dd6483ce69f89db194b0d7d14ed04f67059e708fc7636fe37183ffec38c28168f7c0680ede63e23a53ba0312fd96cc47dd3e147809c8e4642ff5d4ec0e280aaa369b08f41694cc6abbf60716678f7a83d7e163b34e2fe06c203e702e52cb69a5bf1a37aad479d9bfe7feac1a16bf927b3b233e50a3f86a07d6cc69454b0051df0f2941fe86e98259b91945a7fcd0e5c64c4ed96a302f931c23effb8ef97b4e1e3d74b0c392f686e49cbf255961fa03fe32cf60bc9a030dc511564600412f8f57541306f83cc6e8d3bc84796e7439bc2afff98489754bbfb6f28b2c65bda4cc8d5514e482804391bfefe3116798c5f477d67c65c4f82f18d10c864a59fc7f9de325922415422d0c57fa70e94f878acc88d9389dafc99d584194ea5329e1fcc5a8967fb1e7c7a85384d1af35b0baef473a948d4f2c02403e0405160d22e793b079cba61377a2f25a78da9233bd6706192d32f80b6f51bc86452f47bd0e8e498033fcdf84fc16298b97d6e4dd4392f37869dd65af84394fbce984599da74907b70cf0bf3181dba69625aba09995afe37213f7a38bad1e4ee4d2d5741ca334d05f6fb5ed40847ad3cb40d75f3a407e669fa77acae9c7e33fb76a92ba445f6b955e30364957a448702ebf2193f9cc6bace952cb2b36294b34f024d209aae3ffbc8c3d3e3e3984d60ef7759c165d5e99b90b5843a706be29ae6c136117196495f8f575303b156b62e77ca6654975296092a630950283ea808bf266935c5f9e32ef637fba8104c2e766101cde30c48bb77dffed39b1aae769bd378860cfb4dafcc4951893e9549e84821ae78a614c99a368336d86333eb48234fe649eb911519628c65120b395c9cf14a9f2d9398ba6dd8c3ec2ba00bf9d18dabd8162e5df48ef3f1e26fc8e068d14391b192981cba1b9e665649952752c3038aab5b4e108b705c1b90cd83699a1ba2323cdc3eee1f2f1d909eb11da0c3940fce919d65c4920b22358995655410aeb1209a8c34e8621bbd154536a26d29a93c0c4a0b9cc0334e0c5ebe960b3c5e49c305c6d062eb918d5d1f3bcc95c900e06f737623a06a799c2a6bc182357d3f88a059459d02ebc492e7b9b43b1e99a4c8ec157483ecb70ed81e0e70855950f87ab444fdbbe20ee9a34959d63b9569221001942118479bcab4acf18dd56d7d768c066a3c6b2467e5dfba6b5ae5fe5ff1e00489e732b61899ad3b8c82c8ceebe76227ffa9d57003c1449a7d34c71625a1bb8420ed046d355af63c8b3ba4983035de467716bb34f7993970a019c9c87d8a6d66b52361a1b10352bb5eb7f05ae1aa6a487fa3452f8d25a5ab58394543ff0a4e78d75e47324572b42add9ce1768d78d84524fca712965346a542660648230c914a0ce93b460687287a4d420a4894dad96650aa4b473c3a01b1f561ff4ff4c06c5ac946952c9c009c2aaab33bfa7cec11f6a24d75d8112732af0fb6c8a0d1cc8d7d202f4cbcf5810f2d1c8c6bae7748deaebd2174a39828d11daf145e47a16aa86fab16264292ffee07bfa452dce579204d91038939ee866f3a0c46dfc7fd6d85409c96450042920a80a601d11a7a8ad231c6d996a79e6fd710969848bd357dec3b7d20bcd1aaef36657048000192f7e3f0910b954a943f2d9026d1e87e080f2b78299a00aa2c7d86cdf66b6d98bee293b46c44a3bdfa87cfb6f1659d213460596ab89e90c08151252e171d87a4406ef3f0185e21bbd5db0e298874faf77e743899e2af8eaa4b2165c8942c1bc488723d684ba8096d64b3c032e9d07ddc4b8a5bd1027a54ade8e56a73c15fcf1f204b194933c6dfd5a78e449987e682c81970cfe265aed282b7ddb9c7c952859205a42ea2c24abe55883acd0c593edec197b9cfe5fcd56d6b867720fc1fbae83909b65f5adaf21526cedeea64a4d495b409244fc734618590f0efb15d76740234b19cebb88f70d29679d2b0b34a739bbbf2d5e9ff15fcd97df5e1919eebc2d1f469f35f024cd79357963374745e92d8e8d1e29a286f6fbeae0fd3abef755b514503acf5106a2fbb2d2370621ac3c3ae34bb6f14f6e82c71d01ae33a1bb629e294022ef9cc50d295bce35cde0beb14bbeecbb6cb189ca692ed2040bbe0312e9f446f2f7394c6daa130628a03956831b7ba63f0eecb5caae6472dd44459d69b994917e08e05665c16b1e988189f468b170321876918c323e52646481532616d4361c8c2070217d40e052a6dc487c1ef9f030922458e829a3ea15d2670ac3acea32e9af81b2a455d0d63bfed89e911e19fff080c7e973be22061348266b26d3bee8c4c3bfad3ce0aec84b25e3cc41b46a902f9a119775d0a903a4657df112ece8e789aa7995fc90033d503ccde74901b1b40c440936fe0dba6444eba823b328c8c8187135be38a651526cd1e159ed806714547f09f45be5928efe384e91ff6be03ea8992b35eca865ab4cf5ba7788ba95642fa530f58547fe597cc3dd8bbe223bd008aea8c37e7433547d2ac5cb716c2403472d7a96a76d41e9f3b92a7db1f7694e28446bee478bbdb1c2c6fa787daf10ec2f539d87e3d5e43ebdcd43f2da2736d15cc9708b74c69a7419fac92a3c1c307867ea243ac4401835ecb459a888ad58cd2141808100afb3e3bb056fd9f42b2df7d6f426b4db8befab9582cea9954f58f2d899e1ccb4dfde1a9d7a8b2076403ffb8c7e14661038f952dac328d5b21e7f1e4258ca8aaaa43f426c8213383bf60d60640a9e67378097d5f07e05dda7159ef477439be71c28904ba4226708319cf854f92c62f51d7128338578eb259c0681e7bdd27426fffdee0081fc9430db6782e2b013dc7e76e4ed043dfd7a2078b41230ff437dfbd240d3f11f2063a808ae011147a4b6111b4fe3339deb4c0e5bed8ab5af0a991175ffd33941c586a1da510774e25eab6858f1128cb7f7e2a53645f909c96a4c9dfb9323bafbb9d75a7d979238b3da069ab16fb7e621a7a99195b78e11f85262d9d6413a7b88ca0f6fca17cf9f73a7332e7733a3e153d4372dd2e6d9af0b7883a99c0b5925f817422085808ef25a71bdea61755449dab91704b250ea29fe5e66e776217fd38054bce90e2ad5d8ceb32575834ea5e07542f2f1795c2c1df6579b7dda3b3ce9382bb9838e7324251a9f7848c56cf213ba968e632fa9f99e2a1f4f916231a70ccaa450fcf8b9a2c44f4097b65dc4fe63b218b6a3181ab5a06d69483442ad61277cae828c7b90e2caed91e8f9f02582a642fdcaf72b4f1d3c14102d2663726555288f48393a178ede1692d3a8209bda7e3a3e1608c4c1ee2fab5a4ce7960e4dbdd00579261cccffc49cc8a876f250833694809739ebe0a252951904b994ae9c190b0cc612e2c67d25be98720887dd8c9964ca698fc29151f833496ec3959c05a8785c72bf0f1c9c9ea41616c9aba960ac21c38f71c82ebf3757e86da00bdbfda319940c1bd8e468f9d83654d87d6c1a7d85cfcb233841ad0118e830e333d7bb1c19972f24f6efce9ed585a915a4bfd49518c485f9ce5c5df734f32ec825f8f259ba25cf4b9d0c1c1118d42d7110609b5b44c4b0d92c70101319793807f2f209e7b5460c9ce8993a11fc9613c96735cd7e2a3358756e0b4c5e24104c76fdd19bc5ca4123f6d07557bcc4c350b3c5b74eb45418afd2213bd52ab2d71658a59367441eb73c37cea82492073d2f38b3e446740b338cbd202f28dba9004044ad4b5b7085ff19a4dfab50603f4344bb2526fb144882695c2a31d86a4163f092e697065f55290b99167fab57c5f22e51fa1520ac48ce50cf54841ae22cc480ab4abbf078469e65a94969e510af6d58c50b140a38c03420d33f9faeb35b44cecfa0cd19cd27c20026bd19b2971c4f0d32456ba4dd4af61bfce234b471c54c013f7183559e4dd12e8fa1c297733da6d20c57b838a1db8fbfd4a367b5969328df2cb18688b7099a504e559af5edbb54c7b58441ae15c693385e0ec40f3043fbb0457292e82cbcf3afd3b3d9d510691ccd1441fcd45c9342522010f140ddd6ae4b79c326acf5f2873031911a4ef7b53363121c20ef450e5f64a63729969d038be74661ad9260ff39d738a476e306b15746e4c3a72aa2168a8c276de4b3da973cf8ea489320e4d1df7abd1f6dd38d9bfffd4ad26c3f9d874377a4718e13e77524c1cdf751fe433b72cbf89514103df8dae6aee905f24a1222633578607909f4473397f0ef7880069eb16772188793ae979cc9ba90adc6df93f059726f8ca4f94c1b986c8ac1b0ade97c23b139d303fb36037939c63bcf0b0a33cda54344edbc759a3157abddf7afc3e034ad2881a55854b6ac679316d99255a5d0a93a96b0b1efbdf8dffca1c4e9c6ebb11c7667bff42aecb0529b809eba6f7af3eb794614bf4dd266a19248af7b679b5f5a7c5896d3ad9fc23616d579e98d7959793f9a4d96f058544e38108ee04fbd9b91249633cc405c0f3593d4a337ae3e0115ec1af1f34f96d633044897193b6a132f29043922fd57866d9e004d3aefb8c36e8145226b1dbe7866f46790097f1052e6b18b50f6c83d4ad670dd0784cc73662481f415d03348427054a3eb50695ebb104fd45ce6a4a23da4386c3d384cc8bc7aba67ddaa49a0e49e98262a55c7947d1effabf398f919862fa38adbb723b1b9d1526dc11f970444a05389b3211cca97e62f94c66d785a96e8c507cafae02b6d0dd8fa0df684b3ed339374850a9e3d540eae25bff5803dac9c5d8be61b2bcf2055ffd946df6f5ceabffbe84dbea695e0b67f3b8bad1443eeb743ff7e4943140c036832b9b5344b8db3d85ba958f6935646b601b2fe497ef8ea1645fe41760c70610326c5253cd8c84831588827fe8ee0ecc661b87103e6e99f567308f580e2387d6dce7901883b8e428bbc9cbc19c1e850bf598f204258c9b6f09c4334a8f95021b94b05ae197dbdc972d6bed5e8452f39cfb98fb60b510085ed739dffd4241ed0eab4962d34762bfc8cbda6dd52b3a324612c32ed80b9d77c6c41443a9c2a40dbea51da644dc6becb9546843687bf9f4221b8bf436863833f258dbe98bd165488157301d74ff152483d6015459b11daf5f1af98293264d91b96f63885d7fc45d3ba117c22b420994b93b0aa207fd221370a55bb1cb130314ec9d21624a01136a5f51a5d1e028585896b7ae63cfc84c9f493430cce38d1fbfbfa7db9dfc0479ea027ddfc38c13c7bbe957b04a31e90ca14aa4e9f9c12e339d09811eb18f32151835f4d396da9decad13363c93de8d7499a941b168ea2367dfbfe3fef4bad204a12bd07ee0762de00cc193707d3889efbf13ad72c54b210b066b699e8f7c67b07648b743fae60b5f09f3455be11edad01c5b4a70141f969253a63754ebedc338e83755e8b762b023b60abc12be5c584a57308b10c8a335666d84171edfcadcdcd17a6fd30450985ffa2e690d00e5bd21169a0404777f51c37cbb89c745559bc7e81814d6010d331b16ffc4fe2e88f37256e29871a4f0f8c959b78927ae0ee449559e3f895327227e859fb6cecb20d4e6671f4d9d46a76163ef3c3490d1801ca8039b5934c1293deee598acc83a4b776d335912d0a2c602c653f8f3e9a2aa249958d94fca07a4de4366cc5349aa2f3f599f33bab74ddc01944be806a35b5141c95dc778e6ec102a609c7346211953510d7105d436112868d718b72b0b861aedebb4c77d6f2ee6b7101bc00ed406a89923e19fef2dae304f528ab56e9086fce342e4f19c509809fa399b2c150e6911ff5b96ee8627d226ec33c0b84c04ab0afcfc4d89793cfcf6111aa8fbd217cf5706aad49d4ce6480b7e57fc77c07fc047073657405630200030ac6a03fb2c4143a07d1599e22ea844e3594b77f4a960e172b3c04a7b9f7eb647887e94bdae208da814a7a0d2b216bc0a730da54cd34522ae1b71ea70a305a217014067b0ed40a77a3d1e92afd3c447018c8c60d4b8e0ded12f49671bcfa36a707fc047073657406210212bf0ea45b733dfde8ecb5e896306c4165c666c99fc5d1ab887f71393a975cea07fc047073657407210325b27bf6f8d1b06dbb69ed3944095d71b9a4558fdb574e2f032bbf1adae138d607fc047073657408040000000007fc0470736574094920000000000007a12018e6f3734441bfcadd5d94f97741e750dd28201a658d4785c5bede038f1a1179290dc688efae09c0a437af462dffc2fc5adf6bcbbae64cc40e099300c98e4cbd07fc04707365740a4301000190d59bf3b61acfc27a23ea0f5994bf35cf81c62489a2f658673775457d0eadd732c4aaeb4275077bfb347f483378e6fa1570a1912e2a8a725a7a76eb0c2e23ca000103086ce2ad0d0000000007fc0470736574012108ef054b3ea0c1c3a6eaf67bdb0759cc44d7fa953fbe2df1df4d39a59dd6cc9d8f07fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b207fc047073657403210a93480fd89d465d14eb4e976496a7098fd22f5072f5740d60d12eec8c8de917710104220020f6b43d56e004e9d0b1ec2fc3c95511d81af08420992be8dec7f86cdf8970b3e207fc047073657404fd4e1060330000000000000001ff1017007f0c699812e97d8507c899e760dbf4e630430cbcec3bbbaeebddc10f855c3ff8c3e5f54785fb6f527922b522c7d4cb56bd1e6e74afe3e2149b66d3c9a2d54e8255aef17ac0f30687ddc5425ffb31ca3b51b9848200eef211bd3e0f6a7883ce56d7ae74f382f67bc67f03482bdd1b39a6730b60043037cc4f53bb18159aed2ff23fdaed63283b6c97d14ea9674784fdd0e6e38553e96ae815338ec811fd3be74ab655791568137868d626a13beb59e019fa445c25d5b289cfb84517428c7ed913e73ce4a984ecd5ea6e307553665d6e0944e9698821d690b2b4cffad1e4b1e7c987229488e01c63c3244c435a35813695250f9a665e8603fdcdf2e65e5544354f886ca40a85b863d45221e536efde55f7ec8fa055e9f07bd988774ff59772eb96b5e0c79bab01cb3fe141a461dcfb03a5469f6f39f23413abd729ff9a450fbe564d8126eff14f00b9f8ccb05452ba471b79ecb9e9e8fadb67427bab207c24a0c2e6799da4a052ab408ddaa8cb56f1dc4bce5459a70aa35b87bdc85798e9f0e828a5c36d0254b98769a3c0fce7bf97188afd3d32aba2f5a1a52c057204b086c04220e2061bf68e76db32a0a0b6703e3584a652a72b32404b307209cad70fd4e451441d2aae539a168a72cc7ee2a51795b8325a7740e788a897b4c2ec053df02b77996503d50131f86936b4e0e9ac07ac77e2fb1171a36728b908e8e27e772750bf63710a7c137d87d41582da6d6b0f1c423e4a3834e7c45234f64974d2b544060bc82c80a74fc1078972ce74e87b71fcca7c63d6a1c7bb852cfe3b12751c9c02401490fbad794b90e2e7aa304c5447f4b37d92ea304f52580f38bb892fbfaa01e63fe6c2dd2b25a092bc6a5b0cc918a7e99f68baa286c19834e738cd400dd452b60106bfaf25d2da9767cae07bb82eae3100435d1c95bb9fe2053660a3991eec9b40af76e8d7564c6d07aca2da552c73624cd756dc72e714d8e784c9efe415f12d92cf56a02e886c329d8b7ea996c465c45da1f84f56226e816b3ada59c100177d9cb4bc0a593372105fea9e207730b57ac2cd97a4baea2b1b92cbae6977842bfc6279cc260970a41f9b8641a576d80266715cd0e56a2379b854d607a9cbe64b575cc70855b4d46bcb1c7df88dacf065d7a6fb52c911f60dd54100225163e738c8f34c820f285ca2d2677dc63e12cccf9f1c8e875af4c0eed38d15d8b685bd82843ab1e7d96202d411e839db71ef148c9ef7dcf1f088ff8f04c0d408ee9c8326b9dd73683aff18a7602064c4eb56c47b525e1918315a189e48651a944379fba7d5e1473ddb4bdbfcea6ca72d8ae5c06a37093602c8d2979c4c41f6633aac369ca839fc15b0b06aeebcb7eeb178a4ece0d221a3bfee4b87a019df15db5b27f27ca9954b073092dad3f3f5cd8b78e715ec42af635ad915693cd547668a5c6e9ee375c3789578288d07e655e1f1be71313cf51a4e2b6f66fd4c740d249a29a85459daeefaa618f42ac70089b86f07d2c80eb74062e617ef375008d94a8e06426a044622f3e46f39a47857c01b5c3fafcdcbec916d15e7dca650d09324af15274631e17a55b1c251cc72f9154f940e47055c0946465f4b4b6c9b3627b309e9ff0066e0079b590b21a135ef3287df488acc3d367b7d4488707f888432fd38cf1854cf1f57e0db57468217b6b482639c92c9bd3f3556b9b5b00963700fb9043bf85e1edc39b575c56cfaa75eba62077492d0a764cc6737014d94b99696f7a9e0401cd54b0415c6c05eee5967896f6c8d9d49d6015b787c1d563022841bd47cc7d5341cb7eff22b46a99c8920f8e3ee615ccc873c8b759ae18e12a8ec8d684f15f5a48e3c1935c94e379fc6956c1e0f3c6167775c107aa59db7a05c9b4ed00cee4d7af7cf3ab32a185444a3c7ecdc24dbf766fb097bcb0af185c4015af363dd0296eb4d87bae677800fed3f31339d85a3b6eb688e6e8bc08b1b5709f482463b8d968ce7c0aa4396c5fbcb7e8f98f64498810ecca8a428bd721f390a95da48e602b04dd20716be3384e1003ea605d8492b649d78b331025a6977d5269ffe676dda60b3096e7e1e849d3fab7f176de7d02ddf036380cd642aea57b9ee43f7b8f53288870c1f1db0c2de7d29d91e57a56c6de7b585c35c3e01d2b3600e244193adc74100ca404534b5cf6831268d30b37184273fb797428e990952dccdd01476ac837defc5339b4aaed1d0af010fcf52d07fdca34210e9b7fc6e6ada5e9158b7d0b556ad3b28ca925cc24fe26f71878278f8ded022c8c1fa742dd44a17cf52cf5e5b7fde2aec0833010d2a70882b4cc1b294fb5e0a6d59f711f0c6656822a12707b3921ce5d835c23b1c699cd9fcadb7b9f2f21660f9a303d41ff817f5d1581d2f3d6c653de4a70ed1a3cf9af81bc07672467b87f38ea179a8d7a7c64dbc26d466de0a2197df8807d493d9f809513dff4fcead72c1c5b3b5460a7a665bb275073dcf64c941ae7aa70d42ea0ac1fb04cb9d34debaa6593ebe71c549f941827149fd76f9f124c311b189868e4ab43e9a020fee48e4d7c92008ae04ea5bb10f55e400c2adec535c35c44bc9e75e5547e79bf18e8d3a726522f1ab886030f2a4522bcc4c91e0f9657e32adae30be072fa0d54d1789f471e258c2191783026cb14c6b796f997c352ac64f048dddfcc745075967e16d7bcf57cd67798b66d2446d9445d0d49ed922f91eaa57ab43afa1ca90ba1e7c6c4e20416577ff70f6ae365b36adec6c485d598717bec53d58e28301589338fb62ff2b325e6a04c44a68fcf91f90b018dca2b35e5230aea6f5681977db3eeae1673c4a10b07efcc3ebe7b0fabe67737d852f50dab5e0eab00e0a42360fdc8c33de0180b48c59302a50e59f1579cd8a5565ed4f78c9d91a4bb25ad99c917c579016199e7bbafae97dc1f943e63c022542d8a29def106afcd48996a221878a5ef787259187846283c641bce500834dd452ccb058607f4a5ce5ec2a9449e2a17ea699a5a3d23febe7ae2fb46f4be2fe472f813ceb932b20f131cf799f3d6413b7701bf7c17518ad79c53eb9ad2866535b5b492006ddeccb7e59e77525d5b064696d91436a6ee3d4d0452d49f753bb7d26da00af042739fee0308571c801d6c63358e03925b76a1dbfbb1b6a9663cdb1a2a58f643f84365eab5dcd9421e73007d4569c68a758f0aa6e3531170470812cba605017082b2efc45144f885e09919e522a1233001b798a9e09acdf6a55b52a7ccb189bc52879ba263e18485a285db5b4e178dd4d69d873cb92b1dd046aa9f2905b5161195e750a84f56bb08dac7971ed8bfe11bb5368068877bc4cc3227f04c6e94a374616fc2c8d3991660a53b9ce5c6751fb4331d2398a13946e36f916bb4f93323496e5d90f3a6da812979032eb4318b24657a8cf2610d10b147e4d6e5b6abd1ed9e5c10f4ac3606d374bd87071b1bc7464b8c3a2dfb58692804e9cb6cd9858375c6ce775ccc1d4cb9cdebc85181384143a33e03def2e117bf4f0b4e58efee8d6eddb8da2bbdeb0a618dfae4a5a6e05c7b3e3c73a9c73851d1ca1dc6c6f4fd12c7e6a26c47b3eb24c4e3077b2389dfb6dc0898e8a1e761df61700a7d245bdb78f8dbf3f54695ca36bfdbd20bce3b2cf53fa0d7f23c557c981138725233189c1ee2bf87b603c91627fe9623363f2a8c1790ce8737cfa4c557195b338a09e863526fe5acdba9f2da6f0c633cbe21aa4855c3e8636d6a1265f45bcadd46e7a0c2655363c04678999091cb669edfaa8268dafc27f2c33cbd65e587cb6b921f55d2e0fceb204ebb7088c2e58a5ad33d95e901c658d1966b5bb5c0b20648188f1a4a4a307265652faa07cf34cf5faae1963c18dc6b6394a3b9e3fa6de9d0f43ce15d58e10b7b1f6e4d1dcd3ef67a11c84c0a72194ca652bb193d379e564028652d9bf1850f2a89314f478320c925ce58c5ecf2a81b656e7608510dadbebb5f5b1672a8e5679d5883883eb29ce4d31c33a2b551e37850211829a70d36256ca8b335ab103ee256535f4763e274b6b400814ec35b8acade51cfe0b5a858e8016e89a4c2726e5eb5b2cafa0168d4850679dcf5ea30cb638d27d2700a82cfb73e9ec0b5f207e537418c4b5a740382bb7e335229831d67dce6209850e810eeda29cf346da502257c37ca1a3769693315692eff9014a919c850373a053b9cffff0270420bd1b074a83992a485564354b8d71da32063b7a3bcbefecdc54661d877804786fe0a344c0bb01cf640baf82d913c08dc8b014f94f5e01842e2cbe05be286db2e5907c9319c7c473948e4a1c918cfefcf61ebe86b6b1d83bd76131ab57032a5dfe9872698bbe679caf9137dc004fccb243474c992bc11872f9028fb353ab63d83738ca5034cb99e9df87b141511d04daa8326be73b20c699cd801fab724ae99fe3072866e09312971c89b5bfda0f70d8868e9b39aab187bd4dd6a2e5446e101d3e9dc44ccd4f0b7b8c61840005ca6db4c4d10453c74c7da686641c5f6dbde3d64523735bbcfefb78cb9fc83cbb43b28ac27480634b0eab13637eff98a54c86a16f9397fa38d21ba8e820d77db362ba1d765c965ec37f98f7552aa28c167a0aaa5e0e4571d45ca335965f4df679b48b1aa12837e120f4f1efb73941eddb63521752c4a2a8de312791d6645a0e590323f87a02b2117d71a5c1404dfecdaed7b527b6dbe5b67c9db65c2c12107f03c49523ab229c347f4f5c842047c4b0239f29da30b4f701ecfcda49b2aa96e51edc281ae87c94bc5ec5df8ac129e1bbf9a5b3f21a2dfd6cb0949b13daa3fbc05b68f5966e8c2d69839377fef7bf68c2123094e17bdadb684f9df502509dfb1c3760d10614bbdf603167466d9c71742738ba729d2c2f816d918ab4baffcea95991439a8e52d3010a4391dd875c41109d1130f53670d3376abd7f79a359150fee27538e76193217577092dca74225e4c93f3454657b6c119873de20d134be8909a8677cd612596f065c6a22ab29f738b51108d29435fbc44554c63ba7212b8e0dfc937efad6d7d98979e5348f41f48403b6c17d27d976629fd99a5587446dc75896e4d81341d6500937fc07d72681b3938f3cb4dbf30e9bcdce272574c9e8d4c9305b5b9866bbe3a4cd2d661d34cdcb90ab195a44b6a91d7ac1fd66845b398fcec93487ebbc65abbefb91f1b282b6c7fcdbaa07444838c577310d706847a8be8c6e2d503de53e5d472435038c70f60f835723b089b98a00e927b08c06b055b67d7b716b2f10b9e638575bdd6d181bc0b045b570bc91f574b0c250f663ce355ddbf450a1f3d303552888d976225047f3816fb7dd7c9798fef0323c8c76fe91e2553782f766acb1c1a999c7309d19d835e424d747183be0b3f4bae9bde806b991aefb5b53dd7ee1f8a8b4c3e34b033b2a55902ac3ce94e66b8487b998ef30845745a9de19ea882511189e20c19f4cca1f3710cfeb024df0e7ee597174f1b4c7816f09394da6c30c5a5e7d672699f2772e7536f6e5940087e87d00ba3e8efcc5442ca57990c290e56c036e3b22218507a762de22c28f0fb7293d3d32deefe2e5d92f64555fb02013d5e383f577483181c264ed8338bc22aa834b4668f7dcf111e9e80b4fe787bcf22718a773a7af904b5f32914efc41584b1277a99f8a9612b28e140fc26117bf97b4d01c183ac0bdade40c812dbf711c1d10879b3a02798d23eedf4c56bc9eeb40f74fddda67444885d68ffe215a604470561a67de0fef197ebc51368a95b0175a4d37e793b048f48100fa84e18a79b9ed8a2a9b8e8362e57d4e7bf0af3d7608c8e669db8064f038cad923ae0187d1a7ffdb732018136bec4793e17dfb78218581b1f40081356db8e817efb9d8cb4eb0cef3c5e8181007fc04707365740563020003318739a04eeef538b5aa533a99325e69d6fc4e7c15d1d9c2a8e58e210a07b8bb5ea2d82a081398ab792b691933efdc867db671cc0d947d6f9e8f82c368a735fed64e50b23eeb6a49c228b09fa0030b4c88e8cf7b17b7fed5b313f90e0a90553307fc04707365740621027d07ae478c0aa607321643cb5e8ed59ee1f5ff4d9d55efedec066ccb1f5d537d07fc0470736574072103c3581a2ce34495158ec797a1b3b0f3fe25cf28abadca7703eb0cd2348afa633f07fc047073657408040000000007fc0470736574094920000000000dade26c633d2bbaabbcf43fb55d348142fb4298904909e18c4b73c952a5eecc464bb330404b01ec35a2fee7fbe4ecdb8869bfdc8c9552fe5229a61d6c0e98397e020b1007fc04707365740a43010001455786dc62ff9326a2b449f428136e7aa130895ebea05497ed5eaccb73a61ff4c9799902b6a94c73b33ae2064e654d3986d8b931fe322b3c0fd401653242749900010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000001030840420f000000000007fc0470736574012109c5ba9abdcf24dcd65cd842bb47f78d6cf84f178efc80216d94ff8a2585826d6007fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210a29206a6e6974d903b467480e654c2180efbdbda5f69e8a87d0529501734cd158010422002037831b3ee29fc96f8e61ccb98fbe2dcb03e189dd29cfecc691b5a7442d8548e807fc047073657404fd4e1060330000000000000001736c1f018f94d3c1aeecfc5e79481642ae10aa575e15948ecb3a54b85b2274822d874cf225334a0603c3701fc52635fd65e8a946895af1092760785855a1c67b846e206246bb2eb1c7a660e6213aedab1ff06645630fe6b84c78ab6b74615866ba3cfe138e553d325b0ebdd542cf4a04a9276faf2e25594936cbc56aa6d70db453ad0868e47992d0ecd1227aba88a66b0d3844e26d586630c23fd6d8c162fceebab92e9d5500acf05a81fdf0b6d41db6f6513e8f25234380fb0561c2765480b6fe83d9e74d6913e1925916a3a90f9eef648a8f1caf476244191dc98cf2c39a172a0240b36034c31c9f8782aaccad02a0928d92970cf443f0331e5f580d52a6b9e6caad1c51a30e5fa0503c795fb40b0bcf047fcd873f922bd831c115b7627f77fed8fa76aad6d1250cd84184595f5163142bf77f385b47c49b529b7dda32fee16cf081631bfcb948c2426eb17b0c3bae41b16d5abc688afa285bc99579a30a05f57d0cd7bf777dd172ae03f3982ad912e0177f9cbebec46d6e57741b83289959bf6f68d816cf5ebabfc09676bcfb7766aaf6f5f04179a22d75273b82c94b7a9095eefa90574ba83aa85ea3ec3599fdd72a4fa267f7c2b944b92b690776b97b3cd4692fcd0079760d6a820ae51e1b0e6ef405d751414face0fdad0994cbf553c60017ea379d035e8ae184df1cf7fb28bfd746adef7df476213509924690e6d284ec2e3cc3d735b072f6582d913bbec5977d21f20bdd596b10eaf9bdada349dd63d99e0c28c2006f6945f09ca0d244d434f86841afa621649c4c1704252ac1a676d9131594297fde37194b901014a176abda3e2a5103b8b73b586382b3f469da738714e2d59b36cf4eee345a7faa4666c33a0d74469e39ca152ce7510f1abbf7cf24d37054b272691780309bd69916a964f2d535082d4403fca3210cbdb357ce06704f0f3aa6d8189badf11539d9e6f867cd4dbb2e3b4e13d6f9bedb459edcf1b520488c9334bec854794d3e5d943e7837e557c3131c085307ef712e122199d245bffbe6520f7af80cd73f6547057bc2b896e5430a27d5cc922ffbb5b4e012861061caf149d42b05b342f061fc5cab91be0645ba78f334c46f64615e60f37c84d641348800c5349f34cfda388fd58e48553722b7b53474475a6b6efc9753883a2b41e4678247a363998c59c37cd6c4706c56f5d5fe7596fb195c11748b66b2001accb18fd56fcb137f059973988f981c166b04c8e8ff2a52d705cbc61ee69c26f271e80995009608334dcef363a0f4eb5cc24fcb2160313836cb1707ec294c1ea357ebb70a478b84dd760a10284dd551ee7c8b086a201b900f7e3aa1e491926d74005fb2c989d32a3809ec1cca10bcad7b68c7be99d865361038b2b3023f421fff764a9ab759faf86c0a0042945969c6ca5f096cc1a592850622d6f60ba2b56287fdb6214d761dfd118ab8806a8557af5dd1d31f3d551e6b749bd37ad351184646d26360f150167a34685e24bd8127ec4970c73231e0ee4659bedbdf0b0fd64895cf13396f52417032f8bb221ea9216166d0a324c0d03603302e8234e9d98444153e91790567554056afa2f1d49d0277d757fd5211fe66a7f61624aed4e5dfea2cbbb8dabc4ea3037f4c6e236f1ee048b499cda952c7c9086f756adeb75022c1925ac0f64e4d7da8ec583e3326792bf13136d6e3eca27c56cb4be411d6b03ba8c29a4f17a860d1d27b93242e345cf7228bc6ce11aa876e7a30159a8dfead13520347ff5e949748ffc4ea51b75a7135456ca0a3b40a39bb082a9f02da862f19c651284ea28779ec034b05c07cbe85588f56b4a5432144ca7c1dd30b9783f231b1fc1ec05869de856a20a784c05f33055379658ec2a2ad24995e00c47ffc7bfea72e91df9d5e9a8bacd3ae3e12df323c0bc759857ff6e7891ef49b0176d0959f8fb2494e0fd85e68f0b8069bfbdea365850c15ca98d8e858936cce10a0d8a7d26877f260673ec3d1bb5f3ec4565726c935593e6b34c48f73e551b908f0a9c632166096efd4887e168831b0eb66c3e0e9e01a31f354e76d5f584273ad8f8ac2af7e4310b3273d106c171892ff3bd3c92f2727920d88b82b4cab6517eac2ac6a808c62da14c35536e47b33f028ead9d5486d872332fb61dc53c8281117f517b0b6d08626a4bf5d9ceef0eeb7828b56cf42aad8cc3cdaaf2c8f8970c6c20e1fa4c8dde0d822de374fad8fc898518129f7251fd9ced6b8f32364a1987cc0969892d1ab885dc9bbfa2a08a3d831a51755a6aadfe1bf78a2dd5372eacdb0e6a9668d2c7d1b720cb911005bae7f6d5d4e112aa4256171b6dd5652f95a4bd7f624b45432a99f14bddbfada9af1ee6dadae06f8392adf959979f46dc9a55439b5515ff57cb5f4934cb1259ed9ecd114014b99b3fa0f325eefa2ab8738e05c5ded5885d7430aa31b3878a10dd0a9ff95668e01477b0ebcc904e4b4f27840de17442661cf246f47d542f85a168187669820e6642cf58b23557c109b11133106fb043781a2da51fed9b2c5fdc426b4c1ee1ec47d2b9e992a2cb3485029d8c9d6dd44d104f0caa06a44b9f0470af5f905d155326e8156155903b9706cf6ff62c6c565ad9d1d25a0e82b1d8164268d83b8c8d4fce0ff202992738237d73dd242cca368fd3d3ca71d6acf49ae0d749a33eef07157adc94a049d2c6de65d2ce6a4c5957853fc89dca92da3f014739b24459f24de757abe4a127ec8f6b774beebfe44bf1eddd84a14f85c9d57ed3a9d8dbfe616db906c2d4b482b0d79f8165cd4c78af23c98cd715b406244bb5353edea403d54b8aa46206c5e8e551d1ee3a1310f7b7ce0e28392eac7e2546867d00b34fa47ae8f2c74ac9db78565bd7c685108de0ff3c0af495fcd31120e5497025416017ba47e6f32d3ad05f2290b471eb21aba1f60878f71e3102c59fe5ec96012f68e73c9bff4f35c25e65d66aa930286c31bd6a59e0d26b33cbedf4e5e43ec5ab3a5822ab352d61bfb45a2d9929dc72c5179acbb217e760683d079002b856bfe25e1951ee428475a5fa9447193778beaf49236a1a67905a55d541e69dc27eafcba25dc6ac6110f8e6528c5a3206cbeb36833544ca08db02c91ce03c1ec97b309014e52c546186d2bf62098b708054fce5cd5bc7e042f7da37780fd1ee3ccce6b8abe4c586f46b0488117d8cc2b42679ddb89dd5e4ff7f5e4a5c2e9c3101dc1d5c6d0415f255164eb38486f78bce5a0dbfc54aef8adfb647804a8358c4e5de06804539bb4ea7f7487f158527cd91081477a5a020152aaf49b4f8077296820d254d13e3c8fdd21906a801e665a4043b21de0edc08c9664be88f3834b0246b6264ffc081846cc200d629cf1ad0db881aded247a8174b4f43970d5680332dba5061f5055a2e69334cf4c8dda7302a9e37091e4f440608dea5399f1a8d748a59b009299a79f9da3c5f2b2648846c64a893bfbe0bd187b7b7767bf42e351a8392ecc744318a9871515d435ee05be0e5c800e0d1cfa6ea10335b48a2f00c17344afcc6d08ddb426031a35d6daf07f48c7980f56bb9fd2588f9a1f119610d70c8af3da487964a170b63ed3ba6c097e8ae750aeeaad733aa300d87a15e558fa6330680cfa4cfad9101ca85d012858b3ce6d944c9bd9ff072a151920018cd52314a595dddb0337a6af4e1aa7905ff54bec93f50fcf5625cb86d5ba0a39bd757139dbdd63b96c940ec14303b6473c48ec00bf1cb5db2ea7cdbcf4f0c4ee6a6336d7a321e57fa1f288b4b635addf70b724364084f05cbba5d52bdfe7b708cf25f7f1aae574c49758e7587eb15bde51dd3343269812c5030e9398a31487dfd86e2e8c9ab8e8ab0713e955f9981b7d8f1aff0424d01499555af7a1a17d041f3eead79e2a4f9528a6356081c550326b1bb4a6610302590430aead818e2195199742dd13fdd928a9ab23b15aa67c41758b2762078b2e3d3957ab202d0ffe80595cd51ff54ca213e21734d475b7b4b924a60580b5ae4c475a71949622806ef418273349811ac08a912880079ea2dacbd0e0a4092af04fe20ee0b2bbe1addcbf4289b5e3dba3125ef6cc09a8787e29930cb27e987eb599669349ac70e8fcc66f5c27aed363e28888b1b2a48e432ad2987606cc5496d76beabe66d1bb6fad2d16116a4d0d98509756e4d641f57d65cdd71ef99262d6047800dc6fd0b2a0d167b8d242cf53c41b8411b26d48b6a95e511d316178d34aa3d6852de377725d9a253717dda8238bbb2ece522f60ff4535f1bae8a71e52d904e5c849d13cb91765cfb20f5f74c9067e24de2d70006e384883e676b33549e708f3c90a0ae967fd54ba1364dd90f00f9660a27ed880dcb1067a4904a3c649d024cf9cb3a8184706c74d461b0efb02ecc4e93a709527eff8753dc3a515d7ccec22c9d08751a74a522f9fe99cc193a5fbe2b0a6cdff1d4e8e3654d4d74ae57fbd8e2582cced7c52051797b02e0637ae4d541cc206a754e960bb8983b341808b783032236a3ba305d2a5460a04da0da20f09a4d4f6c6312435e6c4c6b45a8d17998f969964284fa49cfa49c760dabcec76fb87768369f98153bc3ad86a3bca1acccc12ca8cf61b14d00a1de4623a372364bcc893983fca31b111b9458d809ff8ec7716190987297326bda2bcdef2faf880fcf8cd999d32ea77ec98958cf30e60cc0fcc3055d978fe7037e1328ca6c4c4134afde8d8ae5d71811ecd682a48230273bb9264eaf971717fb815eef06ac881f3d0f4d859b87043d7698ce28668bac8524b1d5763caf11c0dc6534c62c0dbc2291bd62b5d145fd10553b6eaadb1f6bed46b96458fba4bcec7785861955f68552335f36c687735425b9f9d17f2514a734e3fc385901219d20976df1045aa5023d76750a5cb10e70ea07f1c197526f5e71396177f66443999abc0a008d400da3d08c9740cf6662adf93dd1735f8c262f58241fc6cdedc9ef7cc4590b8aac248a831fc7476c0e26f0445f9ed723cfee1fbc2c8ac6315e14bb2f49c2fbbd593d870b8b98cee16f8adc6f841f236e4b499571094e07ad82e44e57f16f728a7187d6c1baf3cd1315b1f9ac4b7dae69e39efb6a03176430f69720e0ef879284f59ffee4e6720d31c51753d666681dd15e7ee5340a8629e8863d5e86b4088a32de13e13e0f92e15511e9bc4d57a25c71743e9b39af867dc17ad3ebe41b9c8f64d1c38573db395382b55a50ec279030ce8f59ba8a13525681efe3862f3c46eddcd3dca4eb3a191771723d3a2875fb2074ee9867632eda4ffadda4c64283202cbed717d3ebcb2030ead311b9c2ca56169f7ce6deb9fec78a76cb9f78ab8251b83bcda6dcdb0e916ba12daac78a151eb76d040507fa29eec866225f87c694233ed07362b097aec619f5df3dc5ad0a58f0cc94eacaf252f1f9dd59bce58904ff7460052a5bd0650111c105aeb34485c4730ed6e3e1c927e2cd956d1b9404fbaf0440afad135a3f320005d666abd4c69e98edcda3691b7e69b2c3c88ef5e1ab9062b0b3f08b8ca38e8f6f7b4d04f3daa358986a3f8bc5f6a13115c31e454c0dfff61613d0dde9dcf4516bad111328b9e88f262988e8c05e5ffe1fa6ba708f5fd51c34e4757814ad0ea5e6fd33cf51b45b84c85882406cd3eb107b96fe546364a9972d19022be3b5121ec64c6454f3a4f9809f49e11611b2131d5af0aa5e65c156f37a6062aa395e763522fe6b1451c0452c6c56443087202605ec9ddac8635e7fc086e330a7d22b15851671565726b2cce451d6288755a04862bc7f934b99de6809533c05b851ed07faf408f83fa9c18f3ed65a94e9788f830f5bb9ec744b71f59d6821899f8640441d47746497dbaf8e9ddbd252e04a5e4b313e2580a1a858e4550a1e5c09a04eae54307fc047073657405630200037f019cde9197e7270a1c7d726ced68dcabcbad740497313348e7ce6ad7d29f0bdf616a31c760921e4f9796161da146c841e86ee758cfe053b88447152770fa72c1d2b4d17de0698a19a7f1cd72c95a4b3851905c2a7a76d894a2885cc28addf607fc0470736574062103d559d2a5a4180f418a69c4bed5508971cda9313722fff71e053d3d82fee9d7bd07fc0470736574072103701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d07fc047073657408040100000007fc047073657409492000000000000f424056773886ab9ae39b4056b656fb255a3af50694cda3ff4d8d4d450f15ffbbbd9043f90fa6f45a7e8563f8d305ab4b991a2c33e51b65f94230c9bdd457a3b31bc007fc04707365740a430100017f113b550d4432ae02cc0d943900d25f6833c26bc1df867fa17b36d215044f889bcf7e3dc63814f6d7894608232902f7cf313b28194f104be657b44bf54471c300010308c0878b3b0000000007fc04707365740121080b9276ef17348a17b4d25e1d429594891f5bbb07fc8e33e4bd35bdf8296b7de607fc047073657402207f8f32be9443b0547b4daedefd9b6b0b655bdad96759a57569bfd429291fa44f07fc047073657403210bb668bf543a321a7cbad607ba5714cfbdff67e8d04c9a4dc539ff7672b84b19cb0104220020e7da55d19cc85b0420c539a90b667d4d85f59ee0ed417493a947c3a2256cc0aa07fc047073657404fd4e1060330000000000000001a000d4005894c852815a527aeb01259da98708628a2f2657826496e2711f7355a941f50a31be4e1df7d245f27e22dfcbb737e28008eee8322b6ee9755cf564411c5f360cd8ebd076db3f857f2a89a6dc3c84a200a3895a0a68b8dd14ca3c294eec37cd45d1cc69ba3e5a114c8a4d8e32eb79cdf984ca018052e503a5a6a71b49c78112909d2f1b1fe97ba13cb6804156d6202dfe1275a223c3b99f77964f3eefe61f87d483f5d8273edf67f4ba970c9abc27627bdac0fbfccdb6582e30429d2d82ee4658e2284927f66b3fcbd59b8282ea88e37198b3e6809a79e9be849ae2080195239e82395ea60e837e3e03459c4a635ae8626d29939026c1b69d6ac6a32a6a8c8a3686d29c636841fe4ad37402bd3c94a58b46a74898b614dd318211822e2927db89bc8d121373ee1aed057436ea6bc084e7a38d612fb9322265c7b04ba2a4187cd70856fd276971057ab4aec79d4e9f1ca9bad675c62c481a621f23ecb7b78b72a90448f40620152a603d4a150d1fb87c37cb281195045dd4730899f58f4813c1c99569828f377e6973bdaeff1bec942f98038d6599a73de84ce4c350ec161074fc30b151949f22687b9c50d05e8339c048beea2e944f77f601797f7fe64cde986746644b4c5f0baa42d6738add8f9773e7ff9199cbfa3bf03cda1e8be64163a3d8cff0875ea6b495e372107b5f6e9602fb6b0654e5fe1bc8a2626e4c9ca3a33bb0d2755055547dbbd2b1548de0b9fd7193ac0d3bf7d4ecc929c3ed0b1112aebe26188476f63dfb75b12187242436f8bcab54de04510dc31f3f3f14204e8b50bf3594432edebf93b26903f87c1dbea50dc9af089c4092654250e1505301daa5b0534645e52f2d3c36e7af6e9e04f7b54111282044afe94411854aa73be65f18fc868f39f79e312f62c4250e61a2511718808980fa0ce8089437569c149f4f9381db1e12e4d2d1ff682f47d63c8cae57eadf9cefd30e52128dceadcee043c84d90555b10095765a0d1f898399cb734477bc068fd2b37f47847332d38182cb23da3800c56dcb2a074aad8ef25d741d569c355fd4a0d806cfb698151873d1e48e24fe359b2182c8a26a1ca1c21ab609fe19dadbf5f8e3ffed3fa7679363ac7e303eb917554fda9d2bbaf6c053e658dfeeb88448f5fb017687f3eecaa3e7ffb6188c18e55678cbe2328a05263a987fc502280d070f181129902cc0f5e415e13f76b3f58dd257136ce579c4a999b127374e2bff94f694c456247c502239e301d21aa870ac1021b97b11a7d1ba0095696a50fe16e888616548d7eef96efa59cff978962a7c6f38aca24282091616f6acdb4bebedf236c043b36a6f12eb82c2c589c6a71080a46cf27e6e663060788c2871ff04d26ecdfe534ee254dce0328134af51228c908c5184eace62cebf5ff692097fc2d272a7275b965ef8b5953fb1ffe8d713cf92bf4b3f6a428bfc3aac0e67aa93e9571902dda5b1f66ce523109092aca555f007ee5529597ec185981f45602be0a8a0f9e9b75c477e0af5d27e546adda7c4a2776b97b9a301f0b6fc15495197cfa6f2975d19bbeb70db18dbad8d0ed06176d97c066ac19d4061d7f7cccedb352788ce5d15c26fd970bb68b879b5571ec5bf2917d7d58249fc8f9b2f5a5efd5e3596451a7be38dd75525fd1c2b0444ef9aab646869322c23e52b1e0b2b420d2220a4c150f0c6b8a4bb245282f546abb74f264b6c75286d05fc7bb2c266b5e9864bc6e9b45ffacfa2352ba898178856db19b41e7e6d90ba0e14226e698ebeb02b3fa349e8ee09d7bfee08a54763ac1db861aa0683b78f6bf07263e3cf0fff0b91225e5e009231155bea60b2e661e7b04d8803cf0c89192cb8f70cf16a6d256bf31a28d2203da20d7d33c7a6f841169a845ac35c8fca9253e807f72e96355c296323f6e0a1d48bff8c43f55a594fa251f69b907d9f97e7b2feb5296d12d331b34ce9107df9da5671effe1120e078956b85abd24c8cc1276d648a2c515791fd40ab74a5c46be36a6426b2c992c22cd3d96d61ddf089ca711c15ec60538057230a2e96a92ccfd605b09a4ead233440b7fed99abc47c50263728b18b2bbf79d814c30b5e96a72393d828446bc3ff14bcfa0c4530b59750c28d7c9639c961ba47320401566962b9fd69df8e87719e0bcfc5e9987d1cd9ff4407dec489ec6e1c0a82037f7f0f94d3a06d131876d541f3885906168181c21a395a51fcd3e2efd3d116948742f8911cb892fa7d4d4d5f83e86c9334732191c632a2a6a74d7f760ccb95e04ff59f8e207ca2491d0a6f48df78254a0019fad13c0251e107639d5669bbe90cd0188042c02eb726d3d33f69e5ca06ac96c733811098c5f9a09210b762e5fc94c493552e060058e811cd302abe0d60be900fb0158126a4d3c188a65ea6c2620aad9c73b0d452413bb26c5e6c3b2d885731bbac7abbd42895d5542bcacd1a93c405daa46bea191af513b0bfc3a1f992700daf4967cbe498bf1df5bbdecbdef4614af954060b7cb7de6833dc0d721eaa570659400f452664ea7c88e410ec5c35e300465ce779d76b0fcf8ecd375f727347bc46e069ac8ad816d35d08f4da02da77a75447a3da203d8ce3a0be9093dbe938dbb62936c7602f9bebd69261fa9fde170c9fb22a345183ae119af5b9a47c1cb0d740ff9771a27493c9bcb0ffeca14c376e0512014dfacbfbcc9d0956db6d642cc892dd7d8a5070b054f26e977ee6e3c121b16163d8ac151df29a5b8c5601d3cba799ceb10f04141c3f6081d7d1b6ea22202b3aed1f932f8cec9c65eea07befa558159499b1033e235078fa7e16be66be6f2ded84e8b47708a59c0c707fd79061945818132b2f2e58242238fb3c3410d2080cc8ff7b9a689d259b2bb63fb40f1af64f19e4dc53ebe3bce60977776328ebf6dcf24cac233e04c5df29d5ffed25609e3b8d96accff140f9f6acf21d9951ccff9f5c9bb27b7c8991b5bc8c2dbed6ff31b2b6e75f6f620ddf45cf6f67b900678567745e607b42d0bdb9d86d80eed1a1923c2386f371e0d2f11306ded8684325e46bfd979059ece5fdda6ee9b5ffd0697a54a64abd7599292509c3a82bb926b743a91affe05b7dfb350b605521dedda17617dec004a378b01682b2998941f02dbbc804c3553ace603226369bfca98ab4aeee767abc892e00f56d79438d075692a547da501c50289e91c3a891646daf82de39c7abdb48b3663bd870eaba0393b66dc7723ba77c588f5246fcf7b877e296fb784834e2cd679b54141860f4696b1b1ac330edcab74b59aff6fe2e36b7e3a459bac93e5068785f455d12c51b486f2063341113b6e196def028901f106b9993e4147ec924af8e6d86155d8dc2aff8b1f9268d9c75e94e253ad38ae65a8cd3cd19161dd5258588f8e74f3da7bc7df4bc6b7d23de75a02c87e8648a7bbd55210110239ed6e6214189825dee3bad6a028c70a2bf249b2294234798311c5695fabbfde68637ab9d066fca327472ca460468d34f74922aac4f1a64ddfdf7ccd4fe12d83ef033b901384ab8149ab50288fcdf087a50c0271046e7824c6c69c33ce1179c9e87dbbca60692027c1c54481a8d4909fcb1f2565c47d50218995fccd8cd64f51874180fd930256880aa1f696c321790cc5c32c49d146f6cd0128b18c52d00417adfc06aa6e279c03f5b21daea9a33390a91dd3c5281444d023362578aa83eb3a81d57ad360d337ab374c1bb3b251a51fef636418e1966cea1b2135bf444a03c39f4619c7acaf3c7a5cdfdf8a3893696e5fc076e915af6ef7817a32f5c815803e92810abc3ca09e421f8f039206e524a47ed746d3ece4c67e1dab74477abe25707b92598e71f6011ac9e3ea85417a6e4f43c55f158c2c73776c63c3af8fa472016ccae79a2b9e99d8c42fa1cd72ce8c9cb3c535719e075a0adf26fbb559ef129512833698d6892ca1a37f176cf6a363cbeaacf40813114285b032412f6d98854bdd758465604eb2ef4756064a16cbc02763323f29722e625ce895315bc6eb1ef3aba39543df3668991b3d7ce33c68ca975c18823cf37e8bf38b87ded0527d61ba10aa43db45b82e23b1582da992b9650f7d146eb420fdf679eddc26eaa3639a6aa41a2149d1b89057a05552b772504cc29132b7f6b3eeb5732a9830fca236fd41645f4da9b38b2207d931944a98ad45585eb659b7e1e43fed797c41705265bd155174f52e45a89ae79e5c1dd264a39758ab5ba42f6b721b926a06d45ceb114a0ad592ec71276bd7fe1cfc592324e58b37500b106355dc6ed962e1830ce33f8a18a0ca236c3301b0bd9029c6dcee2dc37057635fceb494a8352d74963fb2e3f75b91b19bbdaa49abbf11351f88b4d2d0550320a8df123289cb16e1a4de9e71974d920d2b28e59037e3a941e0f7c6b2265ee281d0f9da960d2b707fdbd0a58f0a19ff8003ccec3d1be962eb47f57923ee2a1df81cd2e77430eeb8e0825e8cc93ae0466a19c80dde5b8872491326f877b27db9543c495ce3c5965bbd48f216390115c7c31bacb20bd9f11267d2aedd4586ffaa27664991a100235bc197ea189840540fe0b29a07f24c400ffa2bd81eea1d15496bc14a65fea7e05604f987cd847b0814439e09ef47bdb3f273cf0a56e339e7105542a2ea1fdda77a2dcb792e474cd086341b510cd5b424caaa98497744303dc149759640302337f8d4038396f776dd79fc41fba310214a60ac5e047311bcdb5daeb1d8a601368fd3d84f981aa6fe7fc43d244beabf0a386414c569c8599918f8250487c546f1dd49a7793061a0107ae626beb6bda487f633b1abfa389e182f4f1aa99309ffc7c0b69344d0e9c5c2feb35cfe38f33c6f8b668af1df1bdcbdb5822c6c1931c7ca25258556881584dae524b35c0fc1dedf8604298014dcdfa39c3d38908364b01f55062bf7ad3ce8f85bb765afed8f365b1996a6600d39105203dd47023b5de1e2c2ff78a2e4b1fd6329afd0e86777471721fdf55e7189b18718b9c7d6f7d764b6bba82ea9eb9cd9dd9c09b9ff5ef25b3aa8ab309e6d85f43d6dca7681a49975372a9ee196f917e4e6b390103537d24a35a084c5eb5b378dd1654025fbee39935f599bd4a15741ce19479a93dc9489035158fa0812b6ac13c6f247432cf8ed3a92f86b372cf624e5b7b2a419184ab9b333a461d5fa9df9573f67212435ee96a59a020ab3c1dc0c61440905516a8b6f341e4d9cceac3129b6b7b6b3ba2ebbabef7857d8d7594f778163a5f95f9b443067a94f9e0fe9db323e25ccae107e960ce163d62fa0c6268d99cdf208705b57e3869a8e0687fb95d9244a3fcb447add7b7dfa850e9af704e2619821754988858693e27fce4b0b3be89e42def5bdc8213e02489179150764fc0a542e382f52e8d7cdb80e4aa78ac9b3d1d56ab23c8b885f0f173c4020d2330058dd37a34c0c1fde5142b3176254a6899d47514d9c2e68b040104b134f331dc4d9036505210d4944ab4b7762bbfa364fa3d96c7f87e53bf71d5a5c3c40cad698031e95ff17922952b5d1f9337c93bef79c09d075ac5d73b37a317f6c4d767a183283d1bbc97560d4930e47628d489a2c28267ed8bf0922b519eb3f72c62d710f81f07dffdf6d41b5ed95190ce20e2b94f8cf525be8352568b59a9a9db7840636d2b76494fb3f791bbdfacaf5541ddf8d7c104462dd1c6d3006bc386ac1f55fb8400589bcd895274de80092f69d1c759fdc3c31f2e1681ae75689e587d9b11a5673e069a98c1f77b538829c59e64bf65c1417abb2d8f52b8ac6c4bed00ddbb1a1d04d05d874315f55ed580c6fe3cfc967c15bdfda8be76eb1c77e3a720450dcbce40f772c28097a0925aa159858f38058ace779e4d8467de433e39518554630649613da5d20707fc04707365740563020003891394633c5f31c9ee5ca072b289c5be81c8d6237fb8290032940c0f535b9f72b4a4b8e967bc5c791a60b0567bd7c2fdc5ec71eb54c8f0770c92f5488c50d1416d636dad9c610e29d32d95c6ba93f6ce39a14fd1e6a11c2934090f494dd860d307fc04707365740621029e5980b4f9b9a9fd568c1c4b48631a800c310405ae8b2ac41ddaf87add3062f107fc0470736574072103504bd87c31711ff592af95f3c9c4f78e63f2f30805bfa95bc2d21e5a2db5c4da07fc047073657408040100000007fc0470736574094920000000003b8b87c003c8a469312a60ecdf33a36e22b37eb9c3e380ddab84491cd12ef7c80a6565124063d8d889bbc3a6f2e0d2fb66f78adb6d5a43d5fbb716a5f09a0db9788282a307fc04707365740a430100018474c718fb7fef3e0585e56e19e551b357042f0195930751ce4d22721d93302dd78d263aa3c247113a0958851c182ad93f59216c95ad62b808bad00d4127fdf100 \ No newline at end of file diff --git a/examples/test_vector/raw_blind/blinded_one_inp_signed.hex b/examples/test_vector/raw_blind/blinded_one_inp_signed.hex new file mode 100644 index 00000000..8fbc95f4 --- /dev/null +++ b/examples/test_vector/raw_blind/blinded_one_inp_signed.hex @@ -0,0 +1 @@ +70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c22020334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe7473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc04707365740121089f9677efff5962164a434b0bffe53d90c271deda3eaf30c1d7a55105d24b874707fc047073657403210be6c538eee2e29d44c66a3e66fc8165afa4081557031826a070703cb69faf24380104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e10603300000000000000015bee4d0045b0884354b05d0285abb287ee8e539e8146b92dceaaee6fc01af1a3730cece4c77caefdcfcea1073197ffb8b3ded97d0dda6f49752b31f23e196378a3da2128a8c45ecb7eb1c32f0c0dfa1ce7bc348e799f66d547476e976baece7445790064b34b1526c88cf54928acc906ec0925327d797d36d0db6a43633d7f963ec9b4123a5ea31b655882329afe2108d123f76857fd3ce344fcd651b3d3066aa506625c9309acead85fb6c8a7d977db5dd6cb6e2f99fb3535746258d42d95dd2103bbaf5d45c8a2dfaa2301eba08763bac8ab3b59bd0082a457bc0cb257c43ea6354348e73d8dee101864068f0e8e30e6711f1597d7b529a57e95fc0df813ea160f5f5152f341087868a680c1bbc4b2a427d18cf8f12cbc4304dd3b1ea4c916467ae48b3b53062ea046161170fbe6ca07462cf3e5534d638c91fe6919318c4b2e98ce0ebcb245680d6395ba746fbf510a9a36ad8d933b023eae39973a39162178148a897b5174940db46e4be54794cea11fafbed48b42eed27f9915cfc50c958e88bac551ab1c9bfa03653e558cf87a99ff13440f4808e27fb42ff3cbac9fe10d1de07dad4807c9f945784163214c739a9eed8bb2e7da6e1fdfa4aba5d5157787d0000b220145572d3d9253cde5c1132370c1849847633287660b429f20cf1f72e4ad6ae59154c3b8526fc5d30523ba962a25577427988d326e207c97c81b479540b1c63aed0a4b1dad9279879740b2f6a0bfc37aea8c81b3b35b630dba8d955afbba756b8ce9d2a9802939972c5f70efdee8778a98360eef15faf77e38627fcb4f98569d68ebad3bb667e5db46eb7f4214846e651224673dc998c55bdd9e0a1629497a75b76160865c7ac3af874bc45a42269508f183d2458835b1734791094122f75398da2bb7919c67221ee42242c298d744fcb920726b84bcb78d7eafc9e0f61a02de87ffc5c674390a33d722b50dc35bbc6788fe0fd24be7a9e358113a4e65b49655767b8f3c3bb86cd5a56c79f9148945af63492dffde82d682eca4787f203e49790650395c925e6eb68bbb3adda25bb784365c0b3e241177ca7de58252b8c79c5a865d1030f3bfd4d312bdf83bd28241389085433c34d747f9f45a0a636e700805e619a19739b15077d44d5e8969a37481896acebe7f350449c69b50376b7cf36e906d8a03aa7221de67c7fc4c5408b55e2562f7b783918004d93b284c0a1c3d596c7c18aef5cb77ad09172bfa888f5dceabf57f708f5ab61b1b4159742d3d35332e9942958ea7f39fd35cff66e84ea0ce3b38a85eb80dcc1201146fc3854cb40353a078f5435fe2becfa383b3a62564645250cf7feccff5414581e747166625477c70f5b0f579bd578e03e6b8ff8c3f0e8a82566199ff542b239f2999103cbb61602bc2f346702204e50c9ce7b85f3b8e144227ca8b9766cdbd498191c385e3a4f024c9396763eacdfe41b706fb23d4f065dbaa43d323edf1811950c71d4475a3e0913d3851be4e921cc933bb06b0aa20524267b0e9847b6de3883ab8c5be1eac17b06c946f19dc2244901890cc290f2e66147f7c47a4a29349555ca020e684ccba5f8a6f1be54e356654d3e8678c591633e11d9ee772940d92f07ab83228c0d8840cb9aae514c03358e973a2ef319a39a3ce819efc00e5a493fd582be164c1460f70fb2e103f09a2939f779377275be736ccd41f847133b3d400477582419fbd2d7ff328d88256cfadd750ed7a3c6657e332f635f0a90602b24135e705d1530531b7b738e686d83accd74874e160a5716191447f832d2f899476ec9c53204fc4ade5b75cacdc869b4088e009e12d85beaa3450889de0c86315755ec7744be592f058fafe62f9a89875bd2781d5d6928c8475f555e77eeb6e688bd8d3bee0a54fa1cb29c1b6b438d3f70ee4ac83fcb6f482575997e3a69240c6731b517d8241cd39dba6b02a9e3c94ff2470c61c693d32b1c7d22ba38f0da21d92150115d580249380500109215a05baa63474f773892b1870005e76a68853e6a8cd9f74ab3f41cf0730e702ce94c420dbf66a63f342d927923d7d25ca3c3823b6c352ae2ca127bf811c023205632ddb8f9191c2f3384a61038d1c4d32f808cbaea81d58f0aebc7c89bcb5b98f92849f4fec19cffc78173e601eb412aad24eacd74d17cce3a9ee76670367ed87c2da3db5e4ab01d4d9626d0ae00da7dbf6b8103d5994cf66ca56bb9aab9eabee2b14ef798d3fa5ed1dc16bb500a006c3320941c4c7f6f80c8b1bf79131a5e11ef38db7c42d4c4939711184ea414e2ec0cf5bdce3d7d0d72dc883b2595e5f0fe4c14cddaed61108c90c74b43cbf719bcf7a934bee760ab33f50cedfe3ce61dda3e4bee1ab4dc687f988cfceb207152f7a98c0fa3a114d2d6881034aeba877bbcc368ce591707da45becc48318f0558b3ff65d677578df9fd9e9a18d0cb08f2f5cd4f44925259225b1c4c5ed84ef30d136e7c66251329a223a1db0b485f1c634981f8860aec1f3dbda9442e5438f6fb09c7e1ab43e2b757911834df8502ce35e7be947771f7416f7ea97d26dbad7221614175e6b86f1a323e26c4f469236d1b1bf6ef28823234c73b275c5b0ff39e8c04d9dd8ff7f6a57214d803aa13dbf581295b92ce2b8a9c0e3fa8e4fe4e74c4bc2198fd010cf84681cf5f3fc9df1638c403006478b1cd4204ef3b7a6093116021118a839bd3ac270783e67096457a86d63654e268821ee742241ab5f7e91972da0a94f679b4c093d3f428d5876edf51459331df084f2fa245e3648e0cac904a82a7d323b965a39683ec0b2721105683e67d65fe10b8d41d0dfcd5c31756408c94cf425ae67f95ecac45391516875870386b675e24c462b1f7634f758723e6245da6a39da69cc2390374eb89c6c662d94eeb2f1835e233610072936be248e6a3ff87f9950c2eb2f88568c31dd30d1f0c0396a42f6be2505bf616c63f504dad5312fee2f083b406fa05cf3c998ba24237402681f0ec393de4650ddce3389d4c1541d4705d8f50c2c6d4864934394fba0c40df495fe3a5bf58a5498c1e63b5ee5f9a4b53dbd913252d4808f5bf5fc3c6dd74c86c974fc9e5afb1a7ee3d88738ab1fe9194881cb00197a31d648ae2448db872528d5f7c366188d9d98ea94497cb78ffacb5f6e8a2c8520d8d196fe0ed13e2b2a5a08dc45d8ff58a8c5d3bcbdc5d80c798acd0eabf146459e79db12c2a43c55d14b2dd608475c606fd4f943ae7cf62131691768f710fd718fda3967146daa8d4f34a3b8fe8084f4f3865e9e0cc1a0b0a799fceafd1b74f09f524822179ce38cf0f2c4c8623a3e3ab2cc20f5dccdadffacee62c120657e3d85fea93fab645b5fcdb812b130afbbd5b19e86aee66011b520b0affd84af4bf67a28b309cad66cec25a5de06390a4f2a5af47d59c006010a8ffd214149bbe47bf847fa94c2113723c9476c2e6dd84411bca16dc7bb096aaa3e9a9be27811b52838c78d16cbd89cad379bcc0cd966dadfa428f9d4d8bf133f7d21022ae3f966dbc1409c6d57cc46ff4434fd90d79b6c9e68ed3795ea6191a7e309783badaf8db9fbccbea905ff9d423370810f02add38ad3088cf3393e7c6d585b76fd78c67766fff03ff54c01228c6737da667147a8b5e722370a24db8c0165e34888920d70fb1356eb731d0024676444e2d37860eaaed9790f99069e622afe6ff12d516812022c46420006253248eb292908ab8658cd874ef05bd568547535fd54c1f0b5b816e4d90a1bd55670b96691e7633a2fa45be6a29cf7bbbcf9e5debb4b7a739b36b6b326b719a12cd0f5e3557a2fd94023d8c633fb697b85080a39734013b4ed9a7500aa8093edfc3810d4c890e608951e57af8447349cdf45a076449241a047085702dc3e44814390ceddc878791ab3a6503f48f48af25fe5cddf61e7870ce80e32e5f22d5725bfde4f319c2db7cf3ba3b9952202cd24210ab8d47fcdaa9ad5e2a9d2c7be1ed6b67cbc1a28ebe565ed0337c09c97d2d9608d97905c7201be7d4bac785d64caf783cb3ebff817bc5ff0448899537ce6911c9803744d06ae97e6d3aae037697986cb20061892ae72e05968e9bc54153bd745012af719fe7fd4b917689a6cbb94a9bc8cce51d234ffce7dca804e2b2759f473885855983b7b290806884fc66e724fa848af2de3164c611c630e5bea712cdd9a9943ca3ad55994e5587c5953f415171bdef3c42783b809bc5526a88aae66be626d73394a607ca32256198dbc026d9e09e9659ddb66736215dedee0cf69a24490a53b2c678e6caa2aaef10a4610f802fc5eccf558629554d6388465584514d3dd49fc00fb267c39bb746f76ad3cccabcc3cc882815bd9466d593270a5c9a04945dea7ad06ddb73e66b90270e859a109d52251d911e7177e07295ac9788ce970c1b718fb0c2f327f44fd0cef3cb90071ec85e958e6ae60bd134ae307720cdb10be5a0af8c051295df905447b46206b5e04d775b83e11c348458b1bb310ae25c54e8ed1488a95b2a576002198c80e0e69d14fb7bffcd89935faa6e3f9aed61e0492bb2bc334e389bd559806eff9e16643ddc4372ca83790a99bd4f5b042779ef7e3039858881c8ce0041d43ed6b1f42f18007d046fd6f65907cef73fd5e6cd514c4fa5ebb872b140d0b2ad43c58f2f2216522e78a19828baa34ba56d71414ae869a13cb708762565d754f0754561adbd9145d232034229d2840ef0c96f7af4b232c5e11ae9d018b87177a506b66571affdeb693e170dd12640e86fd430b64509d02694c52a54f23ea96e39be92b65c1abcb262a71d6e884183d4bc6cac8c79f6e94b51abe484794a42a8e1be721cd59383bdbf327a864b2cf27566f1270d82ab6e8b3b934e953b7d48ec4697172e0160b8435e8186a7ea685dd61402b0bef09137fc52e19ecb53674a1c0af17fbb0efc2b4d69572b32600fdfc3c85c79ccb2685c665aef16748b7f15862f435afaeef00f3f927cd3a60c22f137e9cb2c38a9652cee570a76351871ebc9b6fc720b9117b1306e8afb298e3a1d0d4b2c5ad5a21e9de4c0938a98daa35caa711bd6d3312a7e941b060c4a1ec211c31e53da263995d3be74a3ca5a970d3a663245a5d8eace336d9ee847619bb337e777b961805e808f7cfd8f09b93b6ce6c277396e6cf0a6153fb11625f421b02055dc53a0bb24786f4a74211bfdf44193e48b2c15c7c85acd3f4f0bd26644f9dc62653e366178645899dc06d191c785831b98de593519c0ac5849897a01b4f8c7fd559edce1c878bdb9dd091546c64225fa81b1c7dcd6fe72c8131bb160043976a5e891236d3552598c0c4fc734d1cc752c10306dfc8391f6d658ab8b7a1f350e1900edda29775ac8a531032bac968f6981889f13cab07f49c864c3ef3ff23555c170ee9730912465e6bb3b7508e11ba27001d9dc0da8b34954a7c46a2d0b0b75e73ad66278c8acbed3249936a6c76fe975954f7889bbaae5f73747e3ed02811b015beab9d1515e396d37b2a116b3cd8863517b88ee91a854e4740304f7be41123692181a8ae22090956ba619fb948d3f08108da17ac2484d3dd5795b1c716cb5ed3fc08b6449fec814018cd883b02256afadd1f35a04d9d43f661be624c0d59d0d397e8ad6917344203843495162217e79bade11ca32c0a6918350520e6c3f31f866b89e3497b518cdc056e760813ebea7fbf343db50ca2450d5b7a1c5f430859e2040ecfa70cb0c3e863a756ef6ae2e26c68b543d35beb44513f8295872df5b4062c9b80742ca7a9c2ec36a1d9bc936c7fa7328240a2db54463ca04a40dc0c9afe4d570012259c8c436d322483b17a705d60a283e0ea7545110e898ba43ac6130aebde014d237482307ff28f385fce8530814a3b3507fc047073657405630200039d46edc3d830d852edbdf28cbc023dd1666cc4a806ec9d4acb2c97865280698d61fb6b2a073be4ce75cf8193fa9b3d677146575a812873ae7eaa7b08110cb88904273dd65af35115dd116092a56ab45fb57178d8c5ed4cf99474ff760f6e987d07fc0470736574072103701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d0007fc047073657401210821b4e7599b1b4faf29b521f1c442dfb0a893499d374c19db939c0c9a3b9798c707fc047073657403210b8529a893bd81e38743e8e3015095077f6e244a064d64708963c6af5024b6aeb3010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001ddf2f500cf45e30e3911bb51a7ff633c1e329508e42d9aa49fa8248fa4d293b87005db08b17a472580ae2cdd53c40b2a16af885d6dfa3d5b3410d389b045e6c12405c635511ccf008390856f4c6d0ca472148d2969fd39e1307bfbb8f8268ce92398248f08df7268bbe23e13ac3801ad075376bbe9684db9b41d7b1148fa65a129e1d25193ad973ec7af631a0a30c5659409b0d00e6fe44c2c507d52afbee8059aee7650bee9d47936838e77f00cfec9de3b9efe72cdeefd80a19a9c8ddcd6cd9009ec96800f9fcd709f1432b5715466d601b5ed10b0dfbd09b954b109232fb5a24036b887d26bab43da7eaa303e7c8e9d02950610aba09f343a65a7cb4550d6f6e5c0eeb9410b68f85cc3b05b9340e2e9c94a473f2001f5aea7e875d29e8e0aa99c09afec4a5455258c803e55b89cf6e89796a6f3f9fe11ee05ec415c6ac08e0f1adf9dad681f8121f82e3a9402a2fe13a016dc10d4a605578317be0d9d3e02b0989903002bd35ec4970701da6eb6f9fd5e6cefad137dceffaad968d5ce522e77c76e665529f70c9eb805a240834f4c8c795912c37e7efeb73d4bab709aafb66bd645cf0addefdd83d185c1b4465ee16344575eb2aa3acb057c37d7c22cd03adc6701f03d4cfa20ca94458b3b87e3fb7de8fed28a755bcaccbd348dbc7f5aacbb25d19437d87cb48b88fc714f059b328c374fb2695a26537330fae42d3fcff40b07054be10508fa78a9173315506119973a9158828276edc6e8ac771476d7479d07ba73e9c5b58d61367d24435a1062dfa376546e843b34829103168db63b708a13894813a442f889a423054160b85a2584d36951136bafc490056f1c03c44580e2c7a12b170e5c7a3917720bbbb21e5da616649b554b7ff57dc84dc3935271a8aa6fb218becde208d83e76bb6197f5f4f729f3c57b4280c388bf4945206fa769b672ebd47ef2d0f777fc9dfb031cf7c333c6159a75a5b33ebc6c4683ef140a144a5ab15b2b6b72ba14ee7595679a0f7873a75b9a79e03676ae94eaf9ea8d3532460f3075f40c6b9848a183fe46651caeacd51551f308eab6ba8ac27b93865f5dea0fcfe1d7dbb745695dcf187a5955414763c88ec01d299f03cdaec48eba946c7ff6031f28a5e108651401b5447c0d21c449b387765a621c057eea7d494302222e3d1ff6ee9f8a0580671d9fe3ecc310e84b554e5c823d81b8027075f749b2dc8a6bc5a99fed4a0fc46fae59cc2aa9f1471ca2b0d9fafa56927fce0f1df92647ff5aaa083bfcb7078bd8ee142448d4e1f3dda670ad93feb21028a57ee9a8ae8880df5b3276e078555d868be0d8843b495e082d743328620f36f7e106733bcefac15076826cafce271caf1520cc8746948e71ea73eef5de770ecf5e0fe075f04d6c9cef1cc1f262e69fcfc89862877ffd520e25405d9d07efec82386640d97147e4a6df66bb1fa1f20dcc2ee1fd6364bc2024c0a4a2ec00b84616231c02aef16147df509cb830b9fb20111c60613d41169a24ea67c03cea48e1905b00eba8a61e967b9aa5b6e1264b0f3e2c051e47f54f45c6fb78b46b9993c7d223d51036cb77f5e31a4b30892e8fd7ae7028322d6402f60712f712f84443de083767c38ae88a38fd4e4bbec58015f4e49e36ecbde94f23d0061edfff0e9eb62b67327249d728c278f923748e4c1e225791c740abf9493198f073a6cefffba00d23715a14967a72cc7aebb81ecc6e8bc88ae63a47507296a573c415d0163219f3b20974ba3a32572d29a479b88b89c241e63e640bbb93e2da0b73024ff03516ee3ecfbd84348372cfb574817ca878172a779ff131492b86e64c1b7054cc5fdb999df4a9578bbdd6a6a2a798f053f4c7456173cb8cb9b61735e650d0394ea9effbef615629c1f393949095982345bfc02b9a3b3ec64d9173bb1285587048fca8c740b6d2265d3e727069fcb90282c78f1049ed7925113279474d0d873045851b397a8c26036c83906dccd0eabe98f7d6854d9abc2e6ff12d84c2840affa371cc2dc624fbce873f38570dc9eff33f5e200791bc052307f5c718d47fc2a205add3fd9de2a81cb452a0a7b4a08bc133e9816f6ba852735b813a1b906d4dbd1ac8a968a8adfb6aded0d3fb87d80132a4b45ad39274bb72df20d648a26aca4beaafb971e13094b6d6525bd418b91320964bd90d3a98fb32d69222aaef2ea95c6b84b6b6aaeca3f6789944fc7ef17d218abc7667ddff798b74a0d5fbcc33d22d028d207f82286fd777ea1e281d7eb88b665e29fcda067bf9ef82a190c920e260cbc44d394b78e0439bcaa6371f8717ad82e02015f21e5dc273de338102539bc924d5a3f897eb788e0f3cbb6e8a34590ff5370bcb0f671ce28d6ac0adea17f9fda49c784ae84464991fc71a31d320d62c4199e4f4e38f390ffbcc7f15922c3325451371bebfaed8f8a7d3eead71c65878a705a6163ef649bdc6ae7b81110a2cc9576da6b0188337160cd518fb30fd5b0c165ac3de81002490f36d29f3bfefa2b4796c070c6d7d971d18bb5a573cd48cb3c64dec5bbbd7f2f6f60bff3db4833bb64b426c6715021997350aab1751f0d5382087ec133c2c43342b31eca32a4c17db14148fb48f9b7a103b2c0bfc0dc95ffe097bd8f2213a3f7a43c994599b05dc35d469fdc025e23e483039f271d948972417153c849fa8dbf374693dabd59d398732020e90d8f443ba45a00b430168561de21db76b9b714ca8260fdc4ff1a5713e69ed7296f5a5a9d192474bc6e67788d6a44d46b69ac97d0b34b2b08aa79d5ee7e910d99221e1e7f6383628b5383cec4341e5c3f18ec3361b2f2ee5935ed40919a4f8892bf3467803b720d29388b7cd47067eed9be7648fbbafcc7bebfc31a30d02f38b7d6be1579c751aabf679fa6891adf66da2257432c10d399a8d496d1aba381b64a5e3ec9a65cc8419151b638adea994c3590f77bf390152894b2784f7832d986ac51b20ba9719d85509acdedc0282f27065c87bf483fc74b55579ecc233fd6c51822ae03e46ecdb15acf0204b67852d525168f409d2a4309c5d9a14b35905b836091964ff4fec0aa91bbd7f39919eeaebbaf75c63337a4dcd5ae37cb60fa14bd78157ecef35ddb1cfdc366eecd48aebb0f5b395e4161c158c46870fc89f8f5918aabf0577c914511cac7b189384eaddf7f03cc6706a9abf219ff1b293cc9fa2cac88b65af62f0493e30736b596c119149af07e840a43dba8d97455a7a95368395488304626d3b888069a0099c435cc11e5981412f79153577c9115a1812b1c4a9ea42bbf11b40fdf930bcc37e6a6c50a7e3d06bfe7043adb32244ae359993caad00eea5f1de90355326e921145add1020ef3c2cbcbd102e7b9a150ff9672697d31f86572843269978cbc4c3fe067417e84898587d4de009b33094c8e8102c07facbda62a2996e083d0618fd9651e17f0fc025a946e7c28d93e18675ba69d8811dd06a2cc1aae28de5a6af30f305cfd8a090832483e4ff7c974d47ac96d2f3f7baec94f965a116c2734f87f468ec33cee1a58dd2ccdbc8f6a0f2a50a739aadf4007c7a529844cd1ce5efdd42eb28a0f5f04fde00dea586e4b8cdb8469b36940de19feefd342285c29298afcd3ddc368f6365e9ee255f8c2b6f70c1bafafe10720402a668ac3a2cb8ee75cb7004455cb4221486c3dee64f8f25f1c8b1411437cf6ba70afac5182b21f0efc1b5c71dd59f71796d2719741b6d5c757d824cdfd2d9459569427951d6974d94bea1fbeb0e1a23ef88774b270a8bca65f01a00299258847ddce8af8a7626c802b29dd01d1dd9d2a7243b334c29c0943e11ddbfe82cdfbd506e81fab8775a1d3c4f6c0744e1086763ad84e8f51d601e24cd6fcba1409abbb1c6b5c9bb9eab8ca30282801448f86cd5d8ec187866f1ba738863f3995bf4084ebe8f41dc1ffe0cd47d5a95dc5fc0dde1559ba170316e085d0e385ecf8646bf787b5287960f07bff9d3d990b3be4b29f008de18ccb46dc2192d0fbbbb9e701d350aae205fcd2d7314112bdc393e2eeb0a96c5f2c767dea0b82e9549f46a270b2c69915711c144a7737e9003b1c1b8684e9f2abe66ad1678c32a0c6ce401ae2d7507ff32c8f6179433b94764a6e4ea0a363e9a03787502c67ec97724d78c1257499379ecc5cd006a13acbba05dc7f1310bc75b9ec6a09f4d95b2ec11952eb3dc3d2dfeb8678b10b5db162b44af56ada56f7e311d6c5ce46472d9ba2284126f8f579d59a9d3e81bb1b26a7ee59c3cca96252fd4ba8e009a213911619ab4184e83683969cc4c474e934f36e6ba4ea33d9e93e984f1603e259cb7f1a3824dd8764bbe30ae08166aca84e63615e59c31d2a968a0beccd40637db22b971d51a4c35ce83108d8975d758669bd83ff9602718a882b943f80745d95a2d5e07ad0acf8023e24589fd68988a350c0ad571470f8b9a906648895d312100cd18089465de942ae0c7c8bffb5d3705c729990721bb0497dddea6dabdd530c9ec90286cee5ba3e2b699eb2de55de8aa0012b38051862bf0a7a22d6cce4310dd8dc47c206d9eb7bf28f2a53afa8fd10836210c0c461e417be42d89ea659a76d5e3cba08daa885b7f0302c5df05c72f8257f60e2ad954891841e67d8b7184cfa47e51e9b36f6ed1400aa84c5058a1998d07709b8b46630d70c73552ada18713310dff4177e2143aada1038bae9a64e02870a7535598d766f447b4e25dbd009009cc0e3421067bd0cd10735469b308cfd7a094bbdaaef2e31c22dc6c1a6d4c7890020b5bd04ef649b56889b2031c1923a69002448dd5db1142384cf517b08096539cc537532f43afc33ca77beb2dfee921178ad51b04f10495365e60b4b7677880409716e0eaa3a1d649a871234f86787c2792bf1e04a635e3f22b8b3b1382be942605c3855a940cfa3825a2f8993312a512c81f92aea0fca95bd1bdb6b9774ef3f1fd15bfa1eae84ebb48bc9b55fb2141c55cd3188eeb5049b2e6f3360e60c5bb7d91c9028ebc9287b4b5541a4ca290288e08ca46f80eeabbf100f700c873d7e69dee7dc80b842004b2bb93f0bf533488028d1a1137371417d06785372ce942d4c6589cdb57514bde81960475c22393eb02a93b6560fc3e4fb6ba692dc362d38e33a41b7df222e9192fdc0e0499f110e681682e2b500c00947d62280482dde5e26010cf959fc717d0837c98634550376aaeb17c1c80e3c3245c36c68f56fc8be117ff84957a75e4aa4caddb38779f1c50086ad4863002127b6c8067f172aa78137908fff82b2297a53d6232c26a2e94bb25b7f9241013adeb36d1e437f2f6c0d983efdb78608e940390005d63c081eaef21d7f00ddf51a3fbbe6175be436a9b0b75f198c30efee2821ac5f088819eb96ba835999d0012e7a0c244268a658bc6b7845d10178283cb8add0b81405ccd64000c1835111511555eca14f7e3cc9274b45f817039ee62cfd95363cc66f5397f88822f790e0a86b6a69379dae77db24185394d9fc73880d39f6b3233dd8d3b912995b22905563ae8cb2b1f3491feffbbdf2c7d2e89dce1995d2429bb0a4c550860835aef589983d3170387971f8aec15c18450dbc9cd12f402589dfb17216e359072ceec8e9484f9b490a288dd4b268233c40ad23721961c390bae0a181c4cc8eb68eb4b2a5db6944042c6ef67550f9abf3b9084cfe8521b62d727416576a58591dc65f79f871958243ca4415d92d3e588c4205b4a3806d64224e94e8f04dbf7f75888246aae9f894bab1ea69d7e10b4f07e8cf2dcf70cc62571c737cb6b6c68b0e3fbe4b1931c8615a793fa3db0a45090458ff62ce8bf421907877e555cdb97d3709bbd8728c4432288861a56cbfeabbea5f225cd29e31d99eb788704a93696f5e81307fc047073657405630200035ed48216bc666598aac5498e9fe513a9e21eaf842d382767220cf98104bfd8866621efb63b2eed44028cdc9e2f64cc2447fe09f8d8a1eb3214272855b525d9ce24e6daee58effb4af28fcb0a4685d78d7995081b5cafd1cfe343aefc8e9cb23507fc0470736574072102b642f0e5b34746f2f6e3b511d037e16bc720292da4ea2fb31ec09f6fdbe777ce00010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc0470736574012109a05049129b0d9e61c1bb87a9fa02c2435be35e3535082a772f65222f2e4a48cb07fc047073657403210baf4431fbb8f0227bfc0008368606a4b8f2bc1706e1a06cd4d83706217b74f5b90104220020c773786f3addae4c21acb094f39122c032daded1cb6225a64d923a0344087bfd07fc047073657404fd4e1060330000000000000001a4092701f9170991583ec8b5c9b5056ddaba7637f7d6733a57dc4990acba6d15e9eb5dca55528e179b98a9e09e05ba940b59558b768194ca046c22e12ea310ec05bcc795ea419277649d40b145e6f0accc31f2d7eb39b29b51d3ba3410536b9bb37bce9014d352b496b93161fc799b23939ee635f25c4cbd6605029722ac2bfed7d55fe4120c0cc880661ea3313d700e261ba973bea5df4dc4d8a05652d193c5edad01945862943a6756d4b0ddc93ad5a444ecb054bcfbbba3635aa752d9c4fe8f93fc56c8066b3fe344143d65328025f5184308f2016eae65df8b0f1818f59f5a9539eec9c9c94704ecac269b008ef25f67ab2b9ab4e903b1f2372d1d3988b86e5efa639f9f964599df8559622c8d11f6bc4f706c74e4a603a3798005e210f93d9e6b59f8ccac907dc9cff127c552c8c89981e1b91c817cd365422eb6bb0c7ef4ddddabd306b0c32a98464973dec30def4149f8e9f35de6840cbd0ce8a2f2b5dec15e3f3c27d0d2720159cabb3ff2f73b4571eb53ec735d7b0f27bf4d23cd8b0fba14d5921d464beb3fd4f2fcf844f76a7f7f129a3e7e1c4a6e4f07490f2cfbac4e6730148397c73720d35cc5a46fdb1e774e17f7b951887c1c72ae4cb25ae99bee652ebb2ffd9f4c9400ee0870b27a5dd8fdb8224aab6738399587b4513d3441e26b898b35f739d64047ce8de70347042fd18c3797b1de82cfe23521dc8e5c36e286c05d4f81508e282018cd8d263164512e6ab02a22b3d5d489e6074f9370e218a3adf8afd229659f5046749db9615541408739d4f0646a853b25f8e549e85708c368e45f3e1d20ab47a4ed647b820a360e4e351e4caf74810098f0df4bb74e0e69f2db3284c30d24a0d109d16c37509557ce15c81812bde78085f31cd7c93140fc43d6580abc7ca2adfe6354510fd8104c0f1ffe46d24f31bea808376ac27996fb992de15b30abf4df6f4d08860bec27a95bcee0df8f0a5bd7f72938ca9e917f39c5425bff826c4a84a1fd70cff87be2e8619e0d3b729f0aadc10d4b70d10c2c020a99d03424a595294dd34b84c83a2d434b98fb116e2ebc8b2bc444740ce090816218ea61da172e30db5e78a58ef495812c287e3cfab7bffada0d6f2fb3c153f1a21707923ae7ec862d44d357776803336566c3bebc9f53177a3a8825478eafb32a61bc04d82994708725e0ccf5d76dcac946f36895402e84856b3e25567156040510fb299b88c45713175c54fb3d54aa6d383c423807778a92b7d9bc8fa347923749d18c96b20cea608b6380b57c35b4408f724046f6f65fc0d71e237c39376e1ec6dfc6a2ae83ba70d25855c10e6a8f425951c52fac7758353e2068e5cb4b6ea066491e834a47f8a5cfbd6ca91c832fe4f77b3b3140a6c1c70055d52f0aa67e7d3ba18e5bfb09f738141a611002ff9e34188d540ac42f48ee06f6925cbf3cca5ca033c4fe089650a2b86a8350367e9cfbd07fac9667c0171f54311cd5b371576790e51a1fddbec64f8f559cc08b1c0ae5113bb0c532742d90c1a266bd7eaa4b1d06693ff12d6e66c51bb1d12dfaeb70bc56d5870fb5ce4b040f86d7725ae53b18c1b12a84f7e3a2e7d748e8a899fb917987518c6dbbb83e3b62b31b0facd3515cf2e18b4e49218ef48d97df114971b298a6c4af231a432d4ac295d0d1947e09ccf41ab104e5435e7dddc0a6815b636a31be9a0d9cee2f22c745f05f384bd703fbf4bbcfeadf177abf4ca4524f38a681e5436a06abf9ca70c7569a7bcb8337b0b0c280b5f790ebe1258bea9c1da58c65c13991efc1ef8f7c75ef28affa340d84b33398790a9a77d30b0ea35613b39b1b7d8667fd7299e2ff38de468f625df1485ee3b2759f7e27ed707f5807342bad0935ed42609849d7a9d11addf8a92df7933ec44e86ac7f24d55a55284fbc4e4824a2b4f8bcb146d08d7d6d3f551ce1b494d7c0fbd6e82ba4496f3eb2812c986f3e2e627240a9102089725057c1e5dcaa3bcc5efdd6ba7e8a496f609aca3dd82816552c669c255283f9372be7d8f12b044f8c41180aac612245945a4467fa806892d55ff425adbbe92bc892698a32d39d7c23303ef65c980c608b5ac07ee60ed2b4e028a5565f5aa3ecd1e15787c23716697d53603f72738d5ca88c5d8280c3d8af9e83c18be420bb87db4b82a98d3af42f9191d4023eb22f428def2ee99d04d1497cb8205b67c9ca893f7afba077bae5fc11cfeb53d882763f2eb6731bab373923a63dda3ee1c1b6207e0ad0884290333dfae3f80e85cc50a241c73d7d99998e9b14c3e52e660edfe594e1ddcef04402e8fb01efff9c00d9a6ffc955e219fe9c92cbe87865f7a25ac68fa2d360c584c33fa3e40ce91d021465610b85b5f1a98e6908a17c5e138f2dd7da2769bad8789eed99828e8f23952bab7e9ead5fa9bac781fecc93de7927544736fd5c57f1da32be31b85657c313cd02104e1b889426bf4c8e9daa741278ae3966ea673cd3fa164ea7cc947b34f86c92084043ae846aecfbb15535a9b21e27fcda9a21bd821072135554e3015d5a12d1c7f2fe74dcef63fe99c947b55a4253add3668bab2d356d58909b7b0784c0759dbf1036645c5d673edb45245f5e78407978b63d5c25ada76b176dcff53e76801d7c2784d4687561f32e7a32175c0fc77227b3ab0a4054462d042931897530502c85f0b976fe3e3fd5488853b20dbd27d0d736e9f44fe6224b9610af05c462f08d328cefa4bf8530250f5a4d3fa7f58f5a48db45cd68d0dd53a2c66758a20184cf226565ce558e60f0af982b02672f8ab14333349a5e5a5dcea61a09f93addea3858fea4e49c84f5d8269a2fe52f072d66240e7d30b7345c841a135271c07e6a142ad1ef1955e29469d3ad3d952ca9055696dd825253e4a0e4822bf3119f1df2404ff9bfa39568a52fadf98ac6d5a4653862d91b995ab166fa9de73478f7e339941e036b621f7d773a5413471d6e41a68c02ed89a4882bfade2b10e399f4515470ef43930a911609911f8b66bc060ebb186cdc4c1aa3cb573d696b0f48a655f8877f68f678bbf467ec872724181d6f983682389fc8a61a7525bcd61ab37930e6eeaac82b7adaa145e2a0bf67e235a657949708f0640ec802a1741590a505e4a109ba857824e00f8f675ae6bc8a0f32aaec0f38398c33c8f53bb3cd53bf697ece4372f8f252ab4b314769f1393aabb2675f44c05dbd279732824d49e77f3df8841de18a30a9bc321ae43c712dbb9f08b457dd3e3f13244ae03da16acd6d3e861efae625478a89020655f0de72ac174a50834db564d4e60bef51e28954a938c64a6ee290523337153aa4d6cfaa00826d2aae1b57e3ab857e10cf2b497597ef9395fe6afb3d74b21c5d44281f756e018539ce8b7f4bc663db1eac9b976da2f994b193a30bde0394b7648b55ad9bde958ec5bf89eab516d4418451d8cbca637c1d6dfdef61712e76aa7f12151d5717ba1af71c4754136563ff2667bb984a80a51ee9112587cb77ed6fac8f4263fce637adcab9f315d6beefe509f8d34ae9c42c12dd42067584c4a96f6c15fe590bd8616983abbf453ee038f385a126dcb71919dd7143a8c27186e1c6d29d36c1039a8f008088e03fe7b97fd7f285a0817ca746438e0949dcfe49fc67ae967d13d1d1aa37c2e410bb95609c7ba0586c8fccdcca933b1a85ec97d1aff670004f71232bedbd192ba5e5a681867470c4e146221bee322a5f458a2bea1316d3f4be26efbcdeb4b1062e58f2fa23b144cfdda55aad3ed7142653c7948b6d854abb8d1201e9a4de07b8985fc194e58ea08b4c8528adcd73ae6bcf5493b7b8ae1f663ace587a87d5a341828612bf82605ce6cca9253057a8096f396bdacefb56f9a452849f0ac652738b5b3135ae13af532dac7d9d5307f158d844bd3664865724aa9907d2ccaf4cae78e056ee78815cbcdccfc38b71e0c648b1fd9f710a15d799e237c344d53923d3941e73f924b75de975ead5b536d632338fe83a8d02a4b7fa9a6f1a5df9de85c88b944581294609c880ecb5eb45de087524d896486c200ad68f5c45e43894d6e84493bed02602ab075c8dca1e57e3932740c25a9401b5bbc1bf524f4885f77a501bc3c313666fdebe7497a2b55031563810b35f33503cdbeb46de12c0482c70b07b5ca3c4e2aa21be75c33a23e73c03379c434dc8bff54dc40b0a34f476d0fd1de0422ae6edacc8a729ed2443315336b208ceafa68a229592e42f49501cf03fcf04ed2ab60cf7c31bf4d4fb5a8d7b8969ddfa8a725eb9d32827446c141170bec2b0dfb5c0a6e00cf83b856ae71b5ce46dfafdc12f94a7f8f3d1bf1eb769f52f43238ed411a97e55625ddc208affbae8ade42829c82cf4f62423830db9ca4091fffe3d282c578552649a34cbfeef3d58e020978137a44fefdb9083186dfd712301431b78095a0997f0764f3ef29f138ffb322c8e187c260c90a3ef7da5638e8d961d6a6cb1867a86d3eb23419e101e18bbf128c7a3492e8447533b7586a81e52366bdf2c376a57847b8a44f7b55eaeee6ab2f06596a87ccad4a60881bcf4b4b5d5f0de85593b643c0c4fe1771a0cb5bb0402fae0f1a2dd811d6495676ad718b73d4b86fc79ce074aa9924829ceb4dfd4e3ab70fdbd94b5ad1d11ca9e582dc215577056373ed91ccd6472b3ce02dffa8817e66205b179f37bdda376d25d6bbcc83a15ad757bb7843aa03d2f7193d8d707ccf4de1f5c8694aa212219752addd55cbb412c05118c30555ca7ea820bf3440d1dae3332947a1cfc22e366d1ad120b906274883d7769983eab6afea2ec0672a2ed25b2b3390c017566e52a0ebc59e6cebb89918d54f75d4094b943e7725468034d2ecfeb68228060bad59a4deaa40843d0cfe0a1e58855113bb7c5965254b82cf3997924d0a8b1711b1e1e5b28343168f64fa9bdf8e5158f33c91112f61ba782a4f97c5456c9c8756adf53a7d919ad90654a1c035d8686f834750302added0fb7b29a3f4f43f4dec29bedcff3139cbdffe391ad64b48eb1691be659e4e4dab266b7221a2c49e42d8ab06ab6d16efee2a0e0bab140aa34b53f70c808f3966f1a171bdc789e23ca0246328dcb1680c765fe262048b869c56f381bb7ded54e1486c0a3660d990f4bf0e5b4eec4cc0c3abf4edab6346cb531194c27de0e316d9d0a823db00e20e2fb99c2b5383213518d0cf604ae8f69d2921b442b729cc78579dd242314da99f01df55a45dadcc87e4b216aa0d817e60673c9807a75185e52a70f04ab8ea28cad805c37fae5417f89af44d4a5728c187c74ea736f21681fa7f93dd9bcab9b510febf2c4b5c64cbedb6b59b77a8816728dfbaeea423bb7735ae1fba08964a343ef9b5d36dc7f8cba10c8ae69bbddc4e524df73c940f762a2d39ce7ff235425942f2007b4e08f2002018f720e0339bfa2704a9d74798b13c6fda733d965666ec5592d1d6dae7af6efe5c87cf48d40858937b1ecde3445ab2c1ba1e87e1c2f3ce696baf3efb30ecbd9dbfab121a142ebfb4f09f343f37e8c5c0f40408a339cc3e4f4d34742d695dfc037744ba1a794abb67954d3a6509157a3aaf293c886b58cfe9bed8a3dacda2a2ce41f95e72e46baf65ca666f61bb548afe8f92e1c4ada258f46178a4591015678fc542d412f7ed323d2629c127890ad9c2151261c4869f59db9648e09c25bd5ad8cfde27ea4ef22666ae0f30a4d25c3ea1780a6c3eb2d94d0f48c1a61701707e57405e7e9f448452d0a48af07013fe04d6fa62fa5fd54139d95217734ff4e596f2ac975bbfb8fda29f7e6f211eb165480e44fde387d4833f89c7162c1bda3265a5271946c5392921861ad5d9e5b672afe4778301d7a8a8d8a03ca1c99d73b2ee43281c0b88ca4292a5ea29a0e2377007fc04707365740563020003c54f16cef0fd4185f35dc6784047913c89883f72b68aa78c16f144624430909da01633eb2b4b13eccc2b7a7ac5ef3b13505cea54e1d390944b5680e6860184e35111675dce504784f4ae8121dbd1f76df3d55ef722f3f73b5b7723efe2fd34b107fc0470736574072103652ba5800d63722adef5232d31ccc12403055de720f0155fc47bba5750ccedd400 \ No newline at end of file diff --git a/examples/test_vector/raw_blind/blinded_signed.hex b/examples/test_vector/raw_blind/blinded_signed.hex new file mode 100644 index 00000000..d05b88f8 --- /dev/null +++ b/examples/test_vector/raw_blind/blinded_signed.hex @@ -0,0 +1 @@ +70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c22020334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe7473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b220203df8f51c053ba0dfb443cce9793b6dc3339ffb0ce97af4792dade3aae1eb890f6473044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa01010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc04707365740121089f9677efff5962164a434b0bffe53d90c271deda3eaf30c1d7a55105d24b874707fc047073657403210be6c538eee2e29d44c66a3e66fc8165afa4081557031826a070703cb69faf24380104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e10603300000000000000015bee4d0045b0884354b05d0285abb287ee8e539e8146b92dceaaee6fc01af1a3730cece4c77caefdcfcea1073197ffb8b3ded97d0dda6f49752b31f23e196378a3da2128a8c45ecb7eb1c32f0c0dfa1ce7bc348e799f66d547476e976baece7445790064b34b1526c88cf54928acc906ec0925327d797d36d0db6a43633d7f963ec9b4123a5ea31b655882329afe2108d123f76857fd3ce344fcd651b3d3066aa506625c9309acead85fb6c8a7d977db5dd6cb6e2f99fb3535746258d42d95dd2103bbaf5d45c8a2dfaa2301eba08763bac8ab3b59bd0082a457bc0cb257c43ea6354348e73d8dee101864068f0e8e30e6711f1597d7b529a57e95fc0df813ea160f5f5152f341087868a680c1bbc4b2a427d18cf8f12cbc4304dd3b1ea4c916467ae48b3b53062ea046161170fbe6ca07462cf3e5534d638c91fe6919318c4b2e98ce0ebcb245680d6395ba746fbf510a9a36ad8d933b023eae39973a39162178148a897b5174940db46e4be54794cea11fafbed48b42eed27f9915cfc50c958e88bac551ab1c9bfa03653e558cf87a99ff13440f4808e27fb42ff3cbac9fe10d1de07dad4807c9f945784163214c739a9eed8bb2e7da6e1fdfa4aba5d5157787d0000b220145572d3d9253cde5c1132370c1849847633287660b429f20cf1f72e4ad6ae59154c3b8526fc5d30523ba962a25577427988d326e207c97c81b479540b1c63aed0a4b1dad9279879740b2f6a0bfc37aea8c81b3b35b630dba8d955afbba756b8ce9d2a9802939972c5f70efdee8778a98360eef15faf77e38627fcb4f98569d68ebad3bb667e5db46eb7f4214846e651224673dc998c55bdd9e0a1629497a75b76160865c7ac3af874bc45a42269508f183d2458835b1734791094122f75398da2bb7919c67221ee42242c298d744fcb920726b84bcb78d7eafc9e0f61a02de87ffc5c674390a33d722b50dc35bbc6788fe0fd24be7a9e358113a4e65b49655767b8f3c3bb86cd5a56c79f9148945af63492dffde82d682eca4787f203e49790650395c925e6eb68bbb3adda25bb784365c0b3e241177ca7de58252b8c79c5a865d1030f3bfd4d312bdf83bd28241389085433c34d747f9f45a0a636e700805e619a19739b15077d44d5e8969a37481896acebe7f350449c69b50376b7cf36e906d8a03aa7221de67c7fc4c5408b55e2562f7b783918004d93b284c0a1c3d596c7c18aef5cb77ad09172bfa888f5dceabf57f708f5ab61b1b4159742d3d35332e9942958ea7f39fd35cff66e84ea0ce3b38a85eb80dcc1201146fc3854cb40353a078f5435fe2becfa383b3a62564645250cf7feccff5414581e747166625477c70f5b0f579bd578e03e6b8ff8c3f0e8a82566199ff542b239f2999103cbb61602bc2f346702204e50c9ce7b85f3b8e144227ca8b9766cdbd498191c385e3a4f024c9396763eacdfe41b706fb23d4f065dbaa43d323edf1811950c71d4475a3e0913d3851be4e921cc933bb06b0aa20524267b0e9847b6de3883ab8c5be1eac17b06c946f19dc2244901890cc290f2e66147f7c47a4a29349555ca020e684ccba5f8a6f1be54e356654d3e8678c591633e11d9ee772940d92f07ab83228c0d8840cb9aae514c03358e973a2ef319a39a3ce819efc00e5a493fd582be164c1460f70fb2e103f09a2939f779377275be736ccd41f847133b3d400477582419fbd2d7ff328d88256cfadd750ed7a3c6657e332f635f0a90602b24135e705d1530531b7b738e686d83accd74874e160a5716191447f832d2f899476ec9c53204fc4ade5b75cacdc869b4088e009e12d85beaa3450889de0c86315755ec7744be592f058fafe62f9a89875bd2781d5d6928c8475f555e77eeb6e688bd8d3bee0a54fa1cb29c1b6b438d3f70ee4ac83fcb6f482575997e3a69240c6731b517d8241cd39dba6b02a9e3c94ff2470c61c693d32b1c7d22ba38f0da21d92150115d580249380500109215a05baa63474f773892b1870005e76a68853e6a8cd9f74ab3f41cf0730e702ce94c420dbf66a63f342d927923d7d25ca3c3823b6c352ae2ca127bf811c023205632ddb8f9191c2f3384a61038d1c4d32f808cbaea81d58f0aebc7c89bcb5b98f92849f4fec19cffc78173e601eb412aad24eacd74d17cce3a9ee76670367ed87c2da3db5e4ab01d4d9626d0ae00da7dbf6b8103d5994cf66ca56bb9aab9eabee2b14ef798d3fa5ed1dc16bb500a006c3320941c4c7f6f80c8b1bf79131a5e11ef38db7c42d4c4939711184ea414e2ec0cf5bdce3d7d0d72dc883b2595e5f0fe4c14cddaed61108c90c74b43cbf719bcf7a934bee760ab33f50cedfe3ce61dda3e4bee1ab4dc687f988cfceb207152f7a98c0fa3a114d2d6881034aeba877bbcc368ce591707da45becc48318f0558b3ff65d677578df9fd9e9a18d0cb08f2f5cd4f44925259225b1c4c5ed84ef30d136e7c66251329a223a1db0b485f1c634981f8860aec1f3dbda9442e5438f6fb09c7e1ab43e2b757911834df8502ce35e7be947771f7416f7ea97d26dbad7221614175e6b86f1a323e26c4f469236d1b1bf6ef28823234c73b275c5b0ff39e8c04d9dd8ff7f6a57214d803aa13dbf581295b92ce2b8a9c0e3fa8e4fe4e74c4bc2198fd010cf84681cf5f3fc9df1638c403006478b1cd4204ef3b7a6093116021118a839bd3ac270783e67096457a86d63654e268821ee742241ab5f7e91972da0a94f679b4c093d3f428d5876edf51459331df084f2fa245e3648e0cac904a82a7d323b965a39683ec0b2721105683e67d65fe10b8d41d0dfcd5c31756408c94cf425ae67f95ecac45391516875870386b675e24c462b1f7634f758723e6245da6a39da69cc2390374eb89c6c662d94eeb2f1835e233610072936be248e6a3ff87f9950c2eb2f88568c31dd30d1f0c0396a42f6be2505bf616c63f504dad5312fee2f083b406fa05cf3c998ba24237402681f0ec393de4650ddce3389d4c1541d4705d8f50c2c6d4864934394fba0c40df495fe3a5bf58a5498c1e63b5ee5f9a4b53dbd913252d4808f5bf5fc3c6dd74c86c974fc9e5afb1a7ee3d88738ab1fe9194881cb00197a31d648ae2448db872528d5f7c366188d9d98ea94497cb78ffacb5f6e8a2c8520d8d196fe0ed13e2b2a5a08dc45d8ff58a8c5d3bcbdc5d80c798acd0eabf146459e79db12c2a43c55d14b2dd608475c606fd4f943ae7cf62131691768f710fd718fda3967146daa8d4f34a3b8fe8084f4f3865e9e0cc1a0b0a799fceafd1b74f09f524822179ce38cf0f2c4c8623a3e3ab2cc20f5dccdadffacee62c120657e3d85fea93fab645b5fcdb812b130afbbd5b19e86aee66011b520b0affd84af4bf67a28b309cad66cec25a5de06390a4f2a5af47d59c006010a8ffd214149bbe47bf847fa94c2113723c9476c2e6dd84411bca16dc7bb096aaa3e9a9be27811b52838c78d16cbd89cad379bcc0cd966dadfa428f9d4d8bf133f7d21022ae3f966dbc1409c6d57cc46ff4434fd90d79b6c9e68ed3795ea6191a7e309783badaf8db9fbccbea905ff9d423370810f02add38ad3088cf3393e7c6d585b76fd78c67766fff03ff54c01228c6737da667147a8b5e722370a24db8c0165e34888920d70fb1356eb731d0024676444e2d37860eaaed9790f99069e622afe6ff12d516812022c46420006253248eb292908ab8658cd874ef05bd568547535fd54c1f0b5b816e4d90a1bd55670b96691e7633a2fa45be6a29cf7bbbcf9e5debb4b7a739b36b6b326b719a12cd0f5e3557a2fd94023d8c633fb697b85080a39734013b4ed9a7500aa8093edfc3810d4c890e608951e57af8447349cdf45a076449241a047085702dc3e44814390ceddc878791ab3a6503f48f48af25fe5cddf61e7870ce80e32e5f22d5725bfde4f319c2db7cf3ba3b9952202cd24210ab8d47fcdaa9ad5e2a9d2c7be1ed6b67cbc1a28ebe565ed0337c09c97d2d9608d97905c7201be7d4bac785d64caf783cb3ebff817bc5ff0448899537ce6911c9803744d06ae97e6d3aae037697986cb20061892ae72e05968e9bc54153bd745012af719fe7fd4b917689a6cbb94a9bc8cce51d234ffce7dca804e2b2759f473885855983b7b290806884fc66e724fa848af2de3164c611c630e5bea712cdd9a9943ca3ad55994e5587c5953f415171bdef3c42783b809bc5526a88aae66be626d73394a607ca32256198dbc026d9e09e9659ddb66736215dedee0cf69a24490a53b2c678e6caa2aaef10a4610f802fc5eccf558629554d6388465584514d3dd49fc00fb267c39bb746f76ad3cccabcc3cc882815bd9466d593270a5c9a04945dea7ad06ddb73e66b90270e859a109d52251d911e7177e07295ac9788ce970c1b718fb0c2f327f44fd0cef3cb90071ec85e958e6ae60bd134ae307720cdb10be5a0af8c051295df905447b46206b5e04d775b83e11c348458b1bb310ae25c54e8ed1488a95b2a576002198c80e0e69d14fb7bffcd89935faa6e3f9aed61e0492bb2bc334e389bd559806eff9e16643ddc4372ca83790a99bd4f5b042779ef7e3039858881c8ce0041d43ed6b1f42f18007d046fd6f65907cef73fd5e6cd514c4fa5ebb872b140d0b2ad43c58f2f2216522e78a19828baa34ba56d71414ae869a13cb708762565d754f0754561adbd9145d232034229d2840ef0c96f7af4b232c5e11ae9d018b87177a506b66571affdeb693e170dd12640e86fd430b64509d02694c52a54f23ea96e39be92b65c1abcb262a71d6e884183d4bc6cac8c79f6e94b51abe484794a42a8e1be721cd59383bdbf327a864b2cf27566f1270d82ab6e8b3b934e953b7d48ec4697172e0160b8435e8186a7ea685dd61402b0bef09137fc52e19ecb53674a1c0af17fbb0efc2b4d69572b32600fdfc3c85c79ccb2685c665aef16748b7f15862f435afaeef00f3f927cd3a60c22f137e9cb2c38a9652cee570a76351871ebc9b6fc720b9117b1306e8afb298e3a1d0d4b2c5ad5a21e9de4c0938a98daa35caa711bd6d3312a7e941b060c4a1ec211c31e53da263995d3be74a3ca5a970d3a663245a5d8eace336d9ee847619bb337e777b961805e808f7cfd8f09b93b6ce6c277396e6cf0a6153fb11625f421b02055dc53a0bb24786f4a74211bfdf44193e48b2c15c7c85acd3f4f0bd26644f9dc62653e366178645899dc06d191c785831b98de593519c0ac5849897a01b4f8c7fd559edce1c878bdb9dd091546c64225fa81b1c7dcd6fe72c8131bb160043976a5e891236d3552598c0c4fc734d1cc752c10306dfc8391f6d658ab8b7a1f350e1900edda29775ac8a531032bac968f6981889f13cab07f49c864c3ef3ff23555c170ee9730912465e6bb3b7508e11ba27001d9dc0da8b34954a7c46a2d0b0b75e73ad66278c8acbed3249936a6c76fe975954f7889bbaae5f73747e3ed02811b015beab9d1515e396d37b2a116b3cd8863517b88ee91a854e4740304f7be41123692181a8ae22090956ba619fb948d3f08108da17ac2484d3dd5795b1c716cb5ed3fc08b6449fec814018cd883b02256afadd1f35a04d9d43f661be624c0d59d0d397e8ad6917344203843495162217e79bade11ca32c0a6918350520e6c3f31f866b89e3497b518cdc056e760813ebea7fbf343db50ca2450d5b7a1c5f430859e2040ecfa70cb0c3e863a756ef6ae2e26c68b543d35beb44513f8295872df5b4062c9b80742ca7a9c2ec36a1d9bc936c7fa7328240a2db54463ca04a40dc0c9afe4d570012259c8c436d322483b17a705d60a283e0ea7545110e898ba43ac6130aebde014d237482307ff28f385fce8530814a3b3507fc047073657405630200039d46edc3d830d852edbdf28cbc023dd1666cc4a806ec9d4acb2c97865280698d61fb6b2a073be4ce75cf8193fa9b3d677146575a812873ae7eaa7b08110cb88904273dd65af35115dd116092a56ab45fb57178d8c5ed4cf99474ff760f6e987d07fc0470736574072103701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d0007fc047073657401210821b4e7599b1b4faf29b521f1c442dfb0a893499d374c19db939c0c9a3b9798c707fc047073657403210b8529a893bd81e38743e8e3015095077f6e244a064d64708963c6af5024b6aeb3010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001ddf2f500cf45e30e3911bb51a7ff633c1e329508e42d9aa49fa8248fa4d293b87005db08b17a472580ae2cdd53c40b2a16af885d6dfa3d5b3410d389b045e6c12405c635511ccf008390856f4c6d0ca472148d2969fd39e1307bfbb8f8268ce92398248f08df7268bbe23e13ac3801ad075376bbe9684db9b41d7b1148fa65a129e1d25193ad973ec7af631a0a30c5659409b0d00e6fe44c2c507d52afbee8059aee7650bee9d47936838e77f00cfec9de3b9efe72cdeefd80a19a9c8ddcd6cd9009ec96800f9fcd709f1432b5715466d601b5ed10b0dfbd09b954b109232fb5a24036b887d26bab43da7eaa303e7c8e9d02950610aba09f343a65a7cb4550d6f6e5c0eeb9410b68f85cc3b05b9340e2e9c94a473f2001f5aea7e875d29e8e0aa99c09afec4a5455258c803e55b89cf6e89796a6f3f9fe11ee05ec415c6ac08e0f1adf9dad681f8121f82e3a9402a2fe13a016dc10d4a605578317be0d9d3e02b0989903002bd35ec4970701da6eb6f9fd5e6cefad137dceffaad968d5ce522e77c76e665529f70c9eb805a240834f4c8c795912c37e7efeb73d4bab709aafb66bd645cf0addefdd83d185c1b4465ee16344575eb2aa3acb057c37d7c22cd03adc6701f03d4cfa20ca94458b3b87e3fb7de8fed28a755bcaccbd348dbc7f5aacbb25d19437d87cb48b88fc714f059b328c374fb2695a26537330fae42d3fcff40b07054be10508fa78a9173315506119973a9158828276edc6e8ac771476d7479d07ba73e9c5b58d61367d24435a1062dfa376546e843b34829103168db63b708a13894813a442f889a423054160b85a2584d36951136bafc490056f1c03c44580e2c7a12b170e5c7a3917720bbbb21e5da616649b554b7ff57dc84dc3935271a8aa6fb218becde208d83e76bb6197f5f4f729f3c57b4280c388bf4945206fa769b672ebd47ef2d0f777fc9dfb031cf7c333c6159a75a5b33ebc6c4683ef140a144a5ab15b2b6b72ba14ee7595679a0f7873a75b9a79e03676ae94eaf9ea8d3532460f3075f40c6b9848a183fe46651caeacd51551f308eab6ba8ac27b93865f5dea0fcfe1d7dbb745695dcf187a5955414763c88ec01d299f03cdaec48eba946c7ff6031f28a5e108651401b5447c0d21c449b387765a621c057eea7d494302222e3d1ff6ee9f8a0580671d9fe3ecc310e84b554e5c823d81b8027075f749b2dc8a6bc5a99fed4a0fc46fae59cc2aa9f1471ca2b0d9fafa56927fce0f1df92647ff5aaa083bfcb7078bd8ee142448d4e1f3dda670ad93feb21028a57ee9a8ae8880df5b3276e078555d868be0d8843b495e082d743328620f36f7e106733bcefac15076826cafce271caf1520cc8746948e71ea73eef5de770ecf5e0fe075f04d6c9cef1cc1f262e69fcfc89862877ffd520e25405d9d07efec82386640d97147e4a6df66bb1fa1f20dcc2ee1fd6364bc2024c0a4a2ec00b84616231c02aef16147df509cb830b9fb20111c60613d41169a24ea67c03cea48e1905b00eba8a61e967b9aa5b6e1264b0f3e2c051e47f54f45c6fb78b46b9993c7d223d51036cb77f5e31a4b30892e8fd7ae7028322d6402f60712f712f84443de083767c38ae88a38fd4e4bbec58015f4e49e36ecbde94f23d0061edfff0e9eb62b67327249d728c278f923748e4c1e225791c740abf9493198f073a6cefffba00d23715a14967a72cc7aebb81ecc6e8bc88ae63a47507296a573c415d0163219f3b20974ba3a32572d29a479b88b89c241e63e640bbb93e2da0b73024ff03516ee3ecfbd84348372cfb574817ca878172a779ff131492b86e64c1b7054cc5fdb999df4a9578bbdd6a6a2a798f053f4c7456173cb8cb9b61735e650d0394ea9effbef615629c1f393949095982345bfc02b9a3b3ec64d9173bb1285587048fca8c740b6d2265d3e727069fcb90282c78f1049ed7925113279474d0d873045851b397a8c26036c83906dccd0eabe98f7d6854d9abc2e6ff12d84c2840affa371cc2dc624fbce873f38570dc9eff33f5e200791bc052307f5c718d47fc2a205add3fd9de2a81cb452a0a7b4a08bc133e9816f6ba852735b813a1b906d4dbd1ac8a968a8adfb6aded0d3fb87d80132a4b45ad39274bb72df20d648a26aca4beaafb971e13094b6d6525bd418b91320964bd90d3a98fb32d69222aaef2ea95c6b84b6b6aaeca3f6789944fc7ef17d218abc7667ddff798b74a0d5fbcc33d22d028d207f82286fd777ea1e281d7eb88b665e29fcda067bf9ef82a190c920e260cbc44d394b78e0439bcaa6371f8717ad82e02015f21e5dc273de338102539bc924d5a3f897eb788e0f3cbb6e8a34590ff5370bcb0f671ce28d6ac0adea17f9fda49c784ae84464991fc71a31d320d62c4199e4f4e38f390ffbcc7f15922c3325451371bebfaed8f8a7d3eead71c65878a705a6163ef649bdc6ae7b81110a2cc9576da6b0188337160cd518fb30fd5b0c165ac3de81002490f36d29f3bfefa2b4796c070c6d7d971d18bb5a573cd48cb3c64dec5bbbd7f2f6f60bff3db4833bb64b426c6715021997350aab1751f0d5382087ec133c2c43342b31eca32a4c17db14148fb48f9b7a103b2c0bfc0dc95ffe097bd8f2213a3f7a43c994599b05dc35d469fdc025e23e483039f271d948972417153c849fa8dbf374693dabd59d398732020e90d8f443ba45a00b430168561de21db76b9b714ca8260fdc4ff1a5713e69ed7296f5a5a9d192474bc6e67788d6a44d46b69ac97d0b34b2b08aa79d5ee7e910d99221e1e7f6383628b5383cec4341e5c3f18ec3361b2f2ee5935ed40919a4f8892bf3467803b720d29388b7cd47067eed9be7648fbbafcc7bebfc31a30d02f38b7d6be1579c751aabf679fa6891adf66da2257432c10d399a8d496d1aba381b64a5e3ec9a65cc8419151b638adea994c3590f77bf390152894b2784f7832d986ac51b20ba9719d85509acdedc0282f27065c87bf483fc74b55579ecc233fd6c51822ae03e46ecdb15acf0204b67852d525168f409d2a4309c5d9a14b35905b836091964ff4fec0aa91bbd7f39919eeaebbaf75c63337a4dcd5ae37cb60fa14bd78157ecef35ddb1cfdc366eecd48aebb0f5b395e4161c158c46870fc89f8f5918aabf0577c914511cac7b189384eaddf7f03cc6706a9abf219ff1b293cc9fa2cac88b65af62f0493e30736b596c119149af07e840a43dba8d97455a7a95368395488304626d3b888069a0099c435cc11e5981412f79153577c9115a1812b1c4a9ea42bbf11b40fdf930bcc37e6a6c50a7e3d06bfe7043adb32244ae359993caad00eea5f1de90355326e921145add1020ef3c2cbcbd102e7b9a150ff9672697d31f86572843269978cbc4c3fe067417e84898587d4de009b33094c8e8102c07facbda62a2996e083d0618fd9651e17f0fc025a946e7c28d93e18675ba69d8811dd06a2cc1aae28de5a6af30f305cfd8a090832483e4ff7c974d47ac96d2f3f7baec94f965a116c2734f87f468ec33cee1a58dd2ccdbc8f6a0f2a50a739aadf4007c7a529844cd1ce5efdd42eb28a0f5f04fde00dea586e4b8cdb8469b36940de19feefd342285c29298afcd3ddc368f6365e9ee255f8c2b6f70c1bafafe10720402a668ac3a2cb8ee75cb7004455cb4221486c3dee64f8f25f1c8b1411437cf6ba70afac5182b21f0efc1b5c71dd59f71796d2719741b6d5c757d824cdfd2d9459569427951d6974d94bea1fbeb0e1a23ef88774b270a8bca65f01a00299258847ddce8af8a7626c802b29dd01d1dd9d2a7243b334c29c0943e11ddbfe82cdfbd506e81fab8775a1d3c4f6c0744e1086763ad84e8f51d601e24cd6fcba1409abbb1c6b5c9bb9eab8ca30282801448f86cd5d8ec187866f1ba738863f3995bf4084ebe8f41dc1ffe0cd47d5a95dc5fc0dde1559ba170316e085d0e385ecf8646bf787b5287960f07bff9d3d990b3be4b29f008de18ccb46dc2192d0fbbbb9e701d350aae205fcd2d7314112bdc393e2eeb0a96c5f2c767dea0b82e9549f46a270b2c69915711c144a7737e9003b1c1b8684e9f2abe66ad1678c32a0c6ce401ae2d7507ff32c8f6179433b94764a6e4ea0a363e9a03787502c67ec97724d78c1257499379ecc5cd006a13acbba05dc7f1310bc75b9ec6a09f4d95b2ec11952eb3dc3d2dfeb8678b10b5db162b44af56ada56f7e311d6c5ce46472d9ba2284126f8f579d59a9d3e81bb1b26a7ee59c3cca96252fd4ba8e009a213911619ab4184e83683969cc4c474e934f36e6ba4ea33d9e93e984f1603e259cb7f1a3824dd8764bbe30ae08166aca84e63615e59c31d2a968a0beccd40637db22b971d51a4c35ce83108d8975d758669bd83ff9602718a882b943f80745d95a2d5e07ad0acf8023e24589fd68988a350c0ad571470f8b9a906648895d312100cd18089465de942ae0c7c8bffb5d3705c729990721bb0497dddea6dabdd530c9ec90286cee5ba3e2b699eb2de55de8aa0012b38051862bf0a7a22d6cce4310dd8dc47c206d9eb7bf28f2a53afa8fd10836210c0c461e417be42d89ea659a76d5e3cba08daa885b7f0302c5df05c72f8257f60e2ad954891841e67d8b7184cfa47e51e9b36f6ed1400aa84c5058a1998d07709b8b46630d70c73552ada18713310dff4177e2143aada1038bae9a64e02870a7535598d766f447b4e25dbd009009cc0e3421067bd0cd10735469b308cfd7a094bbdaaef2e31c22dc6c1a6d4c7890020b5bd04ef649b56889b2031c1923a69002448dd5db1142384cf517b08096539cc537532f43afc33ca77beb2dfee921178ad51b04f10495365e60b4b7677880409716e0eaa3a1d649a871234f86787c2792bf1e04a635e3f22b8b3b1382be942605c3855a940cfa3825a2f8993312a512c81f92aea0fca95bd1bdb6b9774ef3f1fd15bfa1eae84ebb48bc9b55fb2141c55cd3188eeb5049b2e6f3360e60c5bb7d91c9028ebc9287b4b5541a4ca290288e08ca46f80eeabbf100f700c873d7e69dee7dc80b842004b2bb93f0bf533488028d1a1137371417d06785372ce942d4c6589cdb57514bde81960475c22393eb02a93b6560fc3e4fb6ba692dc362d38e33a41b7df222e9192fdc0e0499f110e681682e2b500c00947d62280482dde5e26010cf959fc717d0837c98634550376aaeb17c1c80e3c3245c36c68f56fc8be117ff84957a75e4aa4caddb38779f1c50086ad4863002127b6c8067f172aa78137908fff82b2297a53d6232c26a2e94bb25b7f9241013adeb36d1e437f2f6c0d983efdb78608e940390005d63c081eaef21d7f00ddf51a3fbbe6175be436a9b0b75f198c30efee2821ac5f088819eb96ba835999d0012e7a0c244268a658bc6b7845d10178283cb8add0b81405ccd64000c1835111511555eca14f7e3cc9274b45f817039ee62cfd95363cc66f5397f88822f790e0a86b6a69379dae77db24185394d9fc73880d39f6b3233dd8d3b912995b22905563ae8cb2b1f3491feffbbdf2c7d2e89dce1995d2429bb0a4c550860835aef589983d3170387971f8aec15c18450dbc9cd12f402589dfb17216e359072ceec8e9484f9b490a288dd4b268233c40ad23721961c390bae0a181c4cc8eb68eb4b2a5db6944042c6ef67550f9abf3b9084cfe8521b62d727416576a58591dc65f79f871958243ca4415d92d3e588c4205b4a3806d64224e94e8f04dbf7f75888246aae9f894bab1ea69d7e10b4f07e8cf2dcf70cc62571c737cb6b6c68b0e3fbe4b1931c8615a793fa3db0a45090458ff62ce8bf421907877e555cdb97d3709bbd8728c4432288861a56cbfeabbea5f225cd29e31d99eb788704a93696f5e81307fc047073657405630200035ed48216bc666598aac5498e9fe513a9e21eaf842d382767220cf98104bfd8866621efb63b2eed44028cdc9e2f64cc2447fe09f8d8a1eb3214272855b525d9ce24e6daee58effb4af28fcb0a4685d78d7995081b5cafd1cfe343aefc8e9cb23507fc0470736574072102b642f0e5b34746f2f6e3b511d037e16bc720292da4ea2fb31ec09f6fdbe777ce00010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc0470736574012109a05049129b0d9e61c1bb87a9fa02c2435be35e3535082a772f65222f2e4a48cb07fc047073657403210baf4431fbb8f0227bfc0008368606a4b8f2bc1706e1a06cd4d83706217b74f5b90104220020c773786f3addae4c21acb094f39122c032daded1cb6225a64d923a0344087bfd07fc047073657404fd4e1060330000000000000001a4092701f9170991583ec8b5c9b5056ddaba7637f7d6733a57dc4990acba6d15e9eb5dca55528e179b98a9e09e05ba940b59558b768194ca046c22e12ea310ec05bcc795ea419277649d40b145e6f0accc31f2d7eb39b29b51d3ba3410536b9bb37bce9014d352b496b93161fc799b23939ee635f25c4cbd6605029722ac2bfed7d55fe4120c0cc880661ea3313d700e261ba973bea5df4dc4d8a05652d193c5edad01945862943a6756d4b0ddc93ad5a444ecb054bcfbbba3635aa752d9c4fe8f93fc56c8066b3fe344143d65328025f5184308f2016eae65df8b0f1818f59f5a9539eec9c9c94704ecac269b008ef25f67ab2b9ab4e903b1f2372d1d3988b86e5efa639f9f964599df8559622c8d11f6bc4f706c74e4a603a3798005e210f93d9e6b59f8ccac907dc9cff127c552c8c89981e1b91c817cd365422eb6bb0c7ef4ddddabd306b0c32a98464973dec30def4149f8e9f35de6840cbd0ce8a2f2b5dec15e3f3c27d0d2720159cabb3ff2f73b4571eb53ec735d7b0f27bf4d23cd8b0fba14d5921d464beb3fd4f2fcf844f76a7f7f129a3e7e1c4a6e4f07490f2cfbac4e6730148397c73720d35cc5a46fdb1e774e17f7b951887c1c72ae4cb25ae99bee652ebb2ffd9f4c9400ee0870b27a5dd8fdb8224aab6738399587b4513d3441e26b898b35f739d64047ce8de70347042fd18c3797b1de82cfe23521dc8e5c36e286c05d4f81508e282018cd8d263164512e6ab02a22b3d5d489e6074f9370e218a3adf8afd229659f5046749db9615541408739d4f0646a853b25f8e549e85708c368e45f3e1d20ab47a4ed647b820a360e4e351e4caf74810098f0df4bb74e0e69f2db3284c30d24a0d109d16c37509557ce15c81812bde78085f31cd7c93140fc43d6580abc7ca2adfe6354510fd8104c0f1ffe46d24f31bea808376ac27996fb992de15b30abf4df6f4d08860bec27a95bcee0df8f0a5bd7f72938ca9e917f39c5425bff826c4a84a1fd70cff87be2e8619e0d3b729f0aadc10d4b70d10c2c020a99d03424a595294dd34b84c83a2d434b98fb116e2ebc8b2bc444740ce090816218ea61da172e30db5e78a58ef495812c287e3cfab7bffada0d6f2fb3c153f1a21707923ae7ec862d44d357776803336566c3bebc9f53177a3a8825478eafb32a61bc04d82994708725e0ccf5d76dcac946f36895402e84856b3e25567156040510fb299b88c45713175c54fb3d54aa6d383c423807778a92b7d9bc8fa347923749d18c96b20cea608b6380b57c35b4408f724046f6f65fc0d71e237c39376e1ec6dfc6a2ae83ba70d25855c10e6a8f425951c52fac7758353e2068e5cb4b6ea066491e834a47f8a5cfbd6ca91c832fe4f77b3b3140a6c1c70055d52f0aa67e7d3ba18e5bfb09f738141a611002ff9e34188d540ac42f48ee06f6925cbf3cca5ca033c4fe089650a2b86a8350367e9cfbd07fac9667c0171f54311cd5b371576790e51a1fddbec64f8f559cc08b1c0ae5113bb0c532742d90c1a266bd7eaa4b1d06693ff12d6e66c51bb1d12dfaeb70bc56d5870fb5ce4b040f86d7725ae53b18c1b12a84f7e3a2e7d748e8a899fb917987518c6dbbb83e3b62b31b0facd3515cf2e18b4e49218ef48d97df114971b298a6c4af231a432d4ac295d0d1947e09ccf41ab104e5435e7dddc0a6815b636a31be9a0d9cee2f22c745f05f384bd703fbf4bbcfeadf177abf4ca4524f38a681e5436a06abf9ca70c7569a7bcb8337b0b0c280b5f790ebe1258bea9c1da58c65c13991efc1ef8f7c75ef28affa340d84b33398790a9a77d30b0ea35613b39b1b7d8667fd7299e2ff38de468f625df1485ee3b2759f7e27ed707f5807342bad0935ed42609849d7a9d11addf8a92df7933ec44e86ac7f24d55a55284fbc4e4824a2b4f8bcb146d08d7d6d3f551ce1b494d7c0fbd6e82ba4496f3eb2812c986f3e2e627240a9102089725057c1e5dcaa3bcc5efdd6ba7e8a496f609aca3dd82816552c669c255283f9372be7d8f12b044f8c41180aac612245945a4467fa806892d55ff425adbbe92bc892698a32d39d7c23303ef65c980c608b5ac07ee60ed2b4e028a5565f5aa3ecd1e15787c23716697d53603f72738d5ca88c5d8280c3d8af9e83c18be420bb87db4b82a98d3af42f9191d4023eb22f428def2ee99d04d1497cb8205b67c9ca893f7afba077bae5fc11cfeb53d882763f2eb6731bab373923a63dda3ee1c1b6207e0ad0884290333dfae3f80e85cc50a241c73d7d99998e9b14c3e52e660edfe594e1ddcef04402e8fb01efff9c00d9a6ffc955e219fe9c92cbe87865f7a25ac68fa2d360c584c33fa3e40ce91d021465610b85b5f1a98e6908a17c5e138f2dd7da2769bad8789eed99828e8f23952bab7e9ead5fa9bac781fecc93de7927544736fd5c57f1da32be31b85657c313cd02104e1b889426bf4c8e9daa741278ae3966ea673cd3fa164ea7cc947b34f86c92084043ae846aecfbb15535a9b21e27fcda9a21bd821072135554e3015d5a12d1c7f2fe74dcef63fe99c947b55a4253add3668bab2d356d58909b7b0784c0759dbf1036645c5d673edb45245f5e78407978b63d5c25ada76b176dcff53e76801d7c2784d4687561f32e7a32175c0fc77227b3ab0a4054462d042931897530502c85f0b976fe3e3fd5488853b20dbd27d0d736e9f44fe6224b9610af05c462f08d328cefa4bf8530250f5a4d3fa7f58f5a48db45cd68d0dd53a2c66758a20184cf226565ce558e60f0af982b02672f8ab14333349a5e5a5dcea61a09f93addea3858fea4e49c84f5d8269a2fe52f072d66240e7d30b7345c841a135271c07e6a142ad1ef1955e29469d3ad3d952ca9055696dd825253e4a0e4822bf3119f1df2404ff9bfa39568a52fadf98ac6d5a4653862d91b995ab166fa9de73478f7e339941e036b621f7d773a5413471d6e41a68c02ed89a4882bfade2b10e399f4515470ef43930a911609911f8b66bc060ebb186cdc4c1aa3cb573d696b0f48a655f8877f68f678bbf467ec872724181d6f983682389fc8a61a7525bcd61ab37930e6eeaac82b7adaa145e2a0bf67e235a657949708f0640ec802a1741590a505e4a109ba857824e00f8f675ae6bc8a0f32aaec0f38398c33c8f53bb3cd53bf697ece4372f8f252ab4b314769f1393aabb2675f44c05dbd279732824d49e77f3df8841de18a30a9bc321ae43c712dbb9f08b457dd3e3f13244ae03da16acd6d3e861efae625478a89020655f0de72ac174a50834db564d4e60bef51e28954a938c64a6ee290523337153aa4d6cfaa00826d2aae1b57e3ab857e10cf2b497597ef9395fe6afb3d74b21c5d44281f756e018539ce8b7f4bc663db1eac9b976da2f994b193a30bde0394b7648b55ad9bde958ec5bf89eab516d4418451d8cbca637c1d6dfdef61712e76aa7f12151d5717ba1af71c4754136563ff2667bb984a80a51ee9112587cb77ed6fac8f4263fce637adcab9f315d6beefe509f8d34ae9c42c12dd42067584c4a96f6c15fe590bd8616983abbf453ee038f385a126dcb71919dd7143a8c27186e1c6d29d36c1039a8f008088e03fe7b97fd7f285a0817ca746438e0949dcfe49fc67ae967d13d1d1aa37c2e410bb95609c7ba0586c8fccdcca933b1a85ec97d1aff670004f71232bedbd192ba5e5a681867470c4e146221bee322a5f458a2bea1316d3f4be26efbcdeb4b1062e58f2fa23b144cfdda55aad3ed7142653c7948b6d854abb8d1201e9a4de07b8985fc194e58ea08b4c8528adcd73ae6bcf5493b7b8ae1f663ace587a87d5a341828612bf82605ce6cca9253057a8096f396bdacefb56f9a452849f0ac652738b5b3135ae13af532dac7d9d5307f158d844bd3664865724aa9907d2ccaf4cae78e056ee78815cbcdccfc38b71e0c648b1fd9f710a15d799e237c344d53923d3941e73f924b75de975ead5b536d632338fe83a8d02a4b7fa9a6f1a5df9de85c88b944581294609c880ecb5eb45de087524d896486c200ad68f5c45e43894d6e84493bed02602ab075c8dca1e57e3932740c25a9401b5bbc1bf524f4885f77a501bc3c313666fdebe7497a2b55031563810b35f33503cdbeb46de12c0482c70b07b5ca3c4e2aa21be75c33a23e73c03379c434dc8bff54dc40b0a34f476d0fd1de0422ae6edacc8a729ed2443315336b208ceafa68a229592e42f49501cf03fcf04ed2ab60cf7c31bf4d4fb5a8d7b8969ddfa8a725eb9d32827446c141170bec2b0dfb5c0a6e00cf83b856ae71b5ce46dfafdc12f94a7f8f3d1bf1eb769f52f43238ed411a97e55625ddc208affbae8ade42829c82cf4f62423830db9ca4091fffe3d282c578552649a34cbfeef3d58e020978137a44fefdb9083186dfd712301431b78095a0997f0764f3ef29f138ffb322c8e187c260c90a3ef7da5638e8d961d6a6cb1867a86d3eb23419e101e18bbf128c7a3492e8447533b7586a81e52366bdf2c376a57847b8a44f7b55eaeee6ab2f06596a87ccad4a60881bcf4b4b5d5f0de85593b643c0c4fe1771a0cb5bb0402fae0f1a2dd811d6495676ad718b73d4b86fc79ce074aa9924829ceb4dfd4e3ab70fdbd94b5ad1d11ca9e582dc215577056373ed91ccd6472b3ce02dffa8817e66205b179f37bdda376d25d6bbcc83a15ad757bb7843aa03d2f7193d8d707ccf4de1f5c8694aa212219752addd55cbb412c05118c30555ca7ea820bf3440d1dae3332947a1cfc22e366d1ad120b906274883d7769983eab6afea2ec0672a2ed25b2b3390c017566e52a0ebc59e6cebb89918d54f75d4094b943e7725468034d2ecfeb68228060bad59a4deaa40843d0cfe0a1e58855113bb7c5965254b82cf3997924d0a8b1711b1e1e5b28343168f64fa9bdf8e5158f33c91112f61ba782a4f97c5456c9c8756adf53a7d919ad90654a1c035d8686f834750302added0fb7b29a3f4f43f4dec29bedcff3139cbdffe391ad64b48eb1691be659e4e4dab266b7221a2c49e42d8ab06ab6d16efee2a0e0bab140aa34b53f70c808f3966f1a171bdc789e23ca0246328dcb1680c765fe262048b869c56f381bb7ded54e1486c0a3660d990f4bf0e5b4eec4cc0c3abf4edab6346cb531194c27de0e316d9d0a823db00e20e2fb99c2b5383213518d0cf604ae8f69d2921b442b729cc78579dd242314da99f01df55a45dadcc87e4b216aa0d817e60673c9807a75185e52a70f04ab8ea28cad805c37fae5417f89af44d4a5728c187c74ea736f21681fa7f93dd9bcab9b510febf2c4b5c64cbedb6b59b77a8816728dfbaeea423bb7735ae1fba08964a343ef9b5d36dc7f8cba10c8ae69bbddc4e524df73c940f762a2d39ce7ff235425942f2007b4e08f2002018f720e0339bfa2704a9d74798b13c6fda733d965666ec5592d1d6dae7af6efe5c87cf48d40858937b1ecde3445ab2c1ba1e87e1c2f3ce696baf3efb30ecbd9dbfab121a142ebfb4f09f343f37e8c5c0f40408a339cc3e4f4d34742d695dfc037744ba1a794abb67954d3a6509157a3aaf293c886b58cfe9bed8a3dacda2a2ce41f95e72e46baf65ca666f61bb548afe8f92e1c4ada258f46178a4591015678fc542d412f7ed323d2629c127890ad9c2151261c4869f59db9648e09c25bd5ad8cfde27ea4ef22666ae0f30a4d25c3ea1780a6c3eb2d94d0f48c1a61701707e57405e7e9f448452d0a48af07013fe04d6fa62fa5fd54139d95217734ff4e596f2ac975bbfb8fda29f7e6f211eb165480e44fde387d4833f89c7162c1bda3265a5271946c5392921861ad5d9e5b672afe4778301d7a8a8d8a03ca1c99d73b2ee43281c0b88ca4292a5ea29a0e2377007fc04707365740563020003c54f16cef0fd4185f35dc6784047913c89883f72b68aa78c16f144624430909da01633eb2b4b13eccc2b7a7ac5ef3b13505cea54e1d390944b5680e6860184e35111675dce504784f4ae8121dbd1f76df3d55ef722f3f73b5b7723efe2fd34b107fc0470736574072103652ba5800d63722adef5232d31ccc12403055de720f0155fc47bba5750ccedd400 \ No newline at end of file diff --git a/examples/test_vector/raw_blind/blinded_unsigned.hex b/examples/test_vector/raw_blind/blinded_unsigned.hex new file mode 100644 index 00000000..04935958 --- /dev/null +++ b/examples/test_vector/raw_blind/blinded_unsigned.hex @@ -0,0 +1 @@ +70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc04707365740121089f9677efff5962164a434b0bffe53d90c271deda3eaf30c1d7a55105d24b874707fc047073657403210be6c538eee2e29d44c66a3e66fc8165afa4081557031826a070703cb69faf24380104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e10603300000000000000015bee4d0045b0884354b05d0285abb287ee8e539e8146b92dceaaee6fc01af1a3730cece4c77caefdcfcea1073197ffb8b3ded97d0dda6f49752b31f23e196378a3da2128a8c45ecb7eb1c32f0c0dfa1ce7bc348e799f66d547476e976baece7445790064b34b1526c88cf54928acc906ec0925327d797d36d0db6a43633d7f963ec9b4123a5ea31b655882329afe2108d123f76857fd3ce344fcd651b3d3066aa506625c9309acead85fb6c8a7d977db5dd6cb6e2f99fb3535746258d42d95dd2103bbaf5d45c8a2dfaa2301eba08763bac8ab3b59bd0082a457bc0cb257c43ea6354348e73d8dee101864068f0e8e30e6711f1597d7b529a57e95fc0df813ea160f5f5152f341087868a680c1bbc4b2a427d18cf8f12cbc4304dd3b1ea4c916467ae48b3b53062ea046161170fbe6ca07462cf3e5534d638c91fe6919318c4b2e98ce0ebcb245680d6395ba746fbf510a9a36ad8d933b023eae39973a39162178148a897b5174940db46e4be54794cea11fafbed48b42eed27f9915cfc50c958e88bac551ab1c9bfa03653e558cf87a99ff13440f4808e27fb42ff3cbac9fe10d1de07dad4807c9f945784163214c739a9eed8bb2e7da6e1fdfa4aba5d5157787d0000b220145572d3d9253cde5c1132370c1849847633287660b429f20cf1f72e4ad6ae59154c3b8526fc5d30523ba962a25577427988d326e207c97c81b479540b1c63aed0a4b1dad9279879740b2f6a0bfc37aea8c81b3b35b630dba8d955afbba756b8ce9d2a9802939972c5f70efdee8778a98360eef15faf77e38627fcb4f98569d68ebad3bb667e5db46eb7f4214846e651224673dc998c55bdd9e0a1629497a75b76160865c7ac3af874bc45a42269508f183d2458835b1734791094122f75398da2bb7919c67221ee42242c298d744fcb920726b84bcb78d7eafc9e0f61a02de87ffc5c674390a33d722b50dc35bbc6788fe0fd24be7a9e358113a4e65b49655767b8f3c3bb86cd5a56c79f9148945af63492dffde82d682eca4787f203e49790650395c925e6eb68bbb3adda25bb784365c0b3e241177ca7de58252b8c79c5a865d1030f3bfd4d312bdf83bd28241389085433c34d747f9f45a0a636e700805e619a19739b15077d44d5e8969a37481896acebe7f350449c69b50376b7cf36e906d8a03aa7221de67c7fc4c5408b55e2562f7b783918004d93b284c0a1c3d596c7c18aef5cb77ad09172bfa888f5dceabf57f708f5ab61b1b4159742d3d35332e9942958ea7f39fd35cff66e84ea0ce3b38a85eb80dcc1201146fc3854cb40353a078f5435fe2becfa383b3a62564645250cf7feccff5414581e747166625477c70f5b0f579bd578e03e6b8ff8c3f0e8a82566199ff542b239f2999103cbb61602bc2f346702204e50c9ce7b85f3b8e144227ca8b9766cdbd498191c385e3a4f024c9396763eacdfe41b706fb23d4f065dbaa43d323edf1811950c71d4475a3e0913d3851be4e921cc933bb06b0aa20524267b0e9847b6de3883ab8c5be1eac17b06c946f19dc2244901890cc290f2e66147f7c47a4a29349555ca020e684ccba5f8a6f1be54e356654d3e8678c591633e11d9ee772940d92f07ab83228c0d8840cb9aae514c03358e973a2ef319a39a3ce819efc00e5a493fd582be164c1460f70fb2e103f09a2939f779377275be736ccd41f847133b3d400477582419fbd2d7ff328d88256cfadd750ed7a3c6657e332f635f0a90602b24135e705d1530531b7b738e686d83accd74874e160a5716191447f832d2f899476ec9c53204fc4ade5b75cacdc869b4088e009e12d85beaa3450889de0c86315755ec7744be592f058fafe62f9a89875bd2781d5d6928c8475f555e77eeb6e688bd8d3bee0a54fa1cb29c1b6b438d3f70ee4ac83fcb6f482575997e3a69240c6731b517d8241cd39dba6b02a9e3c94ff2470c61c693d32b1c7d22ba38f0da21d92150115d580249380500109215a05baa63474f773892b1870005e76a68853e6a8cd9f74ab3f41cf0730e702ce94c420dbf66a63f342d927923d7d25ca3c3823b6c352ae2ca127bf811c023205632ddb8f9191c2f3384a61038d1c4d32f808cbaea81d58f0aebc7c89bcb5b98f92849f4fec19cffc78173e601eb412aad24eacd74d17cce3a9ee76670367ed87c2da3db5e4ab01d4d9626d0ae00da7dbf6b8103d5994cf66ca56bb9aab9eabee2b14ef798d3fa5ed1dc16bb500a006c3320941c4c7f6f80c8b1bf79131a5e11ef38db7c42d4c4939711184ea414e2ec0cf5bdce3d7d0d72dc883b2595e5f0fe4c14cddaed61108c90c74b43cbf719bcf7a934bee760ab33f50cedfe3ce61dda3e4bee1ab4dc687f988cfceb207152f7a98c0fa3a114d2d6881034aeba877bbcc368ce591707da45becc48318f0558b3ff65d677578df9fd9e9a18d0cb08f2f5cd4f44925259225b1c4c5ed84ef30d136e7c66251329a223a1db0b485f1c634981f8860aec1f3dbda9442e5438f6fb09c7e1ab43e2b757911834df8502ce35e7be947771f7416f7ea97d26dbad7221614175e6b86f1a323e26c4f469236d1b1bf6ef28823234c73b275c5b0ff39e8c04d9dd8ff7f6a57214d803aa13dbf581295b92ce2b8a9c0e3fa8e4fe4e74c4bc2198fd010cf84681cf5f3fc9df1638c403006478b1cd4204ef3b7a6093116021118a839bd3ac270783e67096457a86d63654e268821ee742241ab5f7e91972da0a94f679b4c093d3f428d5876edf51459331df084f2fa245e3648e0cac904a82a7d323b965a39683ec0b2721105683e67d65fe10b8d41d0dfcd5c31756408c94cf425ae67f95ecac45391516875870386b675e24c462b1f7634f758723e6245da6a39da69cc2390374eb89c6c662d94eeb2f1835e233610072936be248e6a3ff87f9950c2eb2f88568c31dd30d1f0c0396a42f6be2505bf616c63f504dad5312fee2f083b406fa05cf3c998ba24237402681f0ec393de4650ddce3389d4c1541d4705d8f50c2c6d4864934394fba0c40df495fe3a5bf58a5498c1e63b5ee5f9a4b53dbd913252d4808f5bf5fc3c6dd74c86c974fc9e5afb1a7ee3d88738ab1fe9194881cb00197a31d648ae2448db872528d5f7c366188d9d98ea94497cb78ffacb5f6e8a2c8520d8d196fe0ed13e2b2a5a08dc45d8ff58a8c5d3bcbdc5d80c798acd0eabf146459e79db12c2a43c55d14b2dd608475c606fd4f943ae7cf62131691768f710fd718fda3967146daa8d4f34a3b8fe8084f4f3865e9e0cc1a0b0a799fceafd1b74f09f524822179ce38cf0f2c4c8623a3e3ab2cc20f5dccdadffacee62c120657e3d85fea93fab645b5fcdb812b130afbbd5b19e86aee66011b520b0affd84af4bf67a28b309cad66cec25a5de06390a4f2a5af47d59c006010a8ffd214149bbe47bf847fa94c2113723c9476c2e6dd84411bca16dc7bb096aaa3e9a9be27811b52838c78d16cbd89cad379bcc0cd966dadfa428f9d4d8bf133f7d21022ae3f966dbc1409c6d57cc46ff4434fd90d79b6c9e68ed3795ea6191a7e309783badaf8db9fbccbea905ff9d423370810f02add38ad3088cf3393e7c6d585b76fd78c67766fff03ff54c01228c6737da667147a8b5e722370a24db8c0165e34888920d70fb1356eb731d0024676444e2d37860eaaed9790f99069e622afe6ff12d516812022c46420006253248eb292908ab8658cd874ef05bd568547535fd54c1f0b5b816e4d90a1bd55670b96691e7633a2fa45be6a29cf7bbbcf9e5debb4b7a739b36b6b326b719a12cd0f5e3557a2fd94023d8c633fb697b85080a39734013b4ed9a7500aa8093edfc3810d4c890e608951e57af8447349cdf45a076449241a047085702dc3e44814390ceddc878791ab3a6503f48f48af25fe5cddf61e7870ce80e32e5f22d5725bfde4f319c2db7cf3ba3b9952202cd24210ab8d47fcdaa9ad5e2a9d2c7be1ed6b67cbc1a28ebe565ed0337c09c97d2d9608d97905c7201be7d4bac785d64caf783cb3ebff817bc5ff0448899537ce6911c9803744d06ae97e6d3aae037697986cb20061892ae72e05968e9bc54153bd745012af719fe7fd4b917689a6cbb94a9bc8cce51d234ffce7dca804e2b2759f473885855983b7b290806884fc66e724fa848af2de3164c611c630e5bea712cdd9a9943ca3ad55994e5587c5953f415171bdef3c42783b809bc5526a88aae66be626d73394a607ca32256198dbc026d9e09e9659ddb66736215dedee0cf69a24490a53b2c678e6caa2aaef10a4610f802fc5eccf558629554d6388465584514d3dd49fc00fb267c39bb746f76ad3cccabcc3cc882815bd9466d593270a5c9a04945dea7ad06ddb73e66b90270e859a109d52251d911e7177e07295ac9788ce970c1b718fb0c2f327f44fd0cef3cb90071ec85e958e6ae60bd134ae307720cdb10be5a0af8c051295df905447b46206b5e04d775b83e11c348458b1bb310ae25c54e8ed1488a95b2a576002198c80e0e69d14fb7bffcd89935faa6e3f9aed61e0492bb2bc334e389bd559806eff9e16643ddc4372ca83790a99bd4f5b042779ef7e3039858881c8ce0041d43ed6b1f42f18007d046fd6f65907cef73fd5e6cd514c4fa5ebb872b140d0b2ad43c58f2f2216522e78a19828baa34ba56d71414ae869a13cb708762565d754f0754561adbd9145d232034229d2840ef0c96f7af4b232c5e11ae9d018b87177a506b66571affdeb693e170dd12640e86fd430b64509d02694c52a54f23ea96e39be92b65c1abcb262a71d6e884183d4bc6cac8c79f6e94b51abe484794a42a8e1be721cd59383bdbf327a864b2cf27566f1270d82ab6e8b3b934e953b7d48ec4697172e0160b8435e8186a7ea685dd61402b0bef09137fc52e19ecb53674a1c0af17fbb0efc2b4d69572b32600fdfc3c85c79ccb2685c665aef16748b7f15862f435afaeef00f3f927cd3a60c22f137e9cb2c38a9652cee570a76351871ebc9b6fc720b9117b1306e8afb298e3a1d0d4b2c5ad5a21e9de4c0938a98daa35caa711bd6d3312a7e941b060c4a1ec211c31e53da263995d3be74a3ca5a970d3a663245a5d8eace336d9ee847619bb337e777b961805e808f7cfd8f09b93b6ce6c277396e6cf0a6153fb11625f421b02055dc53a0bb24786f4a74211bfdf44193e48b2c15c7c85acd3f4f0bd26644f9dc62653e366178645899dc06d191c785831b98de593519c0ac5849897a01b4f8c7fd559edce1c878bdb9dd091546c64225fa81b1c7dcd6fe72c8131bb160043976a5e891236d3552598c0c4fc734d1cc752c10306dfc8391f6d658ab8b7a1f350e1900edda29775ac8a531032bac968f6981889f13cab07f49c864c3ef3ff23555c170ee9730912465e6bb3b7508e11ba27001d9dc0da8b34954a7c46a2d0b0b75e73ad66278c8acbed3249936a6c76fe975954f7889bbaae5f73747e3ed02811b015beab9d1515e396d37b2a116b3cd8863517b88ee91a854e4740304f7be41123692181a8ae22090956ba619fb948d3f08108da17ac2484d3dd5795b1c716cb5ed3fc08b6449fec814018cd883b02256afadd1f35a04d9d43f661be624c0d59d0d397e8ad6917344203843495162217e79bade11ca32c0a6918350520e6c3f31f866b89e3497b518cdc056e760813ebea7fbf343db50ca2450d5b7a1c5f430859e2040ecfa70cb0c3e863a756ef6ae2e26c68b543d35beb44513f8295872df5b4062c9b80742ca7a9c2ec36a1d9bc936c7fa7328240a2db54463ca04a40dc0c9afe4d570012259c8c436d322483b17a705d60a283e0ea7545110e898ba43ac6130aebde014d237482307ff28f385fce8530814a3b3507fc047073657405630200039d46edc3d830d852edbdf28cbc023dd1666cc4a806ec9d4acb2c97865280698d61fb6b2a073be4ce75cf8193fa9b3d677146575a812873ae7eaa7b08110cb88904273dd65af35115dd116092a56ab45fb57178d8c5ed4cf99474ff760f6e987d07fc0470736574072103701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d0007fc047073657401210821b4e7599b1b4faf29b521f1c442dfb0a893499d374c19db939c0c9a3b9798c707fc047073657403210b8529a893bd81e38743e8e3015095077f6e244a064d64708963c6af5024b6aeb3010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001ddf2f500cf45e30e3911bb51a7ff633c1e329508e42d9aa49fa8248fa4d293b87005db08b17a472580ae2cdd53c40b2a16af885d6dfa3d5b3410d389b045e6c12405c635511ccf008390856f4c6d0ca472148d2969fd39e1307bfbb8f8268ce92398248f08df7268bbe23e13ac3801ad075376bbe9684db9b41d7b1148fa65a129e1d25193ad973ec7af631a0a30c5659409b0d00e6fe44c2c507d52afbee8059aee7650bee9d47936838e77f00cfec9de3b9efe72cdeefd80a19a9c8ddcd6cd9009ec96800f9fcd709f1432b5715466d601b5ed10b0dfbd09b954b109232fb5a24036b887d26bab43da7eaa303e7c8e9d02950610aba09f343a65a7cb4550d6f6e5c0eeb9410b68f85cc3b05b9340e2e9c94a473f2001f5aea7e875d29e8e0aa99c09afec4a5455258c803e55b89cf6e89796a6f3f9fe11ee05ec415c6ac08e0f1adf9dad681f8121f82e3a9402a2fe13a016dc10d4a605578317be0d9d3e02b0989903002bd35ec4970701da6eb6f9fd5e6cefad137dceffaad968d5ce522e77c76e665529f70c9eb805a240834f4c8c795912c37e7efeb73d4bab709aafb66bd645cf0addefdd83d185c1b4465ee16344575eb2aa3acb057c37d7c22cd03adc6701f03d4cfa20ca94458b3b87e3fb7de8fed28a755bcaccbd348dbc7f5aacbb25d19437d87cb48b88fc714f059b328c374fb2695a26537330fae42d3fcff40b07054be10508fa78a9173315506119973a9158828276edc6e8ac771476d7479d07ba73e9c5b58d61367d24435a1062dfa376546e843b34829103168db63b708a13894813a442f889a423054160b85a2584d36951136bafc490056f1c03c44580e2c7a12b170e5c7a3917720bbbb21e5da616649b554b7ff57dc84dc3935271a8aa6fb218becde208d83e76bb6197f5f4f729f3c57b4280c388bf4945206fa769b672ebd47ef2d0f777fc9dfb031cf7c333c6159a75a5b33ebc6c4683ef140a144a5ab15b2b6b72ba14ee7595679a0f7873a75b9a79e03676ae94eaf9ea8d3532460f3075f40c6b9848a183fe46651caeacd51551f308eab6ba8ac27b93865f5dea0fcfe1d7dbb745695dcf187a5955414763c88ec01d299f03cdaec48eba946c7ff6031f28a5e108651401b5447c0d21c449b387765a621c057eea7d494302222e3d1ff6ee9f8a0580671d9fe3ecc310e84b554e5c823d81b8027075f749b2dc8a6bc5a99fed4a0fc46fae59cc2aa9f1471ca2b0d9fafa56927fce0f1df92647ff5aaa083bfcb7078bd8ee142448d4e1f3dda670ad93feb21028a57ee9a8ae8880df5b3276e078555d868be0d8843b495e082d743328620f36f7e106733bcefac15076826cafce271caf1520cc8746948e71ea73eef5de770ecf5e0fe075f04d6c9cef1cc1f262e69fcfc89862877ffd520e25405d9d07efec82386640d97147e4a6df66bb1fa1f20dcc2ee1fd6364bc2024c0a4a2ec00b84616231c02aef16147df509cb830b9fb20111c60613d41169a24ea67c03cea48e1905b00eba8a61e967b9aa5b6e1264b0f3e2c051e47f54f45c6fb78b46b9993c7d223d51036cb77f5e31a4b30892e8fd7ae7028322d6402f60712f712f84443de083767c38ae88a38fd4e4bbec58015f4e49e36ecbde94f23d0061edfff0e9eb62b67327249d728c278f923748e4c1e225791c740abf9493198f073a6cefffba00d23715a14967a72cc7aebb81ecc6e8bc88ae63a47507296a573c415d0163219f3b20974ba3a32572d29a479b88b89c241e63e640bbb93e2da0b73024ff03516ee3ecfbd84348372cfb574817ca878172a779ff131492b86e64c1b7054cc5fdb999df4a9578bbdd6a6a2a798f053f4c7456173cb8cb9b61735e650d0394ea9effbef615629c1f393949095982345bfc02b9a3b3ec64d9173bb1285587048fca8c740b6d2265d3e727069fcb90282c78f1049ed7925113279474d0d873045851b397a8c26036c83906dccd0eabe98f7d6854d9abc2e6ff12d84c2840affa371cc2dc624fbce873f38570dc9eff33f5e200791bc052307f5c718d47fc2a205add3fd9de2a81cb452a0a7b4a08bc133e9816f6ba852735b813a1b906d4dbd1ac8a968a8adfb6aded0d3fb87d80132a4b45ad39274bb72df20d648a26aca4beaafb971e13094b6d6525bd418b91320964bd90d3a98fb32d69222aaef2ea95c6b84b6b6aaeca3f6789944fc7ef17d218abc7667ddff798b74a0d5fbcc33d22d028d207f82286fd777ea1e281d7eb88b665e29fcda067bf9ef82a190c920e260cbc44d394b78e0439bcaa6371f8717ad82e02015f21e5dc273de338102539bc924d5a3f897eb788e0f3cbb6e8a34590ff5370bcb0f671ce28d6ac0adea17f9fda49c784ae84464991fc71a31d320d62c4199e4f4e38f390ffbcc7f15922c3325451371bebfaed8f8a7d3eead71c65878a705a6163ef649bdc6ae7b81110a2cc9576da6b0188337160cd518fb30fd5b0c165ac3de81002490f36d29f3bfefa2b4796c070c6d7d971d18bb5a573cd48cb3c64dec5bbbd7f2f6f60bff3db4833bb64b426c6715021997350aab1751f0d5382087ec133c2c43342b31eca32a4c17db14148fb48f9b7a103b2c0bfc0dc95ffe097bd8f2213a3f7a43c994599b05dc35d469fdc025e23e483039f271d948972417153c849fa8dbf374693dabd59d398732020e90d8f443ba45a00b430168561de21db76b9b714ca8260fdc4ff1a5713e69ed7296f5a5a9d192474bc6e67788d6a44d46b69ac97d0b34b2b08aa79d5ee7e910d99221e1e7f6383628b5383cec4341e5c3f18ec3361b2f2ee5935ed40919a4f8892bf3467803b720d29388b7cd47067eed9be7648fbbafcc7bebfc31a30d02f38b7d6be1579c751aabf679fa6891adf66da2257432c10d399a8d496d1aba381b64a5e3ec9a65cc8419151b638adea994c3590f77bf390152894b2784f7832d986ac51b20ba9719d85509acdedc0282f27065c87bf483fc74b55579ecc233fd6c51822ae03e46ecdb15acf0204b67852d525168f409d2a4309c5d9a14b35905b836091964ff4fec0aa91bbd7f39919eeaebbaf75c63337a4dcd5ae37cb60fa14bd78157ecef35ddb1cfdc366eecd48aebb0f5b395e4161c158c46870fc89f8f5918aabf0577c914511cac7b189384eaddf7f03cc6706a9abf219ff1b293cc9fa2cac88b65af62f0493e30736b596c119149af07e840a43dba8d97455a7a95368395488304626d3b888069a0099c435cc11e5981412f79153577c9115a1812b1c4a9ea42bbf11b40fdf930bcc37e6a6c50a7e3d06bfe7043adb32244ae359993caad00eea5f1de90355326e921145add1020ef3c2cbcbd102e7b9a150ff9672697d31f86572843269978cbc4c3fe067417e84898587d4de009b33094c8e8102c07facbda62a2996e083d0618fd9651e17f0fc025a946e7c28d93e18675ba69d8811dd06a2cc1aae28de5a6af30f305cfd8a090832483e4ff7c974d47ac96d2f3f7baec94f965a116c2734f87f468ec33cee1a58dd2ccdbc8f6a0f2a50a739aadf4007c7a529844cd1ce5efdd42eb28a0f5f04fde00dea586e4b8cdb8469b36940de19feefd342285c29298afcd3ddc368f6365e9ee255f8c2b6f70c1bafafe10720402a668ac3a2cb8ee75cb7004455cb4221486c3dee64f8f25f1c8b1411437cf6ba70afac5182b21f0efc1b5c71dd59f71796d2719741b6d5c757d824cdfd2d9459569427951d6974d94bea1fbeb0e1a23ef88774b270a8bca65f01a00299258847ddce8af8a7626c802b29dd01d1dd9d2a7243b334c29c0943e11ddbfe82cdfbd506e81fab8775a1d3c4f6c0744e1086763ad84e8f51d601e24cd6fcba1409abbb1c6b5c9bb9eab8ca30282801448f86cd5d8ec187866f1ba738863f3995bf4084ebe8f41dc1ffe0cd47d5a95dc5fc0dde1559ba170316e085d0e385ecf8646bf787b5287960f07bff9d3d990b3be4b29f008de18ccb46dc2192d0fbbbb9e701d350aae205fcd2d7314112bdc393e2eeb0a96c5f2c767dea0b82e9549f46a270b2c69915711c144a7737e9003b1c1b8684e9f2abe66ad1678c32a0c6ce401ae2d7507ff32c8f6179433b94764a6e4ea0a363e9a03787502c67ec97724d78c1257499379ecc5cd006a13acbba05dc7f1310bc75b9ec6a09f4d95b2ec11952eb3dc3d2dfeb8678b10b5db162b44af56ada56f7e311d6c5ce46472d9ba2284126f8f579d59a9d3e81bb1b26a7ee59c3cca96252fd4ba8e009a213911619ab4184e83683969cc4c474e934f36e6ba4ea33d9e93e984f1603e259cb7f1a3824dd8764bbe30ae08166aca84e63615e59c31d2a968a0beccd40637db22b971d51a4c35ce83108d8975d758669bd83ff9602718a882b943f80745d95a2d5e07ad0acf8023e24589fd68988a350c0ad571470f8b9a906648895d312100cd18089465de942ae0c7c8bffb5d3705c729990721bb0497dddea6dabdd530c9ec90286cee5ba3e2b699eb2de55de8aa0012b38051862bf0a7a22d6cce4310dd8dc47c206d9eb7bf28f2a53afa8fd10836210c0c461e417be42d89ea659a76d5e3cba08daa885b7f0302c5df05c72f8257f60e2ad954891841e67d8b7184cfa47e51e9b36f6ed1400aa84c5058a1998d07709b8b46630d70c73552ada18713310dff4177e2143aada1038bae9a64e02870a7535598d766f447b4e25dbd009009cc0e3421067bd0cd10735469b308cfd7a094bbdaaef2e31c22dc6c1a6d4c7890020b5bd04ef649b56889b2031c1923a69002448dd5db1142384cf517b08096539cc537532f43afc33ca77beb2dfee921178ad51b04f10495365e60b4b7677880409716e0eaa3a1d649a871234f86787c2792bf1e04a635e3f22b8b3b1382be942605c3855a940cfa3825a2f8993312a512c81f92aea0fca95bd1bdb6b9774ef3f1fd15bfa1eae84ebb48bc9b55fb2141c55cd3188eeb5049b2e6f3360e60c5bb7d91c9028ebc9287b4b5541a4ca290288e08ca46f80eeabbf100f700c873d7e69dee7dc80b842004b2bb93f0bf533488028d1a1137371417d06785372ce942d4c6589cdb57514bde81960475c22393eb02a93b6560fc3e4fb6ba692dc362d38e33a41b7df222e9192fdc0e0499f110e681682e2b500c00947d62280482dde5e26010cf959fc717d0837c98634550376aaeb17c1c80e3c3245c36c68f56fc8be117ff84957a75e4aa4caddb38779f1c50086ad4863002127b6c8067f172aa78137908fff82b2297a53d6232c26a2e94bb25b7f9241013adeb36d1e437f2f6c0d983efdb78608e940390005d63c081eaef21d7f00ddf51a3fbbe6175be436a9b0b75f198c30efee2821ac5f088819eb96ba835999d0012e7a0c244268a658bc6b7845d10178283cb8add0b81405ccd64000c1835111511555eca14f7e3cc9274b45f817039ee62cfd95363cc66f5397f88822f790e0a86b6a69379dae77db24185394d9fc73880d39f6b3233dd8d3b912995b22905563ae8cb2b1f3491feffbbdf2c7d2e89dce1995d2429bb0a4c550860835aef589983d3170387971f8aec15c18450dbc9cd12f402589dfb17216e359072ceec8e9484f9b490a288dd4b268233c40ad23721961c390bae0a181c4cc8eb68eb4b2a5db6944042c6ef67550f9abf3b9084cfe8521b62d727416576a58591dc65f79f871958243ca4415d92d3e588c4205b4a3806d64224e94e8f04dbf7f75888246aae9f894bab1ea69d7e10b4f07e8cf2dcf70cc62571c737cb6b6c68b0e3fbe4b1931c8615a793fa3db0a45090458ff62ce8bf421907877e555cdb97d3709bbd8728c4432288861a56cbfeabbea5f225cd29e31d99eb788704a93696f5e81307fc047073657405630200035ed48216bc666598aac5498e9fe513a9e21eaf842d382767220cf98104bfd8866621efb63b2eed44028cdc9e2f64cc2447fe09f8d8a1eb3214272855b525d9ce24e6daee58effb4af28fcb0a4685d78d7995081b5cafd1cfe343aefc8e9cb23507fc0470736574072102b642f0e5b34746f2f6e3b511d037e16bc720292da4ea2fb31ec09f6fdbe777ce00010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc0470736574012109a05049129b0d9e61c1bb87a9fa02c2435be35e3535082a772f65222f2e4a48cb07fc047073657403210baf4431fbb8f0227bfc0008368606a4b8f2bc1706e1a06cd4d83706217b74f5b90104220020c773786f3addae4c21acb094f39122c032daded1cb6225a64d923a0344087bfd07fc047073657404fd4e1060330000000000000001a4092701f9170991583ec8b5c9b5056ddaba7637f7d6733a57dc4990acba6d15e9eb5dca55528e179b98a9e09e05ba940b59558b768194ca046c22e12ea310ec05bcc795ea419277649d40b145e6f0accc31f2d7eb39b29b51d3ba3410536b9bb37bce9014d352b496b93161fc799b23939ee635f25c4cbd6605029722ac2bfed7d55fe4120c0cc880661ea3313d700e261ba973bea5df4dc4d8a05652d193c5edad01945862943a6756d4b0ddc93ad5a444ecb054bcfbbba3635aa752d9c4fe8f93fc56c8066b3fe344143d65328025f5184308f2016eae65df8b0f1818f59f5a9539eec9c9c94704ecac269b008ef25f67ab2b9ab4e903b1f2372d1d3988b86e5efa639f9f964599df8559622c8d11f6bc4f706c74e4a603a3798005e210f93d9e6b59f8ccac907dc9cff127c552c8c89981e1b91c817cd365422eb6bb0c7ef4ddddabd306b0c32a98464973dec30def4149f8e9f35de6840cbd0ce8a2f2b5dec15e3f3c27d0d2720159cabb3ff2f73b4571eb53ec735d7b0f27bf4d23cd8b0fba14d5921d464beb3fd4f2fcf844f76a7f7f129a3e7e1c4a6e4f07490f2cfbac4e6730148397c73720d35cc5a46fdb1e774e17f7b951887c1c72ae4cb25ae99bee652ebb2ffd9f4c9400ee0870b27a5dd8fdb8224aab6738399587b4513d3441e26b898b35f739d64047ce8de70347042fd18c3797b1de82cfe23521dc8e5c36e286c05d4f81508e282018cd8d263164512e6ab02a22b3d5d489e6074f9370e218a3adf8afd229659f5046749db9615541408739d4f0646a853b25f8e549e85708c368e45f3e1d20ab47a4ed647b820a360e4e351e4caf74810098f0df4bb74e0e69f2db3284c30d24a0d109d16c37509557ce15c81812bde78085f31cd7c93140fc43d6580abc7ca2adfe6354510fd8104c0f1ffe46d24f31bea808376ac27996fb992de15b30abf4df6f4d08860bec27a95bcee0df8f0a5bd7f72938ca9e917f39c5425bff826c4a84a1fd70cff87be2e8619e0d3b729f0aadc10d4b70d10c2c020a99d03424a595294dd34b84c83a2d434b98fb116e2ebc8b2bc444740ce090816218ea61da172e30db5e78a58ef495812c287e3cfab7bffada0d6f2fb3c153f1a21707923ae7ec862d44d357776803336566c3bebc9f53177a3a8825478eafb32a61bc04d82994708725e0ccf5d76dcac946f36895402e84856b3e25567156040510fb299b88c45713175c54fb3d54aa6d383c423807778a92b7d9bc8fa347923749d18c96b20cea608b6380b57c35b4408f724046f6f65fc0d71e237c39376e1ec6dfc6a2ae83ba70d25855c10e6a8f425951c52fac7758353e2068e5cb4b6ea066491e834a47f8a5cfbd6ca91c832fe4f77b3b3140a6c1c70055d52f0aa67e7d3ba18e5bfb09f738141a611002ff9e34188d540ac42f48ee06f6925cbf3cca5ca033c4fe089650a2b86a8350367e9cfbd07fac9667c0171f54311cd5b371576790e51a1fddbec64f8f559cc08b1c0ae5113bb0c532742d90c1a266bd7eaa4b1d06693ff12d6e66c51bb1d12dfaeb70bc56d5870fb5ce4b040f86d7725ae53b18c1b12a84f7e3a2e7d748e8a899fb917987518c6dbbb83e3b62b31b0facd3515cf2e18b4e49218ef48d97df114971b298a6c4af231a432d4ac295d0d1947e09ccf41ab104e5435e7dddc0a6815b636a31be9a0d9cee2f22c745f05f384bd703fbf4bbcfeadf177abf4ca4524f38a681e5436a06abf9ca70c7569a7bcb8337b0b0c280b5f790ebe1258bea9c1da58c65c13991efc1ef8f7c75ef28affa340d84b33398790a9a77d30b0ea35613b39b1b7d8667fd7299e2ff38de468f625df1485ee3b2759f7e27ed707f5807342bad0935ed42609849d7a9d11addf8a92df7933ec44e86ac7f24d55a55284fbc4e4824a2b4f8bcb146d08d7d6d3f551ce1b494d7c0fbd6e82ba4496f3eb2812c986f3e2e627240a9102089725057c1e5dcaa3bcc5efdd6ba7e8a496f609aca3dd82816552c669c255283f9372be7d8f12b044f8c41180aac612245945a4467fa806892d55ff425adbbe92bc892698a32d39d7c23303ef65c980c608b5ac07ee60ed2b4e028a5565f5aa3ecd1e15787c23716697d53603f72738d5ca88c5d8280c3d8af9e83c18be420bb87db4b82a98d3af42f9191d4023eb22f428def2ee99d04d1497cb8205b67c9ca893f7afba077bae5fc11cfeb53d882763f2eb6731bab373923a63dda3ee1c1b6207e0ad0884290333dfae3f80e85cc50a241c73d7d99998e9b14c3e52e660edfe594e1ddcef04402e8fb01efff9c00d9a6ffc955e219fe9c92cbe87865f7a25ac68fa2d360c584c33fa3e40ce91d021465610b85b5f1a98e6908a17c5e138f2dd7da2769bad8789eed99828e8f23952bab7e9ead5fa9bac781fecc93de7927544736fd5c57f1da32be31b85657c313cd02104e1b889426bf4c8e9daa741278ae3966ea673cd3fa164ea7cc947b34f86c92084043ae846aecfbb15535a9b21e27fcda9a21bd821072135554e3015d5a12d1c7f2fe74dcef63fe99c947b55a4253add3668bab2d356d58909b7b0784c0759dbf1036645c5d673edb45245f5e78407978b63d5c25ada76b176dcff53e76801d7c2784d4687561f32e7a32175c0fc77227b3ab0a4054462d042931897530502c85f0b976fe3e3fd5488853b20dbd27d0d736e9f44fe6224b9610af05c462f08d328cefa4bf8530250f5a4d3fa7f58f5a48db45cd68d0dd53a2c66758a20184cf226565ce558e60f0af982b02672f8ab14333349a5e5a5dcea61a09f93addea3858fea4e49c84f5d8269a2fe52f072d66240e7d30b7345c841a135271c07e6a142ad1ef1955e29469d3ad3d952ca9055696dd825253e4a0e4822bf3119f1df2404ff9bfa39568a52fadf98ac6d5a4653862d91b995ab166fa9de73478f7e339941e036b621f7d773a5413471d6e41a68c02ed89a4882bfade2b10e399f4515470ef43930a911609911f8b66bc060ebb186cdc4c1aa3cb573d696b0f48a655f8877f68f678bbf467ec872724181d6f983682389fc8a61a7525bcd61ab37930e6eeaac82b7adaa145e2a0bf67e235a657949708f0640ec802a1741590a505e4a109ba857824e00f8f675ae6bc8a0f32aaec0f38398c33c8f53bb3cd53bf697ece4372f8f252ab4b314769f1393aabb2675f44c05dbd279732824d49e77f3df8841de18a30a9bc321ae43c712dbb9f08b457dd3e3f13244ae03da16acd6d3e861efae625478a89020655f0de72ac174a50834db564d4e60bef51e28954a938c64a6ee290523337153aa4d6cfaa00826d2aae1b57e3ab857e10cf2b497597ef9395fe6afb3d74b21c5d44281f756e018539ce8b7f4bc663db1eac9b976da2f994b193a30bde0394b7648b55ad9bde958ec5bf89eab516d4418451d8cbca637c1d6dfdef61712e76aa7f12151d5717ba1af71c4754136563ff2667bb984a80a51ee9112587cb77ed6fac8f4263fce637adcab9f315d6beefe509f8d34ae9c42c12dd42067584c4a96f6c15fe590bd8616983abbf453ee038f385a126dcb71919dd7143a8c27186e1c6d29d36c1039a8f008088e03fe7b97fd7f285a0817ca746438e0949dcfe49fc67ae967d13d1d1aa37c2e410bb95609c7ba0586c8fccdcca933b1a85ec97d1aff670004f71232bedbd192ba5e5a681867470c4e146221bee322a5f458a2bea1316d3f4be26efbcdeb4b1062e58f2fa23b144cfdda55aad3ed7142653c7948b6d854abb8d1201e9a4de07b8985fc194e58ea08b4c8528adcd73ae6bcf5493b7b8ae1f663ace587a87d5a341828612bf82605ce6cca9253057a8096f396bdacefb56f9a452849f0ac652738b5b3135ae13af532dac7d9d5307f158d844bd3664865724aa9907d2ccaf4cae78e056ee78815cbcdccfc38b71e0c648b1fd9f710a15d799e237c344d53923d3941e73f924b75de975ead5b536d632338fe83a8d02a4b7fa9a6f1a5df9de85c88b944581294609c880ecb5eb45de087524d896486c200ad68f5c45e43894d6e84493bed02602ab075c8dca1e57e3932740c25a9401b5bbc1bf524f4885f77a501bc3c313666fdebe7497a2b55031563810b35f33503cdbeb46de12c0482c70b07b5ca3c4e2aa21be75c33a23e73c03379c434dc8bff54dc40b0a34f476d0fd1de0422ae6edacc8a729ed2443315336b208ceafa68a229592e42f49501cf03fcf04ed2ab60cf7c31bf4d4fb5a8d7b8969ddfa8a725eb9d32827446c141170bec2b0dfb5c0a6e00cf83b856ae71b5ce46dfafdc12f94a7f8f3d1bf1eb769f52f43238ed411a97e55625ddc208affbae8ade42829c82cf4f62423830db9ca4091fffe3d282c578552649a34cbfeef3d58e020978137a44fefdb9083186dfd712301431b78095a0997f0764f3ef29f138ffb322c8e187c260c90a3ef7da5638e8d961d6a6cb1867a86d3eb23419e101e18bbf128c7a3492e8447533b7586a81e52366bdf2c376a57847b8a44f7b55eaeee6ab2f06596a87ccad4a60881bcf4b4b5d5f0de85593b643c0c4fe1771a0cb5bb0402fae0f1a2dd811d6495676ad718b73d4b86fc79ce074aa9924829ceb4dfd4e3ab70fdbd94b5ad1d11ca9e582dc215577056373ed91ccd6472b3ce02dffa8817e66205b179f37bdda376d25d6bbcc83a15ad757bb7843aa03d2f7193d8d707ccf4de1f5c8694aa212219752addd55cbb412c05118c30555ca7ea820bf3440d1dae3332947a1cfc22e366d1ad120b906274883d7769983eab6afea2ec0672a2ed25b2b3390c017566e52a0ebc59e6cebb89918d54f75d4094b943e7725468034d2ecfeb68228060bad59a4deaa40843d0cfe0a1e58855113bb7c5965254b82cf3997924d0a8b1711b1e1e5b28343168f64fa9bdf8e5158f33c91112f61ba782a4f97c5456c9c8756adf53a7d919ad90654a1c035d8686f834750302added0fb7b29a3f4f43f4dec29bedcff3139cbdffe391ad64b48eb1691be659e4e4dab266b7221a2c49e42d8ab06ab6d16efee2a0e0bab140aa34b53f70c808f3966f1a171bdc789e23ca0246328dcb1680c765fe262048b869c56f381bb7ded54e1486c0a3660d990f4bf0e5b4eec4cc0c3abf4edab6346cb531194c27de0e316d9d0a823db00e20e2fb99c2b5383213518d0cf604ae8f69d2921b442b729cc78579dd242314da99f01df55a45dadcc87e4b216aa0d817e60673c9807a75185e52a70f04ab8ea28cad805c37fae5417f89af44d4a5728c187c74ea736f21681fa7f93dd9bcab9b510febf2c4b5c64cbedb6b59b77a8816728dfbaeea423bb7735ae1fba08964a343ef9b5d36dc7f8cba10c8ae69bbddc4e524df73c940f762a2d39ce7ff235425942f2007b4e08f2002018f720e0339bfa2704a9d74798b13c6fda733d965666ec5592d1d6dae7af6efe5c87cf48d40858937b1ecde3445ab2c1ba1e87e1c2f3ce696baf3efb30ecbd9dbfab121a142ebfb4f09f343f37e8c5c0f40408a339cc3e4f4d34742d695dfc037744ba1a794abb67954d3a6509157a3aaf293c886b58cfe9bed8a3dacda2a2ce41f95e72e46baf65ca666f61bb548afe8f92e1c4ada258f46178a4591015678fc542d412f7ed323d2629c127890ad9c2151261c4869f59db9648e09c25bd5ad8cfde27ea4ef22666ae0f30a4d25c3ea1780a6c3eb2d94d0f48c1a61701707e57405e7e9f448452d0a48af07013fe04d6fa62fa5fd54139d95217734ff4e596f2ac975bbfb8fda29f7e6f211eb165480e44fde387d4833f89c7162c1bda3265a5271946c5392921861ad5d9e5b672afe4778301d7a8a8d8a03ca1c99d73b2ee43281c0b88ca4292a5ea29a0e2377007fc04707365740563020003c54f16cef0fd4185f35dc6784047913c89883f72b68aa78c16f144624430909da01633eb2b4b13eccc2b7a7ac5ef3b13505cea54e1d390944b5680e6860184e35111675dce504784f4ae8121dbd1f76df3d55ef722f3f73b5b7723efe2fd34b107fc0470736574072103652ba5800d63722adef5232d31ccc12403055de720f0155fc47bba5750ccedd400 \ No newline at end of file diff --git a/examples/test_vector/raw_blind/extracted_tx.hex b/examples/test_vector/raw_blind/extracted_tx.hex new file mode 100644 index 00000000..4072b162 --- /dev/null +++ b/examples/test_vector/raw_blind/extracted_tx.hex @@ -0,0 +1 @@ +020000000102cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae0000000000ffffffffcbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae0100000000ffffffff040be6c538eee2e29d44c66a3e66fc8165afa4081557031826a070703cb69faf2438089f9677efff5962164a434b0bffe53d90c271deda3eaf30c1d7a55105d24b874703701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d930b8529a893bd81e38743e8e3015095077f6e244a064d64708963c6af5024b6aeb30821b4e7599b1b4faf29b521f1c442dfb0a893499d374c19db939c0c9a3b9798c702b642f0e5b34746f2f6e3b511d037e16bc720292da4ea2fb31ec09f6fdbe777ce22002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84501230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20100000000000001f400000baf4431fbb8f0227bfc0008368606a4b8f2bc1706e1a06cd4d83706217b74f5b909a05049129b0d9e61c1bb87a9fa02c2435be35e3535082a772f65222f2e4a48cb03652ba5800d63722adef5232d31ccc12403055de720f0155fc47bba5750ccedd4220020c773786f3addae4c21acb094f39122c032daded1cb6225a64d923a0344087bfd00000000000002473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801210334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe700000002473044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa012103df8f51c053ba0dfb443cce9793b6dc3339ffb0ce97af4792dade3aae1eb890f600630200039d46edc3d830d852edbdf28cbc023dd1666cc4a806ec9d4acb2c97865280698d61fb6b2a073be4ce75cf8193fa9b3d677146575a812873ae7eaa7b08110cb88904273dd65af35115dd116092a56ab45fb57178d8c5ed4cf99474ff760f6e987dfd4e10603300000000000000015bee4d0045b0884354b05d0285abb287ee8e539e8146b92dceaaee6fc01af1a3730cece4c77caefdcfcea1073197ffb8b3ded97d0dda6f49752b31f23e196378a3da2128a8c45ecb7eb1c32f0c0dfa1ce7bc348e799f66d547476e976baece7445790064b34b1526c88cf54928acc906ec0925327d797d36d0db6a43633d7f963ec9b4123a5ea31b655882329afe2108d123f76857fd3ce344fcd651b3d3066aa506625c9309acead85fb6c8a7d977db5dd6cb6e2f99fb3535746258d42d95dd2103bbaf5d45c8a2dfaa2301eba08763bac8ab3b59bd0082a457bc0cb257c43ea6354348e73d8dee101864068f0e8e30e6711f1597d7b529a57e95fc0df813ea160f5f5152f341087868a680c1bbc4b2a427d18cf8f12cbc4304dd3b1ea4c916467ae48b3b53062ea046161170fbe6ca07462cf3e5534d638c91fe6919318c4b2e98ce0ebcb245680d6395ba746fbf510a9a36ad8d933b023eae39973a39162178148a897b5174940db46e4be54794cea11fafbed48b42eed27f9915cfc50c958e88bac551ab1c9bfa03653e558cf87a99ff13440f4808e27fb42ff3cbac9fe10d1de07dad4807c9f945784163214c739a9eed8bb2e7da6e1fdfa4aba5d5157787d0000b220145572d3d9253cde5c1132370c1849847633287660b429f20cf1f72e4ad6ae59154c3b8526fc5d30523ba962a25577427988d326e207c97c81b479540b1c63aed0a4b1dad9279879740b2f6a0bfc37aea8c81b3b35b630dba8d955afbba756b8ce9d2a9802939972c5f70efdee8778a98360eef15faf77e38627fcb4f98569d68ebad3bb667e5db46eb7f4214846e651224673dc998c55bdd9e0a1629497a75b76160865c7ac3af874bc45a42269508f183d2458835b1734791094122f75398da2bb7919c67221ee42242c298d744fcb920726b84bcb78d7eafc9e0f61a02de87ffc5c674390a33d722b50dc35bbc6788fe0fd24be7a9e358113a4e65b49655767b8f3c3bb86cd5a56c79f9148945af63492dffde82d682eca4787f203e49790650395c925e6eb68bbb3adda25bb784365c0b3e241177ca7de58252b8c79c5a865d1030f3bfd4d312bdf83bd28241389085433c34d747f9f45a0a636e700805e619a19739b15077d44d5e8969a37481896acebe7f350449c69b50376b7cf36e906d8a03aa7221de67c7fc4c5408b55e2562f7b783918004d93b284c0a1c3d596c7c18aef5cb77ad09172bfa888f5dceabf57f708f5ab61b1b4159742d3d35332e9942958ea7f39fd35cff66e84ea0ce3b38a85eb80dcc1201146fc3854cb40353a078f5435fe2becfa383b3a62564645250cf7feccff5414581e747166625477c70f5b0f579bd578e03e6b8ff8c3f0e8a82566199ff542b239f2999103cbb61602bc2f346702204e50c9ce7b85f3b8e144227ca8b9766cdbd498191c385e3a4f024c9396763eacdfe41b706fb23d4f065dbaa43d323edf1811950c71d4475a3e0913d3851be4e921cc933bb06b0aa20524267b0e9847b6de3883ab8c5be1eac17b06c946f19dc2244901890cc290f2e66147f7c47a4a29349555ca020e684ccba5f8a6f1be54e356654d3e8678c591633e11d9ee772940d92f07ab83228c0d8840cb9aae514c03358e973a2ef319a39a3ce819efc00e5a493fd582be164c1460f70fb2e103f09a2939f779377275be736ccd41f847133b3d400477582419fbd2d7ff328d88256cfadd750ed7a3c6657e332f635f0a90602b24135e705d1530531b7b738e686d83accd74874e160a5716191447f832d2f899476ec9c53204fc4ade5b75cacdc869b4088e009e12d85beaa3450889de0c86315755ec7744be592f058fafe62f9a89875bd2781d5d6928c8475f555e77eeb6e688bd8d3bee0a54fa1cb29c1b6b438d3f70ee4ac83fcb6f482575997e3a69240c6731b517d8241cd39dba6b02a9e3c94ff2470c61c693d32b1c7d22ba38f0da21d92150115d580249380500109215a05baa63474f773892b1870005e76a68853e6a8cd9f74ab3f41cf0730e702ce94c420dbf66a63f342d927923d7d25ca3c3823b6c352ae2ca127bf811c023205632ddb8f9191c2f3384a61038d1c4d32f808cbaea81d58f0aebc7c89bcb5b98f92849f4fec19cffc78173e601eb412aad24eacd74d17cce3a9ee76670367ed87c2da3db5e4ab01d4d9626d0ae00da7dbf6b8103d5994cf66ca56bb9aab9eabee2b14ef798d3fa5ed1dc16bb500a006c3320941c4c7f6f80c8b1bf79131a5e11ef38db7c42d4c4939711184ea414e2ec0cf5bdce3d7d0d72dc883b2595e5f0fe4c14cddaed61108c90c74b43cbf719bcf7a934bee760ab33f50cedfe3ce61dda3e4bee1ab4dc687f988cfceb207152f7a98c0fa3a114d2d6881034aeba877bbcc368ce591707da45becc48318f0558b3ff65d677578df9fd9e9a18d0cb08f2f5cd4f44925259225b1c4c5ed84ef30d136e7c66251329a223a1db0b485f1c634981f8860aec1f3dbda9442e5438f6fb09c7e1ab43e2b757911834df8502ce35e7be947771f7416f7ea97d26dbad7221614175e6b86f1a323e26c4f469236d1b1bf6ef28823234c73b275c5b0ff39e8c04d9dd8ff7f6a57214d803aa13dbf581295b92ce2b8a9c0e3fa8e4fe4e74c4bc2198fd010cf84681cf5f3fc9df1638c403006478b1cd4204ef3b7a6093116021118a839bd3ac270783e67096457a86d63654e268821ee742241ab5f7e91972da0a94f679b4c093d3f428d5876edf51459331df084f2fa245e3648e0cac904a82a7d323b965a39683ec0b2721105683e67d65fe10b8d41d0dfcd5c31756408c94cf425ae67f95ecac45391516875870386b675e24c462b1f7634f758723e6245da6a39da69cc2390374eb89c6c662d94eeb2f1835e233610072936be248e6a3ff87f9950c2eb2f88568c31dd30d1f0c0396a42f6be2505bf616c63f504dad5312fee2f083b406fa05cf3c998ba24237402681f0ec393de4650ddce3389d4c1541d4705d8f50c2c6d4864934394fba0c40df495fe3a5bf58a5498c1e63b5ee5f9a4b53dbd913252d4808f5bf5fc3c6dd74c86c974fc9e5afb1a7ee3d88738ab1fe9194881cb00197a31d648ae2448db872528d5f7c366188d9d98ea94497cb78ffacb5f6e8a2c8520d8d196fe0ed13e2b2a5a08dc45d8ff58a8c5d3bcbdc5d80c798acd0eabf146459e79db12c2a43c55d14b2dd608475c606fd4f943ae7cf62131691768f710fd718fda3967146daa8d4f34a3b8fe8084f4f3865e9e0cc1a0b0a799fceafd1b74f09f524822179ce38cf0f2c4c8623a3e3ab2cc20f5dccdadffacee62c120657e3d85fea93fab645b5fcdb812b130afbbd5b19e86aee66011b520b0affd84af4bf67a28b309cad66cec25a5de06390a4f2a5af47d59c006010a8ffd214149bbe47bf847fa94c2113723c9476c2e6dd84411bca16dc7bb096aaa3e9a9be27811b52838c78d16cbd89cad379bcc0cd966dadfa428f9d4d8bf133f7d21022ae3f966dbc1409c6d57cc46ff4434fd90d79b6c9e68ed3795ea6191a7e309783badaf8db9fbccbea905ff9d423370810f02add38ad3088cf3393e7c6d585b76fd78c67766fff03ff54c01228c6737da667147a8b5e722370a24db8c0165e34888920d70fb1356eb731d0024676444e2d37860eaaed9790f99069e622afe6ff12d516812022c46420006253248eb292908ab8658cd874ef05bd568547535fd54c1f0b5b816e4d90a1bd55670b96691e7633a2fa45be6a29cf7bbbcf9e5debb4b7a739b36b6b326b719a12cd0f5e3557a2fd94023d8c633fb697b85080a39734013b4ed9a7500aa8093edfc3810d4c890e608951e57af8447349cdf45a076449241a047085702dc3e44814390ceddc878791ab3a6503f48f48af25fe5cddf61e7870ce80e32e5f22d5725bfde4f319c2db7cf3ba3b9952202cd24210ab8d47fcdaa9ad5e2a9d2c7be1ed6b67cbc1a28ebe565ed0337c09c97d2d9608d97905c7201be7d4bac785d64caf783cb3ebff817bc5ff0448899537ce6911c9803744d06ae97e6d3aae037697986cb20061892ae72e05968e9bc54153bd745012af719fe7fd4b917689a6cbb94a9bc8cce51d234ffce7dca804e2b2759f473885855983b7b290806884fc66e724fa848af2de3164c611c630e5bea712cdd9a9943ca3ad55994e5587c5953f415171bdef3c42783b809bc5526a88aae66be626d73394a607ca32256198dbc026d9e09e9659ddb66736215dedee0cf69a24490a53b2c678e6caa2aaef10a4610f802fc5eccf558629554d6388465584514d3dd49fc00fb267c39bb746f76ad3cccabcc3cc882815bd9466d593270a5c9a04945dea7ad06ddb73e66b90270e859a109d52251d911e7177e07295ac9788ce970c1b718fb0c2f327f44fd0cef3cb90071ec85e958e6ae60bd134ae307720cdb10be5a0af8c051295df905447b46206b5e04d775b83e11c348458b1bb310ae25c54e8ed1488a95b2a576002198c80e0e69d14fb7bffcd89935faa6e3f9aed61e0492bb2bc334e389bd559806eff9e16643ddc4372ca83790a99bd4f5b042779ef7e3039858881c8ce0041d43ed6b1f42f18007d046fd6f65907cef73fd5e6cd514c4fa5ebb872b140d0b2ad43c58f2f2216522e78a19828baa34ba56d71414ae869a13cb708762565d754f0754561adbd9145d232034229d2840ef0c96f7af4b232c5e11ae9d018b87177a506b66571affdeb693e170dd12640e86fd430b64509d02694c52a54f23ea96e39be92b65c1abcb262a71d6e884183d4bc6cac8c79f6e94b51abe484794a42a8e1be721cd59383bdbf327a864b2cf27566f1270d82ab6e8b3b934e953b7d48ec4697172e0160b8435e8186a7ea685dd61402b0bef09137fc52e19ecb53674a1c0af17fbb0efc2b4d69572b32600fdfc3c85c79ccb2685c665aef16748b7f15862f435afaeef00f3f927cd3a60c22f137e9cb2c38a9652cee570a76351871ebc9b6fc720b9117b1306e8afb298e3a1d0d4b2c5ad5a21e9de4c0938a98daa35caa711bd6d3312a7e941b060c4a1ec211c31e53da263995d3be74a3ca5a970d3a663245a5d8eace336d9ee847619bb337e777b961805e808f7cfd8f09b93b6ce6c277396e6cf0a6153fb11625f421b02055dc53a0bb24786f4a74211bfdf44193e48b2c15c7c85acd3f4f0bd26644f9dc62653e366178645899dc06d191c785831b98de593519c0ac5849897a01b4f8c7fd559edce1c878bdb9dd091546c64225fa81b1c7dcd6fe72c8131bb160043976a5e891236d3552598c0c4fc734d1cc752c10306dfc8391f6d658ab8b7a1f350e1900edda29775ac8a531032bac968f6981889f13cab07f49c864c3ef3ff23555c170ee9730912465e6bb3b7508e11ba27001d9dc0da8b34954a7c46a2d0b0b75e73ad66278c8acbed3249936a6c76fe975954f7889bbaae5f73747e3ed02811b015beab9d1515e396d37b2a116b3cd8863517b88ee91a854e4740304f7be41123692181a8ae22090956ba619fb948d3f08108da17ac2484d3dd5795b1c716cb5ed3fc08b6449fec814018cd883b02256afadd1f35a04d9d43f661be624c0d59d0d397e8ad6917344203843495162217e79bade11ca32c0a6918350520e6c3f31f866b89e3497b518cdc056e760813ebea7fbf343db50ca2450d5b7a1c5f430859e2040ecfa70cb0c3e863a756ef6ae2e26c68b543d35beb44513f8295872df5b4062c9b80742ca7a9c2ec36a1d9bc936c7fa7328240a2db54463ca04a40dc0c9afe4d570012259c8c436d322483b17a705d60a283e0ea7545110e898ba43ac6130aebde014d237482307ff28f385fce8530814a3b35630200035ed48216bc666598aac5498e9fe513a9e21eaf842d382767220cf98104bfd8866621efb63b2eed44028cdc9e2f64cc2447fe09f8d8a1eb3214272855b525d9ce24e6daee58effb4af28fcb0a4685d78d7995081b5cafd1cfe343aefc8e9cb235fd4e1060330000000000000001ddf2f500cf45e30e3911bb51a7ff633c1e329508e42d9aa49fa8248fa4d293b87005db08b17a472580ae2cdd53c40b2a16af885d6dfa3d5b3410d389b045e6c12405c635511ccf008390856f4c6d0ca472148d2969fd39e1307bfbb8f8268ce92398248f08df7268bbe23e13ac3801ad075376bbe9684db9b41d7b1148fa65a129e1d25193ad973ec7af631a0a30c5659409b0d00e6fe44c2c507d52afbee8059aee7650bee9d47936838e77f00cfec9de3b9efe72cdeefd80a19a9c8ddcd6cd9009ec96800f9fcd709f1432b5715466d601b5ed10b0dfbd09b954b109232fb5a24036b887d26bab43da7eaa303e7c8e9d02950610aba09f343a65a7cb4550d6f6e5c0eeb9410b68f85cc3b05b9340e2e9c94a473f2001f5aea7e875d29e8e0aa99c09afec4a5455258c803e55b89cf6e89796a6f3f9fe11ee05ec415c6ac08e0f1adf9dad681f8121f82e3a9402a2fe13a016dc10d4a605578317be0d9d3e02b0989903002bd35ec4970701da6eb6f9fd5e6cefad137dceffaad968d5ce522e77c76e665529f70c9eb805a240834f4c8c795912c37e7efeb73d4bab709aafb66bd645cf0addefdd83d185c1b4465ee16344575eb2aa3acb057c37d7c22cd03adc6701f03d4cfa20ca94458b3b87e3fb7de8fed28a755bcaccbd348dbc7f5aacbb25d19437d87cb48b88fc714f059b328c374fb2695a26537330fae42d3fcff40b07054be10508fa78a9173315506119973a9158828276edc6e8ac771476d7479d07ba73e9c5b58d61367d24435a1062dfa376546e843b34829103168db63b708a13894813a442f889a423054160b85a2584d36951136bafc490056f1c03c44580e2c7a12b170e5c7a3917720bbbb21e5da616649b554b7ff57dc84dc3935271a8aa6fb218becde208d83e76bb6197f5f4f729f3c57b4280c388bf4945206fa769b672ebd47ef2d0f777fc9dfb031cf7c333c6159a75a5b33ebc6c4683ef140a144a5ab15b2b6b72ba14ee7595679a0f7873a75b9a79e03676ae94eaf9ea8d3532460f3075f40c6b9848a183fe46651caeacd51551f308eab6ba8ac27b93865f5dea0fcfe1d7dbb745695dcf187a5955414763c88ec01d299f03cdaec48eba946c7ff6031f28a5e108651401b5447c0d21c449b387765a621c057eea7d494302222e3d1ff6ee9f8a0580671d9fe3ecc310e84b554e5c823d81b8027075f749b2dc8a6bc5a99fed4a0fc46fae59cc2aa9f1471ca2b0d9fafa56927fce0f1df92647ff5aaa083bfcb7078bd8ee142448d4e1f3dda670ad93feb21028a57ee9a8ae8880df5b3276e078555d868be0d8843b495e082d743328620f36f7e106733bcefac15076826cafce271caf1520cc8746948e71ea73eef5de770ecf5e0fe075f04d6c9cef1cc1f262e69fcfc89862877ffd520e25405d9d07efec82386640d97147e4a6df66bb1fa1f20dcc2ee1fd6364bc2024c0a4a2ec00b84616231c02aef16147df509cb830b9fb20111c60613d41169a24ea67c03cea48e1905b00eba8a61e967b9aa5b6e1264b0f3e2c051e47f54f45c6fb78b46b9993c7d223d51036cb77f5e31a4b30892e8fd7ae7028322d6402f60712f712f84443de083767c38ae88a38fd4e4bbec58015f4e49e36ecbde94f23d0061edfff0e9eb62b67327249d728c278f923748e4c1e225791c740abf9493198f073a6cefffba00d23715a14967a72cc7aebb81ecc6e8bc88ae63a47507296a573c415d0163219f3b20974ba3a32572d29a479b88b89c241e63e640bbb93e2da0b73024ff03516ee3ecfbd84348372cfb574817ca878172a779ff131492b86e64c1b7054cc5fdb999df4a9578bbdd6a6a2a798f053f4c7456173cb8cb9b61735e650d0394ea9effbef615629c1f393949095982345bfc02b9a3b3ec64d9173bb1285587048fca8c740b6d2265d3e727069fcb90282c78f1049ed7925113279474d0d873045851b397a8c26036c83906dccd0eabe98f7d6854d9abc2e6ff12d84c2840affa371cc2dc624fbce873f38570dc9eff33f5e200791bc052307f5c718d47fc2a205add3fd9de2a81cb452a0a7b4a08bc133e9816f6ba852735b813a1b906d4dbd1ac8a968a8adfb6aded0d3fb87d80132a4b45ad39274bb72df20d648a26aca4beaafb971e13094b6d6525bd418b91320964bd90d3a98fb32d69222aaef2ea95c6b84b6b6aaeca3f6789944fc7ef17d218abc7667ddff798b74a0d5fbcc33d22d028d207f82286fd777ea1e281d7eb88b665e29fcda067bf9ef82a190c920e260cbc44d394b78e0439bcaa6371f8717ad82e02015f21e5dc273de338102539bc924d5a3f897eb788e0f3cbb6e8a34590ff5370bcb0f671ce28d6ac0adea17f9fda49c784ae84464991fc71a31d320d62c4199e4f4e38f390ffbcc7f15922c3325451371bebfaed8f8a7d3eead71c65878a705a6163ef649bdc6ae7b81110a2cc9576da6b0188337160cd518fb30fd5b0c165ac3de81002490f36d29f3bfefa2b4796c070c6d7d971d18bb5a573cd48cb3c64dec5bbbd7f2f6f60bff3db4833bb64b426c6715021997350aab1751f0d5382087ec133c2c43342b31eca32a4c17db14148fb48f9b7a103b2c0bfc0dc95ffe097bd8f2213a3f7a43c994599b05dc35d469fdc025e23e483039f271d948972417153c849fa8dbf374693dabd59d398732020e90d8f443ba45a00b430168561de21db76b9b714ca8260fdc4ff1a5713e69ed7296f5a5a9d192474bc6e67788d6a44d46b69ac97d0b34b2b08aa79d5ee7e910d99221e1e7f6383628b5383cec4341e5c3f18ec3361b2f2ee5935ed40919a4f8892bf3467803b720d29388b7cd47067eed9be7648fbbafcc7bebfc31a30d02f38b7d6be1579c751aabf679fa6891adf66da2257432c10d399a8d496d1aba381b64a5e3ec9a65cc8419151b638adea994c3590f77bf390152894b2784f7832d986ac51b20ba9719d85509acdedc0282f27065c87bf483fc74b55579ecc233fd6c51822ae03e46ecdb15acf0204b67852d525168f409d2a4309c5d9a14b35905b836091964ff4fec0aa91bbd7f39919eeaebbaf75c63337a4dcd5ae37cb60fa14bd78157ecef35ddb1cfdc366eecd48aebb0f5b395e4161c158c46870fc89f8f5918aabf0577c914511cac7b189384eaddf7f03cc6706a9abf219ff1b293cc9fa2cac88b65af62f0493e30736b596c119149af07e840a43dba8d97455a7a95368395488304626d3b888069a0099c435cc11e5981412f79153577c9115a1812b1c4a9ea42bbf11b40fdf930bcc37e6a6c50a7e3d06bfe7043adb32244ae359993caad00eea5f1de90355326e921145add1020ef3c2cbcbd102e7b9a150ff9672697d31f86572843269978cbc4c3fe067417e84898587d4de009b33094c8e8102c07facbda62a2996e083d0618fd9651e17f0fc025a946e7c28d93e18675ba69d8811dd06a2cc1aae28de5a6af30f305cfd8a090832483e4ff7c974d47ac96d2f3f7baec94f965a116c2734f87f468ec33cee1a58dd2ccdbc8f6a0f2a50a739aadf4007c7a529844cd1ce5efdd42eb28a0f5f04fde00dea586e4b8cdb8469b36940de19feefd342285c29298afcd3ddc368f6365e9ee255f8c2b6f70c1bafafe10720402a668ac3a2cb8ee75cb7004455cb4221486c3dee64f8f25f1c8b1411437cf6ba70afac5182b21f0efc1b5c71dd59f71796d2719741b6d5c757d824cdfd2d9459569427951d6974d94bea1fbeb0e1a23ef88774b270a8bca65f01a00299258847ddce8af8a7626c802b29dd01d1dd9d2a7243b334c29c0943e11ddbfe82cdfbd506e81fab8775a1d3c4f6c0744e1086763ad84e8f51d601e24cd6fcba1409abbb1c6b5c9bb9eab8ca30282801448f86cd5d8ec187866f1ba738863f3995bf4084ebe8f41dc1ffe0cd47d5a95dc5fc0dde1559ba170316e085d0e385ecf8646bf787b5287960f07bff9d3d990b3be4b29f008de18ccb46dc2192d0fbbbb9e701d350aae205fcd2d7314112bdc393e2eeb0a96c5f2c767dea0b82e9549f46a270b2c69915711c144a7737e9003b1c1b8684e9f2abe66ad1678c32a0c6ce401ae2d7507ff32c8f6179433b94764a6e4ea0a363e9a03787502c67ec97724d78c1257499379ecc5cd006a13acbba05dc7f1310bc75b9ec6a09f4d95b2ec11952eb3dc3d2dfeb8678b10b5db162b44af56ada56f7e311d6c5ce46472d9ba2284126f8f579d59a9d3e81bb1b26a7ee59c3cca96252fd4ba8e009a213911619ab4184e83683969cc4c474e934f36e6ba4ea33d9e93e984f1603e259cb7f1a3824dd8764bbe30ae08166aca84e63615e59c31d2a968a0beccd40637db22b971d51a4c35ce83108d8975d758669bd83ff9602718a882b943f80745d95a2d5e07ad0acf8023e24589fd68988a350c0ad571470f8b9a906648895d312100cd18089465de942ae0c7c8bffb5d3705c729990721bb0497dddea6dabdd530c9ec90286cee5ba3e2b699eb2de55de8aa0012b38051862bf0a7a22d6cce4310dd8dc47c206d9eb7bf28f2a53afa8fd10836210c0c461e417be42d89ea659a76d5e3cba08daa885b7f0302c5df05c72f8257f60e2ad954891841e67d8b7184cfa47e51e9b36f6ed1400aa84c5058a1998d07709b8b46630d70c73552ada18713310dff4177e2143aada1038bae9a64e02870a7535598d766f447b4e25dbd009009cc0e3421067bd0cd10735469b308cfd7a094bbdaaef2e31c22dc6c1a6d4c7890020b5bd04ef649b56889b2031c1923a69002448dd5db1142384cf517b08096539cc537532f43afc33ca77beb2dfee921178ad51b04f10495365e60b4b7677880409716e0eaa3a1d649a871234f86787c2792bf1e04a635e3f22b8b3b1382be942605c3855a940cfa3825a2f8993312a512c81f92aea0fca95bd1bdb6b9774ef3f1fd15bfa1eae84ebb48bc9b55fb2141c55cd3188eeb5049b2e6f3360e60c5bb7d91c9028ebc9287b4b5541a4ca290288e08ca46f80eeabbf100f700c873d7e69dee7dc80b842004b2bb93f0bf533488028d1a1137371417d06785372ce942d4c6589cdb57514bde81960475c22393eb02a93b6560fc3e4fb6ba692dc362d38e33a41b7df222e9192fdc0e0499f110e681682e2b500c00947d62280482dde5e26010cf959fc717d0837c98634550376aaeb17c1c80e3c3245c36c68f56fc8be117ff84957a75e4aa4caddb38779f1c50086ad4863002127b6c8067f172aa78137908fff82b2297a53d6232c26a2e94bb25b7f9241013adeb36d1e437f2f6c0d983efdb78608e940390005d63c081eaef21d7f00ddf51a3fbbe6175be436a9b0b75f198c30efee2821ac5f088819eb96ba835999d0012e7a0c244268a658bc6b7845d10178283cb8add0b81405ccd64000c1835111511555eca14f7e3cc9274b45f817039ee62cfd95363cc66f5397f88822f790e0a86b6a69379dae77db24185394d9fc73880d39f6b3233dd8d3b912995b22905563ae8cb2b1f3491feffbbdf2c7d2e89dce1995d2429bb0a4c550860835aef589983d3170387971f8aec15c18450dbc9cd12f402589dfb17216e359072ceec8e9484f9b490a288dd4b268233c40ad23721961c390bae0a181c4cc8eb68eb4b2a5db6944042c6ef67550f9abf3b9084cfe8521b62d727416576a58591dc65f79f871958243ca4415d92d3e588c4205b4a3806d64224e94e8f04dbf7f75888246aae9f894bab1ea69d7e10b4f07e8cf2dcf70cc62571c737cb6b6c68b0e3fbe4b1931c8615a793fa3db0a45090458ff62ce8bf421907877e555cdb97d3709bbd8728c4432288861a56cbfeabbea5f225cd29e31d99eb788704a93696f5e813000063020003c54f16cef0fd4185f35dc6784047913c89883f72b68aa78c16f144624430909da01633eb2b4b13eccc2b7a7ac5ef3b13505cea54e1d390944b5680e6860184e35111675dce504784f4ae8121dbd1f76df3d55ef722f3f73b5b7723efe2fd34b1fd4e1060330000000000000001a4092701f9170991583ec8b5c9b5056ddaba7637f7d6733a57dc4990acba6d15e9eb5dca55528e179b98a9e09e05ba940b59558b768194ca046c22e12ea310ec05bcc795ea419277649d40b145e6f0accc31f2d7eb39b29b51d3ba3410536b9bb37bce9014d352b496b93161fc799b23939ee635f25c4cbd6605029722ac2bfed7d55fe4120c0cc880661ea3313d700e261ba973bea5df4dc4d8a05652d193c5edad01945862943a6756d4b0ddc93ad5a444ecb054bcfbbba3635aa752d9c4fe8f93fc56c8066b3fe344143d65328025f5184308f2016eae65df8b0f1818f59f5a9539eec9c9c94704ecac269b008ef25f67ab2b9ab4e903b1f2372d1d3988b86e5efa639f9f964599df8559622c8d11f6bc4f706c74e4a603a3798005e210f93d9e6b59f8ccac907dc9cff127c552c8c89981e1b91c817cd365422eb6bb0c7ef4ddddabd306b0c32a98464973dec30def4149f8e9f35de6840cbd0ce8a2f2b5dec15e3f3c27d0d2720159cabb3ff2f73b4571eb53ec735d7b0f27bf4d23cd8b0fba14d5921d464beb3fd4f2fcf844f76a7f7f129a3e7e1c4a6e4f07490f2cfbac4e6730148397c73720d35cc5a46fdb1e774e17f7b951887c1c72ae4cb25ae99bee652ebb2ffd9f4c9400ee0870b27a5dd8fdb8224aab6738399587b4513d3441e26b898b35f739d64047ce8de70347042fd18c3797b1de82cfe23521dc8e5c36e286c05d4f81508e282018cd8d263164512e6ab02a22b3d5d489e6074f9370e218a3adf8afd229659f5046749db9615541408739d4f0646a853b25f8e549e85708c368e45f3e1d20ab47a4ed647b820a360e4e351e4caf74810098f0df4bb74e0e69f2db3284c30d24a0d109d16c37509557ce15c81812bde78085f31cd7c93140fc43d6580abc7ca2adfe6354510fd8104c0f1ffe46d24f31bea808376ac27996fb992de15b30abf4df6f4d08860bec27a95bcee0df8f0a5bd7f72938ca9e917f39c5425bff826c4a84a1fd70cff87be2e8619e0d3b729f0aadc10d4b70d10c2c020a99d03424a595294dd34b84c83a2d434b98fb116e2ebc8b2bc444740ce090816218ea61da172e30db5e78a58ef495812c287e3cfab7bffada0d6f2fb3c153f1a21707923ae7ec862d44d357776803336566c3bebc9f53177a3a8825478eafb32a61bc04d82994708725e0ccf5d76dcac946f36895402e84856b3e25567156040510fb299b88c45713175c54fb3d54aa6d383c423807778a92b7d9bc8fa347923749d18c96b20cea608b6380b57c35b4408f724046f6f65fc0d71e237c39376e1ec6dfc6a2ae83ba70d25855c10e6a8f425951c52fac7758353e2068e5cb4b6ea066491e834a47f8a5cfbd6ca91c832fe4f77b3b3140a6c1c70055d52f0aa67e7d3ba18e5bfb09f738141a611002ff9e34188d540ac42f48ee06f6925cbf3cca5ca033c4fe089650a2b86a8350367e9cfbd07fac9667c0171f54311cd5b371576790e51a1fddbec64f8f559cc08b1c0ae5113bb0c532742d90c1a266bd7eaa4b1d06693ff12d6e66c51bb1d12dfaeb70bc56d5870fb5ce4b040f86d7725ae53b18c1b12a84f7e3a2e7d748e8a899fb917987518c6dbbb83e3b62b31b0facd3515cf2e18b4e49218ef48d97df114971b298a6c4af231a432d4ac295d0d1947e09ccf41ab104e5435e7dddc0a6815b636a31be9a0d9cee2f22c745f05f384bd703fbf4bbcfeadf177abf4ca4524f38a681e5436a06abf9ca70c7569a7bcb8337b0b0c280b5f790ebe1258bea9c1da58c65c13991efc1ef8f7c75ef28affa340d84b33398790a9a77d30b0ea35613b39b1b7d8667fd7299e2ff38de468f625df1485ee3b2759f7e27ed707f5807342bad0935ed42609849d7a9d11addf8a92df7933ec44e86ac7f24d55a55284fbc4e4824a2b4f8bcb146d08d7d6d3f551ce1b494d7c0fbd6e82ba4496f3eb2812c986f3e2e627240a9102089725057c1e5dcaa3bcc5efdd6ba7e8a496f609aca3dd82816552c669c255283f9372be7d8f12b044f8c41180aac612245945a4467fa806892d55ff425adbbe92bc892698a32d39d7c23303ef65c980c608b5ac07ee60ed2b4e028a5565f5aa3ecd1e15787c23716697d53603f72738d5ca88c5d8280c3d8af9e83c18be420bb87db4b82a98d3af42f9191d4023eb22f428def2ee99d04d1497cb8205b67c9ca893f7afba077bae5fc11cfeb53d882763f2eb6731bab373923a63dda3ee1c1b6207e0ad0884290333dfae3f80e85cc50a241c73d7d99998e9b14c3e52e660edfe594e1ddcef04402e8fb01efff9c00d9a6ffc955e219fe9c92cbe87865f7a25ac68fa2d360c584c33fa3e40ce91d021465610b85b5f1a98e6908a17c5e138f2dd7da2769bad8789eed99828e8f23952bab7e9ead5fa9bac781fecc93de7927544736fd5c57f1da32be31b85657c313cd02104e1b889426bf4c8e9daa741278ae3966ea673cd3fa164ea7cc947b34f86c92084043ae846aecfbb15535a9b21e27fcda9a21bd821072135554e3015d5a12d1c7f2fe74dcef63fe99c947b55a4253add3668bab2d356d58909b7b0784c0759dbf1036645c5d673edb45245f5e78407978b63d5c25ada76b176dcff53e76801d7c2784d4687561f32e7a32175c0fc77227b3ab0a4054462d042931897530502c85f0b976fe3e3fd5488853b20dbd27d0d736e9f44fe6224b9610af05c462f08d328cefa4bf8530250f5a4d3fa7f58f5a48db45cd68d0dd53a2c66758a20184cf226565ce558e60f0af982b02672f8ab14333349a5e5a5dcea61a09f93addea3858fea4e49c84f5d8269a2fe52f072d66240e7d30b7345c841a135271c07e6a142ad1ef1955e29469d3ad3d952ca9055696dd825253e4a0e4822bf3119f1df2404ff9bfa39568a52fadf98ac6d5a4653862d91b995ab166fa9de73478f7e339941e036b621f7d773a5413471d6e41a68c02ed89a4882bfade2b10e399f4515470ef43930a911609911f8b66bc060ebb186cdc4c1aa3cb573d696b0f48a655f8877f68f678bbf467ec872724181d6f983682389fc8a61a7525bcd61ab37930e6eeaac82b7adaa145e2a0bf67e235a657949708f0640ec802a1741590a505e4a109ba857824e00f8f675ae6bc8a0f32aaec0f38398c33c8f53bb3cd53bf697ece4372f8f252ab4b314769f1393aabb2675f44c05dbd279732824d49e77f3df8841de18a30a9bc321ae43c712dbb9f08b457dd3e3f13244ae03da16acd6d3e861efae625478a89020655f0de72ac174a50834db564d4e60bef51e28954a938c64a6ee290523337153aa4d6cfaa00826d2aae1b57e3ab857e10cf2b497597ef9395fe6afb3d74b21c5d44281f756e018539ce8b7f4bc663db1eac9b976da2f994b193a30bde0394b7648b55ad9bde958ec5bf89eab516d4418451d8cbca637c1d6dfdef61712e76aa7f12151d5717ba1af71c4754136563ff2667bb984a80a51ee9112587cb77ed6fac8f4263fce637adcab9f315d6beefe509f8d34ae9c42c12dd42067584c4a96f6c15fe590bd8616983abbf453ee038f385a126dcb71919dd7143a8c27186e1c6d29d36c1039a8f008088e03fe7b97fd7f285a0817ca746438e0949dcfe49fc67ae967d13d1d1aa37c2e410bb95609c7ba0586c8fccdcca933b1a85ec97d1aff670004f71232bedbd192ba5e5a681867470c4e146221bee322a5f458a2bea1316d3f4be26efbcdeb4b1062e58f2fa23b144cfdda55aad3ed7142653c7948b6d854abb8d1201e9a4de07b8985fc194e58ea08b4c8528adcd73ae6bcf5493b7b8ae1f663ace587a87d5a341828612bf82605ce6cca9253057a8096f396bdacefb56f9a452849f0ac652738b5b3135ae13af532dac7d9d5307f158d844bd3664865724aa9907d2ccaf4cae78e056ee78815cbcdccfc38b71e0c648b1fd9f710a15d799e237c344d53923d3941e73f924b75de975ead5b536d632338fe83a8d02a4b7fa9a6f1a5df9de85c88b944581294609c880ecb5eb45de087524d896486c200ad68f5c45e43894d6e84493bed02602ab075c8dca1e57e3932740c25a9401b5bbc1bf524f4885f77a501bc3c313666fdebe7497a2b55031563810b35f33503cdbeb46de12c0482c70b07b5ca3c4e2aa21be75c33a23e73c03379c434dc8bff54dc40b0a34f476d0fd1de0422ae6edacc8a729ed2443315336b208ceafa68a229592e42f49501cf03fcf04ed2ab60cf7c31bf4d4fb5a8d7b8969ddfa8a725eb9d32827446c141170bec2b0dfb5c0a6e00cf83b856ae71b5ce46dfafdc12f94a7f8f3d1bf1eb769f52f43238ed411a97e55625ddc208affbae8ade42829c82cf4f62423830db9ca4091fffe3d282c578552649a34cbfeef3d58e020978137a44fefdb9083186dfd712301431b78095a0997f0764f3ef29f138ffb322c8e187c260c90a3ef7da5638e8d961d6a6cb1867a86d3eb23419e101e18bbf128c7a3492e8447533b7586a81e52366bdf2c376a57847b8a44f7b55eaeee6ab2f06596a87ccad4a60881bcf4b4b5d5f0de85593b643c0c4fe1771a0cb5bb0402fae0f1a2dd811d6495676ad718b73d4b86fc79ce074aa9924829ceb4dfd4e3ab70fdbd94b5ad1d11ca9e582dc215577056373ed91ccd6472b3ce02dffa8817e66205b179f37bdda376d25d6bbcc83a15ad757bb7843aa03d2f7193d8d707ccf4de1f5c8694aa212219752addd55cbb412c05118c30555ca7ea820bf3440d1dae3332947a1cfc22e366d1ad120b906274883d7769983eab6afea2ec0672a2ed25b2b3390c017566e52a0ebc59e6cebb89918d54f75d4094b943e7725468034d2ecfeb68228060bad59a4deaa40843d0cfe0a1e58855113bb7c5965254b82cf3997924d0a8b1711b1e1e5b28343168f64fa9bdf8e5158f33c91112f61ba782a4f97c5456c9c8756adf53a7d919ad90654a1c035d8686f834750302added0fb7b29a3f4f43f4dec29bedcff3139cbdffe391ad64b48eb1691be659e4e4dab266b7221a2c49e42d8ab06ab6d16efee2a0e0bab140aa34b53f70c808f3966f1a171bdc789e23ca0246328dcb1680c765fe262048b869c56f381bb7ded54e1486c0a3660d990f4bf0e5b4eec4cc0c3abf4edab6346cb531194c27de0e316d9d0a823db00e20e2fb99c2b5383213518d0cf604ae8f69d2921b442b729cc78579dd242314da99f01df55a45dadcc87e4b216aa0d817e60673c9807a75185e52a70f04ab8ea28cad805c37fae5417f89af44d4a5728c187c74ea736f21681fa7f93dd9bcab9b510febf2c4b5c64cbedb6b59b77a8816728dfbaeea423bb7735ae1fba08964a343ef9b5d36dc7f8cba10c8ae69bbddc4e524df73c940f762a2d39ce7ff235425942f2007b4e08f2002018f720e0339bfa2704a9d74798b13c6fda733d965666ec5592d1d6dae7af6efe5c87cf48d40858937b1ecde3445ab2c1ba1e87e1c2f3ce696baf3efb30ecbd9dbfab121a142ebfb4f09f343f37e8c5c0f40408a339cc3e4f4d34742d695dfc037744ba1a794abb67954d3a6509157a3aaf293c886b58cfe9bed8a3dacda2a2ce41f95e72e46baf65ca666f61bb548afe8f92e1c4ada258f46178a4591015678fc542d412f7ed323d2629c127890ad9c2151261c4869f59db9648e09c25bd5ad8cfde27ea4ef22666ae0f30a4d25c3ea1780a6c3eb2d94d0f48c1a61701707e57405e7e9f448452d0a48af07013fe04d6fa62fa5fd54139d95217734ff4e596f2ac975bbfb8fda29f7e6f211eb165480e44fde387d4833f89c7162c1bda3265a5271946c5392921861ad5d9e5b672afe4778301d7a8a8d8a03ca1c99d73b2ee43281c0b88ca4292a5ea29a0e23770 \ No newline at end of file diff --git a/examples/test_vector/raw_blind/finalized.hex b/examples/test_vector/raw_blind/finalized.hex new file mode 100644 index 00000000..c214eea9 --- /dev/null +++ b/examples/test_vector/raw_blind/finalized.hex @@ -0,0 +1 @@ +70736574ff01020402000000010401020105010401fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c01086b02473044022040d1802d6e10da4c27f05eff807550e614b3d2fa20c663dbf1ebf162d3952689022001f477c953b7c543bce877e3297fccb00ef5dba21d427e79c8bfb8522713309801210334c307ad8142e7c8a6bf1ad3552b12fbb860885ea7f2d76c1f49f93a7c4bbbe7010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b01086b02473044022017c696503f5e1539fe5cb8dd05f793bd3b6e39f193028a7299a80c94c817a02d022007889009088f46cd9d9f4d137815704170410f53d503b68c1e020292a85b93fa012103df8f51c053ba0dfb443cce9793b6dc3339ffb0ce97af4792dade3aae1eb890f6010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc04707365740121089f9677efff5962164a434b0bffe53d90c271deda3eaf30c1d7a55105d24b874707fc047073657403210be6c538eee2e29d44c66a3e66fc8165afa4081557031826a070703cb69faf24380104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e10603300000000000000015bee4d0045b0884354b05d0285abb287ee8e539e8146b92dceaaee6fc01af1a3730cece4c77caefdcfcea1073197ffb8b3ded97d0dda6f49752b31f23e196378a3da2128a8c45ecb7eb1c32f0c0dfa1ce7bc348e799f66d547476e976baece7445790064b34b1526c88cf54928acc906ec0925327d797d36d0db6a43633d7f963ec9b4123a5ea31b655882329afe2108d123f76857fd3ce344fcd651b3d3066aa506625c9309acead85fb6c8a7d977db5dd6cb6e2f99fb3535746258d42d95dd2103bbaf5d45c8a2dfaa2301eba08763bac8ab3b59bd0082a457bc0cb257c43ea6354348e73d8dee101864068f0e8e30e6711f1597d7b529a57e95fc0df813ea160f5f5152f341087868a680c1bbc4b2a427d18cf8f12cbc4304dd3b1ea4c916467ae48b3b53062ea046161170fbe6ca07462cf3e5534d638c91fe6919318c4b2e98ce0ebcb245680d6395ba746fbf510a9a36ad8d933b023eae39973a39162178148a897b5174940db46e4be54794cea11fafbed48b42eed27f9915cfc50c958e88bac551ab1c9bfa03653e558cf87a99ff13440f4808e27fb42ff3cbac9fe10d1de07dad4807c9f945784163214c739a9eed8bb2e7da6e1fdfa4aba5d5157787d0000b220145572d3d9253cde5c1132370c1849847633287660b429f20cf1f72e4ad6ae59154c3b8526fc5d30523ba962a25577427988d326e207c97c81b479540b1c63aed0a4b1dad9279879740b2f6a0bfc37aea8c81b3b35b630dba8d955afbba756b8ce9d2a9802939972c5f70efdee8778a98360eef15faf77e38627fcb4f98569d68ebad3bb667e5db46eb7f4214846e651224673dc998c55bdd9e0a1629497a75b76160865c7ac3af874bc45a42269508f183d2458835b1734791094122f75398da2bb7919c67221ee42242c298d744fcb920726b84bcb78d7eafc9e0f61a02de87ffc5c674390a33d722b50dc35bbc6788fe0fd24be7a9e358113a4e65b49655767b8f3c3bb86cd5a56c79f9148945af63492dffde82d682eca4787f203e49790650395c925e6eb68bbb3adda25bb784365c0b3e241177ca7de58252b8c79c5a865d1030f3bfd4d312bdf83bd28241389085433c34d747f9f45a0a636e700805e619a19739b15077d44d5e8969a37481896acebe7f350449c69b50376b7cf36e906d8a03aa7221de67c7fc4c5408b55e2562f7b783918004d93b284c0a1c3d596c7c18aef5cb77ad09172bfa888f5dceabf57f708f5ab61b1b4159742d3d35332e9942958ea7f39fd35cff66e84ea0ce3b38a85eb80dcc1201146fc3854cb40353a078f5435fe2becfa383b3a62564645250cf7feccff5414581e747166625477c70f5b0f579bd578e03e6b8ff8c3f0e8a82566199ff542b239f2999103cbb61602bc2f346702204e50c9ce7b85f3b8e144227ca8b9766cdbd498191c385e3a4f024c9396763eacdfe41b706fb23d4f065dbaa43d323edf1811950c71d4475a3e0913d3851be4e921cc933bb06b0aa20524267b0e9847b6de3883ab8c5be1eac17b06c946f19dc2244901890cc290f2e66147f7c47a4a29349555ca020e684ccba5f8a6f1be54e356654d3e8678c591633e11d9ee772940d92f07ab83228c0d8840cb9aae514c03358e973a2ef319a39a3ce819efc00e5a493fd582be164c1460f70fb2e103f09a2939f779377275be736ccd41f847133b3d400477582419fbd2d7ff328d88256cfadd750ed7a3c6657e332f635f0a90602b24135e705d1530531b7b738e686d83accd74874e160a5716191447f832d2f899476ec9c53204fc4ade5b75cacdc869b4088e009e12d85beaa3450889de0c86315755ec7744be592f058fafe62f9a89875bd2781d5d6928c8475f555e77eeb6e688bd8d3bee0a54fa1cb29c1b6b438d3f70ee4ac83fcb6f482575997e3a69240c6731b517d8241cd39dba6b02a9e3c94ff2470c61c693d32b1c7d22ba38f0da21d92150115d580249380500109215a05baa63474f773892b1870005e76a68853e6a8cd9f74ab3f41cf0730e702ce94c420dbf66a63f342d927923d7d25ca3c3823b6c352ae2ca127bf811c023205632ddb8f9191c2f3384a61038d1c4d32f808cbaea81d58f0aebc7c89bcb5b98f92849f4fec19cffc78173e601eb412aad24eacd74d17cce3a9ee76670367ed87c2da3db5e4ab01d4d9626d0ae00da7dbf6b8103d5994cf66ca56bb9aab9eabee2b14ef798d3fa5ed1dc16bb500a006c3320941c4c7f6f80c8b1bf79131a5e11ef38db7c42d4c4939711184ea414e2ec0cf5bdce3d7d0d72dc883b2595e5f0fe4c14cddaed61108c90c74b43cbf719bcf7a934bee760ab33f50cedfe3ce61dda3e4bee1ab4dc687f988cfceb207152f7a98c0fa3a114d2d6881034aeba877bbcc368ce591707da45becc48318f0558b3ff65d677578df9fd9e9a18d0cb08f2f5cd4f44925259225b1c4c5ed84ef30d136e7c66251329a223a1db0b485f1c634981f8860aec1f3dbda9442e5438f6fb09c7e1ab43e2b757911834df8502ce35e7be947771f7416f7ea97d26dbad7221614175e6b86f1a323e26c4f469236d1b1bf6ef28823234c73b275c5b0ff39e8c04d9dd8ff7f6a57214d803aa13dbf581295b92ce2b8a9c0e3fa8e4fe4e74c4bc2198fd010cf84681cf5f3fc9df1638c403006478b1cd4204ef3b7a6093116021118a839bd3ac270783e67096457a86d63654e268821ee742241ab5f7e91972da0a94f679b4c093d3f428d5876edf51459331df084f2fa245e3648e0cac904a82a7d323b965a39683ec0b2721105683e67d65fe10b8d41d0dfcd5c31756408c94cf425ae67f95ecac45391516875870386b675e24c462b1f7634f758723e6245da6a39da69cc2390374eb89c6c662d94eeb2f1835e233610072936be248e6a3ff87f9950c2eb2f88568c31dd30d1f0c0396a42f6be2505bf616c63f504dad5312fee2f083b406fa05cf3c998ba24237402681f0ec393de4650ddce3389d4c1541d4705d8f50c2c6d4864934394fba0c40df495fe3a5bf58a5498c1e63b5ee5f9a4b53dbd913252d4808f5bf5fc3c6dd74c86c974fc9e5afb1a7ee3d88738ab1fe9194881cb00197a31d648ae2448db872528d5f7c366188d9d98ea94497cb78ffacb5f6e8a2c8520d8d196fe0ed13e2b2a5a08dc45d8ff58a8c5d3bcbdc5d80c798acd0eabf146459e79db12c2a43c55d14b2dd608475c606fd4f943ae7cf62131691768f710fd718fda3967146daa8d4f34a3b8fe8084f4f3865e9e0cc1a0b0a799fceafd1b74f09f524822179ce38cf0f2c4c8623a3e3ab2cc20f5dccdadffacee62c120657e3d85fea93fab645b5fcdb812b130afbbd5b19e86aee66011b520b0affd84af4bf67a28b309cad66cec25a5de06390a4f2a5af47d59c006010a8ffd214149bbe47bf847fa94c2113723c9476c2e6dd84411bca16dc7bb096aaa3e9a9be27811b52838c78d16cbd89cad379bcc0cd966dadfa428f9d4d8bf133f7d21022ae3f966dbc1409c6d57cc46ff4434fd90d79b6c9e68ed3795ea6191a7e309783badaf8db9fbccbea905ff9d423370810f02add38ad3088cf3393e7c6d585b76fd78c67766fff03ff54c01228c6737da667147a8b5e722370a24db8c0165e34888920d70fb1356eb731d0024676444e2d37860eaaed9790f99069e622afe6ff12d516812022c46420006253248eb292908ab8658cd874ef05bd568547535fd54c1f0b5b816e4d90a1bd55670b96691e7633a2fa45be6a29cf7bbbcf9e5debb4b7a739b36b6b326b719a12cd0f5e3557a2fd94023d8c633fb697b85080a39734013b4ed9a7500aa8093edfc3810d4c890e608951e57af8447349cdf45a076449241a047085702dc3e44814390ceddc878791ab3a6503f48f48af25fe5cddf61e7870ce80e32e5f22d5725bfde4f319c2db7cf3ba3b9952202cd24210ab8d47fcdaa9ad5e2a9d2c7be1ed6b67cbc1a28ebe565ed0337c09c97d2d9608d97905c7201be7d4bac785d64caf783cb3ebff817bc5ff0448899537ce6911c9803744d06ae97e6d3aae037697986cb20061892ae72e05968e9bc54153bd745012af719fe7fd4b917689a6cbb94a9bc8cce51d234ffce7dca804e2b2759f473885855983b7b290806884fc66e724fa848af2de3164c611c630e5bea712cdd9a9943ca3ad55994e5587c5953f415171bdef3c42783b809bc5526a88aae66be626d73394a607ca32256198dbc026d9e09e9659ddb66736215dedee0cf69a24490a53b2c678e6caa2aaef10a4610f802fc5eccf558629554d6388465584514d3dd49fc00fb267c39bb746f76ad3cccabcc3cc882815bd9466d593270a5c9a04945dea7ad06ddb73e66b90270e859a109d52251d911e7177e07295ac9788ce970c1b718fb0c2f327f44fd0cef3cb90071ec85e958e6ae60bd134ae307720cdb10be5a0af8c051295df905447b46206b5e04d775b83e11c348458b1bb310ae25c54e8ed1488a95b2a576002198c80e0e69d14fb7bffcd89935faa6e3f9aed61e0492bb2bc334e389bd559806eff9e16643ddc4372ca83790a99bd4f5b042779ef7e3039858881c8ce0041d43ed6b1f42f18007d046fd6f65907cef73fd5e6cd514c4fa5ebb872b140d0b2ad43c58f2f2216522e78a19828baa34ba56d71414ae869a13cb708762565d754f0754561adbd9145d232034229d2840ef0c96f7af4b232c5e11ae9d018b87177a506b66571affdeb693e170dd12640e86fd430b64509d02694c52a54f23ea96e39be92b65c1abcb262a71d6e884183d4bc6cac8c79f6e94b51abe484794a42a8e1be721cd59383bdbf327a864b2cf27566f1270d82ab6e8b3b934e953b7d48ec4697172e0160b8435e8186a7ea685dd61402b0bef09137fc52e19ecb53674a1c0af17fbb0efc2b4d69572b32600fdfc3c85c79ccb2685c665aef16748b7f15862f435afaeef00f3f927cd3a60c22f137e9cb2c38a9652cee570a76351871ebc9b6fc720b9117b1306e8afb298e3a1d0d4b2c5ad5a21e9de4c0938a98daa35caa711bd6d3312a7e941b060c4a1ec211c31e53da263995d3be74a3ca5a970d3a663245a5d8eace336d9ee847619bb337e777b961805e808f7cfd8f09b93b6ce6c277396e6cf0a6153fb11625f421b02055dc53a0bb24786f4a74211bfdf44193e48b2c15c7c85acd3f4f0bd26644f9dc62653e366178645899dc06d191c785831b98de593519c0ac5849897a01b4f8c7fd559edce1c878bdb9dd091546c64225fa81b1c7dcd6fe72c8131bb160043976a5e891236d3552598c0c4fc734d1cc752c10306dfc8391f6d658ab8b7a1f350e1900edda29775ac8a531032bac968f6981889f13cab07f49c864c3ef3ff23555c170ee9730912465e6bb3b7508e11ba27001d9dc0da8b34954a7c46a2d0b0b75e73ad66278c8acbed3249936a6c76fe975954f7889bbaae5f73747e3ed02811b015beab9d1515e396d37b2a116b3cd8863517b88ee91a854e4740304f7be41123692181a8ae22090956ba619fb948d3f08108da17ac2484d3dd5795b1c716cb5ed3fc08b6449fec814018cd883b02256afadd1f35a04d9d43f661be624c0d59d0d397e8ad6917344203843495162217e79bade11ca32c0a6918350520e6c3f31f866b89e3497b518cdc056e760813ebea7fbf343db50ca2450d5b7a1c5f430859e2040ecfa70cb0c3e863a756ef6ae2e26c68b543d35beb44513f8295872df5b4062c9b80742ca7a9c2ec36a1d9bc936c7fa7328240a2db54463ca04a40dc0c9afe4d570012259c8c436d322483b17a705d60a283e0ea7545110e898ba43ac6130aebde014d237482307ff28f385fce8530814a3b3507fc047073657405630200039d46edc3d830d852edbdf28cbc023dd1666cc4a806ec9d4acb2c97865280698d61fb6b2a073be4ce75cf8193fa9b3d677146575a812873ae7eaa7b08110cb88904273dd65af35115dd116092a56ab45fb57178d8c5ed4cf99474ff760f6e987d07fc0470736574072103701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d0007fc047073657401210821b4e7599b1b4faf29b521f1c442dfb0a893499d374c19db939c0c9a3b9798c707fc047073657403210b8529a893bd81e38743e8e3015095077f6e244a064d64708963c6af5024b6aeb3010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001ddf2f500cf45e30e3911bb51a7ff633c1e329508e42d9aa49fa8248fa4d293b87005db08b17a472580ae2cdd53c40b2a16af885d6dfa3d5b3410d389b045e6c12405c635511ccf008390856f4c6d0ca472148d2969fd39e1307bfbb8f8268ce92398248f08df7268bbe23e13ac3801ad075376bbe9684db9b41d7b1148fa65a129e1d25193ad973ec7af631a0a30c5659409b0d00e6fe44c2c507d52afbee8059aee7650bee9d47936838e77f00cfec9de3b9efe72cdeefd80a19a9c8ddcd6cd9009ec96800f9fcd709f1432b5715466d601b5ed10b0dfbd09b954b109232fb5a24036b887d26bab43da7eaa303e7c8e9d02950610aba09f343a65a7cb4550d6f6e5c0eeb9410b68f85cc3b05b9340e2e9c94a473f2001f5aea7e875d29e8e0aa99c09afec4a5455258c803e55b89cf6e89796a6f3f9fe11ee05ec415c6ac08e0f1adf9dad681f8121f82e3a9402a2fe13a016dc10d4a605578317be0d9d3e02b0989903002bd35ec4970701da6eb6f9fd5e6cefad137dceffaad968d5ce522e77c76e665529f70c9eb805a240834f4c8c795912c37e7efeb73d4bab709aafb66bd645cf0addefdd83d185c1b4465ee16344575eb2aa3acb057c37d7c22cd03adc6701f03d4cfa20ca94458b3b87e3fb7de8fed28a755bcaccbd348dbc7f5aacbb25d19437d87cb48b88fc714f059b328c374fb2695a26537330fae42d3fcff40b07054be10508fa78a9173315506119973a9158828276edc6e8ac771476d7479d07ba73e9c5b58d61367d24435a1062dfa376546e843b34829103168db63b708a13894813a442f889a423054160b85a2584d36951136bafc490056f1c03c44580e2c7a12b170e5c7a3917720bbbb21e5da616649b554b7ff57dc84dc3935271a8aa6fb218becde208d83e76bb6197f5f4f729f3c57b4280c388bf4945206fa769b672ebd47ef2d0f777fc9dfb031cf7c333c6159a75a5b33ebc6c4683ef140a144a5ab15b2b6b72ba14ee7595679a0f7873a75b9a79e03676ae94eaf9ea8d3532460f3075f40c6b9848a183fe46651caeacd51551f308eab6ba8ac27b93865f5dea0fcfe1d7dbb745695dcf187a5955414763c88ec01d299f03cdaec48eba946c7ff6031f28a5e108651401b5447c0d21c449b387765a621c057eea7d494302222e3d1ff6ee9f8a0580671d9fe3ecc310e84b554e5c823d81b8027075f749b2dc8a6bc5a99fed4a0fc46fae59cc2aa9f1471ca2b0d9fafa56927fce0f1df92647ff5aaa083bfcb7078bd8ee142448d4e1f3dda670ad93feb21028a57ee9a8ae8880df5b3276e078555d868be0d8843b495e082d743328620f36f7e106733bcefac15076826cafce271caf1520cc8746948e71ea73eef5de770ecf5e0fe075f04d6c9cef1cc1f262e69fcfc89862877ffd520e25405d9d07efec82386640d97147e4a6df66bb1fa1f20dcc2ee1fd6364bc2024c0a4a2ec00b84616231c02aef16147df509cb830b9fb20111c60613d41169a24ea67c03cea48e1905b00eba8a61e967b9aa5b6e1264b0f3e2c051e47f54f45c6fb78b46b9993c7d223d51036cb77f5e31a4b30892e8fd7ae7028322d6402f60712f712f84443de083767c38ae88a38fd4e4bbec58015f4e49e36ecbde94f23d0061edfff0e9eb62b67327249d728c278f923748e4c1e225791c740abf9493198f073a6cefffba00d23715a14967a72cc7aebb81ecc6e8bc88ae63a47507296a573c415d0163219f3b20974ba3a32572d29a479b88b89c241e63e640bbb93e2da0b73024ff03516ee3ecfbd84348372cfb574817ca878172a779ff131492b86e64c1b7054cc5fdb999df4a9578bbdd6a6a2a798f053f4c7456173cb8cb9b61735e650d0394ea9effbef615629c1f393949095982345bfc02b9a3b3ec64d9173bb1285587048fca8c740b6d2265d3e727069fcb90282c78f1049ed7925113279474d0d873045851b397a8c26036c83906dccd0eabe98f7d6854d9abc2e6ff12d84c2840affa371cc2dc624fbce873f38570dc9eff33f5e200791bc052307f5c718d47fc2a205add3fd9de2a81cb452a0a7b4a08bc133e9816f6ba852735b813a1b906d4dbd1ac8a968a8adfb6aded0d3fb87d80132a4b45ad39274bb72df20d648a26aca4beaafb971e13094b6d6525bd418b91320964bd90d3a98fb32d69222aaef2ea95c6b84b6b6aaeca3f6789944fc7ef17d218abc7667ddff798b74a0d5fbcc33d22d028d207f82286fd777ea1e281d7eb88b665e29fcda067bf9ef82a190c920e260cbc44d394b78e0439bcaa6371f8717ad82e02015f21e5dc273de338102539bc924d5a3f897eb788e0f3cbb6e8a34590ff5370bcb0f671ce28d6ac0adea17f9fda49c784ae84464991fc71a31d320d62c4199e4f4e38f390ffbcc7f15922c3325451371bebfaed8f8a7d3eead71c65878a705a6163ef649bdc6ae7b81110a2cc9576da6b0188337160cd518fb30fd5b0c165ac3de81002490f36d29f3bfefa2b4796c070c6d7d971d18bb5a573cd48cb3c64dec5bbbd7f2f6f60bff3db4833bb64b426c6715021997350aab1751f0d5382087ec133c2c43342b31eca32a4c17db14148fb48f9b7a103b2c0bfc0dc95ffe097bd8f2213a3f7a43c994599b05dc35d469fdc025e23e483039f271d948972417153c849fa8dbf374693dabd59d398732020e90d8f443ba45a00b430168561de21db76b9b714ca8260fdc4ff1a5713e69ed7296f5a5a9d192474bc6e67788d6a44d46b69ac97d0b34b2b08aa79d5ee7e910d99221e1e7f6383628b5383cec4341e5c3f18ec3361b2f2ee5935ed40919a4f8892bf3467803b720d29388b7cd47067eed9be7648fbbafcc7bebfc31a30d02f38b7d6be1579c751aabf679fa6891adf66da2257432c10d399a8d496d1aba381b64a5e3ec9a65cc8419151b638adea994c3590f77bf390152894b2784f7832d986ac51b20ba9719d85509acdedc0282f27065c87bf483fc74b55579ecc233fd6c51822ae03e46ecdb15acf0204b67852d525168f409d2a4309c5d9a14b35905b836091964ff4fec0aa91bbd7f39919eeaebbaf75c63337a4dcd5ae37cb60fa14bd78157ecef35ddb1cfdc366eecd48aebb0f5b395e4161c158c46870fc89f8f5918aabf0577c914511cac7b189384eaddf7f03cc6706a9abf219ff1b293cc9fa2cac88b65af62f0493e30736b596c119149af07e840a43dba8d97455a7a95368395488304626d3b888069a0099c435cc11e5981412f79153577c9115a1812b1c4a9ea42bbf11b40fdf930bcc37e6a6c50a7e3d06bfe7043adb32244ae359993caad00eea5f1de90355326e921145add1020ef3c2cbcbd102e7b9a150ff9672697d31f86572843269978cbc4c3fe067417e84898587d4de009b33094c8e8102c07facbda62a2996e083d0618fd9651e17f0fc025a946e7c28d93e18675ba69d8811dd06a2cc1aae28de5a6af30f305cfd8a090832483e4ff7c974d47ac96d2f3f7baec94f965a116c2734f87f468ec33cee1a58dd2ccdbc8f6a0f2a50a739aadf4007c7a529844cd1ce5efdd42eb28a0f5f04fde00dea586e4b8cdb8469b36940de19feefd342285c29298afcd3ddc368f6365e9ee255f8c2b6f70c1bafafe10720402a668ac3a2cb8ee75cb7004455cb4221486c3dee64f8f25f1c8b1411437cf6ba70afac5182b21f0efc1b5c71dd59f71796d2719741b6d5c757d824cdfd2d9459569427951d6974d94bea1fbeb0e1a23ef88774b270a8bca65f01a00299258847ddce8af8a7626c802b29dd01d1dd9d2a7243b334c29c0943e11ddbfe82cdfbd506e81fab8775a1d3c4f6c0744e1086763ad84e8f51d601e24cd6fcba1409abbb1c6b5c9bb9eab8ca30282801448f86cd5d8ec187866f1ba738863f3995bf4084ebe8f41dc1ffe0cd47d5a95dc5fc0dde1559ba170316e085d0e385ecf8646bf787b5287960f07bff9d3d990b3be4b29f008de18ccb46dc2192d0fbbbb9e701d350aae205fcd2d7314112bdc393e2eeb0a96c5f2c767dea0b82e9549f46a270b2c69915711c144a7737e9003b1c1b8684e9f2abe66ad1678c32a0c6ce401ae2d7507ff32c8f6179433b94764a6e4ea0a363e9a03787502c67ec97724d78c1257499379ecc5cd006a13acbba05dc7f1310bc75b9ec6a09f4d95b2ec11952eb3dc3d2dfeb8678b10b5db162b44af56ada56f7e311d6c5ce46472d9ba2284126f8f579d59a9d3e81bb1b26a7ee59c3cca96252fd4ba8e009a213911619ab4184e83683969cc4c474e934f36e6ba4ea33d9e93e984f1603e259cb7f1a3824dd8764bbe30ae08166aca84e63615e59c31d2a968a0beccd40637db22b971d51a4c35ce83108d8975d758669bd83ff9602718a882b943f80745d95a2d5e07ad0acf8023e24589fd68988a350c0ad571470f8b9a906648895d312100cd18089465de942ae0c7c8bffb5d3705c729990721bb0497dddea6dabdd530c9ec90286cee5ba3e2b699eb2de55de8aa0012b38051862bf0a7a22d6cce4310dd8dc47c206d9eb7bf28f2a53afa8fd10836210c0c461e417be42d89ea659a76d5e3cba08daa885b7f0302c5df05c72f8257f60e2ad954891841e67d8b7184cfa47e51e9b36f6ed1400aa84c5058a1998d07709b8b46630d70c73552ada18713310dff4177e2143aada1038bae9a64e02870a7535598d766f447b4e25dbd009009cc0e3421067bd0cd10735469b308cfd7a094bbdaaef2e31c22dc6c1a6d4c7890020b5bd04ef649b56889b2031c1923a69002448dd5db1142384cf517b08096539cc537532f43afc33ca77beb2dfee921178ad51b04f10495365e60b4b7677880409716e0eaa3a1d649a871234f86787c2792bf1e04a635e3f22b8b3b1382be942605c3855a940cfa3825a2f8993312a512c81f92aea0fca95bd1bdb6b9774ef3f1fd15bfa1eae84ebb48bc9b55fb2141c55cd3188eeb5049b2e6f3360e60c5bb7d91c9028ebc9287b4b5541a4ca290288e08ca46f80eeabbf100f700c873d7e69dee7dc80b842004b2bb93f0bf533488028d1a1137371417d06785372ce942d4c6589cdb57514bde81960475c22393eb02a93b6560fc3e4fb6ba692dc362d38e33a41b7df222e9192fdc0e0499f110e681682e2b500c00947d62280482dde5e26010cf959fc717d0837c98634550376aaeb17c1c80e3c3245c36c68f56fc8be117ff84957a75e4aa4caddb38779f1c50086ad4863002127b6c8067f172aa78137908fff82b2297a53d6232c26a2e94bb25b7f9241013adeb36d1e437f2f6c0d983efdb78608e940390005d63c081eaef21d7f00ddf51a3fbbe6175be436a9b0b75f198c30efee2821ac5f088819eb96ba835999d0012e7a0c244268a658bc6b7845d10178283cb8add0b81405ccd64000c1835111511555eca14f7e3cc9274b45f817039ee62cfd95363cc66f5397f88822f790e0a86b6a69379dae77db24185394d9fc73880d39f6b3233dd8d3b912995b22905563ae8cb2b1f3491feffbbdf2c7d2e89dce1995d2429bb0a4c550860835aef589983d3170387971f8aec15c18450dbc9cd12f402589dfb17216e359072ceec8e9484f9b490a288dd4b268233c40ad23721961c390bae0a181c4cc8eb68eb4b2a5db6944042c6ef67550f9abf3b9084cfe8521b62d727416576a58591dc65f79f871958243ca4415d92d3e588c4205b4a3806d64224e94e8f04dbf7f75888246aae9f894bab1ea69d7e10b4f07e8cf2dcf70cc62571c737cb6b6c68b0e3fbe4b1931c8615a793fa3db0a45090458ff62ce8bf421907877e555cdb97d3709bbd8728c4432288861a56cbfeabbea5f225cd29e31d99eb788704a93696f5e81307fc047073657405630200035ed48216bc666598aac5498e9fe513a9e21eaf842d382767220cf98104bfd8866621efb63b2eed44028cdc9e2f64cc2447fe09f8d8a1eb3214272855b525d9ce24e6daee58effb4af28fcb0a4685d78d7995081b5cafd1cfe343aefc8e9cb23507fc0470736574072102b642f0e5b34746f2f6e3b511d037e16bc720292da4ea2fb31ec09f6fdbe777ce00010308f40100000000000007fc04707365740220230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b20104000007fc0470736574012109a05049129b0d9e61c1bb87a9fa02c2435be35e3535082a772f65222f2e4a48cb07fc047073657403210baf4431fbb8f0227bfc0008368606a4b8f2bc1706e1a06cd4d83706217b74f5b90104220020c773786f3addae4c21acb094f39122c032daded1cb6225a64d923a0344087bfd07fc047073657404fd4e1060330000000000000001a4092701f9170991583ec8b5c9b5056ddaba7637f7d6733a57dc4990acba6d15e9eb5dca55528e179b98a9e09e05ba940b59558b768194ca046c22e12ea310ec05bcc795ea419277649d40b145e6f0accc31f2d7eb39b29b51d3ba3410536b9bb37bce9014d352b496b93161fc799b23939ee635f25c4cbd6605029722ac2bfed7d55fe4120c0cc880661ea3313d700e261ba973bea5df4dc4d8a05652d193c5edad01945862943a6756d4b0ddc93ad5a444ecb054bcfbbba3635aa752d9c4fe8f93fc56c8066b3fe344143d65328025f5184308f2016eae65df8b0f1818f59f5a9539eec9c9c94704ecac269b008ef25f67ab2b9ab4e903b1f2372d1d3988b86e5efa639f9f964599df8559622c8d11f6bc4f706c74e4a603a3798005e210f93d9e6b59f8ccac907dc9cff127c552c8c89981e1b91c817cd365422eb6bb0c7ef4ddddabd306b0c32a98464973dec30def4149f8e9f35de6840cbd0ce8a2f2b5dec15e3f3c27d0d2720159cabb3ff2f73b4571eb53ec735d7b0f27bf4d23cd8b0fba14d5921d464beb3fd4f2fcf844f76a7f7f129a3e7e1c4a6e4f07490f2cfbac4e6730148397c73720d35cc5a46fdb1e774e17f7b951887c1c72ae4cb25ae99bee652ebb2ffd9f4c9400ee0870b27a5dd8fdb8224aab6738399587b4513d3441e26b898b35f739d64047ce8de70347042fd18c3797b1de82cfe23521dc8e5c36e286c05d4f81508e282018cd8d263164512e6ab02a22b3d5d489e6074f9370e218a3adf8afd229659f5046749db9615541408739d4f0646a853b25f8e549e85708c368e45f3e1d20ab47a4ed647b820a360e4e351e4caf74810098f0df4bb74e0e69f2db3284c30d24a0d109d16c37509557ce15c81812bde78085f31cd7c93140fc43d6580abc7ca2adfe6354510fd8104c0f1ffe46d24f31bea808376ac27996fb992de15b30abf4df6f4d08860bec27a95bcee0df8f0a5bd7f72938ca9e917f39c5425bff826c4a84a1fd70cff87be2e8619e0d3b729f0aadc10d4b70d10c2c020a99d03424a595294dd34b84c83a2d434b98fb116e2ebc8b2bc444740ce090816218ea61da172e30db5e78a58ef495812c287e3cfab7bffada0d6f2fb3c153f1a21707923ae7ec862d44d357776803336566c3bebc9f53177a3a8825478eafb32a61bc04d82994708725e0ccf5d76dcac946f36895402e84856b3e25567156040510fb299b88c45713175c54fb3d54aa6d383c423807778a92b7d9bc8fa347923749d18c96b20cea608b6380b57c35b4408f724046f6f65fc0d71e237c39376e1ec6dfc6a2ae83ba70d25855c10e6a8f425951c52fac7758353e2068e5cb4b6ea066491e834a47f8a5cfbd6ca91c832fe4f77b3b3140a6c1c70055d52f0aa67e7d3ba18e5bfb09f738141a611002ff9e34188d540ac42f48ee06f6925cbf3cca5ca033c4fe089650a2b86a8350367e9cfbd07fac9667c0171f54311cd5b371576790e51a1fddbec64f8f559cc08b1c0ae5113bb0c532742d90c1a266bd7eaa4b1d06693ff12d6e66c51bb1d12dfaeb70bc56d5870fb5ce4b040f86d7725ae53b18c1b12a84f7e3a2e7d748e8a899fb917987518c6dbbb83e3b62b31b0facd3515cf2e18b4e49218ef48d97df114971b298a6c4af231a432d4ac295d0d1947e09ccf41ab104e5435e7dddc0a6815b636a31be9a0d9cee2f22c745f05f384bd703fbf4bbcfeadf177abf4ca4524f38a681e5436a06abf9ca70c7569a7bcb8337b0b0c280b5f790ebe1258bea9c1da58c65c13991efc1ef8f7c75ef28affa340d84b33398790a9a77d30b0ea35613b39b1b7d8667fd7299e2ff38de468f625df1485ee3b2759f7e27ed707f5807342bad0935ed42609849d7a9d11addf8a92df7933ec44e86ac7f24d55a55284fbc4e4824a2b4f8bcb146d08d7d6d3f551ce1b494d7c0fbd6e82ba4496f3eb2812c986f3e2e627240a9102089725057c1e5dcaa3bcc5efdd6ba7e8a496f609aca3dd82816552c669c255283f9372be7d8f12b044f8c41180aac612245945a4467fa806892d55ff425adbbe92bc892698a32d39d7c23303ef65c980c608b5ac07ee60ed2b4e028a5565f5aa3ecd1e15787c23716697d53603f72738d5ca88c5d8280c3d8af9e83c18be420bb87db4b82a98d3af42f9191d4023eb22f428def2ee99d04d1497cb8205b67c9ca893f7afba077bae5fc11cfeb53d882763f2eb6731bab373923a63dda3ee1c1b6207e0ad0884290333dfae3f80e85cc50a241c73d7d99998e9b14c3e52e660edfe594e1ddcef04402e8fb01efff9c00d9a6ffc955e219fe9c92cbe87865f7a25ac68fa2d360c584c33fa3e40ce91d021465610b85b5f1a98e6908a17c5e138f2dd7da2769bad8789eed99828e8f23952bab7e9ead5fa9bac781fecc93de7927544736fd5c57f1da32be31b85657c313cd02104e1b889426bf4c8e9daa741278ae3966ea673cd3fa164ea7cc947b34f86c92084043ae846aecfbb15535a9b21e27fcda9a21bd821072135554e3015d5a12d1c7f2fe74dcef63fe99c947b55a4253add3668bab2d356d58909b7b0784c0759dbf1036645c5d673edb45245f5e78407978b63d5c25ada76b176dcff53e76801d7c2784d4687561f32e7a32175c0fc77227b3ab0a4054462d042931897530502c85f0b976fe3e3fd5488853b20dbd27d0d736e9f44fe6224b9610af05c462f08d328cefa4bf8530250f5a4d3fa7f58f5a48db45cd68d0dd53a2c66758a20184cf226565ce558e60f0af982b02672f8ab14333349a5e5a5dcea61a09f93addea3858fea4e49c84f5d8269a2fe52f072d66240e7d30b7345c841a135271c07e6a142ad1ef1955e29469d3ad3d952ca9055696dd825253e4a0e4822bf3119f1df2404ff9bfa39568a52fadf98ac6d5a4653862d91b995ab166fa9de73478f7e339941e036b621f7d773a5413471d6e41a68c02ed89a4882bfade2b10e399f4515470ef43930a911609911f8b66bc060ebb186cdc4c1aa3cb573d696b0f48a655f8877f68f678bbf467ec872724181d6f983682389fc8a61a7525bcd61ab37930e6eeaac82b7adaa145e2a0bf67e235a657949708f0640ec802a1741590a505e4a109ba857824e00f8f675ae6bc8a0f32aaec0f38398c33c8f53bb3cd53bf697ece4372f8f252ab4b314769f1393aabb2675f44c05dbd279732824d49e77f3df8841de18a30a9bc321ae43c712dbb9f08b457dd3e3f13244ae03da16acd6d3e861efae625478a89020655f0de72ac174a50834db564d4e60bef51e28954a938c64a6ee290523337153aa4d6cfaa00826d2aae1b57e3ab857e10cf2b497597ef9395fe6afb3d74b21c5d44281f756e018539ce8b7f4bc663db1eac9b976da2f994b193a30bde0394b7648b55ad9bde958ec5bf89eab516d4418451d8cbca637c1d6dfdef61712e76aa7f12151d5717ba1af71c4754136563ff2667bb984a80a51ee9112587cb77ed6fac8f4263fce637adcab9f315d6beefe509f8d34ae9c42c12dd42067584c4a96f6c15fe590bd8616983abbf453ee038f385a126dcb71919dd7143a8c27186e1c6d29d36c1039a8f008088e03fe7b97fd7f285a0817ca746438e0949dcfe49fc67ae967d13d1d1aa37c2e410bb95609c7ba0586c8fccdcca933b1a85ec97d1aff670004f71232bedbd192ba5e5a681867470c4e146221bee322a5f458a2bea1316d3f4be26efbcdeb4b1062e58f2fa23b144cfdda55aad3ed7142653c7948b6d854abb8d1201e9a4de07b8985fc194e58ea08b4c8528adcd73ae6bcf5493b7b8ae1f663ace587a87d5a341828612bf82605ce6cca9253057a8096f396bdacefb56f9a452849f0ac652738b5b3135ae13af532dac7d9d5307f158d844bd3664865724aa9907d2ccaf4cae78e056ee78815cbcdccfc38b71e0c648b1fd9f710a15d799e237c344d53923d3941e73f924b75de975ead5b536d632338fe83a8d02a4b7fa9a6f1a5df9de85c88b944581294609c880ecb5eb45de087524d896486c200ad68f5c45e43894d6e84493bed02602ab075c8dca1e57e3932740c25a9401b5bbc1bf524f4885f77a501bc3c313666fdebe7497a2b55031563810b35f33503cdbeb46de12c0482c70b07b5ca3c4e2aa21be75c33a23e73c03379c434dc8bff54dc40b0a34f476d0fd1de0422ae6edacc8a729ed2443315336b208ceafa68a229592e42f49501cf03fcf04ed2ab60cf7c31bf4d4fb5a8d7b8969ddfa8a725eb9d32827446c141170bec2b0dfb5c0a6e00cf83b856ae71b5ce46dfafdc12f94a7f8f3d1bf1eb769f52f43238ed411a97e55625ddc208affbae8ade42829c82cf4f62423830db9ca4091fffe3d282c578552649a34cbfeef3d58e020978137a44fefdb9083186dfd712301431b78095a0997f0764f3ef29f138ffb322c8e187c260c90a3ef7da5638e8d961d6a6cb1867a86d3eb23419e101e18bbf128c7a3492e8447533b7586a81e52366bdf2c376a57847b8a44f7b55eaeee6ab2f06596a87ccad4a60881bcf4b4b5d5f0de85593b643c0c4fe1771a0cb5bb0402fae0f1a2dd811d6495676ad718b73d4b86fc79ce074aa9924829ceb4dfd4e3ab70fdbd94b5ad1d11ca9e582dc215577056373ed91ccd6472b3ce02dffa8817e66205b179f37bdda376d25d6bbcc83a15ad757bb7843aa03d2f7193d8d707ccf4de1f5c8694aa212219752addd55cbb412c05118c30555ca7ea820bf3440d1dae3332947a1cfc22e366d1ad120b906274883d7769983eab6afea2ec0672a2ed25b2b3390c017566e52a0ebc59e6cebb89918d54f75d4094b943e7725468034d2ecfeb68228060bad59a4deaa40843d0cfe0a1e58855113bb7c5965254b82cf3997924d0a8b1711b1e1e5b28343168f64fa9bdf8e5158f33c91112f61ba782a4f97c5456c9c8756adf53a7d919ad90654a1c035d8686f834750302added0fb7b29a3f4f43f4dec29bedcff3139cbdffe391ad64b48eb1691be659e4e4dab266b7221a2c49e42d8ab06ab6d16efee2a0e0bab140aa34b53f70c808f3966f1a171bdc789e23ca0246328dcb1680c765fe262048b869c56f381bb7ded54e1486c0a3660d990f4bf0e5b4eec4cc0c3abf4edab6346cb531194c27de0e316d9d0a823db00e20e2fb99c2b5383213518d0cf604ae8f69d2921b442b729cc78579dd242314da99f01df55a45dadcc87e4b216aa0d817e60673c9807a75185e52a70f04ab8ea28cad805c37fae5417f89af44d4a5728c187c74ea736f21681fa7f93dd9bcab9b510febf2c4b5c64cbedb6b59b77a8816728dfbaeea423bb7735ae1fba08964a343ef9b5d36dc7f8cba10c8ae69bbddc4e524df73c940f762a2d39ce7ff235425942f2007b4e08f2002018f720e0339bfa2704a9d74798b13c6fda733d965666ec5592d1d6dae7af6efe5c87cf48d40858937b1ecde3445ab2c1ba1e87e1c2f3ce696baf3efb30ecbd9dbfab121a142ebfb4f09f343f37e8c5c0f40408a339cc3e4f4d34742d695dfc037744ba1a794abb67954d3a6509157a3aaf293c886b58cfe9bed8a3dacda2a2ce41f95e72e46baf65ca666f61bb548afe8f92e1c4ada258f46178a4591015678fc542d412f7ed323d2629c127890ad9c2151261c4869f59db9648e09c25bd5ad8cfde27ea4ef22666ae0f30a4d25c3ea1780a6c3eb2d94d0f48c1a61701707e57405e7e9f448452d0a48af07013fe04d6fa62fa5fd54139d95217734ff4e596f2ac975bbfb8fda29f7e6f211eb165480e44fde387d4833f89c7162c1bda3265a5271946c5392921861ad5d9e5b672afe4778301d7a8a8d8a03ca1c99d73b2ee43281c0b88ca4292a5ea29a0e2377007fc04707365740563020003c54f16cef0fd4185f35dc6784047913c89883f72b68aa78c16f144624430909da01633eb2b4b13eccc2b7a7ac5ef3b13505cea54e1d390944b5680e6860184e35111675dce504784f4ae8121dbd1f76df3d55ef722f3f73b5b7723efe2fd34b107fc0470736574072103652ba5800d63722adef5232d31ccc12403055de720f0155fc47bba5750ccedd400 \ No newline at end of file diff --git a/examples/test_vector/raw_blind/two_inp_two_out.hex b/examples/test_vector/raw_blind/two_inp_two_out.hex new file mode 100644 index 00000000..df94bdb1 --- /dev/null +++ b/examples/test_vector/raw_blind/two_inp_two_out.hex @@ -0,0 +1 @@ +70736574ff01020402000000010401020105010201fb04020000000001017a0bab8c49f1fce77440be124c72ce22bb23b58c6f52baf4cdde1f656056cd6b96440980610bc88e4ab656c2e5ff6fe6c6a39967a1c0d386682240c5ff039148dc335d03b636cc4beba2967c418a9443e161cd0ac77bec5e44c4bf98e72fc28857abca331600142d2186719dc0c245e7b4a30f17834f371ca7377c010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04000000000001017a0b90df52169792d13db9b7d074d091aaa3e83aff261b1cc19d291441b62e7a03190899a91403ca5cd8bded09945bc99c2f980fd27601cada66833a5f4bc108baf63902e8bed2778bf381d17241be029f228664c7d1522ced55379e275b83fe805b370216001403bb7619d51d2af2c5538d3908ead081a7ef2b2b010e20cbbe4671915035276d8df41a7ef0b6a36dd101de48a4076046fdba3b0bec20ae010f04010000000007fc04707365740121089f9677efff5962164a434b0bffe53d90c271deda3eaf30c1d7a55105d24b874707fc047073657403210be6c538eee2e29d44c66a3e66fc8165afa4081557031826a070703cb69faf24380104220020c731ad44c00a2928d508ad82b05868cb42733a5b0d0b208f483307ce8c328d9307fc047073657404fd4e10603300000000000000015bee4d0045b0884354b05d0285abb287ee8e539e8146b92dceaaee6fc01af1a3730cece4c77caefdcfcea1073197ffb8b3ded97d0dda6f49752b31f23e196378a3da2128a8c45ecb7eb1c32f0c0dfa1ce7bc348e799f66d547476e976baece7445790064b34b1526c88cf54928acc906ec0925327d797d36d0db6a43633d7f963ec9b4123a5ea31b655882329afe2108d123f76857fd3ce344fcd651b3d3066aa506625c9309acead85fb6c8a7d977db5dd6cb6e2f99fb3535746258d42d95dd2103bbaf5d45c8a2dfaa2301eba08763bac8ab3b59bd0082a457bc0cb257c43ea6354348e73d8dee101864068f0e8e30e6711f1597d7b529a57e95fc0df813ea160f5f5152f341087868a680c1bbc4b2a427d18cf8f12cbc4304dd3b1ea4c916467ae48b3b53062ea046161170fbe6ca07462cf3e5534d638c91fe6919318c4b2e98ce0ebcb245680d6395ba746fbf510a9a36ad8d933b023eae39973a39162178148a897b5174940db46e4be54794cea11fafbed48b42eed27f9915cfc50c958e88bac551ab1c9bfa03653e558cf87a99ff13440f4808e27fb42ff3cbac9fe10d1de07dad4807c9f945784163214c739a9eed8bb2e7da6e1fdfa4aba5d5157787d0000b220145572d3d9253cde5c1132370c1849847633287660b429f20cf1f72e4ad6ae59154c3b8526fc5d30523ba962a25577427988d326e207c97c81b479540b1c63aed0a4b1dad9279879740b2f6a0bfc37aea8c81b3b35b630dba8d955afbba756b8ce9d2a9802939972c5f70efdee8778a98360eef15faf77e38627fcb4f98569d68ebad3bb667e5db46eb7f4214846e651224673dc998c55bdd9e0a1629497a75b76160865c7ac3af874bc45a42269508f183d2458835b1734791094122f75398da2bb7919c67221ee42242c298d744fcb920726b84bcb78d7eafc9e0f61a02de87ffc5c674390a33d722b50dc35bbc6788fe0fd24be7a9e358113a4e65b49655767b8f3c3bb86cd5a56c79f9148945af63492dffde82d682eca4787f203e49790650395c925e6eb68bbb3adda25bb784365c0b3e241177ca7de58252b8c79c5a865d1030f3bfd4d312bdf83bd28241389085433c34d747f9f45a0a636e700805e619a19739b15077d44d5e8969a37481896acebe7f350449c69b50376b7cf36e906d8a03aa7221de67c7fc4c5408b55e2562f7b783918004d93b284c0a1c3d596c7c18aef5cb77ad09172bfa888f5dceabf57f708f5ab61b1b4159742d3d35332e9942958ea7f39fd35cff66e84ea0ce3b38a85eb80dcc1201146fc3854cb40353a078f5435fe2becfa383b3a62564645250cf7feccff5414581e747166625477c70f5b0f579bd578e03e6b8ff8c3f0e8a82566199ff542b239f2999103cbb61602bc2f346702204e50c9ce7b85f3b8e144227ca8b9766cdbd498191c385e3a4f024c9396763eacdfe41b706fb23d4f065dbaa43d323edf1811950c71d4475a3e0913d3851be4e921cc933bb06b0aa20524267b0e9847b6de3883ab8c5be1eac17b06c946f19dc2244901890cc290f2e66147f7c47a4a29349555ca020e684ccba5f8a6f1be54e356654d3e8678c591633e11d9ee772940d92f07ab83228c0d8840cb9aae514c03358e973a2ef319a39a3ce819efc00e5a493fd582be164c1460f70fb2e103f09a2939f779377275be736ccd41f847133b3d400477582419fbd2d7ff328d88256cfadd750ed7a3c6657e332f635f0a90602b24135e705d1530531b7b738e686d83accd74874e160a5716191447f832d2f899476ec9c53204fc4ade5b75cacdc869b4088e009e12d85beaa3450889de0c86315755ec7744be592f058fafe62f9a89875bd2781d5d6928c8475f555e77eeb6e688bd8d3bee0a54fa1cb29c1b6b438d3f70ee4ac83fcb6f482575997e3a69240c6731b517d8241cd39dba6b02a9e3c94ff2470c61c693d32b1c7d22ba38f0da21d92150115d580249380500109215a05baa63474f773892b1870005e76a68853e6a8cd9f74ab3f41cf0730e702ce94c420dbf66a63f342d927923d7d25ca3c3823b6c352ae2ca127bf811c023205632ddb8f9191c2f3384a61038d1c4d32f808cbaea81d58f0aebc7c89bcb5b98f92849f4fec19cffc78173e601eb412aad24eacd74d17cce3a9ee76670367ed87c2da3db5e4ab01d4d9626d0ae00da7dbf6b8103d5994cf66ca56bb9aab9eabee2b14ef798d3fa5ed1dc16bb500a006c3320941c4c7f6f80c8b1bf79131a5e11ef38db7c42d4c4939711184ea414e2ec0cf5bdce3d7d0d72dc883b2595e5f0fe4c14cddaed61108c90c74b43cbf719bcf7a934bee760ab33f50cedfe3ce61dda3e4bee1ab4dc687f988cfceb207152f7a98c0fa3a114d2d6881034aeba877bbcc368ce591707da45becc48318f0558b3ff65d677578df9fd9e9a18d0cb08f2f5cd4f44925259225b1c4c5ed84ef30d136e7c66251329a223a1db0b485f1c634981f8860aec1f3dbda9442e5438f6fb09c7e1ab43e2b757911834df8502ce35e7be947771f7416f7ea97d26dbad7221614175e6b86f1a323e26c4f469236d1b1bf6ef28823234c73b275c5b0ff39e8c04d9dd8ff7f6a57214d803aa13dbf581295b92ce2b8a9c0e3fa8e4fe4e74c4bc2198fd010cf84681cf5f3fc9df1638c403006478b1cd4204ef3b7a6093116021118a839bd3ac270783e67096457a86d63654e268821ee742241ab5f7e91972da0a94f679b4c093d3f428d5876edf51459331df084f2fa245e3648e0cac904a82a7d323b965a39683ec0b2721105683e67d65fe10b8d41d0dfcd5c31756408c94cf425ae67f95ecac45391516875870386b675e24c462b1f7634f758723e6245da6a39da69cc2390374eb89c6c662d94eeb2f1835e233610072936be248e6a3ff87f9950c2eb2f88568c31dd30d1f0c0396a42f6be2505bf616c63f504dad5312fee2f083b406fa05cf3c998ba24237402681f0ec393de4650ddce3389d4c1541d4705d8f50c2c6d4864934394fba0c40df495fe3a5bf58a5498c1e63b5ee5f9a4b53dbd913252d4808f5bf5fc3c6dd74c86c974fc9e5afb1a7ee3d88738ab1fe9194881cb00197a31d648ae2448db872528d5f7c366188d9d98ea94497cb78ffacb5f6e8a2c8520d8d196fe0ed13e2b2a5a08dc45d8ff58a8c5d3bcbdc5d80c798acd0eabf146459e79db12c2a43c55d14b2dd608475c606fd4f943ae7cf62131691768f710fd718fda3967146daa8d4f34a3b8fe8084f4f3865e9e0cc1a0b0a799fceafd1b74f09f524822179ce38cf0f2c4c8623a3e3ab2cc20f5dccdadffacee62c120657e3d85fea93fab645b5fcdb812b130afbbd5b19e86aee66011b520b0affd84af4bf67a28b309cad66cec25a5de06390a4f2a5af47d59c006010a8ffd214149bbe47bf847fa94c2113723c9476c2e6dd84411bca16dc7bb096aaa3e9a9be27811b52838c78d16cbd89cad379bcc0cd966dadfa428f9d4d8bf133f7d21022ae3f966dbc1409c6d57cc46ff4434fd90d79b6c9e68ed3795ea6191a7e309783badaf8db9fbccbea905ff9d423370810f02add38ad3088cf3393e7c6d585b76fd78c67766fff03ff54c01228c6737da667147a8b5e722370a24db8c0165e34888920d70fb1356eb731d0024676444e2d37860eaaed9790f99069e622afe6ff12d516812022c46420006253248eb292908ab8658cd874ef05bd568547535fd54c1f0b5b816e4d90a1bd55670b96691e7633a2fa45be6a29cf7bbbcf9e5debb4b7a739b36b6b326b719a12cd0f5e3557a2fd94023d8c633fb697b85080a39734013b4ed9a7500aa8093edfc3810d4c890e608951e57af8447349cdf45a076449241a047085702dc3e44814390ceddc878791ab3a6503f48f48af25fe5cddf61e7870ce80e32e5f22d5725bfde4f319c2db7cf3ba3b9952202cd24210ab8d47fcdaa9ad5e2a9d2c7be1ed6b67cbc1a28ebe565ed0337c09c97d2d9608d97905c7201be7d4bac785d64caf783cb3ebff817bc5ff0448899537ce6911c9803744d06ae97e6d3aae037697986cb20061892ae72e05968e9bc54153bd745012af719fe7fd4b917689a6cbb94a9bc8cce51d234ffce7dca804e2b2759f473885855983b7b290806884fc66e724fa848af2de3164c611c630e5bea712cdd9a9943ca3ad55994e5587c5953f415171bdef3c42783b809bc5526a88aae66be626d73394a607ca32256198dbc026d9e09e9659ddb66736215dedee0cf69a24490a53b2c678e6caa2aaef10a4610f802fc5eccf558629554d6388465584514d3dd49fc00fb267c39bb746f76ad3cccabcc3cc882815bd9466d593270a5c9a04945dea7ad06ddb73e66b90270e859a109d52251d911e7177e07295ac9788ce970c1b718fb0c2f327f44fd0cef3cb90071ec85e958e6ae60bd134ae307720cdb10be5a0af8c051295df905447b46206b5e04d775b83e11c348458b1bb310ae25c54e8ed1488a95b2a576002198c80e0e69d14fb7bffcd89935faa6e3f9aed61e0492bb2bc334e389bd559806eff9e16643ddc4372ca83790a99bd4f5b042779ef7e3039858881c8ce0041d43ed6b1f42f18007d046fd6f65907cef73fd5e6cd514c4fa5ebb872b140d0b2ad43c58f2f2216522e78a19828baa34ba56d71414ae869a13cb708762565d754f0754561adbd9145d232034229d2840ef0c96f7af4b232c5e11ae9d018b87177a506b66571affdeb693e170dd12640e86fd430b64509d02694c52a54f23ea96e39be92b65c1abcb262a71d6e884183d4bc6cac8c79f6e94b51abe484794a42a8e1be721cd59383bdbf327a864b2cf27566f1270d82ab6e8b3b934e953b7d48ec4697172e0160b8435e8186a7ea685dd61402b0bef09137fc52e19ecb53674a1c0af17fbb0efc2b4d69572b32600fdfc3c85c79ccb2685c665aef16748b7f15862f435afaeef00f3f927cd3a60c22f137e9cb2c38a9652cee570a76351871ebc9b6fc720b9117b1306e8afb298e3a1d0d4b2c5ad5a21e9de4c0938a98daa35caa711bd6d3312a7e941b060c4a1ec211c31e53da263995d3be74a3ca5a970d3a663245a5d8eace336d9ee847619bb337e777b961805e808f7cfd8f09b93b6ce6c277396e6cf0a6153fb11625f421b02055dc53a0bb24786f4a74211bfdf44193e48b2c15c7c85acd3f4f0bd26644f9dc62653e366178645899dc06d191c785831b98de593519c0ac5849897a01b4f8c7fd559edce1c878bdb9dd091546c64225fa81b1c7dcd6fe72c8131bb160043976a5e891236d3552598c0c4fc734d1cc752c10306dfc8391f6d658ab8b7a1f350e1900edda29775ac8a531032bac968f6981889f13cab07f49c864c3ef3ff23555c170ee9730912465e6bb3b7508e11ba27001d9dc0da8b34954a7c46a2d0b0b75e73ad66278c8acbed3249936a6c76fe975954f7889bbaae5f73747e3ed02811b015beab9d1515e396d37b2a116b3cd8863517b88ee91a854e4740304f7be41123692181a8ae22090956ba619fb948d3f08108da17ac2484d3dd5795b1c716cb5ed3fc08b6449fec814018cd883b02256afadd1f35a04d9d43f661be624c0d59d0d397e8ad6917344203843495162217e79bade11ca32c0a6918350520e6c3f31f866b89e3497b518cdc056e760813ebea7fbf343db50ca2450d5b7a1c5f430859e2040ecfa70cb0c3e863a756ef6ae2e26c68b543d35beb44513f8295872df5b4062c9b80742ca7a9c2ec36a1d9bc936c7fa7328240a2db54463ca04a40dc0c9afe4d570012259c8c436d322483b17a705d60a283e0ea7545110e898ba43ac6130aebde014d237482307ff28f385fce8530814a3b3507fc047073657405630200039d46edc3d830d852edbdf28cbc023dd1666cc4a806ec9d4acb2c97865280698d61fb6b2a073be4ce75cf8193fa9b3d677146575a812873ae7eaa7b08110cb88904273dd65af35115dd116092a56ab45fb57178d8c5ed4cf99474ff760f6e987d07fc0470736574072103701e53a6d0b960398abf678382a4d17c5c153d1a2c8ffb76b302058ce915bb0d0007fc047073657401210821b4e7599b1b4faf29b521f1c442dfb0a893499d374c19db939c0c9a3b9798c707fc047073657403210b8529a893bd81e38743e8e3015095077f6e244a064d64708963c6af5024b6aeb3010422002058037c2d81d3122185c8704d4276e2629122c95b8ea68575c451628d37eea84507fc047073657404fd4e1060330000000000000001ddf2f500cf45e30e3911bb51a7ff633c1e329508e42d9aa49fa8248fa4d293b87005db08b17a472580ae2cdd53c40b2a16af885d6dfa3d5b3410d389b045e6c12405c635511ccf008390856f4c6d0ca472148d2969fd39e1307bfbb8f8268ce92398248f08df7268bbe23e13ac3801ad075376bbe9684db9b41d7b1148fa65a129e1d25193ad973ec7af631a0a30c5659409b0d00e6fe44c2c507d52afbee8059aee7650bee9d47936838e77f00cfec9de3b9efe72cdeefd80a19a9c8ddcd6cd9009ec96800f9fcd709f1432b5715466d601b5ed10b0dfbd09b954b109232fb5a24036b887d26bab43da7eaa303e7c8e9d02950610aba09f343a65a7cb4550d6f6e5c0eeb9410b68f85cc3b05b9340e2e9c94a473f2001f5aea7e875d29e8e0aa99c09afec4a5455258c803e55b89cf6e89796a6f3f9fe11ee05ec415c6ac08e0f1adf9dad681f8121f82e3a9402a2fe13a016dc10d4a605578317be0d9d3e02b0989903002bd35ec4970701da6eb6f9fd5e6cefad137dceffaad968d5ce522e77c76e665529f70c9eb805a240834f4c8c795912c37e7efeb73d4bab709aafb66bd645cf0addefdd83d185c1b4465ee16344575eb2aa3acb057c37d7c22cd03adc6701f03d4cfa20ca94458b3b87e3fb7de8fed28a755bcaccbd348dbc7f5aacbb25d19437d87cb48b88fc714f059b328c374fb2695a26537330fae42d3fcff40b07054be10508fa78a9173315506119973a9158828276edc6e8ac771476d7479d07ba73e9c5b58d61367d24435a1062dfa376546e843b34829103168db63b708a13894813a442f889a423054160b85a2584d36951136bafc490056f1c03c44580e2c7a12b170e5c7a3917720bbbb21e5da616649b554b7ff57dc84dc3935271a8aa6fb218becde208d83e76bb6197f5f4f729f3c57b4280c388bf4945206fa769b672ebd47ef2d0f777fc9dfb031cf7c333c6159a75a5b33ebc6c4683ef140a144a5ab15b2b6b72ba14ee7595679a0f7873a75b9a79e03676ae94eaf9ea8d3532460f3075f40c6b9848a183fe46651caeacd51551f308eab6ba8ac27b93865f5dea0fcfe1d7dbb745695dcf187a5955414763c88ec01d299f03cdaec48eba946c7ff6031f28a5e108651401b5447c0d21c449b387765a621c057eea7d494302222e3d1ff6ee9f8a0580671d9fe3ecc310e84b554e5c823d81b8027075f749b2dc8a6bc5a99fed4a0fc46fae59cc2aa9f1471ca2b0d9fafa56927fce0f1df92647ff5aaa083bfcb7078bd8ee142448d4e1f3dda670ad93feb21028a57ee9a8ae8880df5b3276e078555d868be0d8843b495e082d743328620f36f7e106733bcefac15076826cafce271caf1520cc8746948e71ea73eef5de770ecf5e0fe075f04d6c9cef1cc1f262e69fcfc89862877ffd520e25405d9d07efec82386640d97147e4a6df66bb1fa1f20dcc2ee1fd6364bc2024c0a4a2ec00b84616231c02aef16147df509cb830b9fb20111c60613d41169a24ea67c03cea48e1905b00eba8a61e967b9aa5b6e1264b0f3e2c051e47f54f45c6fb78b46b9993c7d223d51036cb77f5e31a4b30892e8fd7ae7028322d6402f60712f712f84443de083767c38ae88a38fd4e4bbec58015f4e49e36ecbde94f23d0061edfff0e9eb62b67327249d728c278f923748e4c1e225791c740abf9493198f073a6cefffba00d23715a14967a72cc7aebb81ecc6e8bc88ae63a47507296a573c415d0163219f3b20974ba3a32572d29a479b88b89c241e63e640bbb93e2da0b73024ff03516ee3ecfbd84348372cfb574817ca878172a779ff131492b86e64c1b7054cc5fdb999df4a9578bbdd6a6a2a798f053f4c7456173cb8cb9b61735e650d0394ea9effbef615629c1f393949095982345bfc02b9a3b3ec64d9173bb1285587048fca8c740b6d2265d3e727069fcb90282c78f1049ed7925113279474d0d873045851b397a8c26036c83906dccd0eabe98f7d6854d9abc2e6ff12d84c2840affa371cc2dc624fbce873f38570dc9eff33f5e200791bc052307f5c718d47fc2a205add3fd9de2a81cb452a0a7b4a08bc133e9816f6ba852735b813a1b906d4dbd1ac8a968a8adfb6aded0d3fb87d80132a4b45ad39274bb72df20d648a26aca4beaafb971e13094b6d6525bd418b91320964bd90d3a98fb32d69222aaef2ea95c6b84b6b6aaeca3f6789944fc7ef17d218abc7667ddff798b74a0d5fbcc33d22d028d207f82286fd777ea1e281d7eb88b665e29fcda067bf9ef82a190c920e260cbc44d394b78e0439bcaa6371f8717ad82e02015f21e5dc273de338102539bc924d5a3f897eb788e0f3cbb6e8a34590ff5370bcb0f671ce28d6ac0adea17f9fda49c784ae84464991fc71a31d320d62c4199e4f4e38f390ffbcc7f15922c3325451371bebfaed8f8a7d3eead71c65878a705a6163ef649bdc6ae7b81110a2cc9576da6b0188337160cd518fb30fd5b0c165ac3de81002490f36d29f3bfefa2b4796c070c6d7d971d18bb5a573cd48cb3c64dec5bbbd7f2f6f60bff3db4833bb64b426c6715021997350aab1751f0d5382087ec133c2c43342b31eca32a4c17db14148fb48f9b7a103b2c0bfc0dc95ffe097bd8f2213a3f7a43c994599b05dc35d469fdc025e23e483039f271d948972417153c849fa8dbf374693dabd59d398732020e90d8f443ba45a00b430168561de21db76b9b714ca8260fdc4ff1a5713e69ed7296f5a5a9d192474bc6e67788d6a44d46b69ac97d0b34b2b08aa79d5ee7e910d99221e1e7f6383628b5383cec4341e5c3f18ec3361b2f2ee5935ed40919a4f8892bf3467803b720d29388b7cd47067eed9be7648fbbafcc7bebfc31a30d02f38b7d6be1579c751aabf679fa6891adf66da2257432c10d399a8d496d1aba381b64a5e3ec9a65cc8419151b638adea994c3590f77bf390152894b2784f7832d986ac51b20ba9719d85509acdedc0282f27065c87bf483fc74b55579ecc233fd6c51822ae03e46ecdb15acf0204b67852d525168f409d2a4309c5d9a14b35905b836091964ff4fec0aa91bbd7f39919eeaebbaf75c63337a4dcd5ae37cb60fa14bd78157ecef35ddb1cfdc366eecd48aebb0f5b395e4161c158c46870fc89f8f5918aabf0577c914511cac7b189384eaddf7f03cc6706a9abf219ff1b293cc9fa2cac88b65af62f0493e30736b596c119149af07e840a43dba8d97455a7a95368395488304626d3b888069a0099c435cc11e5981412f79153577c9115a1812b1c4a9ea42bbf11b40fdf930bcc37e6a6c50a7e3d06bfe7043adb32244ae359993caad00eea5f1de90355326e921145add1020ef3c2cbcbd102e7b9a150ff9672697d31f86572843269978cbc4c3fe067417e84898587d4de009b33094c8e8102c07facbda62a2996e083d0618fd9651e17f0fc025a946e7c28d93e18675ba69d8811dd06a2cc1aae28de5a6af30f305cfd8a090832483e4ff7c974d47ac96d2f3f7baec94f965a116c2734f87f468ec33cee1a58dd2ccdbc8f6a0f2a50a739aadf4007c7a529844cd1ce5efdd42eb28a0f5f04fde00dea586e4b8cdb8469b36940de19feefd342285c29298afcd3ddc368f6365e9ee255f8c2b6f70c1bafafe10720402a668ac3a2cb8ee75cb7004455cb4221486c3dee64f8f25f1c8b1411437cf6ba70afac5182b21f0efc1b5c71dd59f71796d2719741b6d5c757d824cdfd2d9459569427951d6974d94bea1fbeb0e1a23ef88774b270a8bca65f01a00299258847ddce8af8a7626c802b29dd01d1dd9d2a7243b334c29c0943e11ddbfe82cdfbd506e81fab8775a1d3c4f6c0744e1086763ad84e8f51d601e24cd6fcba1409abbb1c6b5c9bb9eab8ca30282801448f86cd5d8ec187866f1ba738863f3995bf4084ebe8f41dc1ffe0cd47d5a95dc5fc0dde1559ba170316e085d0e385ecf8646bf787b5287960f07bff9d3d990b3be4b29f008de18ccb46dc2192d0fbbbb9e701d350aae205fcd2d7314112bdc393e2eeb0a96c5f2c767dea0b82e9549f46a270b2c69915711c144a7737e9003b1c1b8684e9f2abe66ad1678c32a0c6ce401ae2d7507ff32c8f6179433b94764a6e4ea0a363e9a03787502c67ec97724d78c1257499379ecc5cd006a13acbba05dc7f1310bc75b9ec6a09f4d95b2ec11952eb3dc3d2dfeb8678b10b5db162b44af56ada56f7e311d6c5ce46472d9ba2284126f8f579d59a9d3e81bb1b26a7ee59c3cca96252fd4ba8e009a213911619ab4184e83683969cc4c474e934f36e6ba4ea33d9e93e984f1603e259cb7f1a3824dd8764bbe30ae08166aca84e63615e59c31d2a968a0beccd40637db22b971d51a4c35ce83108d8975d758669bd83ff9602718a882b943f80745d95a2d5e07ad0acf8023e24589fd68988a350c0ad571470f8b9a906648895d312100cd18089465de942ae0c7c8bffb5d3705c729990721bb0497dddea6dabdd530c9ec90286cee5ba3e2b699eb2de55de8aa0012b38051862bf0a7a22d6cce4310dd8dc47c206d9eb7bf28f2a53afa8fd10836210c0c461e417be42d89ea659a76d5e3cba08daa885b7f0302c5df05c72f8257f60e2ad954891841e67d8b7184cfa47e51e9b36f6ed1400aa84c5058a1998d07709b8b46630d70c73552ada18713310dff4177e2143aada1038bae9a64e02870a7535598d766f447b4e25dbd009009cc0e3421067bd0cd10735469b308cfd7a094bbdaaef2e31c22dc6c1a6d4c7890020b5bd04ef649b56889b2031c1923a69002448dd5db1142384cf517b08096539cc537532f43afc33ca77beb2dfee921178ad51b04f10495365e60b4b7677880409716e0eaa3a1d649a871234f86787c2792bf1e04a635e3f22b8b3b1382be942605c3855a940cfa3825a2f8993312a512c81f92aea0fca95bd1bdb6b9774ef3f1fd15bfa1eae84ebb48bc9b55fb2141c55cd3188eeb5049b2e6f3360e60c5bb7d91c9028ebc9287b4b5541a4ca290288e08ca46f80eeabbf100f700c873d7e69dee7dc80b842004b2bb93f0bf533488028d1a1137371417d06785372ce942d4c6589cdb57514bde81960475c22393eb02a93b6560fc3e4fb6ba692dc362d38e33a41b7df222e9192fdc0e0499f110e681682e2b500c00947d62280482dde5e26010cf959fc717d0837c98634550376aaeb17c1c80e3c3245c36c68f56fc8be117ff84957a75e4aa4caddb38779f1c50086ad4863002127b6c8067f172aa78137908fff82b2297a53d6232c26a2e94bb25b7f9241013adeb36d1e437f2f6c0d983efdb78608e940390005d63c081eaef21d7f00ddf51a3fbbe6175be436a9b0b75f198c30efee2821ac5f088819eb96ba835999d0012e7a0c244268a658bc6b7845d10178283cb8add0b81405ccd64000c1835111511555eca14f7e3cc9274b45f817039ee62cfd95363cc66f5397f88822f790e0a86b6a69379dae77db24185394d9fc73880d39f6b3233dd8d3b912995b22905563ae8cb2b1f3491feffbbdf2c7d2e89dce1995d2429bb0a4c550860835aef589983d3170387971f8aec15c18450dbc9cd12f402589dfb17216e359072ceec8e9484f9b490a288dd4b268233c40ad23721961c390bae0a181c4cc8eb68eb4b2a5db6944042c6ef67550f9abf3b9084cfe8521b62d727416576a58591dc65f79f871958243ca4415d92d3e588c4205b4a3806d64224e94e8f04dbf7f75888246aae9f894bab1ea69d7e10b4f07e8cf2dcf70cc62571c737cb6b6c68b0e3fbe4b1931c8615a793fa3db0a45090458ff62ce8bf421907877e555cdb97d3709bbd8728c4432288861a56cbfeabbea5f225cd29e31d99eb788704a93696f5e81307fc047073657405630200035ed48216bc666598aac5498e9fe513a9e21eaf842d382767220cf98104bfd8866621efb63b2eed44028cdc9e2f64cc2447fe09f8d8a1eb3214272855b525d9ce24e6daee58effb4af28fcb0a4685d78d7995081b5cafd1cfe343aefc8e9cb23507fc0470736574072102b642f0e5b34746f2f6e3b511d037e16bc720292da4ea2fb31ec09f6fdbe777ce00 \ No newline at end of file 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 97cb1100..5bbfee40 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -1,37 +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 [package.metadata] cargo-fuzz = true -[features] -afl_fuzz = ["afl"] -honggfuzz_fuzz = ["honggfuzz"] - [dependencies] -honggfuzz = { version = "0.5", optional = true } -afl = { version = "0.11", optional = true } -elements = { path = "..", features = ["fuzztarget", "serde-feature"] } +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 c0cbe2c9..00000000 --- a/fuzz/travis-fuzz.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -set -e -cargo install --force honggfuzz -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 66ca8d85..55ea613c 100644 --- a/src/address.rs +++ b/src/address.rs @@ -15,22 +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 blech32; +use crate::schnorr::{TapTweak, TweakedPublicKey, UntweakedPublicKey}; +use crate::taproot::TapNodeHash; -use {PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash}; -use {opcodes, script}; +use crate::{opcodes, script}; +use crate::{PubkeyHash, ScriptHash, WScriptHash}; /// Encoding error #[derive(Debug, PartialEq)] @@ -38,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 @@ -56,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 { @@ -71,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) @@ -85,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) + } } } } @@ -118,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 { @@ -129,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. @@ -138,13 +178,22 @@ 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. + pub const LIQUID_TESTNET: AddressParams = AddressParams { + p2pkh_prefix: 36, + p2sh_prefix: 19, + blinded_prefix: 23, + 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), @@ -153,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, @@ -176,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] @@ -184,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, } } @@ -204,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, } @@ -231,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, } } @@ -259,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, } @@ -275,12 +329,51 @@ 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, + } + } + + /// Creates a pay to taproot address from an untweaked key. + pub fn p2tr( + secp: &Secp256k1, + internal_key: UntweakedPublicKey, + merkle_root: Option, + blinder: Option, + params: &'static AddressParams, + ) -> Address { + Address { + params, + payload: { + let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root); + Payload::WitnessProgram { + version: Fe32::P, + program: output_key.into_inner().serialize().to_vec(), + } + }, + blinding_pubkey: blinder, + } + } + + /// Creates a pay to taproot address from a pre-tweaked output key. + /// + /// This method is not recommended for use, [`Address::p2tr()`] should be used where possible. + pub fn p2tr_tweaked( + output_key: TweakedPublicKey, + blinder: Option, + params: &'static AddressParams, + ) -> Address { + Address { + params, + payload: Payload::WitnessProgram { + version: Fe32::P, + program: output_key.into_inner().serialize().to_vec(), + }, blinding_pubkey: blinder, } } @@ -293,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 { @@ -325,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, @@ -350,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, @@ -363,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 { @@ -480,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, @@ -495,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) } } @@ -511,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) => { @@ -526,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 { @@ -544,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(()) } } } @@ -587,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 { @@ -605,37 +684,35 @@ impl FromStr for Address { // shorthands let liq = &AddressParams::LIQUID; let ele = &AddressParams::ELEMENTS; + let liq_test = &AddressParams::LIQUID_TESTNET; + + let net_arr = [liq, ele, liq_test]; - // Bech32. let prefix = find_prefix(s); - if match_prefix(prefix, liq.bech_hrp) { - return Address::from_bech32(s, false, liq); - } - if match_prefix(prefix, liq.blech_hrp) { - return Address::from_bech32(s, true, liq); - } - if match_prefix(prefix, ele.bech_hrp) { - return Address::from_bech32(s, false, ele); - } - if match_prefix(prefix, ele.blech_hrp) { - return Address::from_bech32(s, true, ele); + for net in &net_arr { + // Bech32. + if match_prefix(prefix, net.bech_hrp) { + return Address::from_bech32(s, false, net); + } + if match_prefix(prefix, net.blech_hrp) { + return Address::from_bech32(s, true, net); + } } // 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]; - if p == liq.p2pkh_prefix || p == liq.p2sh_prefix || p == liq.blinded_prefix { - return Address::from_base58(&data, liq); - } - if p == ele.p2pkh_prefix || p == ele.p2sh_prefix || p == ele.blinded_prefix { - return Address::from_base58(&data, ele); + for net in &net_arr { + if p == net.p2pkh_prefix || p == net.p2sh_prefix || p == net.blinded_prefix { + return Address::from_base58(&data, net); + } } Err(AddressError::InvalidAddress(s.to_owned())) @@ -698,9 +775,9 @@ impl serde::Serialize for Address { #[cfg(test)] mod test { use super::*; - use bitcoin::util::key; + use crate::Script; + use bitcoin::key; use secp256k1_zkp::{PublicKey, Secp256k1}; - use Script; #[cfg(feature = "serde")] use serde_json; @@ -719,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"; @@ -788,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()); @@ -806,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 75301d43..65ae360e 100644 --- a/src/blind.rs +++ b/src/blind.rs @@ -15,19 +15,22 @@ //! # Transactions Blinding //! -use std::{self, fmt}; +use internals::slice::SliceExt; +use std::{self, collections::BTreeMap, fmt}; -use secp256k1_zkp::{self, PedersenCommitment, SecretKey, Tag, Tweak, Verification, ZERO_TWEAK, rand::{CryptoRng, RngCore}}; -use secp256k1_zkp::{Generator, RangeProof, Secp256k1, Signing, SurjectionProof}; +use secp256k1_zkp::{ + self, + rand::{CryptoRng, RngCore}, + PedersenCommitment, SecretKey, Tag, Tweak, Verification, ZERO_TWEAK, +}; +use secp256k1_zkp::{Generator, Secp256k1, Signing}; -use AddressParams; +use crate::{AddressParams, RangeProof, Script, TxIn, SurjectionProof}; -use {Address, AssetId, Transaction, TxOut, TxOutWitness, - confidential::{Asset, AssetBlindingFactor, Nonce, Value, - ValueBlindingFactor -}}; - -use hashes; +use crate::{ + confidential::{Asset, AssetBlindingFactor, Nonce, Value, ValueBlindingFactor}, + Address, AssetId, Transaction, TxOut, TxOutWitness, +}; /// Transaction Output related errors #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -36,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 @@ -53,16 +54,20 @@ 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, "Zero value explicit amounts must be provably unspendable.\ - See IsUnspendable in elements") + write!( + f, + "Zero value explicit amounts must be provably unspendable.\ + See IsUnspendable in elements" + ) } TxOutError::ZeroValueCommitment => { - write!(f, "Tried to create pedersen commitment with zero value.\ + write!( + f, + "Tried to create pedersen commitment with zero value.\ Zero value is only allowed for provable unspendable scripts, - in which case the verification check can ignore the txout") + in which case the verification check can ignore the txout" + ) } TxOutError::IncorrectBlindingFactors => { write!(f, "Incorrect Blinding factors") @@ -78,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), @@ -106,7 +111,11 @@ impl fmt::Display for VerificationError { write!(f, "Surjection Proof Error {} : for output index {}", i, e) } VerificationError::SurjectionProofVerificationError(i) => { - write!(f, "Surjection proof verification failed for output index {}", i) + write!( + f, + "Surjection proof verification failed for output index {}", + i + ) } VerificationError::IssuanceTransactionInput(i) => { write!(f, "Issuance transaction input {} not supported yet", i) @@ -121,7 +130,10 @@ impl fmt::Display for VerificationError { write!(f, "Output index {} txout: {}", i, e) } VerificationError::BalanceCheckFailed => { - write!(f, "Confidential transaction verification balance check failed") + write!( + f, + "Confidential transaction verification balance check failed" + ) } VerificationError::RangeProofMissing(i) => { write!(f, "Missing Rangeproof for output index {}", i) @@ -146,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, @@ -188,26 +200,151 @@ impl From for ConfidentialTxOutError { ConfidentialTxOutError::Upstream(from) } } -/// The Rangeproof message -pub(crate) struct RangeProofMessage { - pub(crate) asset: AssetId, - pub(crate) bf: AssetBlindingFactor, -} -impl RangeProofMessage { - pub(crate) 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()) + } - message + /// 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), + } + } + } + + 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))] -#[derive(Debug, PartialEq, Eq, Clone, Hash)] +#[cfg_attr( + feature = "serde", + derive(serde::Serialize, serde::Deserialize), +)] +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] pub struct TxOutSecrets { /// Asset pub asset: AssetId, @@ -220,7 +357,6 @@ pub struct TxOutSecrets { } impl TxOutSecrets { - /// Create a new [`TxOutSecrets`] pub fn new( asset: AssetId, @@ -228,291 +364,494 @@ impl TxOutSecrets { value: u64, value_bf: ValueBlindingFactor, ) -> Self { - Self {asset, asset_bf, value, value_bf } + Self { + asset, + asset_bf, + value, + value_bf, + } } - /// 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(crate) fn surjection_inputs( - secret: Option<&Self>, + pub fn surjection_inputs(&self, secp: &Secp256k1) -> (Generator, Tag, Tweak) { + let tag = self.asset.into_tag(); + let bf = self.asset_bf.into_inner(); + let gen = Generator::new_blinded(secp, tag, bf); + (gen, tag, bf) + } + + /// Gets the required fields for last value blinding factor calculation from [`TxOutSecrets`] + pub fn value_blind_inputs(&self) -> (u64, AssetBlindingFactor, ValueBlindingFactor) { + (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`] +/// +/// Explicit assets can be provided as [`SurjectionInput::Unknown`]. There is no +/// need to construct a `Known` variant with secrets +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SurjectionInput { + /// Unknown inputs for whom we don't know the secrets(asset tags/blinding factors) + Unknown(Asset), + /// Known inputs for whom we know blinding factors + Known { + /// Asset + asset: AssetId, + /// Asset Blinding Factor + asset_bf: AssetBlindingFactor, + }, +} + +impl From for SurjectionInput { + fn from(v: TxOutSecrets) -> Self { + Self::Known { + asset: v.asset, + asset_bf: v.asset_bf, + } + } +} + +impl From for SurjectionInput { + fn from(v: Asset) -> Self { + Self::Unknown(v) + } +} + +impl SurjectionInput { + /// Creates a new [`SurjectionInput`] from commitment + pub fn from_comm(asset: Asset) -> Self { + Self::Unknown(asset) + } + + /// Creates a new [`SurjectionInput`] from [`TxOutSecrets`] + pub fn from_txout_secrets(secrets: TxOutSecrets) -> Self { + Self::from(secrets) + } + + /// Handy method to convert [`SurjectionInput`] into a surjection target + /// that can be used while creating a new [`SurjectionProof`]. + /// + /// Only errors when the input asset is Null. + pub fn surjection_target( + &self, secp: &Secp256k1, - asset: Asset, - ) -> Result<(Generator, Tag, Tweak), TxOutError> - { - let gen = asset.into_asset_gen(secp) - .ok_or(TxOutError::UnExpectedNullAsset)?; - match secret { - None => { - let tag = Tag::from([0u8; 32]); - Ok((gen, tag, ZERO_TWEAK)) + ) -> Result<(Generator, Tag, Tweak), TxOutError> { + match self { + SurjectionInput::Unknown(asset) => { + let gen = asset + .into_asset_gen(secp) + .ok_or(TxOutError::UnExpectedNullAsset)?; + // Return the input as 0 tag and 0 tweak. This also correctly handles explicit case + Ok((gen, Tag::default(), ZERO_TWEAK)) } - Some(secret) => { - let tag = secret.asset.into_tag(); - let bf = secret.asset_bf.into_inner(); - let gen1 = Generator::new_blinded(secp, tag, bf); - if gen1 != gen { - return Err(TxOutError::IncorrectBlindingFactors); - } + SurjectionInput::Known { asset, asset_bf } => { + let tag = asset.into_tag(); + let bf = asset_bf.into_inner(); + let gen = Generator::new_blinded(secp, tag, bf); Ok((gen, tag, bf)) } } } +} - /// Gets the required fields for last value blinding factor calculation from [`TxOutSecrets`] - pub(crate) fn value_blind_inputs(&self) -> (u64, AssetBlindingFactor, ValueBlindingFactor) { - return (self.value, self.asset_bf, self.value_bf); +impl Asset { + /// Blinds the asset such that there is a surjection proof between + /// the input assets and the output blinded asset. + /// + /// # Returns: + /// + /// A pair of blinded asset and corresponding proof as ([`Asset`], [`SurjectionProof`]) + pub fn blind( + self, + rng: &mut R, + secp: &Secp256k1, + asset_bf: AssetBlindingFactor, + spent_utxo_secrets: &[S], + ) -> Result<(Self, SurjectionProof), ConfidentialTxOutError> + where + R: RngCore + CryptoRng, + C: Signing, + S: Into + Copy, + { + let asset = self + .explicit() + .ok_or(ConfidentialTxOutError::ExpectedExplicitAsset)?; + let out_asset = Asset::new_confidential(secp, asset, asset_bf); + + let inputs = spent_utxo_secrets + .iter() + .enumerate() + .map(|(i, surject_inp)| { + (*surject_inp) + .into() + .surjection_target(secp) + .map_err(|e| ConfidentialTxOutError::TxOutError(i, e)) + }) + .collect::, _>>()?; + + let surjection_proof = SurjectionProof::new(secp, rng, asset, asset_bf, inputs)?; + + Ok((out_asset, surjection_proof)) + } +} + +impl Value { + /// Blinds the values and outputs the blinded value along with [`RangeProof`]. + /// This computes the nonce by doing an ECDH with `receiver_blinding_pk` and `ephemeral_sk` + /// + /// # Returns: + /// + /// * 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, + vbf: ValueBlindingFactor, + receiver_blinding_pk: secp256k1_zkp::PublicKey, + ephemeral_sk: SecretKey, + spk: &Script, + msg: &RangeProofMessage, + ) -> Result<(Self, Nonce, RangeProof), ConfidentialTxOutError> { + let (nonce, shared_secret) = + Nonce::with_ephemeral_sk(secp, ephemeral_sk, &receiver_blinding_pk); + + let (value_commit, rangeproof) = + self.blind_with_shared_secret(secp, vbf, shared_secret, spk, msg)?; + Ok((value_commit, nonce, rangeproof)) + } + + /// 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( + self, + secp: &Secp256k1, + vbf: ValueBlindingFactor, + shared_secret: SecretKey, + spk: &Script, + msg: &RangeProofMessage, + ) -> Result<(Self, RangeProof), ConfidentialTxOutError> { + let value = self + .explicit() + .ok_or(ConfidentialTxOutError::ExpectedExplicitValue)?; + let out_asset_commitment = msg.commitment(secp); + let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, vbf); + + let rangeproof = RangeProof::new( + secp, + TxOut::RANGEPROOF_MIN_VALUE, + value_commitment.commitment().expect("confidential value"), + value, + vbf.into_inner(), + &msg.to_byte_array(), + spk.as_bytes(), + shared_secret, + TxOut::RANGEPROOF_EXP_SHIFT, + TxOut::RANGEPROOF_MIN_PRIV_BITS, + out_asset_commitment, + )?; + Ok((value_commitment, rangeproof)) } } impl TxOut { - pub(crate) const RANGEPROOF_MIN_VALUE: u64 = 1; - pub(crate) const RANGEPROOF_EXP_SHIFT: i32 = 0; - pub(crate) const RANGEPROOF_MIN_PRIV_BITS: u8 = 52; - pub(crate) const MAX_MONEY: u64 = 21_000_000 * 100_000_000; + /// Rangeproof minimum value + pub const RANGEPROOF_MIN_VALUE: u64 = 1; + /// Rangeproof exponent shift + pub const RANGEPROOF_EXP_SHIFT: i32 = 0; + /// Rangeproof Minimum private bits + pub const RANGEPROOF_MIN_PRIV_BITS: u8 = 52; /// Creates a new confidential output that is **not** the last one in the transaction. - /// Provide input secret information by creating [`TxOutSecrets`] type. - /// The inputs secrets must be consistent with the target_asset confidential [`Asset`] - /// It is not necessary to supply `[TxOutSecrets]` for explicit assets - pub fn new_not_last_confidential( + /// 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`,...] + /// + /// If the issuance or re-issuance is null, it should not be added to `spent_utxo_secrets` + /// + /// # Returns: + /// + /// A tuple of ([`TxOut`], [`AssetBlindingFactor`], [`ValueBlindingFactor`], ephemeral secret key [`SecretKey`]) + /// sampled from the given rng + pub fn new_not_last_confidential( rng: &mut R, secp: &Secp256k1, value: u64, - address: Address, + address: &Address, asset: AssetId, - spent_utxo_secrets: &[(Asset, Option<&TxOutSecrets>)], - ) -> Result<(Self, AssetBlindingFactor, ValueBlindingFactor), ConfidentialTxOutError> + spent_utxo_secrets: &[S], + ) -> Result<(Self, AssetBlindingFactor, ValueBlindingFactor, SecretKey), ConfidentialTxOutError> where R: RngCore + CryptoRng, C: Signing, + S: Into + Copy, { - let out_abf = AssetBlindingFactor::new(rng); - let out_asset = Asset::new_confidential(secp, asset, out_abf); - - let out_asset_commitment = out_asset.commitment().expect("confidential asset"); - let out_vbf = ValueBlindingFactor::new(rng); - let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, out_vbf); - - let receiver_blinding_pk = &address + let spk = address.script_pubkey(); + let blinder = address .blinding_pubkey .ok_or(ConfidentialTxOutError::NoBlindingKeyInAddress)?; - let (nonce, shared_secret) = Nonce::new_confidential(rng, secp, receiver_blinding_pk); + let asset_bf = AssetBlindingFactor::new(rng); + let value_bf = ValueBlindingFactor::new(rng); + let out_secrets = TxOutSecrets::new(asset, asset_bf, value, value_bf); + let ephemeral_sk = SecretKey::new(rng); - let message = RangeProofMessage { asset, bf: out_abf }; - let rangeproof = RangeProof::new( + let txout = Self::with_txout_secrets( + rng, secp, - Self::RANGEPROOF_MIN_VALUE, - value_commitment.commitment().expect("confidential value"), - value, - out_vbf.0, - &message.to_bytes(), - address.script_pubkey().as_bytes(), - shared_secret, - Self::RANGEPROOF_EXP_SHIFT, - Self::RANGEPROOF_MIN_PRIV_BITS, - out_asset_commitment, + spk, + blinder, + ephemeral_sk, + out_secrets, + spent_utxo_secrets, )?; + Ok((txout, asset_bf, value_bf, ephemeral_sk)) + } - let inputs = spent_utxo_secrets - .iter() - .enumerate() - .map(|(i, (asset, sec))| { - TxOutSecrets::surjection_inputs(*sec, secp, *asset) - .map_err(|e| ConfidentialTxOutError::TxOutError(i, e)) - }) - .collect::, _>>()?; - - let surjection_proof = SurjectionProof::new( + /// Similar to [`TxOut::new_not_last_confidential`], but takes input + /// the asset, value blinding factors and ephemeral secret key instead of sampling + /// them from rng. The `rng` is only used in surjection proof creation while + /// selecting inputs + /// + /// Use the `txout_secrets` to specify the secrets to use while creating this output. + /// Use the [`ValueBlindingFactor::last`] method to compute the blinding factor for the + /// last input. + // + // TODO: In upstream secp-zkp, create a non-rng based function. + pub fn with_txout_secrets( + rng: &mut R, + secp: &Secp256k1, + spk: Script, + receiver_blinding_pk: secp256k1_zkp::PublicKey, + ephemeral_sk: SecretKey, + out_secrets: TxOutSecrets, + spent_utxo_secrets: &[S], + ) -> Result + where + R: RngCore + CryptoRng, + C: Signing, + S: Into + Copy, + { + let exp_asset = Asset::Explicit(out_secrets.asset); + let (out_asset, surjection_proof) = + exp_asset.blind(rng, secp, out_secrets.asset_bf, spent_utxo_secrets)?; + + let msg = RangeProofMessage::new( + out_secrets.asset, + out_secrets.asset_bf, + ); + let exp_value = Value::Explicit(out_secrets.value); + let (out_value, nonce, rangeproof) = exp_value.blind( secp, - rng, - asset.into_tag(), - out_abf.into_inner(), - inputs.as_ref(), + out_secrets.value_bf, + receiver_blinding_pk, + ephemeral_sk, + &spk, + &msg, )?; let txout = TxOut { asset: out_asset, - value: value_commitment, + value: out_value, nonce, - script_pubkey: address.script_pubkey(), + script_pubkey: spk, witness: TxOutWitness { - surjection_proof: Some(surjection_proof), - rangeproof: Some(rangeproof), + surjection_proof, + rangeproof, }, }; - - Ok((txout, out_abf, out_vbf)) + Ok(txout) } - /// Convert a explicit TxOut into a Confidential TxOut. - /// The blinding key is provided by the blinder paramter. + /// 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. - pub fn to_non_last_confidential( - &mut self, + /// + /// # Returns: + /// + /// A tuple of ([`AssetBlindingFactor`], [`ValueBlindingFactor`], ephemeral secret key [`SecretKey`]) + /// sampled from the given rng + pub fn to_non_last_confidential( + &self, rng: &mut R, secp: &Secp256k1, blinder: secp256k1_zkp::PublicKey, - spent_utxo_secrets: &[(Asset, Option<&TxOutSecrets>)], - ) -> Result<(AssetBlindingFactor, ValueBlindingFactor), ConfidentialTxOutError> + spent_utxo_secrets: &[S], + ) -> Result<(TxOut, AssetBlindingFactor, ValueBlindingFactor, SecretKey), ConfidentialTxOutError> where R: RngCore + CryptoRng, C: Signing, + S: Into + Copy, { - let (txout, abf, vbf) = Self::new_not_last_confidential( + let (txout, abf, vbf, ephemeral_sk) = Self::new_not_last_confidential( rng, secp, 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() .ok_or(ConfidentialTxOutError::ExpectedExplicitAsset)?, spent_utxo_secrets, )?; - *self = txout; - Ok((abf, vbf)) + Ok((txout, abf, vbf, ephemeral_sk)) } // Internally used function for getting the generator from asset // Used in the amount verification check - fn get_asset_gen ( + fn get_asset_gen( &self, secp: &Secp256k1, ) -> Result { - self.asset.into_asset_gen(secp).ok_or(TxOutError::UnExpectedNullAsset) + self.asset + .into_asset_gen(secp) + .ok_or(TxOutError::UnExpectedNullAsset) } // Get the pedersen commitment for the txout. Used internally // in tx verification. - fn get_value_commit ( + fn get_value_commit( &self, secp: &Secp256k1, ) -> Result { // 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::ZeroValueCommitment); } + return Err(TxOutError::NonUnspendableZeroValue); } let asset_comm = self.get_asset_gen(secp)?; - Ok(PedersenCommitment::new_unblinded(secp, value, asset_comm)) + Ok(PedersenCommitment::new_unblinded(secp, value, asset_comm)) } Value::Confidential(comm) => Ok(comm), } } /// Creates a new confidential output that IS the last one in the transaction. - /// Provide input Asset information by creating [`TxInputAsset`] type. + /// + /// 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`,...] + /// 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, value: u64, - address: Address, asset: AssetId, - spent_utxo_secrets: &[(Asset, &TxOutSecrets)], + spk: Script, + blinder: secp256k1_zkp::PublicKey, + spent_utxo_secrets: &[TxOutSecrets], output_secrets: &[&TxOutSecrets], - ) -> Result<(Self, AssetBlindingFactor, ValueBlindingFactor), ConfidentialTxOutError> + ) -> Result<(Self, AssetBlindingFactor, ValueBlindingFactor, SecretKey), ConfidentialTxOutError> where R: RngCore + CryptoRng, C: Signing, { - // Check for Null Assets at start. - // Maybe just remove this variant altogether? - for (i, (asset, _sec)) in spent_utxo_secrets.iter().enumerate(){ - if asset.is_null() { - return Err(ConfidentialTxOutError::TxOutError(i, TxOutError::UnExpectedNullAsset)); - } - } - let (surjection_proof_inputs, value_blind_inputs) = spent_utxo_secrets + let out_abf = AssetBlindingFactor::new(rng); + let ephemeral_sk = SecretKey::new(rng); + + let (txout, out_vbf) = TxOut::with_secrets_last( + rng, + secp, + value, + spk, + blinder, + asset, + ephemeral_sk, + out_abf, + spent_utxo_secrets, + output_secrets, + )?; + Ok((txout, out_abf, out_vbf, ephemeral_sk)) + } + + /// 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, + value: u64, + spk: Script, + blinder: secp256k1_zkp::PublicKey, + asset: AssetId, + ephemeral_sk: SecretKey, + out_abf: AssetBlindingFactor, + spent_utxo_secrets: &[TxOutSecrets], + output_secrets: &[&TxOutSecrets], + ) -> Result<(Self, ValueBlindingFactor), ConfidentialTxOutError> + where + R: RngCore + CryptoRng, + C: Signing, + { + let value_blind_inputs = spent_utxo_secrets .iter() - .map(|(asset, sec)| { - let gen = asset.into_asset_gen(secp).expect("Null"); - ((gen, sec.asset.into_tag(), sec.asset_bf.0), (sec.value_blind_inputs())) - }) - .unzip::<_, _, Vec<_>, Vec<_>>(); + .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_abf = AssetBlindingFactor::new(rng); - let out_asset = Asset::new_confidential(secp, asset, out_abf); - - let out_asset_commitment = out_asset.commitment().expect("confidential asset"); - let out_vbf = - ValueBlindingFactor::last(secp, value, out_abf, &value_blind_inputs, &value_blind_outputs); - let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, out_vbf); - - let receiver_blinding_pk = &address - .blinding_pubkey - .ok_or(ConfidentialTxOutError::NoBlindingKeyInAddress)?; - let (nonce, shared_secret) = Nonce::new_confidential(rng, secp, receiver_blinding_pk); - - let message = RangeProofMessage { asset, bf: out_abf }; - let rangeproof = RangeProof::new( + let out_vbf = ValueBlindingFactor::last( secp, - Self::RANGEPROOF_MIN_VALUE, - value_commitment.commitment().expect("confidential value"), value, - out_vbf.0, - &message.to_bytes(), - address.script_pubkey().as_bytes(), - shared_secret, - Self::RANGEPROOF_EXP_SHIFT, - Self::RANGEPROOF_MIN_PRIV_BITS, - out_asset_commitment, - )?; - - let surjection_proof = SurjectionProof::new( - secp, + out_abf, + &value_blind_inputs, + &value_blind_outputs, + ); + let out_secrets = TxOutSecrets::new(asset, out_abf, value, out_vbf); + let txout = TxOut::with_txout_secrets( rng, - asset.into_tag(), - out_abf.into_inner(), - surjection_proof_inputs.as_ref(), + secp, + spk, + blinder, + ephemeral_sk, + out_secrets, + spent_utxo_secrets, )?; - let txout = TxOut { - asset: out_asset, - value: value_commitment, - nonce, - script_pubkey: address.script_pubkey(), - witness: TxOutWitness { - surjection_proof: Some(surjection_proof), - rangeproof: Some(rangeproof), - }, - }; - - Ok((txout, out_abf, out_vbf)) + Ok((txout, out_vbf)) } /// 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 @@ -531,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, }) @@ -551,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, @@ -558,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"), } @@ -580,56 +924,142 @@ 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 TxIn { + /// Blind issuances for this [`TxIn`]. Asset amount and token amount must be + /// set in [`AssetIssuance`](crate::AssetIssuance) field for this input + pub fn blind_issuances_with_bfs( + &mut self, + secp: &Secp256k1, + issue_vbf: ValueBlindingFactor, + token_vbf: ValueBlindingFactor, + issue_sk: SecretKey, + token_sk: SecretKey, + ) -> Result<(), BlindError> { + if !self.has_issuance() { + return Err(BlindError::NoIssuanceToBlind); + } + let (asset_id, token_id) = self.issuance_ids(); + let arr = vec![ + (issue_vbf, self.asset_issuance.amount, issue_sk, asset_id), + ( + token_vbf, + self.asset_issuance.inflation_keys, + token_sk, + token_id, + ), + ]; + for (i, (bf, amt, blind_sk, asset)) in arr.into_iter().enumerate() { + let v = match amt { + Value::Null => continue, // nothing to blind + Value::Explicit(0) => return Err(BlindError::ZeroValueBlindingNotAllowed), + Value::Confidential(_) => return Err(BlindError::IssuanceAmountMustBeExplicit), + Value::Explicit(v) => Value::Explicit(v), + }; + let spk = Script::new(); + let msg = RangeProofMessage::new( + asset, + 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 = prf; + } else { + self.asset_issuance.inflation_keys = comm; + self.witness.inflation_keys_rangeproof = prf; + } + } + Ok(()) } -} -impl From for UnblindError { - fn from(from: hashes::Error) -> Self { - UnblindError::MalformedAssetId(from) + /// 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`) + pub fn blind_issuances( + &mut self, + secp: &Secp256k1, + rng: &mut R, + ) -> Result< + ( + ValueBlindingFactor, + SecretKey, + ValueBlindingFactor, + SecretKey, + ), + BlindError, + > { + let issue_vbf = ValueBlindingFactor::new(rng); + let token_vbf = ValueBlindingFactor::new(rng); + let issue_sk = SecretKey::new(rng); + let token_sk = SecretKey::new(rng); + self.blind_issuances_with_bfs(secp, issue_vbf, token_vbf, issue_sk, token_sk)?; + Ok((issue_vbf, issue_sk, token_vbf, token_sk)) } } +/// Inputs or pseudo-inputs. +#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)] +pub enum CtLocationType { + /// Regular input + 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, @@ -651,45 +1081,68 @@ impl Transaction { &self, secp: &Secp256k1, spent_utxos: &[TxOut], - ) -> Result<(), VerificationError> - { + ) -> Result<(), VerificationError> { if spent_utxos.len() != self.input.len() { return Err(VerificationError::UtxoInputLenMismatch); } // Issuances and reissuances not supported yet let mut in_commits = vec![]; let mut out_commits = vec![]; + let mut domain = vec![]; for (i, inp) in self.input.iter().enumerate() { - if inp.has_issuance() { - return Err(VerificationError::IssuanceTransactionInput(i)); - } + let gen = spent_utxos[i] + .get_asset_gen(secp) + .map_err(|e| VerificationError::SpentTxOutError(i, e))?; + domain.push(gen); in_commits.push( - spent_utxos[i].get_value_commit(secp) - .map_err(|e| VerificationError::SpentTxOutError(i, e))? + spent_utxos[i] + .get_value_commit(secp) + .map_err(|e| VerificationError::SpentTxOutError(i, e))?, ); + if inp.has_issuance() { + let (asset_id, token_id) = inp.issuance_ids(); + let arr = [ + (inp.asset_issuance.amount, asset_id), + (inp.asset_issuance.inflation_keys, token_id), + ]; + for (amt, asset) in &arr { + match amt { + 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); + } + Value::Confidential(comm) => { + let gen = Generator::new_unblinded(secp, asset.into_tag()); + domain.push(gen); + in_commits.push(*comm); + } + } + } + } } - let domain = spent_utxos - .iter() - .enumerate() - .map(|(i, out)| - out.get_asset_gen(secp).map_err(|e| VerificationError::SpentTxOutError(i, e)) - ) - .collect::, _>>()?; for (i, out) in self.output.iter().enumerate() { - // Compute the value commitments and asset generator - let out_commit = out.get_value_commit(secp) + let out_commit = out + .get_value_commit(secp) .map_err(|e| VerificationError::SpentTxOutError(i, e))?; out_commits.push(out_commit); // rangeproof checks if let Some(comm) = out.value.commitment() { - let gen = out.get_asset_gen(secp) - .map_err(|e| VerificationError::TxOutError(i, e))?; - let rangeproof = out.witness.rangeproof.as_ref().ok_or( - VerificationError::RangeProofMissing(i))?; - rangeproof.verify(secp, comm, out.script_pubkey.as_bytes(), gen) + let gen = out + .get_asset_gen(secp) + .map_err(|e| VerificationError::TxOutError(i, e))?; + let rangeproof = out + .witness + .rangeproof + .as_ref() + .ok_or(VerificationError::RangeProofMissing(i))?; + rangeproof + .verify(secp, comm, out.script_pubkey.as_bytes(), gen) .map_err(|e| VerificationError::RangeProofError(i, e))?; } else { // No rangeproof checks for explicit values @@ -697,10 +1150,13 @@ impl Transaction { // Surjection proof checks if let Some(gen) = out.asset.commitment() { - let surjectionproof = out.witness.surjection_proof.as_ref().ok_or( - VerificationError::SurjectionProofMissing(i))?; + let surjectionproof = out + .witness + .surjection_proof + .as_ref() + .ok_or(VerificationError::SurjectionProofMissing(i))?; if !surjectionproof.verify(secp, gen, &domain) { - return Err(VerificationError::SurjectionProofVerificationError(i)) + return Err(VerificationError::SurjectionProofVerificationError(i)); } } else { // No surjection proof checks for explicit assets @@ -708,7 +1164,7 @@ impl Transaction { } // Final Balance check if !secp256k1_zkp::verify_commitments_sum_to_equal(secp, &in_commits, &out_commits) { - return Err(VerificationError::BalanceCheckFailed) + return Err(VerificationError::BalanceCheckFailed); } Ok(()) } @@ -723,19 +1179,39 @@ impl Transaction { &mut self, rng: &mut R, secp: &Secp256k1, - spent_utxo_secrets: &[(Asset, &TxOutSecrets)], - ) -> Result, BlindError> + spent_utxo_secrets: &[TxOutSecrets], + blind_issuances: bool, + ) -> Result, BlindError> where R: RngCore + CryptoRng, C: Signing, { + let mut blinds = BTreeMap::new(); // Blinding Issuances unsupported - if self.input.iter().any(|i| i.has_issuance()) { - return Err(BlindError::IssuanceUnsupported) + for (i, txin) in self.input.iter_mut().enumerate() { + if txin.has_issuance() && blind_issuances { + let (iss_vbf, iss_sk, tkn_vbf, tkn_sk) = txin.blind_issuances(secp, rng)?; + if txin.asset_issuance.amount.is_confidential() { + blinds.insert( + CtLocation{ input_index: i, ty: CtLocationType::Issuance }, + (AssetBlindingFactor::zero(), iss_vbf, iss_sk), + ); + } + if txin.asset_issuance.inflation_keys.is_confidential() { + blinds.insert( + CtLocation{ input_index: i, ty: CtLocationType::Reissuance }, + (AssetBlindingFactor::zero(), tkn_vbf, tkn_sk), + ); + } + } } // Everything must be explicit - if !self.output.iter().all(|o| o.asset.is_explicit() && o.value.is_explicit()) { - return Err(BlindError::MustHaveAllExplicitTxOuts) + if !self + .output + .iter() + .all(|o| o.asset.is_explicit() && o.value.is_explicit()) + { + return Err(BlindError::MustHaveAllExplicitTxOuts); } // All outputs with script let num_to_blind = self @@ -744,37 +1220,36 @@ impl Transaction { .filter(|i| !i.is_fee() && i.nonce.is_confidential()) .count(); let mut num_blinded = 0; - let mut blinds = Vec::new(); let mut out_secrets = Vec::new(); let mut last_output_index = None; for (i, out) in self.output.iter_mut().enumerate() { if out.is_fee() || !out.nonce.is_confidential() { - let abf = AssetBlindingFactor::zero(); - let vbf = ValueBlindingFactor::zero(); - out_secrets.push( - TxOutSecrets::new( + out_secrets.push(TxOutSecrets::new( out.asset.explicit().unwrap(), - abf, + AssetBlindingFactor::zero(), out.value.explicit().unwrap(), - vbf, + ValueBlindingFactor::zero(), )); - blinds.push((abf, vbf)); continue; } - let opt_utxo_secrets : Vec<_> = spent_utxo_secrets.iter().map(|(a, sec)| (*a, Some(*sec))).collect(); let blinder = out.nonce.commitment().expect("Confidential"); - let address = Address::from_script(&out.script_pubkey, Some(blinder), &AddressParams::ELEMENTS) - .ok_or(BlindError::InvalidAddress)?; + let address = + Address::from_script(&out.script_pubkey, Some(blinder), &AddressParams::ELEMENTS) + .ok_or(BlindError::InvalidAddress)?; if num_blinded + 1 < num_to_blind { - - let (conf_out, abf, vbf) = TxOut::new_not_last_confidential( - rng, secp, out.value.explicit().unwrap(), address, out.asset.explicit().unwrap(), &opt_utxo_secrets + let (conf_out, abf, vbf, ephemeral_sk) = TxOut::new_not_last_confidential( + rng, + secp, + out.value.explicit().unwrap(), + &address, + out.asset.explicit().unwrap(), + spent_utxo_secrets, )?; - blinds.push((abf, vbf)); - out_secrets.push( - TxOutSecrets::new( + 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, out.value.explicit().unwrap(), @@ -789,23 +1264,32 @@ impl Transaction { } let last_index = last_output_index.expect("Internal output calculation error"); // NLL block - let (value, asset, address) = { + let (value, asset, spk, blinder) = { let out = &self.output[last_index]; let blinder = out.nonce.commitment().expect("Confidential"); ( out.value.explicit().unwrap(), out.asset.explicit().unwrap(), - Address::from_script(&out.script_pubkey, Some(blinder), &AddressParams::ELEMENTS) - .ok_or(BlindError::InvalidAddress)? + out.script_pubkey.clone(), // TODO: Possible to avoid this clone in future with _mut APIs + blinder, ) }; // Get Vec<&T> from Vec let out_secrets = out_secrets.iter().collect::>(); - let (conf_out, abf, vbf) = TxOut::new_last_confidential( - rng, secp, value, address, asset, spent_utxo_secrets, &out_secrets)?; + let (conf_out, abf, vbf, ephemeral_sk) = TxOut::new_last_confidential( + rng, + secp, + value, + asset, + spk, + blinder, + spent_utxo_secrets, + &out_secrets, + )?; - blinds.push((abf, vbf)); + 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) } @@ -818,30 +1302,37 @@ pub enum BlindError { /// This is not a fundamental limitation, just a limitation of how /// the code API is structured InvalidAddress, - /// Issuance unsupported - IssuanceUnsupported, /// Too few blinding inputs TooFewBlindingOutputs, /// All outputs must be explicit asset/amounts MustHaveAllExplicitTxOuts, - /// General TxOut errors + /// General `TxOut` errors ConfidentialTxOutError(ConfidentialTxOutError), + /// No Issuances to blind in this `TxIn` + NoIssuanceToBlind, + /// Zero Value Blinding not allowed + ZeroValueBlindingNotAllowed, + /// Issuance Amount must be explicit + IssuanceAmountMustBeExplicit, } impl fmt::Display for BlindError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { BlindError::InvalidAddress => { - write!(f, "Only sending to valid addresses is supported as of now. \ - Manually construct transactions to send to custom script pubkeys") - } - BlindError::IssuanceUnsupported => { - write!(f, "Transactions containing issuances unsupported") + write!( + f, + "Only sending to valid addresses is supported as of now. \ + Manually construct transactions to send to custom script pubkeys" + ) } BlindError::TooFewBlindingOutputs => { - write!(f, "Transactions must have atleast confidential outputs \ + write!( + f, + "Transactions must have atleast confidential outputs \ marked for blinding. To mark a output for blinding set nonce field\ - with a blinding pubkey") + with a blinding pubkey" + ) } BlindError::MustHaveAllExplicitTxOuts => { write!(f, "Transaction must all outputs explicit") @@ -849,6 +1340,13 @@ impl fmt::Display for BlindError { BlindError::ConfidentialTxOutError(e) => { write!(f, "{}", e) } + BlindError::NoIssuanceToBlind => write!(f, "No Issuance present"), + BlindError::ZeroValueBlindingNotAllowed => { + write!(f, "Zero value blinding is not allowed") + } + BlindError::IssuanceAmountMustBeExplicit => { + write!(f, "Issuance amount must be explicit to blind") + } } } } @@ -861,169 +1359,80 @@ impl From for BlindError { } } -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, - } - } -} - -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 hashes::hex::FromHex; + use super::*; + use crate::confidential; + use crate::encode; + use crate::encode::deserialize; + use crate::Script; + use bitcoin::{PrivateKey, PublicKey}; use rand::thread_rng; use secp256k1_zkp::SECP256K1; - use super::*; - use encode::deserialize; - use confidential; - use Script; - use bitcoin::{self, Network, PrivateKey, PublicKey}; + 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("b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23").unwrap(), - asset_bf: AssetBlindingFactor::from_hex("a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f").unwrap(), - value: bitcoin::Amount::from_str_in("20999997.97999114", bitcoin::Denomination::Bitcoin).unwrap().as_sat(), - value_bf: ValueBlindingFactor::from_hex("e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d").unwrap(), + asset: AssetId::from_str( + "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23", + ) + .unwrap(), + asset_bf: AssetBlindingFactor::from_str( + "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f", + ) + .unwrap(), + value: bitcoin::Amount::from_str_in( + "20999997.97999114", + bitcoin::Denomination::Bitcoin, + ) + .unwrap() + .to_sat(), + value_bf: ValueBlindingFactor::from_str( + "e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d", + ) + .unwrap(), }; #[cfg(feature = "serde")] { use serde_json; - let spent_utxo_secrets_serde: TxOutSecrets = serde_json::from_str(r#" + let spent_utxo_secrets_serde: TxOutSecrets = serde_json::from_str( + r#" { "asset": "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23", "asset_bf": "a5b3d111cdaa5fc111e2723df4caf315864f25fb4610cc737f10d5a55cd4096f", "value": 2099999797999114, "value_bf": "e36a4de359469f547571d117bc5509fb74fba73c84b0cdd6f4edfa7ff7fa457d" - }"#).unwrap(); + }"#, + ) + .unwrap(); assert_eq!(spent_utxo_secrets, spent_utxo_secrets_serde); } let secp = secp256k1_zkp::Secp256k1::new(); - let spent_asset = Asset::from_commitment(&Vec::::from_hex("0baf634b18e1880c96dcf9947b0e0fd2d38d66d723339174df3fd980148c2f0bb3").unwrap()).unwrap(); - let _bfs = tx.blind(&mut thread_rng(), &secp, &[(spent_asset, &spent_utxo_secrets)]).unwrap(); + let _bfs = tx + .blind(&mut thread_rng(), &secp, &[spent_utxo_secrets], false) + .unwrap(); let spent_utxo = TxOut { - asset: Asset::from_commitment(&Vec::::from_hex("0baf634b18e1880c96dcf9947b0e0fd2d38d66d723339174df3fd980148c2f0bb3").unwrap()).unwrap(), - value: Value::from_commitment(&Vec::::from_hex("093baba9076190867fbc5e43132cb2f82245caf603b493d7c0da8b7eda7912fa2c").unwrap()).unwrap(), - nonce: Nonce::from_commitment(&Vec::::from_hex("02a96a456f4936dcf0afbc325ac3798c4464e7b66dd460d564f3f91882d6089a3b").unwrap()).unwrap(), - script_pubkey: Script::from_hex("0014d2bcde17e7744f6377466ca1bd35d212954674c8").unwrap(), + asset: Asset::from_commitment( + &hex::hex!("0baf634b18e1880c96dcf9947b0e0fd2d38d66d723339174df3fd980148c2f0bb3"), + ) + .unwrap(), + value: Value::from_commitment( + &hex::hex!("093baba9076190867fbc5e43132cb2f82245caf603b493d7c0da8b7eda7912fa2c"), + ) + .unwrap(), + nonce: Nonce::from_commitment( + &hex::hex!("02a96a456f4936dcf0afbc325ac3798c4464e7b66dd460d564f3f91882d6089a3b"), + ) + .unwrap(), + script_pubkey: Script::from_hex_no_prefix("0014d2bcde17e7744f6377466ca1bd35d212954674c8") + .unwrap(), witness: TxOutWitness::default(), }; tx.verify_tx_amt_proofs(&secp, &[spent_utxo]).unwrap(); @@ -1036,31 +1445,30 @@ 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, - key: sk, + 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, - key: blinding_sk, + network: bitcoin::NetworkKind::Test, + inner: blinding_sk, }, ); ( - Address::p2wpkh(&pk, Some(blinding_pk.key), &AddressParams::ELEMENTS), + Address::p2wpkh(&pk, Some(blinding_pk.inner), &AddressParams::ELEMENTS), blinding_sk, ) }; let asset = AssetId::default(); let asset_bf = AssetBlindingFactor::new(&mut thread_rng()); - let asset_commitment = Asset::new_confidential(SECP256K1, asset, asset_bf); let value_bf = ValueBlindingFactor::new(&mut thread_rng()); /*let spent_utxo_secrets = &[( asset, @@ -1069,14 +1477,19 @@ mod tests { input_abf, input_vbf, )]; */ - let txout_secrets = TxOutSecrets { asset, asset_bf, value, value_bf}; - let spent_utxo_secrets = [(asset_commitment, Some(&txout_secrets))]; + let txout_secrets = TxOutSecrets { + asset, + asset_bf, + value, + value_bf, + }; + let spent_utxo_secrets = [txout_secrets]; - let (txout, _, _) = TxOut::new_not_last_confidential( + let (txout, _, _, _) = TxOut::new_not_last_confidential( &mut thread_rng(), SECP256K1, value, - address, + &address, asset, &spent_utxo_secrets, ) @@ -1090,8 +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); @@ -1107,8 +1519,9 @@ mod tests { explicit_val, value_comm, asset_gen, - vbf - ).unwrap(); + vbf, + ) + .unwrap(); let res = proof.blind_value_proof_verify(SECP256K1, explicit_val, asset_gen, value_comm); assert!(res); @@ -1116,20 +1529,65 @@ 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); let asset_comm = asset.commitment().unwrap(); // Create the proof - let proof = SurjectionProof::blind_asset_proof( - &mut thread_rng(), - SECP256K1, - id, - abf, - ).unwrap(); + let proof = + SurjectionProof::blind_asset_proof(&mut thread_rng(), SECP256K1, id, abf).unwrap(); let res = proof.blind_asset_proof_verify(SECP256K1, id, asset_comm); assert!(res); } + + #[test] + fn test_partially_blinded_tx() { + // Partially blinded tx with multiple issuances from options project + let secp = secp256k1_zkp::Secp256k1::new(); + let tx_str = include_str!("../tests/data/issue_tx.hex"); + + let bytes = hex::decode_to_vec(tx_str).unwrap(); + let tx = encode::deserialize::(&bytes).unwrap(); + + let mut utxos = [ + TxOut::default(), + TxOut::default(), + TxOut::default(), + TxOut::default(), + ]; + { + utxos[0].asset = Asset::from_commitment( + &hex::hex!("0ae7a52e8e4b07e00548bab151a83e5c9ab2f9a910e10dcee930a1a152a939f99e"), + ) + .unwrap(); + utxos[0].value = Value::Explicit(1); + + utxos[1].asset = Asset::from_commitment( + &hex::hex!("0bc226167e9ee0bb5a86c8f1478ee7d7becb7bfd4d97c26a041e628c5486a8c67a"), + ) + .unwrap(); + utxos[1].value = Value::Explicit(1); + + utxos[2].asset = Asset::from_commitment( + &hex::hex!("0b495dbfc356993c5ac157c3d04fadf6f198a7e35a873df482ad9e4e95daa8aa7e"), + ) + .unwrap(); + utxos[2].value = Value::from_commitment( + &hex::hex!("08e0ac2ab5f3c173d5e0652a2ec209a9a370a4e510178e73c2f22f9e132341abf4"), + ) + .unwrap(); + + utxos[3].asset = Asset::from_commitment( + &hex::hex!("0aa0956d60687982d5e73d52f8c5902478754e5f0e2e5ceff5ae53fa9681c12ae1"), + ) + .unwrap(); + utxos[3].value = Value::from_commitment( + &hex::hex!("094b35f1e86b097ccf0b3a826570c089c724ed9cf22620937500b14acdd169e7bf"), + ) + .unwrap(); + } + tx.verify_tx_amt_proofs(&secp, &utxos).unwrap(); + } } diff --git a/src/block.rs b/src/block.rs index 5dbba01f..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 dynafed; -use Transaction; -use encode::{self, Encodable, Decodable, serialize}; -use {BlockHash, Script, TxMerkleNode, VarInt}; +use crate::dynafed; +use crate::hashes::{HashEngine as _, sha256d}; +use crate::Transaction; +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 { @@ -202,7 +207,7 @@ impl Default for ExtData { } /// Elements block header -#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct BlockHeader { /// Version - should be 0x20000000 except when versionbits signalling pub version: u32, @@ -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(::fast_merkle_root::fast_merkle_root(&leaves[..])) + Some(DynafedRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..]))) } } } @@ -323,7 +321,7 @@ impl Encodable for BlockHeader { } impl Decodable for BlockHeader { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let mut version: u32 = Decodable::consensus_decode(&mut d)?; let is_dyna = if version >> 31 == 1 { version &= 0x7fff_ffff; @@ -355,14 +353,13 @@ impl Decodable for BlockHeader { } /// Elements block -#[derive(Clone, Debug, Default, Eq, Hash, PartialEq)] +#[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct Block { /// Header of the block pub header: BlockHeader, /// Complete list of transaction in the block pub txdata: Vec, } -serde_struct_impl!(Block, header, txdata); impl_consensus_encoding!(Block, header, txdata); impl Block { @@ -372,32 +369,41 @@ impl Block { } /// Get the size of the block + #[deprecated(since = "0.19.1", note = "Please use `Block::size` instead.")] pub fn get_size(&self) -> usize { + self.size() + } + + /// 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 txs_size: usize = self.txdata.iter().map(Transaction::get_size).sum(); + 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 } /// Get the weight of the block + #[deprecated(since = "0.19.1", note = "Please use `Block::weight` instead.")] pub fn get_weight(&self) -> usize { - let base_weight = 4 * (serialize(&self.header).len() + VarInt(self.txdata.len() as u64).len()); - let txs_weight: usize = self.txdata.iter().map(Transaction::get_weight).sum(); + self.weight() + } + + /// 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).size()); + let txs_weight: usize = self.txdata.iter().map(Transaction::weight).sum(); base_weight + txs_weight } } #[cfg(test)] mod tests { - use Block; + use crate::Block; use super::*; - #[test] - fn block() { - // Simple block with only coinbase output - let block: Block = hex_deserialize!( - "00000020a66e4a4baff69735267346d12e59e8a0da848b593813554deb16a6f3\ + const SIMPLE_BLOCK: &str = "\ + 00000020a66e4a4baff69735267346d12e59e8a0da848b593813554deb16a6f3\ 6cd035e9aab0e2451724598471dd4e45f0dca40ca5f4ac62e61957e50925af08\ 59891fcc8842805b020000000151000102000000010100000000000000000000\ 00000000000000000000000000000000000000000000ffffffff03520101ffff\ @@ -406,18 +412,65 @@ mod tests { 459e1b69e8e60fcee2e4940c7a0d5de1b201000000000000000000266a24aa21\ a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d63f1e04\ 7d45000000000000012000000000000000000000000000000000000000000000\ - 000000000000000000000000000000" - ); + 000000000000000000000000000000\ + "; + const DYNAFED_BLOCK: &str = "\ + 000000a0da9d569617d1d65c3390a01c18c4fa7c4d0f4738b6fc2b5c5faf2e8a\ + 463abbaa46eb9123808e1e2ff75e9472fa0f0589b53b7518a69d3d6fcb9228ed\ + 345734ea06b9c45d070000000122002057c555a91edf9552282d88624d1473c2\ + 75e64b7218870eb8fb0335b442976b8d02010000fbee9cea00d8efdc49cfbec3\ + 28537e0d7032194de6ebf3cf42e5c05bb89a08b100040047304402206f55bc87\ + 1387a9840489d47624b02995e774e3b70fed56d1eb43a9a53d4fd3e102201e1c\ + bfbbd1079f5bea3bc216882d3fefbf6f27aa761820d3a88f12e5a5ea7ff00148\ + 3045022100c072816f6561e73ee6c0ae32d55c3eec4da73b035425e4eb05ab50\ + 772591b4360220311bf295010094a489d9b280d9dafb724d776a1d99b9ede31c\ + 4b59bc2095c5c30169522103cadff18e928133df2e670a3715c4e7a81d357de3\ + 6ddaa5016628e70a3e6a452f21021f0d8638c413ef7769cd711ce84c8f192f5a\ + 85f0fd6d8e63ddb4d2cf6740b23b210296db75c11ea3a292a372f6c94f5013ea\ + eb379f701857a702f3b83f88da21be6f53ae0102000000010100000000000000\ + 00000000000000000000000000000000000000000000000000ffffffff035701\ + 01ffffffff020137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c458\ + 94eb059868a8c001000000000000000000016a0137c495f58d698979ff9124e8\ + c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000266a\ + 24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d6\ + 3f1e047d45000000000000012000000000000000000000000000000000000000\ + 000000000000000000000000000000000000\ + "; + + #[cfg(feature = "serde")] + #[test] + fn blockheader_serde() { + let block: Block = hex_deserialize!(&SIMPLE_BLOCK); + roundtrip_header(&block.header); + let block: Block = hex_deserialize!(&DYNAFED_BLOCK); + roundtrip_header(&block.header); + } + #[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(); + + assert_eq!(&header_deser, header); + let header_ser = serde_cbor::to_vec(header).unwrap(); + let header_deser: BlockHeader = serde_cbor::from_slice(&header_ser).unwrap(); + + assert_eq!(&header_deser, header); + } + + #[test] + fn block() { + // Simple block with only coinbase output + let block: Block = hex_deserialize!(SIMPLE_BLOCK); assert_eq!( 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.get_size(), serialize(&block).len()); - assert_eq!(block.get_weight(), 1089); + assert_eq!(block.size(), serialize(&block).len()); + assert_eq!(block.weight(), 1089); // Block with 3 transactions ... the rangeproofs are very large :) let block: Block = hex_deserialize!( @@ -625,10 +678,10 @@ 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.get_size(), serialize(&block).len()); + assert_eq!(block.size(), serialize(&block).len()); // 2-of-3 signed block from Liquid integration tests let block: Block = hex_deserialize!( @@ -656,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); @@ -668,28 +721,7 @@ mod tests { #[test] fn dynafed_block() { // Copied from elements RPC during a functionary integration test run - let block: Block = hex_deserialize!("\ - 000000a0da9d569617d1d65c3390a01c18c4fa7c4d0f4738b6fc2b5c5faf2e8a\ - 463abbaa46eb9123808e1e2ff75e9472fa0f0589b53b7518a69d3d6fcb9228ed\ - 345734ea06b9c45d070000000122002057c555a91edf9552282d88624d1473c2\ - 75e64b7218870eb8fb0335b442976b8d02010000fbee9cea00d8efdc49cfbec3\ - 28537e0d7032194de6ebf3cf42e5c05bb89a08b100040047304402206f55bc87\ - 1387a9840489d47624b02995e774e3b70fed56d1eb43a9a53d4fd3e102201e1c\ - bfbbd1079f5bea3bc216882d3fefbf6f27aa761820d3a88f12e5a5ea7ff00148\ - 3045022100c072816f6561e73ee6c0ae32d55c3eec4da73b035425e4eb05ab50\ - 772591b4360220311bf295010094a489d9b280d9dafb724d776a1d99b9ede31c\ - 4b59bc2095c5c30169522103cadff18e928133df2e670a3715c4e7a81d357de3\ - 6ddaa5016628e70a3e6a452f21021f0d8638c413ef7769cd711ce84c8f192f5a\ - 85f0fd6d8e63ddb4d2cf6740b23b210296db75c11ea3a292a372f6c94f5013ea\ - eb379f701857a702f3b83f88da21be6f53ae0102000000010100000000000000\ - 00000000000000000000000000000000000000000000000000ffffffff035701\ - 01ffffffff020137c495f58d698979ff9124e8c7455fe79b13ddb96afa25c458\ - 94eb059868a8c001000000000000000000016a0137c495f58d698979ff9124e8\ - c7455fe79b13ddb96afa25c45894eb059868a8c001000000000000000000266a\ - 24aa21a9ed94f15ed3a62165e4a0b99699cc28b48e19cb5bc1b1f47155db62d6\ - 3f1e047d45000000000000012000000000000000000000000000000000000000\ - 000000000000000000000000000000000000\ - "); + let block: Block = hex_deserialize!(DYNAFED_BLOCK); // Test that this is a block with compact current params and null proposed params if let ExtData::Dynafed { current, proposed, .. } = block.clone().header.ext { @@ -698,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"); @@ -711,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!("\ @@ -762,4 +794,12 @@ mod tests { "e9a5176b1690a448f76fb691ab4d516e60e13a6e7a49454c62dbf0d611ffcce7" ); } + + #[test] + fn test_failed_block() { + let block_str = include_str!("../tests/data/failedblock.hex"); + + 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 307f7c71..00000000 --- a/src/confidential.rs +++ /dev/null @@ -1,1478 +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 hashes::{sha256d, Hash, hex}; -use secp256k1_zkp::{self, CommitmentSecrets, Generator, PedersenCommitment, - PublicKey, Secp256k1, SecretKey, Signing, Tweak, ZERO_TWEAK, - compute_adaptive_blinding_factor, ecdh::SharedSecret, - rand::{CryptoRng, Rng, RngCore} -}; -#[cfg(feature = "serde")] -use serde::{Deserialize, Deserializer, Serialize, Serializer}; - -use std::{fmt, io, ops::{AddAssign, Neg}, str}; - -use encode::{self, Decodable, Encodable}; -use 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 = { - let buffer = d.fill_buf()?; - - if buffer.is_empty() { - return Err(encode::Error::UnexpectedEOF); - } - - buffer[0] - }; - - match prefix { - 0 => { - // consume null value prefix - d.consume(1); - Ok(Value::Null) - } - 1 => { - // ignore prefix when decoding an explicit value - d.consume(1); - let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); - Ok(Value::Explicit(explicit)) - } - p if p == 0x08 || p == 0x09 => { - let commitment = Decodable::consensus_decode(&mut d)?; - Ok(Value::Confidential(commitment)) - } - 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 = { - let buffer = d.fill_buf()?; - - if buffer.is_empty() { - return Err(encode::Error::UnexpectedEOF); - } - - buffer[0] - }; - - match prefix { - 0 => { - // consume null value prefix - d.consume(1); - Ok(Asset::Null) - } - 1 => { - // ignore prefix when decoding an explicit asset - d.consume(1); - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Asset::Explicit(explicit)) - } - p if p == 0x0a || p == 0x0b => { - let generator = Decodable::consensus_decode(&mut d)?; - Ok(Asset::Confidential(generator)) - } - 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 sender_sk = SecretKey::new(rng); - let sender_pk = PublicKey::from_secret_key(&secp, &sender_sk); - - let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &sender_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 shared_secret = SharedSecret::new_with_hash(pk, sk, |x, y| { - // 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 y.last().unwrap() % 2 == 0 { - 0x02 - } else { - 0x03 - }; - dh_secret[1..].copy_from_slice(&x); - - sha256d::Hash::hash(&dh_secret).into_inner().into() - }); - - 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 = { - let buffer = d.fill_buf()?; - - if buffer.is_empty() { - return Err(encode::Error::UnexpectedEOF); - } - - buffer[0] - }; - - match prefix { - 0 => { - // consume null value prefix - d.consume(1); - Ok(Nonce::Null) - } - 1 => { - // ignore prefix when decoding an explicit asset - d.consume(1); - let explicit = Decodable::consensus_decode(&mut d)?; - Ok(Nonce::Explicit(explicit)) - } - p if p == 0x02 || p == 0x03 => { - let pk = Decodable::consensus_decode(&mut d)?; - Ok(Nonce::Confidential(pk)) - } - 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 mut 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 - if sk.add_assign(sk2.as_ref()).is_err() { - *self = Self::zero(); - } else { - *self = ValueBlindingFactor::from_slice(sk.as_ref()).expect("Valid Tweak") - } - } - } -} - -impl Neg for ValueBlindingFactor { - type Output = Self; - - fn neg(self) -> Self::Output { - if self.0.as_ref() == &[0u8; 32] { - self - } else { - let mut sk = SecretKey::from_slice(self.into_inner().as_ref()).expect("Valid key"); - sk.negate_assign(); - 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::Bytes( - &[ - 2, - 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 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")] - 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 ee3cf5c4..c2f0227c 100644 --- a/src/dynafed.rs +++ b/src/dynafed.rs @@ -16,17 +16,213 @@ use std::{fmt, io}; -use bitcoin; -use bitcoin::hashes::{Hash, sha256, sha256d}; -#[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; +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; -use encode::{self, Encodable, Decodable}; -use Script; +impl_sha256_midstate_wrapper! { + /// The Merkle root of a set of dynafed parameters. + pub struct ParamsRoot([u8; 32]); +} + +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")] +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.0.as_hex()) + } else { + s.serialize_bytes(self.0) + } + } +} + +/// ad-hoc struct to fmt in hex +struct HexBytesArray<'a>(&'a [Vec]); +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, "{}", e.as_hex())?; + } else { + write!(f, ", {}", e.as_hex())?; + } + } + write!(f, "]") + } +} +impl fmt::Debug for HexBytesArray<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self, f) + } +} +#[cfg(feature = "serde")] +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 { + seq.serialize_element(&HexBytes(&b[..]))?; + } + seq.end() + } +} -/// Dynamic federations paramaters, as encoded in a block header +/// Full dynafed parameters with all fields. #[derive(Clone, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct FullParams { + /// "scriptPubKey" used for block signing + pub signblockscript: Script, + /// Maximum, in bytes, of the size of a blocksigning witness + pub signblock_witness_limit: u32, + /// Untweaked `scriptPubKey` used for pegins + 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". + pub fedpegscript: Vec, + /// "Extension space" used by Liquid for PAK key entries + 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) -> ElidedRoot { + fn serialize_hash(obj: &E) -> sha256d::Hash { + let mut engine = sha256d::Hash::engine(); + obj.consensus_encode(&mut engine).expect("engines don't error"); + sha256d::Hash::from_engine(engine) + } + + let leaves = [ + serialize_hash(&self.fedpeg_program).to_byte_array(), + serialize_hash(&self.fedpegscript).to_byte_array(), + serialize_hash(&self.extension_space).to_byte_array(), + ]; + ElidedRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) + } + + /// 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"); + sha256d::Hash::from_engine(engine) + } + + let leaves = [ + 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.to_parts().0, + self.extra_root().to_byte_array(), + ]; + ParamsRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) + } + + /// Turns parameters into compact parameters. + /// This returns self for compact params and [None] for null ones. + pub fn into_compact(self) -> Params { + Params::Compact { + elided_root: self.extra_root(), + signblockscript: self.signblockscript, + signblock_witness_limit: self.signblock_witness_limit, + } + } + + /// 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", &self.signblockscript[..].as_hex()); + s.field("signblock_witness_limit", &self.signblock_witness_limit); + 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() + } + + + #[cfg(feature = "serde")] + fn serde_serialize(&self, s: S, name: &'static str) -> Result { + let mut st = s.serialize_struct(name, 5)?; + st.serialize_field("signblockscript", &self.signblockscript)?; + st.serialize_field("signblock_witness_limit", &self.signblock_witness_limit)?; + st.serialize_field("fedpeg_program", &self.fedpeg_program)?; + st.serialize_field("fedpegscript", &HexBytes(&self.fedpegscript))?; + st.serialize_field("extension_space", &HexBytesArray(&self.extension_space))?; + st.end() + } +} + +impl fmt::Debug for FullParams { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.fmt_debug(f, "FullParams") + } +} + +impl Encodable for FullParams { + fn consensus_encode(&self, mut s: S) -> Result { + let ret = Encodable::consensus_encode(&self.signblockscript, &mut s)? + + Encodable::consensus_encode(&self.signblock_witness_limit, &mut s)? + + Encodable::consensus_encode(&self.fedpeg_program, &mut s)? + + Encodable::consensus_encode(&self.fedpegscript, &mut s)? + + Encodable::consensus_encode(&self.extension_space, &mut s)?; + Ok(ret) + } +} + +impl Decodable for FullParams { + fn consensus_decode(mut d: D) -> Result { + Ok(FullParams { + signblockscript: Decodable::consensus_decode(&mut d)?, + signblock_witness_limit: Decodable::consensus_decode(&mut d)?, + fedpeg_program: Decodable::consensus_decode(&mut d)?, + fedpegscript: Decodable::consensus_decode(&mut d)?, + extension_space: Decodable::consensus_decode(&mut d)?, + }) + } +} + +/// 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 @@ -37,188 +233,123 @@ 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 { - /// "scriptPubKey" used for block signing - signblockscript: Script, - /// Maximum, in bytes, of the size of a blocksigning witness - signblock_witness_limit: u32, - /// Untweaked `scriptPubKey` used for pegins - fedpeg_program: Script, - /// 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, - /// "Extension space" used by Liquid for PAK key entries - extension_space: Vec>, - }, + Full(FullParams), } impl fmt::Debug for Params { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // Null and Compact have nice Debug formats, but Full has these annoying Vec's. - // For Full, we write the fedpeg program and script and the PAK list as hex. - - // ad-hoc struct to fmt in hex - struct HexBytes<'a>(&'a [u8]); - impl<'a> fmt::Debug for HexBytes<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - bitcoin::hashes::hex::format_hex(&self.0[..], f) - } - } - // ad-hoc struct to fmt in hex - struct HexBytesArray<'a>(&'a [Vec]); - impl<'a> fmt::Debug for HexBytesArray<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[")?; - for (i, e) in self.0.iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - bitcoin::hashes::hex::format_hex(&e[..], f)?; - } - write!(f, "]") - } - } - match self { 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() } - Params::Full { - signblockscript, - signblock_witness_limit, - fedpeg_program, - fedpegscript, - extension_space, - } => { - let mut s = f.debug_struct("Full"); - s.field("signblockscript", &HexBytes(&signblockscript[..])); - s.field("signblock_witness_limit", signblock_witness_limit); - s.field("fedpeg_program", &HexBytes(&fedpeg_program[..])); - s.field("fedpegscript", &HexBytes(&fedpegscript[..])); - s.field("extension_space", &HexBytesArray(&extension_space)); - s.finish() - } + Params::Full(ref full) => full.fmt_debug(f, "Full"), } } } 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, Params::Compact { .. } => false, - Params::Full { .. } => false, + Params::Full(..) => false, } } - /// Check whether this is [Params::Compact]. + /// Check whether this is [`Params::Compact`]. pub fn is_compact(&self) -> bool { match *self { Params::Null => false, Params::Compact { .. } => true, - Params::Full { .. } => false, + Params::Full(..) => false, } } - /// Check whether this is [Params::Full]. + /// Check whether this is [`Params::Full`]. pub fn is_full(&self) -> bool { match *self { Params::Null => false, Params::Compact { .. } => false, - Params::Full { .. } => true, + Params::Full(..) => true, } } - /// 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, Params::Compact { ref signblockscript, ..} => Some(signblockscript), - Params::Full { ref signblockscript, ..} => Some(signblockscript), + Params::Full(ref f) => Some(&f.signblockscript), } } - /// 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, Params::Compact { signblock_witness_limit, ..} => Some(signblock_witness_limit), - Params::Full { signblock_witness_limit, ..} => Some(signblock_witness_limit), + Params::Full(ref f) => Some(f.signblock_witness_limit), } } - /// Get the fedpeg_program. Is [None] for non-[Full] params. - pub fn fedpeg_program(&self) -> Option<&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, - Params::Full { ref fedpeg_program, ..} => Some(fedpeg_program), + Params::Full(ref f) => Some(&f.fedpeg_program), } } - /// 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, Params::Compact { .. } => None, - Params::Full { ref fedpegscript, ..} => Some(fedpegscript), + Params::Full(ref f) => Some(&f.fedpegscript), } } - /// 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, Params::Compact { .. } => None, - Params::Full { ref extension_space, ..} => Some(extension_space), + Params::Full(ref f) => Some(&f.extension_space), } } - /// 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), - Params::Full { .. } => None, + Params::Full(..) => None, } } /// 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 serialize_hash(obj: &E) -> sha256d::Hash { - let mut engine = sha256d::Hash::engine(); - obj.consensus_encode(&mut engine).expect("engines don't error"); - sha256d::Hash::from_engine(engine) - } - + 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 fedpeg_program, ref fedpegscript, ref extension_space, .. } => { - let leaves = [ - serialize_hash(fedpeg_program).into_inner(), - serialize_hash(fedpegscript).into_inner(), - serialize_hash(extension_space).into_inner(), - ]; - ::fast_merkle_root::fast_merkle_root(&leaves[..]) - }, + 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"); @@ -226,54 +357,48 @@ 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 = ::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(), ]; - ::fast_merkle_root::fast_merkle_root(&leaves[..]) + ParamsRoot::from_midstate(crate::fast_merkle_root::fast_merkle_root(&leaves[..])) } - /// Turns paramers into compact parameters. - /// This returns self for compact params and [None] for null ones. - pub fn into_compact(self) -> Option { - // Avoid calcualting when it's not needed. - let mut extra_root = None; - if self.is_full() { - extra_root = Some(self.extra_root()); + /// Get the full params when this params are full. + pub fn full(&self) -> Option<&FullParams> { + match self { + Params::Null => None, + Params::Compact { .. } => None, + Params::Full(ref f) => Some(f), } + } + /// Convert into the full params when this params are full. + pub fn into_full(self) -> Option { match self { Params::Null => None, - Params::Compact { signblockscript, signblock_witness_limit, elided_root } => { - Some(Params::Compact { - signblockscript, - signblock_witness_limit, - elided_root, - }) - } - Params::Full { signblockscript, signblock_witness_limit, ..} => { - Some(Params::Compact { - signblockscript, - signblock_witness_limit, - elided_root: extra_root.unwrap(), - }) - } + Params::Compact { .. } => None, + Params::Full(f) => Some(f), } } -} -impl Default for Params { - fn default() -> Params { - Params::Null + /// Turns parameters into compact parameters. + /// This returns self for compact params and [None] for null ones. + pub fn into_compact(self) -> Option { + match self { + Params::Null => None, + s @ Params::Compact { .. } => Some(s), + Params::Full(f) => Some(f.into_compact()), + } } } @@ -293,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 { @@ -344,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 { @@ -422,13 +545,13 @@ impl<'de> Deserialize<'de> for Params { Some(fedpeg_program), Some(HexBytes(fedpegscript)), Some(extension_space), - ) => Ok(Params::Full { + ) => Ok(Params::Full(FullParams { signblockscript, signblock_witness_limit, fedpeg_program, fedpegscript, extension_space: extension_space.into_iter().map(|h| h.0).collect(), - }), + })), ( Some(signblockscript), Some(signblock_witness_limit), @@ -453,6 +576,7 @@ impl<'de> Deserialize<'de> for Params { "fedpeg_program", "fedpegscript", "extension_space", + "elided_root", ]; d.deserialize_struct("Params", FIELDS, Visitor) } @@ -461,36 +585,6 @@ impl<'de> Deserialize<'de> for Params { #[cfg(feature = "serde")] impl Serialize for Params { fn serialize(&self, s: S) -> Result { - use serde::ser::{SerializeSeq, SerializeStruct}; - - // 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<'a> Serialize for HexBytes<'a> { - fn serialize(&self, s: S) -> Result { - if s.is_human_readable() { - s.collect_str(self) - } else { - s.serialize_bytes(&self.0[..]) - } - } - } - // ad-hoc struct to fmt in hex - struct HexBytesArray<'a>(&'a [Vec]); - impl<'a> Serialize for HexBytesArray<'a> { - fn serialize(&self, s: S) -> Result { - let mut seq = s.serialize_seq(Some(self.0.len()))?; - for b in self.0 { - seq.serialize_element(&HexBytes(&b[..]))?; - } - seq.end() - } - } - match *self { Params::Null => { let st = s.serialize_struct("Params", 0)?; @@ -501,27 +595,13 @@ impl Serialize for Params { ref signblock_witness_limit, ref elided_root, } => { - let mut st = s.serialize_struct("Params", 2)?; + let mut st = s.serialize_struct("Params", 3)?; st.serialize_field("signblockscript", signblockscript)?; st.serialize_field("signblock_witness_limit", signblock_witness_limit)?; st.serialize_field("elided_root", elided_root)?; st.end() }, - Params::Full { - ref signblockscript, - ref signblock_witness_limit, - ref fedpeg_program, - ref fedpegscript, - ref extension_space, - } => { - let mut st = s.serialize_struct("Params", 5)?; - st.serialize_field("signblockscript", signblockscript)?; - st.serialize_field("signblock_witness_limit", signblock_witness_limit)?; - st.serialize_field("fedpeg_program", fedpeg_program)?; - st.serialize_field("fedpegscript", &HexBytes(&fedpegscript))?; - st.serialize_field("extension_space", &HexBytesArray(&extension_space))?; - st.end() - }, + Params::Full(ref full) => full.serde_serialize(s, "Params"), } } } @@ -538,43 +618,27 @@ 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 signblockscript, - ref signblock_witness_limit, - ref fedpeg_program, - ref fedpegscript, - ref extension_space, - } => { + Params::Full(ref f) => { Encodable::consensus_encode(&2u8, &mut s)? + - Encodable::consensus_encode(signblockscript, &mut s)? + - Encodable::consensus_encode(signblock_witness_limit, &mut s)? + - Encodable::consensus_encode(fedpeg_program, &mut s)? + - Encodable::consensus_encode(fedpegscript, &mut s)? + - Encodable::consensus_encode(extension_space, &mut s)? + Encodable::consensus_encode(f, &mut s)? }, }) } } impl Decodable for Params { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let ser_type: u8 = Decodable::consensus_decode(&mut d)?; match ser_type { 0 => Ok(Params::Null), 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)?), - }), - 2 => Ok(Params::Full { - signblockscript: Decodable::consensus_decode(&mut d)?, - signblock_witness_limit: Decodable::consensus_decode(&mut d)?, - fedpeg_program: Decodable::consensus_decode(&mut d)?, - fedpegscript: Decodable::consensus_decode(&mut d)?, - extension_space: 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( "bad serialize type for dynafed parameters" )), @@ -586,8 +650,7 @@ 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::*; @@ -622,42 +685,46 @@ mod tests { let signblockscript: Script = vec![1].into(); let signblock_wl = 2; - let fp_program: 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 { + 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" ); - let header = ::block::BlockHeader{ - ext: ::block::ExtData::Dynafed { + let header = crate::block::BlockHeader{ + ext: crate::block::ExtData::Dynafed { current: compact_entry, proposed: full_entry, signblock_witness: vec![], }, - ..Default::default() + version: Default::default(), + 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" ); } @@ -670,24 +737,32 @@ mod tests { #[test] fn into_compact_test() { - let full = Params::Full { + let full = FullParams { signblockscript: vec![0x01, 0x02].into(), signblock_witness_limit: 3, fedpeg_program: vec![0x04, 0x05].into(), fedpegscript: vec![0x06, 0x07], extension_space: vec![vec![0x08, 0x09], vec![0x0a]], }; + let extra_root = full.extra_root(); + let root = full.calculate_root(); + let params = Params::Full(full.clone()); + assert_eq!(params.full(), Some(&full)); + assert_eq!(params.clone().into_full(), Some(full.clone())); + assert_eq!(params.extra_root(), extra_root); + assert_eq!(params.calculate_root(), root); + assert_eq!( - to_debug_string(&full), + to_debug_string(¶ms), "Full { signblockscript: 0102, signblock_witness_limit: 3, fedpeg_program: 0405, fedpegscript: 0607, extension_space: [0809, 0a] }", ); - let extra_root = full.extra_root(); - let compact = full.into_compact().unwrap(); + let compact = params.into_compact().unwrap(); assert_eq!( to_debug_string(&compact), "Compact { signblockscript: 0102, signblock_witness_limit: 3, elided_root: c3058c822b22a13bb7c47cf50d3f3c7817e7d9075ff55a7d16c85b9673e7e553 }", ); + assert_eq!(compact.calculate_root(), full.calculate_root()); assert_eq!(compact.elided_root(), Some(&extra_root)); assert_eq!(compact.extra_root(), extra_root); } @@ -697,13 +772,13 @@ mod tests { fn test_serde_roundtrip() { use serde_json; - let full = Params::Full { + let full = Params::Full(FullParams { signblockscript: vec![0x01, 0x02].into(), signblock_witness_limit: 3, fedpeg_program: vec![0x04, 0x05].into(), fedpegscript: vec![0x06, 0x07], extension_space: vec![vec![0x08, 0x09], vec![0x0a]], - }; + }); let encoded = serde_json::to_string(&full).unwrap(); let decoded: Params = serde_json::from_str(&encoded).unwrap(); assert_eq!(full, decoded); diff --git a/src/encode.rs b/src/encode.rs index d715135b..578e2d87 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -16,27 +16,59 @@ //! use std::io::Cursor; -use std::{error, fmt, io, mem}; -use hashes; +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 transaction::{Transaction, TxIn, TxOut}; -use pset; +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 @@ -56,8 +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), + /// `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 { @@ -68,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) @@ -77,7 +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"), } } } @@ -85,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) } } @@ -128,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) } } @@ -145,7 +203,7 @@ pub trait Encodable { /// Data which can be encoded in a consensus-consistent way pub trait Decodable: Sized { /// Decode an object with a well-defined format - fn consensus_decode(d: D) -> Result; + fn consensus_decode(d: D) -> Result; } /// Encode an object into a vector @@ -157,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 @@ -169,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", + )) } } @@ -183,115 +243,239 @@ pub fn deserialize_partial(data: &[u8]) -> Result<(T, usize), Erro Ok((rv, consumed)) } -impl Encodable for sha256::Midstate { +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()) +} + +// 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 { + crate::LockTime::from(*self).consensus_encode(s) + } +} +impl Decodable for crate::locktime::Height { + fn consensus_decode(d: D) -> Result { + match crate::LockTime::consensus_decode(d)? { + crate::LockTime::Blocks(h) => Ok(h), + x @ crate::LockTime::Seconds(_) => Err(Error::BadLockTime(x)), + } + } +} + +impl Encodable for crate::locktime::Time { + fn consensus_encode(&self, s: S) -> Result { + crate::LockTime::from(*self).consensus_encode(s) + } +} +impl Decodable for crate::locktime::Time { + fn consensus_decode(d: D) -> Result { + match crate::LockTime::consensus_decode(d)? { + crate::LockTime::Seconds(t) => Ok(t), + x @ crate::LockTime::Blocks(_) => Err(Error::BadLockTime(x)), + } + } +} + +impl Encodable for crate::Witness { fn consensus_encode(&self, e: W) -> Result { - self.into_inner().consensus_encode(e) + let mut counter = ByteCounter::new(e); + crate::encoding::encode_to_writer(self, &mut counter)?; + Ok(counter.into_count()) } } -impl Decodable for sha256::Midstate { - fn consensus_decode(d: D) -> Result { - Ok(Self::from_inner(<[u8; 32]>::consensus_decode(d)?)) +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)), + } } } -/// 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)?) - } +/// 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, } + } +} - impl Decodable for $type { - fn consensus_decode(mut d: D) -> Result { - Ok(btcenc::Decodable::consensus_decode(&mut d)?) +// 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_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!(::hashes::sha256d::Hash); -impl_upstream!(bitcoin::Transaction); -impl_upstream!(bitcoin::BlockHash); + +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); + +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 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 -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 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) } + } +} - 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) +impl Encodable for Vec { + #[inline] + fn consensus_encode(&self, s: S) -> Result { + self[..].consensus_encode(s) + } +} + +impl Encodable for Box<[T]> { + #[inline] + fn consensus_encode(&self, s: S) -> Result { + self[..].consensus_encode(s) + } +} + +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_vec!(TxIn); -impl_vec!(TxOut); -impl_vec!(Transaction); +impl Decodable for Box<[T]> { + #[inline] + fn consensus_decode(d: D) -> Result { + let v = Vec::::consensus_decode(d)?; + Ok(v.into()) + } +} -macro_rules! impl_option { - ($type: ty) => { - impl Encodable for Option<$type> { +macro_rules! impl_array { + ( $size:literal ) => { + impl Encodable for [u8; $size] { #[inline] - fn consensus_encode(&self, e: W) -> Result { - match self { - None => Vec::::new().consensus_encode(e), - Some(v) => v.serialize().consensus_encode(e), - } + fn consensus_encode( + &self, + mut w: W, + ) -> core::result::Result { + w.emit_slice(&self[..])?; + Ok($size) } } - impl Decodable for Option<$type> { + impl Decodable for [u8; $size] { #[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(<$type>::from_slice(&v)?)) - } + 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 { @@ -300,34 +484,35 @@ impl Encodable for Tweak { } impl Decodable for Tweak { - fn consensus_decode(d: D) -> Result { + fn consensus_decode(d: D) -> Result { Ok(Tweak::from_inner(<[u8; 32]>::consensus_decode(d)?)?) } } -impl Encodable for RangeProof { - fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) +impl Encodable for sha256::Hash { + fn consensus_encode(&self, s: S) -> Result { + self.to_byte_array().consensus_encode(s) } } -impl Decodable for RangeProof { - fn consensus_decode(d: D) -> Result { - Ok(RangeProof::from_slice(&>::consensus_decode(d)?)?) +impl Decodable for sha256::Hash { + fn consensus_decode(d: D) -> Result { + Ok(Self::from_byte_array( + <::Bytes>::consensus_decode(d)?, + )) } } -impl Encodable for SurjectionProof { - fn consensus_encode(&self, e: W) -> Result { - self.serialize().consensus_encode(e) +impl Encodable for TapLeafHash { + fn consensus_encode(&self, s: S) -> Result { + self.to_byte_array().consensus_encode(s) } } -impl Decodable for SurjectionProof { - fn consensus_decode(d: D) -> Result { - Ok(SurjectionProof::from_slice(&>::consensus_decode(d)?)?) +impl Decodable for TapLeafHash { + fn consensus_decode(d: D) -> Result { + Ok(Self::from_byte_array( + <::Bytes>::consensus_decode(d)?, + )) } } - -impl_option!(RangeProof); -impl_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 new file mode 100644 index 00000000..6957ee8a --- /dev/null +++ b/src/error.rs @@ -0,0 +1,36 @@ +//! Contains error types and other error handling tools. + +pub use crate::parse::ParseIntError; + +/// Impls `std::error::Error` for the specified type with appropriate attributes, possibly returning +/// source. +macro_rules! impl_std_error { + // No source available + ($type:ty) => { + impl std::error::Error for $type {} + }; + // Struct with $field as source + ($type:ty, $field:ident) => { + impl std::error::Error for $type { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.$field) + } + } + }; +} +pub(crate) use impl_std_error; + +/// Formats error. If `std` feature is OFF appends error source (delimited by `: `). We do this +/// because `e.source()` is only available in std builds, without this macro the error source is +/// lost for no-std builds. +macro_rules! write_err { + ($writer:expr, $string:literal $(, $args:expr)*; $source:expr) => { + { + 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