Compare commits

..

2 Commits

Author SHA1 Message Date
Alekos Filini
0facb99c81 Bump version to 0.23.0 2022-10-07 13:23:08 +02:00
Alekos Filini
f76d579313 Bump version to 0.23.0-rc.1 2022-09-29 21:00:19 +02:00
65 changed files with 2009 additions and 3296 deletions

View File

@@ -1,2 +0,0 @@
[advisories]
ignore = ["RUSTSEC-2022-0046"]

View File

@@ -1,17 +0,0 @@
---
name: Enhancement request
about: Request a new feature or change to an existing feature
title: ''
labels: 'enhancement'
assignees: ''
---
**Describe the enhancement**
<!-- A clear and concise description of what you would like added or changed. -->
**Use case**
<!-- Tell us how you or others will use this new feature or change to an existing feature. -->
**Additional context**
<!-- Add any other context about the enhancement here. -->

View File

@@ -34,7 +34,6 @@ Change the `master` branch to the next MINOR+1 version:
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR+1`, eg. `bump_dev_0_22`.
- [ ] Bump the `bump_dev_MAJOR_MINOR+1` branch to the next development MINOR+1 version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR+1.0`.
- Update the `CHANGELOG.md` file.
- The commit message should be "Bump version to MAJOR.MINOR+1.0".
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR+1` branch to `master`.
- Title PR "Bump version to MAJOR.MINOR+1.0".

View File

@@ -34,7 +34,6 @@ Change the `master` branch to the new PATCH+1 version:
- [ ] Create a new PR branch called `bump_dev_MAJOR_MINOR_PATCH+1`, eg. `bump_dev_0_22_1`.
- [ ] Bump the `bump_dev_MAJOR_MINOR` branch to the next development PATCH+1 version.
- Change the `Cargo.toml` version value to `MAJOR.MINOR.PATCH+1`.
- Update the `CHANGELOG.md` file.
- The commit message should be "Bump version to MAJOR.MINOR.PATCH+1".
- [ ] Create PR and merge the `bump_dev_MAJOR_MINOR_PATCH+1` branch to `master`.
- Title PR "Bump version to MAJOR.MINOR.PATCH+1".

View File

@@ -2,9 +2,6 @@ name: Audit
on:
push:
branches:
- 'master'
- 'release/*'
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'

View File

@@ -1,12 +1,4 @@
on:
push:
branches:
- 'master'
- 'release/*'
pull_request:
branches:
- 'master'
- 'release/*'
on: [push, pull_request]
name: Code Coverage

View File

@@ -1,12 +1,4 @@
on:
push:
branches:
- 'master'
- 'release/*'
pull_request:
branches:
- 'master'
- 'release/*'
on: [push, pull_request]
name: CI
@@ -18,9 +10,9 @@ jobs:
strategy:
matrix:
rust:
- version: 1.65.0 # STABLE
- version: 1.60.0 # STABLE
clippy: true
- version: 1.57.0 # MSRV
- version: 1.56.1 # MSRV
features:
- default
- minimal
@@ -59,22 +51,6 @@ jobs:
run: rustup component add clippy
- name: Update toolchain
run: rustup update
- name: Pin dependencies for MSRV
if: matrix.rust.version == '1.57.0'
run: |
cargo update -p log --precise "0.4.18"
cargo update -p tempfile --precise "3.6.0"
cargo update -p hashlink --precise "0.8.1"
cargo update -p regex --precise "1.7.3"
cargo update -p zip:0.6.6 --precise "0.6.3"
cargo update -p rustix --precise "0.37.23"
cargo update -p tokio --precise "1.29.1"
cargo update -p cc --precise "1.0.81"
cargo update -p rustls:0.21.6 --precise "0.21.1"
cargo update -p flate2:1.0.27 --precise "1.0.26"
cargo update -p reqwest --precise "0.11.18"
cargo update -p h2 --precise "0.3.20"
cargo update -p rustls-webpki --precise "0.100.1"
- name: Build
run: cargo build --features ${{ matrix.features }} --no-default-features
- name: Clippy
@@ -170,7 +146,7 @@ jobs:
- run: sudo apt-get update || exit 1
- run: sudo apt-get install -y libclang-common-10-dev clang-10 libc6-dev-i386 || exit 1
- name: Set default toolchain
run: rustup default 1.65.0 # STABLE
run: rustup default 1.56.1 # STABLE
- name: Set profile
run: rustup set profile minimal
- name: Add target wasm32
@@ -178,7 +154,7 @@ jobs:
- name: Update toolchain
run: rustup update
- name: Check
run: cargo check --target wasm32-unknown-unknown --features async-interface,use-esplora-async,dev-getrandom-wasm --no-default-features
run: cargo check --target wasm32-unknown-unknown --features use-esplora-async --no-default-features
fmt:
name: Rust fmt
@@ -197,13 +173,13 @@ jobs:
- name: Check fmt
run: cargo fmt --all -- --config format_code_in_doc_comments=true --check
test_hardware_wallet:
runs-on: ubuntu-20.04
test_harware_wallet:
runs-on: ubuntu-latest
strategy:
matrix:
rust:
- version: 1.65.0 # STABLE
- version: 1.57.0 # MSRV
- version: 1.60.0 # STABLE
- version: 1.56.1 # MSRV
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -223,21 +199,5 @@ jobs:
run: rustup set profile minimal
- name: Update toolchain
run: rustup update
- name: Pin dependencies for MSRV
if: matrix.rust.version == '1.57.0'
run: |
cargo update -p log --precise "0.4.18"
cargo update -p tempfile --precise "3.6.0"
cargo update -p hashlink --precise "0.8.1"
cargo update -p regex --precise "1.7.3"
cargo update -p zip:0.6.6 --precise "0.6.3"
cargo update -p rustix --precise "0.37.23"
cargo update -p tokio --precise "1.29.1"
cargo update -p cc --precise "1.0.81"
cargo update -p rustls:0.21.6 --precise "0.21.1"
cargo update -p flate2:1.0.27 --precise "1.0.26"
cargo update -p reqwest --precise "0.11.18"
cargo update -p h2 --precise "0.3.20"
cargo update -p rustls-webpki --precise "0.100.1"
- name: Test
run: cargo test --features test-hardware-signer

View File

@@ -1,14 +1,6 @@
name: Publish Nightly Docs
on:
push:
branches:
- 'master'
- 'release/*'
pull_request:
branches:
- 'master'
- 'release/*'
on: [push, pull_request]
jobs:
build_docs:
@@ -26,7 +18,7 @@ jobs:
target
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Set default toolchain
run: rustup default nightly-2022-12-14
run: rustup default nightly-2022-01-25
- name: Set profile
run: rustup set profile minimal
- name: Update toolchain

View File

@@ -1,188 +1,13 @@
# Changelog
All notable changes to this project can be found here and in each release's git tag and can be viewed with `git tag -ln100 "v*"`. See also [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
Contributors do not need to change this file but do need to add changelog details in their PR descriptions. The person making the next release will collect changelog details from included PRs and edit this file prior to each release.
All notable changes to this project prior to release **0.22.0** are documented in this file. Future
changelog information can be found in each release's git tag and can be viewed with `git tag -ln100 "v*"`.
Changelog info is also documented on the [GitHub releases](https://github.com/bitcoindevkit/bdk/releases)
page. See [DEVELOPMENT_CYCLE.md](DEVELOPMENT_CYCLE.md) for more details.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [v0.28.2]
### Summary
Reverts the 0.28.1 esplora-client version update from 0.5.0 back to 0.4.0.
## [v0.28.1]
### Summary
This patch release backports (from the BDK 1.0 dev branch) a fix for a bug in the policy condition calculation and adds a new taproot single key descriptor template (BIP-86). The policy condition calculation bug can cause issues when a policy subtree fails due to missing info even if it's not selected when creating a new transaction, errors on unused policy paths are now ignored.
### Fixed
- Backported #932 fix for policy condition calculation #1008
### Added
- Backported #840 taproot descriptor template (BIP-86) #1033
## [v0.28.0]
### Summary
Disable default-features for rust-bitcoin and rust-miniscript dependencies, and for rust-esplora-client optional dependency. New default `std` feature must be enabled unless building for wasm.
### Changed
- Bump bip39 crate to v2.0.0 #875
- Set default-features = false for rust-bitcoin and rust-miniscript #882
- Update esplora client dependency to version 0.4 #884
- Added new `std` feature as part of default features #930
## [v0.27.1]
### Summary
Fixes [RUSTSEC-2022-0090], this issue is only applicable if you are using the optional sqlite database feature.
[RUSTSEC-2022-0090]: https://rustsec.org/advisories/RUSTSEC-2022-0090
### Changed
- Update optional sqlite dependency from 0.27.0 to 0.28.0. #867
## [v0.27.0]
### Summary
A maintenance release with a bump in project MSRV to 1.57.0, updated dependence and a few developer oriented improvements. Improvements include better error formatting, don't default to async/await for wasm32 and adding derived PartialEq and Eq on SyncTime.
### Changed
- Improve display error formatting #814
- Don't default to use async/await on wasm32 #831
- Project MSRV changed from 1.56.1 to 1.57.0 #842
- Update rust-miniscript dependency to latest bug fix release 9.0 #844
### Added
- Derive PartialEq, Eq on SyncTime #837
## [v0.26.0]
### Summary
This release improves Fulcrum electrum server compatibility and fixes public descriptor template key origin paths. We also snuck in small enhancements to configure the electrum client to validate the domain using SSL and sort TransactionDetails by block height and timestamp.
### Fixed
- Make electrum blockchain client `save_tx` function order independent to work with Fulcrum servers. #808
- Fix wrong testnet key origin path in public descriptor templates. #818
- Make README.md code examples compile without errors. #820
### Changed
- Bump `hwi` dependency to `0.4.0`. #825
- Bump `esplora-client` dependency to `0.3` #830
### Added
- For electrum blockchain client, allow user to configure whether to validate the domain using SSL. #805
- Implement ordering for `TransactionDetails`. #812
## [v0.25.0]
### Summary
This release fixes slow sync time and big script_pubkeys table with SQLite, the wallet rescan height for the FullyNodedExport and setting the network for keys in the KeyMap when using descriptor templates. Also added are new blockchain and mnemonic examples.
### Fixed
- Slow sync time and big script_pubkeys table with SQLite.
- Wallet rescan height for the FullyNodedExport.
- Setting the network for keys in the KeyMap when using descriptor templates.
### Added
- Examples for connecting to Esplora, Electrum Server, Neutrino and Bitcoin Core.
- Example for using a mnemonic in a descriptors.
## [v0.24.0]
### Summary
This release contains important dependency updates for `rust-bitcoin` to `0.29` and `rust-miniscript` to `8.0`, plus related crates that also depend on the latest version of `rust-bitcoin`. The release also includes a breaking change to the BDK signer which now produces low-R signatures by default, saving one byte. A bug was found in the `get_checksum` and `get_checksum_bytes` functions, which are now deprecated in favor of fixed versions called `calc_checksum` and `calc_checksum_bytes`. And finally a new `hardware-signer` features was added that re-exports the `hwi` crate, along with a new `hardware_signers.rs` example file.
### Changed
- Updated dependency versions for `rust-bitcoin` to `0.29` and `rust-miniscript` to `8.0`, plus all related crates. @afilini #770
- BDK Signer now produces low-R signatures by default, saving one byte. If you want to preserve the original behavior, set allow_grinding in the SignOptions to false. @vladimirfomene #779
- Deprecated `get_checksum`and `get_checksum_bytes` due to bug where they calculates the checksum of a descriptor that already has a checksum. Use `calc_checksum` and `calc_checksum_bytes` instead. @evanlinjin #765
- Remove deprecated "address validators". @afilini #770
### Added
- New `calc_checksum` and `calc_checksum_bytes`, replace deprecated `get_checksum` and `get_checksum_bytes`. @evanlinjin #765
- Re-export the hwi crate when the feature hardware-signer is on. @danielabrozzoni #758
- New examples/hardware_signer.rs. @danielabrozzoni #758
- Make psbt module public to expose PsbtUtils trait to downstream projects. @notmandatory #782
## [v0.23.0]
### Summary
This release brings new utilities functions on PSBTs like `fee_amount()` and `fee_rate()` and migrates BDK to use our new external esplora client library.
As always many bug fixes, docs and tests improvement are also included.
### Changed
- Update electrum-client to 0.11.0 by @afilini in https://github.com/bitcoindevkit/bdk/pull/737
- Change configs for source-base code coverage by @wszdexdrf in https://github.com/bitcoindevkit/bdk/pull/708
- Improve docs regarding PSBT finalization by @tnull in https://github.com/bitcoindevkit/bdk/pull/753
- Update compiler example to a Policy example by @rajarshimaitra in https://github.com/bitcoindevkit/bdk/pull/730
- Fix the release process by @afilini in https://github.com/bitcoindevkit/bdk/pull/754
- Remove redundant duplicated keys check by @afilini in https://github.com/bitcoindevkit/bdk/pull/761
- Remove genesis_block lazy initialization by @shobitb in https://github.com/bitcoindevkit/bdk/pull/756
- Fix `Wallet::descriptor_checksum` to actually return the checksum by @evanlinjin in https://github.com/bitcoindevkit/bdk/pull/763
- Use the esplora client crate by @afilini in https://github.com/bitcoindevkit/bdk/pull/764
### Added
- Run code coverage on every PR by @danielabrozzoni in https://github.com/bitcoindevkit/bdk/pull/747
- Add psbt_signer.rs example by @notmandatory in https://github.com/bitcoindevkit/bdk/pull/744
- Add fee_amount() and fee_rate() functions to PsbtUtils trait by @notmandatory in https://github.com/bitcoindevkit/bdk/pull/728
- Add tests to improve coverage by @vladimirfomene in https://github.com/bitcoindevkit/bdk/pull/745
- Enable signing taproot transactions with only `non_witness_utxos` by @afilini in https://github.com/bitcoindevkit/bdk/pull/757
- Add datatype for is_spent sqlite column by @vladimirfomene in https://github.com/bitcoindevkit/bdk/pull/713
- Add vscode filter to gitignore by @evanlinjin in https://github.com/bitcoindevkit/bdk/pull/762
## [v0.22.0]
### Summary
This release brings support for hardware signers on desktop through the HWI library.
It also includes fixes and improvements which are part of our ongoing effort of integrating
BDK and LDK together.
### Changed
- FeeRate function name as_sat_vb to as_sat_per_vb. #678
- Verify signatures after signing. #718
- Dependency electrum-client to 0.11.0. #737
### Added
- Functions to create FeeRate from sats/kvbytes and sats/kwu. #678
- Custom hardware wallet signer HwiSigner in wallet::hardwaresigner module. #682
- Function allow_dust on TxBuilder. #689
- Implementation of Deref<Target=UrlClient> for EsploraBlockchain. #722
- Implementation of Deref<Target=Client> for ElectrumBlockchain #705
- Implementation of Deref<Target=Client> for RpcBlockchain. #731
## [v0.21.0]
## [v0.21.0] - [v0.20.0]
- Add `descriptor::checksum::get_checksum_bytes` method.
- Add `Excess` enum to handle remaining amount after coin selection.
@@ -195,7 +20,7 @@ BDK and LDK together.
- New `RpcBlockchain` implementation with various fixes.
- Return balance in separate categories, namely `confirmed`, `trusted_pending`, `untrusted_pending` & `immature`.
## [v0.20.0]
## [v0.20.0] - [v0.19.0]
- New MSRV set to `1.56.1`
- Fee sniping discouraging through nLockTime - if the user specifies a `current_height`, we use that as a nlocktime, otherwise we use the last sync height (or 0 if we never synced)
@@ -206,7 +31,7 @@ BDK and LDK together.
- Deprecate `AddressValidator`
- Fix Electrum wallet sync potentially causing address index decrement - compare proposed index and current index before applying batch operations during sync.
## [v0.19.0]
## [v0.19.0] - [v0.18.0]
- added `OldestFirstCoinSelection` impl to `CoinSelectionAlgorithm`
- New MSRV set to `1.56`
@@ -222,7 +47,7 @@ BDK and LDK together.
- Support for `tr()` descriptors in the `descriptor!()` macro
- Add support for Bitcoin Core 23.0 when using the `rpc` blockchain
## [v0.18.0]
## [v0.18.0] - [v0.17.0]
- Add `sqlite-bundled` feature for deployments that need a bundled version of sqlite, i.e. for mobile platforms.
- Added `Wallet::get_signers()`, `Wallet::descriptor_checksum()` and `Wallet::get_address_validators()`, exposed the `AsDerived` trait.
@@ -232,7 +57,7 @@ BDK and LDK together.
- Rename `WalletExport` to `FullyNodedExport`, deprecate the former.
- Bump `miniscript` dependency version to `^6.1`.
## [v0.17.0]
## [v0.17.0] - [v0.16.1]
- Removed default verification from `wallet::sync`. sync-time verification is added in `script_sync` and is activated by `verify` feature flag.
- `verify` flag removed from `TransactionDetails`.
@@ -253,45 +78,45 @@ To decouple the `Wallet` from the `Blockchain` we've made major changes:
- Removed `max_addresses` sync parameter which determined how many addresses to cache before syncing since this can just be done with `ensure_addresses_cached`.
- remove `flush` method from the `Database` trait.
## [v0.16.1]
## [v0.16.1] - [v0.16.0]
- Pin tokio dependency version to ~1.14 to prevent errors due to their new MSRV 1.49.0
## [v0.16.0]
## [v0.16.0] - [v0.15.0]
- Disable `reqwest` default features.
- Added `reqwest-default-tls` feature: Use this to restore the TLS defaults of reqwest if you don't want to add a dependency to it in your own manifest.
- Use dust_value from rust-bitcoin
- Fixed generating WIF in the correct network format.
## [v0.15.0]
## [v0.15.0] - [v0.14.0]
- Overhauled sync logic for electrum and esplora.
- Unify ureq and reqwest esplora backends to have the same configuration parameters. This means reqwest now has a timeout parameter and ureq has a concurrency parameter.
- Fixed esplora fee estimation.
## [v0.14.0]
## [v0.14.0] - [v0.13.0]
- BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39.
- Add new method on the `TxBuilder` to embed data in the transaction via `OP_RETURN`. To allow that a fix to check the dust only on spendable output has been introduced.
- Update the `Database` trait to store the last sync timestamp and block height
- Rename `ConfirmationTime` to `BlockTime`
## [v0.13.0]
## [v0.13.0] - [v0.12.0]
- Exposed `get_tx()` method from `Database` to `Wallet`.
## [v0.12.0]
## [v0.12.0] - [v0.11.0]
- Activate `miniscript/use-serde` feature to allow consumers of the library to access it via the re-exported `miniscript` crate.
- Add support for proxies in `EsploraBlockchain`
- Added `SqliteDatabase` that implements `Database` backed by a sqlite database using `rusqlite` crate.
## [v0.11.0]
## [v0.11.0] - [v0.10.0]
- Added `flush` method to the `Database` trait to explicitly flush to disk latest changes on the db.
## [v0.10.0]
## [v0.10.0] - [v0.9.0]
- Added `RpcBlockchain` in the `AnyBlockchain` struct to allow using Rpc backend where `AnyBlockchain` is used (eg `bdk-cli`)
- Removed hard dependency on `tokio`.
@@ -305,21 +130,21 @@ To decouple the `Wallet` from the `Blockchain` we've made major changes:
- Removed `stop_gap` from `Blockchain` trait and added it to only `ElectrumBlockchain` and `EsploraBlockchain` structs.
- Added a `ureq` backend for use when not using feature `async-interface` or target WASM. `ureq` is a blocking HTTP client.
## [v0.9.0]
## [v0.9.0] - [v0.8.0]
### Wallet
- Added Bitcoin core RPC added as blockchain backend
- Added a `verify` feature that can be enable to verify the unconfirmed txs we download against the consensus rules
## [v0.8.0]
## [v0.8.0] - [v0.7.0]
### Wallet
- Added an option that must be explicitly enabled to allow signing using non-`SIGHASH_ALL` sighashes (#350)
#### Changed
`get_address` now returns an `AddressInfo` struct that includes the index and derefs to `Address`.
## [v0.7.0]
## [v0.7.0] - [v0.6.0]
### Policy
#### Changed
@@ -334,7 +159,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
- Require and validate `non_witness_utxo` for SegWit signatures by default, can be adjusted with `SignOptions`
- Replace the opt-in builder option `force_non_witness_utxo` with the opposite `only_witness_utxo`. From now on we will provide the `non_witness_utxo`, unless explicitly asked not to.
## [v0.6.0]
## [v0.6.0] - [v0.5.1]
### Misc
#### Changed
@@ -358,13 +183,13 @@ Timelocks are considered (optionally) in building the `satisfaction` field
#### Fixed
- Fixed `coin_select` calculation for UTXOs where `value < fee` that caused over-/underflow errors.
## [v0.5.1]
## [v0.5.1] - [v0.5.0]
### Misc
#### Changed
- Pin `hyper` to `=0.14.4` to make it compile on Rust 1.45
## [v0.5.0]
## [v0.5.0] - [v0.4.0]
### Misc
#### Changed
@@ -374,7 +199,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
#### Changed
- `FeeRate` constructors `from_sat_per_vb` and `default_min_relay_fee` are now `const` functions
## [v0.4.0]
## [v0.4.0] - [v0.3.0]
### Keys
#### Changed
@@ -403,7 +228,7 @@ Timelocks are considered (optionally) in building the `satisfaction` field
- Removed unneeded `Result<(), PolicyError>` return type for `Satisfaction::finalize()`
- Removed the `TooManyItemsSelected` policy error (see commit message for more details)
## [v0.3.0]
## [v0.3.0] - [v0.2.0]
### Descriptor
#### Changed
@@ -440,7 +265,7 @@ final transaction is created by calling `finish` on the builder.
#### Changed
- Remove `cli.rs` module, `cli-utils` feature and `repl.rs` example; moved to new [`bdk-cli`](https://github.com/bitcoindevkit/bdk-cli) repository
## [v0.2.0]
## [v0.2.0] - [0.1.0-beta.1]
### Project
#### Added
@@ -668,14 +493,3 @@ final transaction is created by calling `finish` on the builder.
[v0.19.0]: https://github.com/bitcoindevkit/bdk/compare/v0.18.0...v0.19.0
[v0.20.0]: https://github.com/bitcoindevkit/bdk/compare/v0.19.0...v0.20.0
[v0.21.0]: https://github.com/bitcoindevkit/bdk/compare/v0.20.0...v0.21.0
[v0.22.0]: https://github.com/bitcoindevkit/bdk/compare/v0.21.0...v0.22.0
[v0.23.0]: https://github.com/bitcoindevkit/bdk/compare/v0.22.0...v0.23.0
[v0.24.0]: https://github.com/bitcoindevkit/bdk/compare/v0.23.0...v0.24.0
[v0.25.0]: https://github.com/bitcoindevkit/bdk/compare/v0.24.0...v0.25.0
[v0.26.0]: https://github.com/bitcoindevkit/bdk/compare/v0.25.0...v0.26.0
[v0.27.0]: https://github.com/bitcoindevkit/bdk/compare/v0.26.0...v0.27.0
[v0.27.1]: https://github.com/bitcoindevkit/bdk/compare/v0.27.0...v0.27.1
[v0.28.0]: https://github.com/bitcoindevkit/bdk/compare/v0.27.1...v0.28.0
[v0.28.1]: https://github.com/bitcoindevkit/bdk/compare/v0.28.0...v0.28.1
[v0.28.2]: https://github.com/bitcoindevkit/bdk/compare/v0.28.1...v0.28.2
[Unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.28.2...HEAD

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk"
version = "0.28.2"
version = "0.23.0"
edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org"
@@ -13,49 +13,46 @@ license = "MIT OR Apache-2.0"
[dependencies]
bdk-macros = "^0.6"
log = "0.4"
miniscript = { version = "10.0", default-features = false, features = ["serde"] }
bitcoin = { version = "0.30", default-features = false, features = ["serde", "base64", "rand-std"] }
log = "^0.4"
miniscript = { version = "7.0", features = ["use-serde"] }
bitcoin = { version = "0.28.1", features = ["use-serde", "base64", "rand"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
rand = "^0.8"
rand = "^0.7"
# Optional dependencies
sled = { version = "0.34", optional = true }
electrum-client = { version = "0.18", optional = true }
esplora-client = { version = "0.6", default-features = false, optional = true }
rusqlite = { version = "0.28.0", optional = true }
electrum-client = { version = "0.11", optional = true }
esplora-client = { version = "0.1.1", default-features = false, optional = true }
rusqlite = { version = "0.27.0", optional = true }
ahash = { version = "0.7.6", optional = true }
futures = { version = "0.3", optional = true }
async-trait = { version = "0.1", optional = true }
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
cc = { version = ">=1.0.64", optional = true }
socks = { version = "0.3", optional = true }
hwi = { version = "0.7", optional = true, features = ["miniscript"] }
hwi = { version = "0.2.2", optional = true }
bip39 = { version = "2.0.0", optional = true }
bip39 = { version = "1.0.1", optional = true }
bitcoinconsensus = { version = "0.19.0-3", optional = true }
# Needed by bdk_blockchain_tests macro and the `rpc` feature
bitcoincore-rpc = { package="core-rpc", version = "0.17", optional = true }
bitcoincore-rpc = { version = "0.15", optional = true }
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", features = ["rt", "macros"] }
tokio = { version = "1", features = ["rt"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = "0.2"
async-trait = "0.1"
js-sys = "0.3"
rand = { version = "^0.7", features = ["wasm-bindgen"] }
[features]
minimal = []
compiler = ["miniscript/compiler"]
verify = ["bitcoinconsensus"]
default = ["std", "key-value-db", "electrum"]
# std feature is always required unless building for wasm32-unknown-unknown target
# if building for wasm user must add dependencies bitcoin/no-std,miniscript/no-std
std = ["bitcoin/std", "miniscript/std"]
default = ["key-value-db", "electrum"]
sqlite = ["rusqlite", "ahash"]
sqlite-bundled = ["sqlite", "rusqlite/bundled"]
compact_filters = ["rocksdb", "socks", "cc"]
@@ -101,19 +98,13 @@ test-esplora = ["electrsd/legacy", "electrsd/esplora_a33e97e1", "electrsd/bitcoi
test-md-docs = ["electrum"]
test-hardware-signer = ["hardware-signer"]
# This feature is used to run `cargo check` in our CI targeting wasm. It's not recommended
# for libraries to explicitly include the "getrandom/js" feature, so we only do it when
# necessary for running our CI. See: https://docs.rs/getrandom/0.2.8/getrandom/#webassembly-support
dev-getrandom-wasm = ["getrandom/js"]
[dev-dependencies]
miniscript = { version = "10.0", features = ["std"] }
bitcoin = { version = "0.30", features = ["std"] }
lazy_static = "1.4"
env_logger = { version = "0.7", default-features = false }
electrsd = "0.24"
assert_matches = "1.5.0"
env_logger = "0.7"
electrsd = "0.20"
[[example]]
name = "address_validator"
[[example]]
name = "compact_filters_balance"
required-features = ["compact_filters"]
@@ -137,31 +128,6 @@ name = "psbt_signer"
path = "examples/psbt_signer.rs"
required-features = ["electrum"]
[[example]]
name = "hardware_signer"
path = "examples/hardware_signer.rs"
required-features = ["electrum", "hardware-signer"]
[[example]]
name = "electrum_backend"
path = "examples/electrum_backend.rs"
required-features = ["electrum"]
[[example]]
name = "esplora_backend_synchronous"
path = "examples/esplora_backend_synchronous.rs"
required-features = ["use-esplora-ureq"]
[[example]]
name = "esplora_backend_asynchronous"
path = "examples/esplora_backend_asynchronous.rs"
required-features = ["use-esplora-reqwest", "reqwest-default-tls", "async-interface"]
[[example]]
name = "mnemonic_to_descriptors"
path = "examples/mnemonic_to_descriptors.rs"
required-features = ["all-keys"]
[workspace]
members = ["macros"]
[package.metadata.docs.rs]

View File

@@ -13,7 +13,7 @@
<a href="https://github.com/bitcoindevkit/bdk/actions?query=workflow%3ACI"><img alt="CI Status" src="https://github.com/bitcoindevkit/bdk/workflows/CI/badge.svg"></a>
<a href="https://coveralls.io/github/bitcoindevkit/bdk?branch=master"><img src="https://coveralls.io/repos/github/bitcoindevkit/bdk/badge.svg?branch=master"/></a>
<a href="https://docs.rs/bdk"><img alt="API Docs" src="https://img.shields.io/badge/docs.rs-bdk-green"/></a>
<a href="https://blog.rust-lang.org/2021/12/02/Rust-1.57.0.html"><img alt="Rustc Version 1.57.0+" src="https://img.shields.io/badge/rustc-1.57.0%2B-lightgrey.svg"/></a>
<a href="https://blog.rust-lang.org/2021/11/01/Rust-1.56.1.html"><img alt="Rustc Version 1.56.1+" src="https://img.shields.io/badge/rustc-1.56.1%2B-lightgrey.svg"/></a>
<a href="https://discord.gg/d7NkDKm"><img alt="Chat on Discord" src="https://img.shields.io/discord/753336465005608961?logo=discord"></a>
</p>
@@ -68,13 +68,12 @@ fn main() -> Result<(), bdk::Error> {
```rust
use bdk::{Wallet, database::MemoryDatabase};
use bdk::wallet::AddressIndex::New;
use bdk::bitcoin::Network;
fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
Network::Testnet,
bitcoin::Network::Testnet,
MemoryDatabase::default(),
)?;
@@ -97,15 +96,14 @@ use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
use bitcoin::base64;
use bdk::bitcoin::consensus::serialize;
use bdk::bitcoin::Network;
use bitcoin::consensus::serialize;
fn main() -> Result<(), bdk::Error> {
let blockchain = ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tpubDDYkZojQFQjht8Tm4jsS3iuEmKjTiEGjG6KnuFNKKJb5A6ZUCUZKdvLdSDWofKi4ToRCwb9poe1XdqfUnP4jaJjCB2Zwv11ZLgSbnZSNecE/1/*)"),
Network::Testnet,
bitcoin::Network::Testnet,
MemoryDatabase::default(),
)?;
@@ -123,7 +121,7 @@ fn main() -> Result<(), bdk::Error> {
};
println!("Transaction details: {:#?}", details);
println!("Unsigned PSBT: {}", base64::encode(psbt.serialize()));
println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt)));
Ok(())
}
@@ -135,21 +133,20 @@ fn main() -> Result<(), bdk::Error> {
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use bitcoin::base64;
use bdk::bitcoin::consensus::deserialize;
use bdk::bitcoin::{psbt::Psbt, Network};
use bitcoin::consensus::deserialize;
fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new(
"wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/0/*)",
Some("wpkh([c258d2e4/84h/1h/0h]tprv8griRPhA7342zfRyB6CqeKF8CJDXYu5pgnj1cjL1u2ngKcJha5jjTRimG82ABzJQ4MQe71CV54xfn25BbhCNfEGGJZnxvCDQCd6JkbvxW6h/1/*)"),
Network::Testnet,
bitcoin::Network::Testnet,
MemoryDatabase::default(),
)?;
let psbt = "...";
let mut psbt = Psbt::deserialize(&base64::decode(psbt).unwrap())?;
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
let _finalized = wallet.sign(&mut psbt, SignOptions::default())?;
let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
Ok(())
}
@@ -174,17 +171,6 @@ cargo test --features test-electrum
The other options are `test-esplora`, `test-rpc` or `test-rpc-legacy` which runs against an older version of Bitcoin Core.
Note that `electrs` and `bitcoind` binaries are automatically downloaded (on mac and linux), to specify you already have installed binaries you must use `--no-default-features` and provide `BITCOIND_EXE` and `ELECTRS_EXE` as environment variables.
## Running under WASM
If you want to run this library under WASM you will probably have to add the following lines to you `Cargo.toml`:
```toml
[dependencies]
getrandom = { version = "0.2", features = ["js"] }
```
This enables the `rand` crate to work in environments where JavaScript is available. See [this link](https://docs.rs/getrandom/0.2.8/getrandom/#webassembly-support) to learn more.
## License
Licensed under either of
@@ -201,38 +187,3 @@ at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
dual licensed as above, without any additional terms or conditions.
## Minimum Supported Rust Version (MSRV)
This library should compile with any combination of features with Rust 1.57.0.
To build with the MSRV you will need to pin dependencies as follows:
```shell
# log 0.4.19 has MSRV 1.60.0
cargo update -p log --precise "0.4.18"
# tempfile 3.7.0 has MSRV 1.63.0
cargo update -p tempfile --precise "3.6.0"
# required for sqlite feature, hashlink 0.8.2 has MSRV 1.61.0
cargo update -p hashlink --precise "0.8.1"
# required for compact_filters feature, regex after 1.7.3 has MSRV 1.60.0
cargo update -p regex --precise "1.7.3"
# zip 0.6.3 has MSRV 1.59.0 but still works
cargo update -p zip:0.6.6 --precise "0.6.3"
# rustix 0.38.0 has MSRV 1.65.0
cargo update -p rustix --precise "0.37.23"
# tokio 0.30.0 has MSRV 1.63.0
cargo update -p tokio --precise "1.29.1"
# cc 1.0.82 is throwing error with rust 1.57.0, "error[E0599]: no method named `retain_mut`..."
cargo update -p cc --precise "1.0.81"
# rustls 0.21.2 has MSRV 1.60.0+
cargo update -p rustls:0.21.6 --precise "0.21.1"
# flate2 1.0.27 has MSRV 1.63.0+
cargo update -p flate2:1.0.27 --precise "1.0.26"
# reqwest 0.11.19 has MSRV 1.63.0+
cargo update -p reqwest --precise "0.11.18"
# h2 0.3.21 has MSRV 1.63.0+
cargo update -p h2 --precise "0.3.20"
# rustls-webpki 0.100.2 has MSRV 1.60+
cargo update -p rustls-webpki --precise "0.100.2"
```

View File

@@ -6,4 +6,4 @@ RUN apt-get install wget -y
RUN wget "https://github.com/LedgerHQ/speculos/blob/master/apps/nanos%23btc%232.1%231c8db8da.elf?raw=true" -O /speculos/btc.elf
ADD automation.json /speculos/automation.json
ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--model", "nanos", "--display", "headless", "--vnc-port", "41000", "btc.elf"]
ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--display", "headless", "--vnc-port", "41000", "btc.elf"]

View File

@@ -0,0 +1,63 @@
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
use std::sync::Arc;
use bdk::bitcoin;
use bdk::database::MemoryDatabase;
use bdk::descriptor::HdKeyPaths;
#[allow(deprecated)]
use bdk::wallet::address_validator::{AddressValidator, AddressValidatorError};
use bdk::KeychainKind;
use bdk::Wallet;
use bdk::wallet::AddressIndex::New;
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::{Network, Script};
#[derive(Debug)]
struct DummyValidator;
#[allow(deprecated)]
impl AddressValidator for DummyValidator {
fn validate(
&self,
keychain: KeychainKind,
hd_keypaths: &HdKeyPaths,
script: &Script,
) -> Result<(), AddressValidatorError> {
let (_, path) = hd_keypaths
.values()
.find(|(fing, _)| fing == &Fingerprint::from_hex("bc123c3e").unwrap())
.ok_or(AddressValidatorError::InvalidScript)?;
println!(
"Validating `{:?}` {} address, script: {}",
keychain, path, script
);
Ok(())
}
}
fn main() -> Result<(), bdk::Error> {
let descriptor = "sh(and_v(v:pk(tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/*),after(630000)))";
let mut wallet = Wallet::new(descriptor, None, Network::Regtest, MemoryDatabase::new())?;
#[allow(deprecated)]
wallet.add_address_validator(Arc::new(DummyValidator));
wallet.get_address(New)?;
wallet.get_address(New)?;
wallet.get_address(New)?;
Ok(())
}

View File

@@ -29,7 +29,7 @@ use bdk::wallet::AddressIndex::New;
use bdk::{KeychainKind, Wallet};
/// Miniscript policy is a high level abstraction of spending conditions. Defined in the
/// rust-miniscript library here https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
/// rust-miscript library here https://docs.rs/miniscript/7.0.0/miniscript/policy/index.html
/// rust-miniscript provides a `compile()` function that can be used to compile any miniscript policy
/// into a descriptor. This descriptor then in turn can be used in bdk a fully functioning wallet
/// can be derived from the policy.

View File

@@ -1,87 +0,0 @@
use std::str::FromStr;
use bdk::bitcoin::bip32::ExtendedPrivKey;
use bdk::bitcoin::Network;
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase;
use bdk::template::Bip84;
use bdk::wallet::export::FullyNodedExport;
use bdk::{KeychainKind, SyncOptions, Wallet};
use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex;
use bitcoin::bip32;
pub mod utils;
use crate::utils::tx::build_signed_tx;
/// This will create a wallet from an xpriv and get the balance by connecting to an Electrum server.
/// If enough amount is available, this will send a transaction to an address.
/// Otherwise, this will display a wallet address to receive funds.
///
/// This can be run with `cargo run --example electrum_backend` in the root folder.
fn main() {
let network = Network::Testnet;
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
let electrum_url = "ssl://electrum.blockstream.info:60002";
run(&network, electrum_url, xpriv);
}
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
Wallet::new(
Bip84(*xpriv, KeychainKind::External),
Some(Bip84(*xpriv, KeychainKind::Internal)),
*network,
MemoryDatabase::default(),
)
.unwrap()
}
fn run(network: &Network, electrum_url: &str, xpriv: &str) {
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
// Apparently it works only with Electrs (not EletrumX)
let blockchain = ElectrumBlockchain::from(Client::new(electrum_url).unwrap());
let wallet = create_wallet(network, &xpriv);
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
let address = wallet.get_address(AddressIndex::New).unwrap().address;
println!("address: {}", address);
let balance = wallet.get_balance().unwrap();
println!("Available coins in BDK wallet : {} sats", balance);
if balance.confirmed > 6500 {
// the wallet sends the amount to itself.
let recipient_address = wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.to_string();
let amount = 5359;
let tx = build_signed_tx(&wallet, &recipient_address, amount);
blockchain.broadcast(&tx).unwrap();
println!("tx id: {}", tx.txid());
} else {
println!("Insufficient Funds. Fund the wallet with the address above");
}
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
.map_err(ToString::to_string)
.map_err(bdk::Error::Generic)
.unwrap();
println!("------\nWallet Backup: {}", export.to_string());
}

View File

@@ -1,93 +0,0 @@
use std::str::FromStr;
use bdk::blockchain::Blockchain;
use bdk::{
blockchain::esplora::EsploraBlockchain,
database::MemoryDatabase,
template::Bip84,
wallet::{export::FullyNodedExport, AddressIndex},
KeychainKind, SyncOptions, Wallet,
};
use bitcoin::{
bip32::{self, ExtendedPrivKey},
Network,
};
pub mod utils;
use crate::utils::tx::build_signed_tx;
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
/// using non blocking asynchronous calls with `reqwest`.
/// If enough amount is available, this will send a transaction to an address.
/// Otherwise, this will display a wallet address to receive funds.
///
/// This can be run with `cargo run --no-default-features --features="use-esplora-reqwest, reqwest-default-tls, async-interface" --example esplora_backend_asynchronous`
/// in the root folder.
#[tokio::main(flavor = "current_thread")]
async fn main() {
let network = Network::Signet;
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
let esplora_url = "https://explorer.bc-2.jp/api";
run(&network, esplora_url, xpriv).await;
}
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
Wallet::new(
Bip84(*xpriv, KeychainKind::External),
Some(Bip84(*xpriv, KeychainKind::Internal)),
*network,
MemoryDatabase::default(),
)
.unwrap()
}
async fn run(network: &Network, esplora_url: &str, xpriv: &str) {
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
let blockchain = EsploraBlockchain::new(esplora_url, 20);
let wallet = create_wallet(network, &xpriv);
wallet
.sync(&blockchain, SyncOptions::default())
.await
.unwrap();
let address = wallet.get_address(AddressIndex::New).unwrap().address;
println!("address: {}", address);
let balance = wallet.get_balance().unwrap();
println!("Available coins in BDK wallet : {} sats", balance);
if balance.confirmed > 10500 {
// the wallet sends the amount to itself.
let recipient_address = wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.to_string();
let amount = 9359;
let tx = build_signed_tx(&wallet, &recipient_address, amount);
let _ = blockchain.broadcast(&tx);
println!("tx id: {}", tx.txid());
} else {
println!("Insufficient Funds. Fund the wallet with the address above");
}
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
.map_err(ToString::to_string)
.map_err(bdk::Error::Generic)
.unwrap();
println!("------\nWallet Backup: {}", export.to_string());
}

View File

@@ -1,89 +0,0 @@
use std::str::FromStr;
use bdk::blockchain::Blockchain;
use bdk::{
blockchain::esplora::EsploraBlockchain,
database::MemoryDatabase,
template::Bip84,
wallet::{export::FullyNodedExport, AddressIndex},
KeychainKind, SyncOptions, Wallet,
};
use bitcoin::{
bip32::{self, ExtendedPrivKey},
Network,
};
pub mod utils;
use crate::utils::tx::build_signed_tx;
/// This will create a wallet from an xpriv and get the balance by connecting to an Esplora server,
/// using blocking calls with `ureq`.
/// If enough amount is available, this will send a transaction to an address.
/// Otherwise, this will display a wallet address to receive funds.
///
/// This can be run with `cargo run --features=use-esplora-ureq --example esplora_backend_synchronous`
/// in the root folder.
fn main() {
let network = Network::Signet;
let xpriv = "tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy";
let esplora_url = "https://explorer.bc-2.jp/api";
run(&network, esplora_url, xpriv);
}
fn create_wallet(network: &Network, xpriv: &ExtendedPrivKey) -> Wallet<MemoryDatabase> {
Wallet::new(
Bip84(*xpriv, KeychainKind::External),
Some(Bip84(*xpriv, KeychainKind::Internal)),
*network,
MemoryDatabase::default(),
)
.unwrap()
}
fn run(network: &Network, esplora_url: &str, xpriv: &str) {
let xpriv = bip32::ExtendedPrivKey::from_str(xpriv).unwrap();
let blockchain = EsploraBlockchain::new(esplora_url, 20);
let wallet = create_wallet(network, &xpriv);
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
let address = wallet.get_address(AddressIndex::New).unwrap().address;
println!("address: {}", address);
let balance = wallet.get_balance().unwrap();
println!("Available coins in BDK wallet : {} sats", balance);
if balance.confirmed > 10500 {
// the wallet sends the amount to itself.
let recipient_address = wallet
.get_address(AddressIndex::New)
.unwrap()
.address
.to_string();
let amount = 9359;
let tx = build_signed_tx(&wallet, &recipient_address, amount);
blockchain.broadcast(&tx).unwrap();
println!("tx id: {}", tx.txid());
} else {
println!("Insufficient Funds. Fund the wallet with the address above");
}
let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true)
.map_err(ToString::to_string)
.map_err(bdk::Error::Generic)
.unwrap();
println!("------\nWallet Backup: {}", export.to_string());
}

View File

@@ -1,106 +0,0 @@
use bdk::bitcoin::{Address, Network};
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase;
use bdk::hwi::HWIClient;
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
use bdk::signer::SignerOrdering;
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
use electrum_client::Client;
use std::str::FromStr;
use std::sync::Arc;
// This example shows how to sync a wallet, create a transaction, sign it
// and broadcast it using an external hardware wallet.
// The hardware wallet must be connected to the computer and unlocked before
// running the example. Also, the `hwi` python package should be installed
// and available in the environment.
//
// To avoid loss of funds, consider using an hardware wallet simulator:
// * Coldcard: https://github.com/Coldcard/firmware
// * Ledger: https://github.com/LedgerHQ/speculos
// * Trezor: https://docs.trezor.io/trezor-firmware/core/emulator/index.html
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Hold tight, I'm connecting to your hardware wallet...");
// Listing all the available hardware wallet devices...
let mut devices = HWIClient::enumerate()?;
if devices.is_empty() {
panic!("No devices found. Either plug in a hardware wallet, or start a simulator.");
}
let first_device = devices.remove(0)?;
// ...and creating a client out of the first one
let client = HWIClient::get_client(&first_device, true, Network::Testnet.into())?;
println!("Look what I found, a {}!", first_device.model);
// Getting the HW's public descriptors
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
println!(
"The hardware wallet's descriptor is: {}",
descriptors.receive[0]
);
// Creating a custom signer from the device
let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
let mut wallet = Wallet::new(
descriptors.receive[0].clone(),
Some(descriptors.internal[0].clone()),
Network::Testnet,
MemoryDatabase::default(),
)?;
// Adding the hardware signer to the BDK wallet
wallet.add_signer(
KeychainKind::External,
SignerOrdering(200),
Arc::new(custom_signer),
);
// create client for Blockstream's testnet electrum server
let blockchain =
ElectrumBlockchain::from(Client::new("ssl://electrum.blockstream.info:60002")?);
println!("Syncing the wallet...");
wallet.sync(&blockchain, SyncOptions::default())?;
// get deposit address
let deposit_address = wallet.get_address(AddressIndex::New)?;
let balance = wallet.get_balance()?;
println!("Wallet balances in SATs: {}", balance);
if balance.get_total() < 10000 {
println!(
"Send some sats from the u01.net testnet faucet to address '{addr}'.\nFaucet URL: https://bitcoinfaucet.uo1.net/?to={addr}",
addr = deposit_address.address
);
return Ok(());
}
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?
.require_network(Network::Testnet)?;
let (mut psbt, _details) = {
let mut builder = wallet.build_tx();
builder
.drain_wallet()
.drain_to(return_address.script_pubkey())
.enable_rbf()
.fee_rate(FeeRate::from_sat_per_vb(5.0));
builder.finish()?
};
// `sign` will call the hardware wallet asking for a signature
assert!(
wallet.sign(&mut psbt, SignOptions::default())?,
"The hardware wallet couldn't finalize the transaction :("
);
println!("Let's broadcast your tx...");
let raw_transaction = psbt.extract_tx();
let txid = raw_transaction.txid();
blockchain.broadcast(&raw_transaction)?;
println!("Transaction broadcasted! TXID: {txid}.\nExplorer URL: https://mempool.space/testnet/tx/{txid}", txid = txid);
Ok(())
}

View File

@@ -1,60 +0,0 @@
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
use bdk::bitcoin::bip32::DerivationPath;
use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::Network;
use bdk::descriptor;
use bdk::descriptor::IntoWalletDescriptor;
use bdk::keys::bip39::{Language, Mnemonic, WordCount};
use bdk::keys::{GeneratableKey, GeneratedKey};
use bdk::miniscript::Tap;
use bdk::Error as BDK_Error;
use std::error::Error;
use std::str::FromStr;
/// This example demonstrates how to generate a mnemonic phrase
/// using BDK and use that to generate a descriptor string.
fn main() -> Result<(), Box<dyn Error>> {
let secp = Secp256k1::new();
// In this example we are generating a 12 words mnemonic phrase
// but it is also possible generate 15, 18, 21 and 24 words
// using their respective `WordCount` variant.
let mnemonic: GeneratedKey<_, Tap> =
Mnemonic::generate((WordCount::Words12, Language::English))
.map_err(|_| BDK_Error::Generic("Mnemonic generation error".to_string()))?;
println!("Mnemonic phrase: {}", *mnemonic);
let mnemonic_with_passphrase = (mnemonic, None);
// define external and internal derivation key path
let external_path = DerivationPath::from_str("m/86h/0h/0h/0").unwrap();
let internal_path = DerivationPath::from_str("m/86h/0h/0h/1").unwrap();
// generate external and internal descriptor from mnemonic
let (external_descriptor, ext_keymap) =
descriptor!(tr((mnemonic_with_passphrase.clone(), external_path)))?
.into_wallet_descriptor(&secp, Network::Testnet)?;
let (internal_descriptor, int_keymap) =
descriptor!(tr((mnemonic_with_passphrase, internal_path)))?
.into_wallet_descriptor(&secp, Network::Testnet)?;
println!("tpub external descriptor: {}", external_descriptor);
println!("tpub internal descriptor: {}", internal_descriptor);
println!(
"tprv external descriptor: {}",
external_descriptor.to_string_with_secret(&ext_keymap)
);
println!(
"tprv internal descriptor: {}",
internal_descriptor.to_string_with_secret(&int_keymap)
);
Ok(())
}

View File

@@ -9,38 +9,20 @@
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase;
use bdk::wallet::AddressIndex;
use bdk::{descriptor, SyncOptions};
use bdk::SyncOptions;
use bdk::{FeeRate, SignOptions, Wallet};
use bitcoin::secp256k1::Secp256k1;
use bitcoin::{Address, Network};
use electrum_client::Client;
use miniscript::descriptor::DescriptorSecretKey;
use std::error::Error;
use std::str::FromStr;
/// This example shows how to sign and broadcast the transaction for a PSBT (Partially Signed
/// Bitcoin Transaction) for a single key, witness public key hash (WPKH) based descriptor wallet.
/// The electrum protocol is used to sync blockchain data from the testnet bitcoin network and
/// wallet data is stored in an ephemeral in-memory database. The process steps are:
/// 1. Create a "signing" wallet and a "watch-only" wallet based on the same private keys.
/// 2. Deposit testnet funds into the watch only wallet.
/// 3. Sync the watch only wallet and create a spending transaction to return all funds to the testnet faucet.
/// 4. Sync the signing wallet and sign and finalize the PSBT created by the watch only wallet.
/// 5. Broadcast the transactions from the finalized PSBT.
fn main() -> Result<(), Box<dyn Error>> {
// test key created with `bdk-cli key generate` and `bdk-cli key derive` commands
let external_secret_xkey = DescriptorSecretKey::from_str("[e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/0/*").unwrap();
let internal_secret_xkey = DescriptorSecretKey::from_str("[e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/1/*").unwrap();
// test keys created with `bdk-cli key generate` and `bdk-cli key derive` commands
let signing_external_descriptor = "wpkh([e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/0/*)";
let signing_internal_descriptor = "wpkh([e9824965/84'/1'/0']tprv8fvem7qWxY3SGCQczQpRpqTKg455wf1zgixn6MZ4ze8gRfHjov5gXBQTadNfDgqs9ERbZZ3Bi1PNYrCCusFLucT39K525MWLpeURjHwUsfX/1/*)";
let secp = Secp256k1::new();
let external_public_xkey = external_secret_xkey.to_public(&secp).unwrap();
let internal_public_xkey = internal_secret_xkey.to_public(&secp).unwrap();
let signing_external_descriptor = descriptor!(wpkh(external_secret_xkey)).unwrap();
let signing_internal_descriptor = descriptor!(wpkh(internal_secret_xkey)).unwrap();
let watch_only_external_descriptor = descriptor!(wpkh(external_public_xkey)).unwrap();
let watch_only_internal_descriptor = descriptor!(wpkh(internal_public_xkey)).unwrap();
let watch_only_external_descriptor = "wpkh([e9824965/84'/1'/0']tpubDCcguXsm6uj79fSQt4V2EF7SF5b26zCuG2ZZNsbNQuw5G9YWSJuGhg2KknQBywRq4VGTu41zYTCh3QeVFyBdbsymgRX9Mrts94SW7obEdqs/0/*)";
let watch_only_internal_descriptor = "wpkh([e9824965/84'/1'/0']tpubDCcguXsm6uj79fSQt4V2EF7SF5b26zCuG2ZZNsbNQuw5G9YWSJuGhg2KknQBywRq4VGTu41zYTCh3QeVFyBdbsymgRX9Mrts94SW7obEdqs/1/*)";
// create client for Blockstream's testnet electrum server
let blockchain =
@@ -92,8 +74,7 @@ fn main() -> Result<(), Box<dyn Error>> {
}
} else {
println!("Creating a PSBT sending 9800 SATs plus fee to the u01.net testnet faucet return address 'tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt'.");
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?
.require_network(Network::Testnet)?;
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?;
let mut builder = watch_only_wallet.build_tx();
builder
.add_recipient(return_address.script_pubkey(), 9_800)

View File

@@ -62,10 +62,7 @@ fn main() -> Result<(), Box<dyn Error>> {
};
// Get a new core address
let core_address = bitcoind
.client
.get_new_address(None, None)?
.require_network(Network::Regtest)?;
let core_address = bitcoind.client.get_new_address(None, None)?;
// Generate 101 blocks and use the above address as coinbase
bitcoind.client.generate_to_address(101, &core_address)?;

View File

@@ -1,33 +0,0 @@
pub(crate) mod tx {
use std::str::FromStr;
use bdk::{database::BatchDatabase, SignOptions, Wallet};
use bitcoin::{Address, Transaction};
pub fn build_signed_tx<D: BatchDatabase>(
wallet: &Wallet<D>,
recipient_address: &str,
amount: u64,
) -> Transaction {
// Create a transaction builder
let mut tx_builder = wallet.build_tx();
let to_address = Address::from_str(recipient_address)
.unwrap()
.require_network(wallet.network())
.unwrap();
// Set recipient of the transaction
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);
// Finalise the transaction and extract PSBT
let (mut psbt, _) = tx_builder.finish().unwrap();
// Sign the above psbt with signing option
wallet.sign(&mut psbt, SignOptions::default()).unwrap();
// Extract the final transaction
psbt.extract_tx()
}
}

View File

@@ -19,7 +19,7 @@ use syn::{parse, ImplItemMethod, ItemImpl, ItemTrait, Token};
fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
let output = quote! {
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
#parsed
};
@@ -32,7 +32,7 @@ fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
let output = quote! {
#output
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#[async_trait(?Send)]
#parsed
};
@@ -42,7 +42,7 @@ fn add_async_trait(mut parsed: ItemTrait) -> TokenStream {
fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
let output = quote! {
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
#parsed
};
@@ -51,7 +51,7 @@ fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
let output = quote! {
#output
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#parsed
};
@@ -60,7 +60,7 @@ fn add_async_method(mut parsed: ImplItemMethod) -> TokenStream {
fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
let output = quote! {
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
#parsed
};
@@ -73,7 +73,7 @@ fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
let output = quote! {
#output
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#[async_trait(?Send)]
#parsed
};
@@ -81,7 +81,7 @@ fn add_async_impl_trait(mut parsed: ItemImpl) -> TokenStream {
output.into()
}
/// Makes a method or every method of a trait `async`, if the `async-interface` feature is enabled.
/// Makes a method or every method of a trait "async" only if the target_arch is "wasm32"
///
/// Requires the `async-trait` crate as a dependency whenever this attribute is used on a trait
/// definition or trait implementation.
@@ -101,18 +101,18 @@ pub fn maybe_async(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
}
/// Awaits, if the `async-interface` feature is enabled.
/// Awaits if target_arch is "wasm32", does nothing otherwise
#[proc_macro]
pub fn maybe_await(expr: TokenStream) -> TokenStream {
let expr: proc_macro2::TokenStream = expr.into();
let quoted = quote! {
{
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
{
#expr
}
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
{
#expr.await
}
@@ -122,20 +122,20 @@ pub fn maybe_await(expr: TokenStream) -> TokenStream {
quoted.into()
}
/// Awaits, if the `async-interface` feature is enabled, uses `tokio::Runtime::block_on()` otherwise
/// Awaits if target_arch is "wasm32", uses `tokio::Runtime::block_on()` otherwise
///
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build.
/// Requires the `tokio` crate as a dependecy with `rt-core` or `rt-threaded` to build on non-wasm32 platforms.
#[proc_macro]
pub fn await_or_block(expr: TokenStream) -> TokenStream {
let expr: proc_macro2::TokenStream = expr.into();
let quoted = quote! {
{
#[cfg(not(feature = "async-interface"))]
#[cfg(all(not(target_arch = "wasm32"), not(feature = "async-interface")))]
{
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(#expr)
}
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
{
#expr.await
}

View File

@@ -131,7 +131,7 @@ impl GetBlockHash for AnyBlockchain {
impl WalletSync for AnyBlockchain {
fn wallet_sync<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
@@ -144,7 +144,7 @@ impl WalletSync for AnyBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(impl_inner_method!(
@@ -178,8 +178,7 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
/// "type" : "electrum",
/// "url" : "ssl://electrum.blockstream.info:50002",
/// "retry": 2,
/// "stop_gap": 20,
/// "validate_domain": true
/// "stop_gap": 20
/// }"#,
/// )
/// .unwrap();
@@ -191,12 +190,11 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
/// socks5: None,
/// timeout: None,
/// stop_gap: 20,
/// validate_domain: true,
/// })
/// );
/// # }
/// ```
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AnyBlockchainConfig {
#[cfg(feature = "electrum")]

View File

@@ -51,7 +51,6 @@
use std::collections::HashSet;
use std::fmt;
use std::ops::DerefMut;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
@@ -275,7 +274,7 @@ impl WalletSync for CompactFiltersBlockchain {
#[allow(clippy::mutex_atomic)] // Mutex is easier to understand than a CAS loop.
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let first_peer = &self.peers[0];
@@ -323,9 +322,6 @@ impl WalletSync for CompactFiltersBlockchain {
cf_sync.prepare_sync(Arc::clone(first_peer))?;
let mut database = database.borrow_mut();
let database = database.deref_mut();
let all_scripts = Arc::new(
database
.iter_script_pubkeys(None)?
@@ -355,7 +351,7 @@ impl WalletSync for CompactFiltersBlockchain {
peer,
|block_hash, filter| {
if !filter
.match_any(block_hash, all_scripts.iter().map(|s| s.as_slice()))?
.match_any(block_hash, &mut all_scripts.iter().map(AsRef::as_ref))?
{
return Ok(false);
}
@@ -483,7 +479,7 @@ impl WalletSync for CompactFiltersBlockchain {
}
/// Data to connect to a Bitcoin P2P peer
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct BitcoinPeerConfig {
/// Peer address such as 127.0.0.1:18333
pub address: String,
@@ -494,7 +490,7 @@ pub struct BitcoinPeerConfig {
}
/// Configuration for a [`CompactFiltersBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct CompactFiltersBlockchainConfig {
/// List of peers to try to connect to for asking headers and filters
pub peers: Vec<BitcoinPeerConfig>,
@@ -570,7 +566,7 @@ pub enum CompactFiltersError {
/// Internal I/O error
Io(std::io::Error),
/// Invalid BIP158 filter
Bip158(bitcoin::bip158::Error),
Bip158(bitcoin::util::bip158::Error),
/// Internal system time error
Time(std::time::SystemTimeError),
@@ -580,27 +576,7 @@ pub enum CompactFiltersError {
impl fmt::Display for CompactFiltersError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidResponse => write!(f, "A peer sent an invalid or unexpected response"),
Self::InvalidHeaders => write!(f, "Invalid headers"),
Self::InvalidFilterHeader => write!(f, "Invalid filter header"),
Self::InvalidFilter => write!(f, "Invalid filters"),
Self::MissingBlock => write!(f, "The peer is missing a block in the valid chain"),
Self::BlockHashNotFound => write!(f, "Block hash not found"),
Self::DataCorruption => write!(
f,
"The data stored in the block filters storage are corrupted"
),
Self::NotConnected => write!(f, "A peer is not connected"),
Self::Timeout => write!(f, "A peer took too long to reply to one of our messages"),
Self::PeerBloomDisabled => write!(f, "Peer doesn't advertise the BLOOM service flag"),
Self::NoPeers => write!(f, "No peers have been specified"),
Self::Db(err) => write!(f, "Internal database error: {}", err),
Self::Io(err) => write!(f, "Internal I/O error: {}", err),
Self::Bip158(err) => write!(f, "Invalid BIP158 filter: {}", err),
Self::Time(err) => write!(f, "Invalid system time: {}", err),
Self::Global(err) => write!(f, "Generic error: {}", err),
}
write!(f, "{:?}", self)
}
}
@@ -608,7 +584,7 @@ impl std::error::Error for CompactFiltersError {}
impl_error!(rocksdb::Error, Db, CompactFiltersError);
impl_error!(std::io::Error, Io, CompactFiltersError);
impl_error!(bitcoin::bip158::Error, Bip158, CompactFiltersError);
impl_error!(bitcoin::util::bip158::Error, Bip158, CompactFiltersError);
impl_error!(std::time::SystemTimeError, Time, CompactFiltersError);
impl From<crate::error::Error> for CompactFiltersError {

View File

@@ -27,7 +27,7 @@ use bitcoin::network::message::{NetworkMessage, RawNetworkMessage};
use bitcoin::network::message_blockdata::*;
use bitcoin::network::message_filter::*;
use bitcoin::network::message_network::VersionMessage;
use bitcoin::network::{Address, Magic};
use bitcoin::network::Address;
use bitcoin::{Block, Network, Transaction, Txid, Wtxid};
use super::CompactFiltersError;
@@ -75,10 +75,7 @@ impl Mempool {
/// Look-up a transaction in the mempool given an [`Inventory`] request
pub fn get_tx(&self, inventory: &Inventory) -> Option<Transaction> {
let identifer = match inventory {
Inventory::Error
| Inventory::Block(_)
| Inventory::WitnessBlock(_)
| Inventory::CompactBlock(_) => return None,
Inventory::Error | Inventory::Block(_) | Inventory::WitnessBlock(_) => return None,
Inventory::Transaction(txid) => TxIdentifier::Txid(*txid),
Inventory::WitnessTransaction(txid) => TxIdentifier::Txid(*txid),
Inventory::WTx(wtxid) => TxIdentifier::Wtxid(*wtxid),
@@ -242,7 +239,7 @@ impl Peer {
/// Send a Bitcoin network message
fn _send(
writer: &mut TcpStream,
magic: Magic,
magic: u32,
payload: NetworkMessage,
) -> Result<(), CompactFiltersError> {
log::trace!("==> {:?}", payload);

View File

@@ -21,17 +21,16 @@ use rand::{thread_rng, Rng};
use rocksdb::{Direction, IteratorMode, ReadOptions, WriteBatch, DB};
use bitcoin::bip158::BlockFilter;
use bitcoin::block::Header;
use bitcoin::blockdata::constants::genesis_block;
use bitcoin::consensus::{deserialize, encode::VarInt, serialize, Decodable, Encodable};
use bitcoin::hash_types::{FilterHash, FilterHeader};
use bitcoin::hashes::Hash;
use bitcoin::pow::Work;
use bitcoin::util::bip158::BlockFilter;
use bitcoin::util::uint::Uint256;
use bitcoin::Block;
use bitcoin::BlockHash;
use bitcoin::BlockHeader;
use bitcoin::Network;
use bitcoin::ScriptBuf;
use super::CompactFiltersError;
@@ -70,7 +69,7 @@ impl StoreEntry {
}
StoreEntry::Block(Some(height)) => prefix.extend_from_slice(&height.to_be_bytes()),
StoreEntry::BlockHeaderIndex(Some(hash)) => {
prefix.extend_from_slice(hash.to_raw_hash().as_ref())
prefix.extend_from_slice(&hash.into_inner())
}
StoreEntry::CFilterTable((filter_type, bundle_index)) => {
prefix.push(*filter_type);
@@ -104,42 +103,42 @@ where
}
impl Encodable for BundleStatus {
fn consensus_encode<W: Write + ?Sized>(&self, e: &mut W) -> Result<usize, std::io::Error> {
fn consensus_encode<W: Write>(&self, mut e: W) -> Result<usize, std::io::Error> {
let mut written = 0;
match self {
BundleStatus::Init => {
written += 0x00u8.consensus_encode(e)?;
written += 0x00u8.consensus_encode(&mut e)?;
}
BundleStatus::CfHeaders { cf_headers } => {
written += 0x01u8.consensus_encode(e)?;
written += VarInt(cf_headers.len() as u64).consensus_encode(e)?;
written += 0x01u8.consensus_encode(&mut e)?;
written += VarInt(cf_headers.len() as u64).consensus_encode(&mut e)?;
for header in cf_headers {
written += header.consensus_encode(e)?;
written += header.consensus_encode(&mut e)?;
}
}
BundleStatus::CFilters { cf_filters } => {
written += 0x02u8.consensus_encode(e)?;
written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
written += 0x02u8.consensus_encode(&mut e)?;
written += VarInt(cf_filters.len() as u64).consensus_encode(&mut e)?;
for filter in cf_filters {
written += filter.consensus_encode(e)?;
written += filter.consensus_encode(&mut e)?;
}
}
BundleStatus::Processed { cf_filters } => {
written += 0x03u8.consensus_encode(e)?;
written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
written += 0x03u8.consensus_encode(&mut e)?;
written += VarInt(cf_filters.len() as u64).consensus_encode(&mut e)?;
for filter in cf_filters {
written += filter.consensus_encode(e)?;
written += filter.consensus_encode(&mut e)?;
}
}
BundleStatus::Pruned => {
written += 0x04u8.consensus_encode(e)?;
written += 0x04u8.consensus_encode(&mut e)?;
}
BundleStatus::Tip { cf_filters } => {
written += 0x05u8.consensus_encode(e)?;
written += VarInt(cf_filters.len() as u64).consensus_encode(e)?;
written += 0x05u8.consensus_encode(&mut e)?;
written += VarInt(cf_filters.len() as u64).consensus_encode(&mut e)?;
for filter in cf_filters {
written += filter.consensus_encode(e)?;
written += filter.consensus_encode(&mut e)?;
}
}
}
@@ -149,53 +148,51 @@ impl Encodable for BundleStatus {
}
impl Decodable for BundleStatus {
fn consensus_decode<D: Read + ?Sized>(
d: &mut D,
) -> Result<Self, bitcoin::consensus::encode::Error> {
let byte_type = u8::consensus_decode(d)?;
fn consensus_decode<D: Read>(mut d: D) -> Result<Self, bitcoin::consensus::encode::Error> {
let byte_type = u8::consensus_decode(&mut d)?;
match byte_type {
0x00 => Ok(BundleStatus::Init),
0x01 => {
let num = VarInt::consensus_decode(d)?;
let num = VarInt::consensus_decode(&mut d)?;
let num = num.0 as usize;
let mut cf_headers = Vec::with_capacity(num);
for _ in 0..num {
cf_headers.push(FilterHeader::consensus_decode(d)?);
cf_headers.push(FilterHeader::consensus_decode(&mut d)?);
}
Ok(BundleStatus::CfHeaders { cf_headers })
}
0x02 => {
let num = VarInt::consensus_decode(d)?;
let num = VarInt::consensus_decode(&mut d)?;
let num = num.0 as usize;
let mut cf_filters = Vec::with_capacity(num);
for _ in 0..num {
cf_filters.push(Vec::<u8>::consensus_decode(d)?);
cf_filters.push(Vec::<u8>::consensus_decode(&mut d)?);
}
Ok(BundleStatus::CFilters { cf_filters })
}
0x03 => {
let num = VarInt::consensus_decode(d)?;
let num = VarInt::consensus_decode(&mut d)?;
let num = num.0 as usize;
let mut cf_filters = Vec::with_capacity(num);
for _ in 0..num {
cf_filters.push(Vec::<u8>::consensus_decode(d)?);
cf_filters.push(Vec::<u8>::consensus_decode(&mut d)?);
}
Ok(BundleStatus::Processed { cf_filters })
}
0x04 => Ok(BundleStatus::Pruned),
0x05 => {
let num = VarInt::consensus_decode(d)?;
let num = VarInt::consensus_decode(&mut d)?;
let num = num.0 as usize;
let mut cf_filters = Vec::with_capacity(num);
for _ in 0..num {
cf_filters.push(Vec::<u8>::consensus_decode(d)?);
cf_filters.push(Vec::<u8>::consensus_decode(&mut d)?);
}
Ok(BundleStatus::Tip { cf_filters })
@@ -229,12 +226,12 @@ impl ChainStore<Full> {
batch.put_cf(
cf_handle,
genesis_key,
(genesis.header, genesis.header.work().to_be_bytes()).serialize(),
(genesis.header, genesis.header.work()).serialize(),
);
batch.put_cf(
cf_handle,
StoreEntry::BlockHeaderIndex(Some(genesis.block_hash())).get_key(),
0usize.to_be_bytes(),
&0usize.to_be_bytes(),
);
store.write(batch)?;
}
@@ -261,7 +258,7 @@ impl ChainStore<Full> {
step *= 2;
}
let (header, _): (Header, [u8; 32]) = SerializeDb::deserialize(
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(
&store_read
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())?
.unwrap(),
@@ -279,11 +276,7 @@ impl ChainStore<Full> {
}
pub fn start_snapshot(&self, from: usize) -> Result<ChainStore<Snapshot>, CompactFiltersError> {
let new_cf_name: String = thread_rng()
.sample_iter(&Alphanumeric)
.map(|byte| byte as char)
.take(16)
.collect();
let new_cf_name: String = thread_rng().sample_iter(&Alphanumeric).take(16).collect();
let new_cf_name = format!("_headers:{}", new_cf_name);
let mut write_store = self.store.write().unwrap();
@@ -293,23 +286,22 @@ impl ChainStore<Full> {
let cf_handle = write_store.cf_handle(&self.cf_name).unwrap();
let new_cf_handle = write_store.cf_handle(&new_cf_name).unwrap();
let (header, work): (Header, [u8; 32]) = SerializeDb::deserialize(
let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(
&write_store
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
.ok_or(CompactFiltersError::DataCorruption)?,
)?;
let work = Work::from_be_bytes(work);
let mut batch = WriteBatch::default();
batch.put_cf(
new_cf_handle,
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
from.to_be_bytes(),
&from.to_be_bytes(),
);
batch.put_cf(
new_cf_handle,
StoreEntry::BlockHeader(Some(from)).get_key(),
(header, work.to_be_bytes()).serialize(),
(header, work).serialize(),
);
write_store.write(batch)?;
@@ -383,7 +375,7 @@ impl ChainStore<Full> {
opts,
IteratorMode::From(&from_key, Direction::Forward),
) {
let (header, _): (Header, [u8; 32]) = SerializeDb::deserialize(&v)?;
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
batch.delete_cf(
cf_handle,
@@ -435,7 +427,7 @@ impl ChainStore<Full> {
let key = StoreEntry::BlockHeader(Some(height)).get_key();
let data = read_store.get_pinned_cf(cf_handle, key)?;
data.map(|data| {
let (header, _): (Header, [u8; 32]) =
let (header, _): (BlockHeader, Uint256) =
deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
Ok::<_, CompactFiltersError>(header.block_hash())
})
@@ -498,7 +490,7 @@ impl ChainStore<Full> {
}
impl<T: StoreType> ChainStore<T> {
pub fn work(&self) -> Result<Work, CompactFiltersError> {
pub fn work(&self) -> Result<Uint256, CompactFiltersError> {
let read_store = self.store.read().unwrap();
let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
@@ -508,13 +500,12 @@ impl<T: StoreType> ChainStore<T> {
Ok(iterator
.last()
.map(|(_, v)| -> Result<_, CompactFiltersError> {
let (_, work): (Header, [u8; 32]) = SerializeDb::deserialize(&v)?;
let work = Work::from_be_bytes(work);
let (_, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
Ok(work)
})
.transpose()?
.unwrap_or_else(|| Work::from_be_bytes([0; 32])))
.unwrap_or_default())
}
pub fn get_height(&self) -> Result<usize, CompactFiltersError> {
@@ -549,7 +540,7 @@ impl<T: StoreType> ChainStore<T> {
iterator
.last()
.map(|(_, v)| -> Result<_, CompactFiltersError> {
let (header, _): (Header, [u8; 32]) = SerializeDb::deserialize(&v)?;
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
Ok(header.block_hash())
})
@@ -559,7 +550,7 @@ impl<T: StoreType> ChainStore<T> {
pub fn apply(
&mut self,
from: usize,
headers: Vec<Header>,
headers: Vec<BlockHeader>,
) -> Result<BlockHash, CompactFiltersError> {
let mut batch = WriteBatch::default();
@@ -569,8 +560,7 @@ impl<T: StoreType> ChainStore<T> {
let (mut last_hash, mut accumulated_work) = read_store
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(from)).get_key())?
.map(|result| {
let (header, work): (Header, [u8; 32]) = SerializeDb::deserialize(&result)?;
let work = Work::from_be_bytes(work);
let (header, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?;
Ok::<_, CompactFiltersError>((header.block_hash(), work))
})
.transpose()?
@@ -588,12 +578,12 @@ impl<T: StoreType> ChainStore<T> {
batch.put_cf(
cf_handle,
StoreEntry::BlockHeaderIndex(Some(header.block_hash())).get_key(),
(height).to_be_bytes(),
&(height).to_be_bytes(),
);
batch.put_cf(
cf_handle,
StoreEntry::BlockHeader(Some(height)).get_key(),
(header, accumulated_work.to_be_bytes()).serialize(),
(header, accumulated_work).serialize(),
);
}
@@ -645,7 +635,7 @@ impl CfStore {
let genesis = genesis_block(headers_store.network);
let filter = BlockFilter::new_script_filter(&genesis, |utxo| {
Err::<ScriptBuf, _>(bitcoin::bip158::Error::UtxoMissing(*utxo))
Err(bitcoin::util::bip158::Error::UtxoMissing(*utxo))
})?;
let first_key = StoreEntry::CFilterTable((filter_type, Some(0))).get_key();
@@ -657,7 +647,7 @@ impl CfStore {
&first_key,
(
BundleStatus::Init,
filter.filter_header(&FilterHeader::from_raw_hash(Hash::all_zeros())),
filter.filter_header(&FilterHeader::from_hash(Default::default())),
)
.serialize(),
)?;

View File

@@ -13,11 +13,10 @@ use std::collections::{BTreeMap, HashMap, VecDeque};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use bitcoin::bip158::BlockFilter;
use bitcoin::hash_types::{BlockHash, FilterHeader};
use bitcoin::hashes::Hash;
use bitcoin::network::message::NetworkMessage;
use bitcoin::network::message_blockdata::GetHeadersMessage;
use bitcoin::util::bip158::BlockFilter;
use super::peer::*;
use super::store::*;
@@ -136,7 +135,7 @@ impl CfSync {
let resp = peer.get_cf_headers(0x00, start_height as u32, stop_hash)?;
assert_eq!(resp.previous_filter_header, checkpoint);
assert!(resp.previous_filter_header == checkpoint);
status =
self.cf_store
.advance_to_cf_headers(index, checkpoint, resp.filter_hashes)?;
@@ -255,7 +254,7 @@ where
peer.send(NetworkMessage::GetHeaders(GetHeadersMessage::new(
locators_vec,
Hash::all_zeros(),
Default::default(),
)))?;
let (mut snapshot, mut last_hash) = if let NetworkMessage::Headers(headers) = peer
.recv("headers", Some(Duration::from_secs(TIMEOUT_SECS)))?
@@ -277,7 +276,7 @@ where
while sync_height < peer.get_version().start_height as usize {
peer.send(NetworkMessage::GetHeaders(GetHeadersMessage::new(
vec![last_hash],
Hash::all_zeros(),
Default::default(),
)))?;
if let NetworkMessage::Headers(headers) = peer
.recv("headers", Some(Duration::from_secs(TIMEOUT_SECS)))?

View File

@@ -25,7 +25,7 @@
//! ```
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
#[allow(unused_imports)]
use log::{debug, error, info, trace};
@@ -117,11 +117,9 @@ impl GetBlockHash for ElectrumBlockchain {
impl WalletSync for ElectrumBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
let mut database = database.borrow_mut();
let database = database.deref_mut();
let mut request = script_sync::start(database, self.stop_gap)?;
let mut block_times = HashMap::<u32, u32>::new();
let mut txid_to_height = HashMap::<Txid, u32>::new();
@@ -283,11 +281,9 @@ impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
.client
.batch_transaction_get(need_fetch.clone())
.map_err(Error::Electrum)?;
let mut txs: HashMap<_, _> = txs.into_iter().map(|tx| (tx.txid(), tx)).collect();
for txid in need_fetch {
if let Some(tx) = txs.remove(txid) {
self.cache.insert(*txid, tx);
}
for (tx, _txid) in txs.into_iter().zip(need_fetch) {
debug_assert_eq!(*_txid, tx.txid());
self.cache.insert(tx.txid(), tx);
}
}
@@ -300,7 +296,7 @@ impl<'a, 'b, D: Database> TxCache<'a, 'b, D> {
}
/// Configuration for an [`ElectrumBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct ElectrumBlockchainConfig {
/// URL of the Electrum server (such as ElectrumX, Esplora, BWT) may start with `ssl://` or `tcp://` and include a port
///
@@ -314,8 +310,6 @@ pub struct ElectrumBlockchainConfig {
pub timeout: Option<u8>,
/// Stop searching addresses for transactions after finding an unused gap of this length
pub stop_gap: usize,
/// Validate the domain when using SSL
pub validate_domain: bool,
}
impl ConfigurableBlockchain for ElectrumBlockchain {
@@ -325,9 +319,8 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
let socks5 = config.socks5.as_ref().map(Socks5Config::new);
let electrum_config = ConfigBuilder::new()
.retry(config.retry)
.timeout(config.timeout)
.socks5(socks5)
.validate_domain(config.validate_domain)
.timeout(config.timeout)?
.socks5(socks5)?
.build();
Ok(ElectrumBlockchain {
@@ -422,7 +415,6 @@ mod test {
retry: 0,
timeout: None,
stop_gap: stop_gap,
validate_domain: true,
})
}
}

View File

@@ -12,7 +12,7 @@
//! Esplora by way of `reqwest` HTTP client.
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
use bitcoin::{Transaction, Txid};
@@ -125,9 +125,8 @@ impl GetTx for EsploraBlockchain {
#[maybe_async]
impl GetBlockHash for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
Ok(await_or_block!(self
.url_client
.get_block_hash(height as u32))?)
let block_header = await_or_block!(self.url_client.get_header(height as u32))?;
Ok(block_header.block_hash())
}
}
@@ -135,12 +134,10 @@ impl GetBlockHash for EsploraBlockchain {
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
use crate::blockchain::script_sync::Request;
let mut database = database.borrow_mut();
let database = database.deref_mut();
let mut request = script_sync::start(database, self.stop_gap)?;
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();

View File

@@ -12,7 +12,6 @@
//! Esplora by way of `ureq` HTTP client.
use std::collections::{HashMap, HashSet};
use std::ops::DerefMut;
#[allow(unused_imports)]
use log::{debug, error, info, trace};
@@ -111,19 +110,18 @@ impl GetTx for EsploraBlockchain {
impl GetBlockHash for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
Ok(self.url_client.get_block_hash(height as u32)?)
let block_header = self.url_client.get_header(height as u32)?;
Ok(block_header.block_hash())
}
}
impl WalletSync for EsploraBlockchain {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
_progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
use crate::blockchain::script_sync::Request;
let mut database = database.borrow_mut();
let database = database.deref_mut();
let mut request = script_sync::start(database, self.stop_gap)?;
let mut tx_index: HashMap<Txid, Tx> = HashMap::new();
let batch_update = loop {
@@ -132,7 +130,7 @@ impl WalletSync for EsploraBlockchain {
let scripts = script_req
.request()
.take(self.concurrency as usize)
.map(bitcoin::ScriptBuf::from);
.cloned();
let mut handles = vec![];
for script in scripts {

View File

@@ -33,7 +33,7 @@ mod blocking;
pub use self::blocking::*;
/// Configuration for an [`EsploraBlockchain`]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, PartialEq)]
pub struct EsploraBlockchainConfig {
/// Base URL of the esplora service
///

View File

@@ -16,7 +16,6 @@
//! [Compact Filters/Neutrino](crate::blockchain::compact_filters), along with a generalized trait
//! [`Blockchain`] that can be implemented to build customized backends.
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::sync::mpsc::{channel, Receiver, Sender};
@@ -134,7 +133,7 @@ pub trait WalletSync {
/// Populate the internal database with transactions and UTXOs
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error>;
@@ -157,7 +156,7 @@ pub trait WalletSync {
/// [`BatchOperations::del_utxo`]: crate::database::BatchOperations::del_utxo
fn wallet_sync<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.wallet_setup(database, progress_update))
@@ -249,8 +248,11 @@ pub trait BlockchainFactory {
/// operations to build a blockchain for a given wallet, so if a wallet needs to be synced
/// often it's recommended to use [`BlockchainFactory::build_for_wallet`] to reuse the same
/// blockchain multiple times.
#[cfg(not(feature = "async-interface"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "async-interface"))))]
#[cfg(not(any(target_arch = "wasm32", feature = "async-interface")))]
#[cfg_attr(
docsrs,
doc(cfg(not(any(target_arch = "wasm32", feature = "async-interface"))))
)]
fn sync_wallet<D: BatchDatabase>(
&self,
wallet: &Wallet<D>,
@@ -375,7 +377,7 @@ impl<T: GetBlockHash> GetBlockHash for Arc<T> {
impl<T: WalletSync> WalletSync for Arc<T> {
fn wallet_setup<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_setup(database, progress_update))
@@ -383,7 +385,7 @@ impl<T: WalletSync> WalletSync for Arc<T> {
fn wallet_sync<D: BatchDatabase>(
&self,
database: &RefCell<D>,
database: &mut D,
progress_update: Box<dyn Progress>,
) -> Result<(), Error> {
maybe_await!(self.deref().wallet_sync(database, progress_update))

View File

@@ -31,26 +31,26 @@
//! let blockchain = RpcBlockchain::from_config(&config);
//! ```
use crate::bitcoin::hashes::hex::ToHex;
use crate::bitcoin::{Network, OutPoint, Transaction, TxOut, Txid};
use crate::blockchain::*;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::descriptor::calc_checksum;
use crate::descriptor::get_checksum;
use crate::error::MissingCachedScripts;
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
use bitcoin::{Script, ScriptBuf};
use bitcoin::Script;
use bitcoincore_rpc::json::{
GetTransactionResultDetailCategory, ImportMultiOptions, ImportMultiRequest,
ImportMultiRequestScriptPubkey, ListTransactionResult, ListUnspentResultEntry, ScanningDetails,
Timestamp,
ImportMultiRequestScriptPubkey, ImportMultiRescanSince, ListTransactionResult,
ListUnspentResultEntry, ScanningDetails,
};
use bitcoincore_rpc::jsonrpc::serde_json::{json, Value};
use bitcoincore_rpc::Auth as RpcAuth;
use bitcoincore_rpc::{Client, RpcApi};
use log::{debug, info};
use serde::{Deserialize, Serialize};
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::ops::{Deref, DerefMut};
use std::ops::Deref;
use std::path::PathBuf;
use std::thread;
use std::time::Duration;
@@ -77,7 +77,7 @@ impl Deref for RpcBlockchain {
}
/// RpcBlockchain configuration options
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RpcConfig {
/// The bitcoin node url
pub url: String,
@@ -96,7 +96,7 @@ pub struct RpcConfig {
/// In general, BDK tries to sync `scriptPubKey`s cached in [`crate::database::Database`] with
/// `scriptPubKey`s imported in the Bitcoin Core Wallet. These parameters are used for determining
/// how the `importdescriptors` RPC calls are to be made.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct RpcSyncParams {
/// The minimum number of scripts to scan for on initial sync.
pub start_script_count: usize,
@@ -167,7 +167,7 @@ impl Blockchain for RpcBlockchain {
.estimate_smart_fee(target as u16, None)?
.fee_rate
.ok_or(Error::FeeRateUnavailable)?
.to_sat() as f64;
.as_sat() as f64;
Ok(FeeRate::from_sat_per_vb((sat_per_kb / 1000f64) as f32))
}
@@ -192,12 +192,10 @@ impl GetBlockHash for RpcBlockchain {
}
impl WalletSync for RpcBlockchain {
fn wallet_setup<D>(&self, db: &RefCell<D>, prog: Box<dyn Progress>) -> Result<(), Error>
fn wallet_setup<D>(&self, db: &mut D, prog: Box<dyn Progress>) -> Result<(), Error>
where
D: BatchDatabase,
{
let mut db = db.borrow_mut();
let db = db.deref_mut();
let batch = DbState::new(db, &self.sync_params, &*prog)?
.sync_with_core(&self.client, self.is_descriptors)?
.as_db_batch()?;
@@ -301,8 +299,8 @@ struct DbState<'a, D> {
params: &'a RpcSyncParams,
prog: &'a dyn Progress,
ext_spks: Vec<ScriptBuf>,
int_spks: Vec<ScriptBuf>,
ext_spks: Vec<Script>,
int_spks: Vec<Script>,
txs: HashMap<Txid, TransactionDetails>,
utxos: HashSet<LocalUtxo>,
last_indexes: HashMap<KeychainKind, u32>,
@@ -412,12 +410,7 @@ impl<'a, D: BatchDatabase> DbState<'a, D> {
updated = true;
TransactionDetails {
txid: tx_res.info.txid,
transaction: None,
received: 0,
sent: 0,
fee: None,
confirmation_time: None,
..Default::default()
}
});
@@ -437,7 +430,7 @@ impl<'a, D: BatchDatabase> DbState<'a, D> {
// update fee (if needed)
if let (None, Some(new_fee)) = (db_tx.fee, tx_res.detail.fee) {
updated = true;
db_tx.fee = Some(new_fee.to_sat().unsigned_abs());
db_tx.fee = Some(new_fee.as_sat().unsigned_abs());
}
// update confirmation time (if needed)
@@ -610,7 +603,7 @@ impl<'a, D: BatchDatabase> DbState<'a, D> {
LocalUtxo {
outpoint: OutPoint::new(entry.txid, entry.vout),
txout: TxOut {
value: entry.amount.to_sat(),
value: entry.amount.as_sat(),
script_pubkey: entry.script_pub_key,
},
keychain,
@@ -667,7 +660,7 @@ fn import_descriptors<'a, S>(
scripts_iter: S,
) -> Result<(), Error>
where
S: Iterator<Item = &'a ScriptBuf>,
S: Iterator<Item = &'a Script>,
{
let requests = Value::Array(
scripts_iter
@@ -695,11 +688,11 @@ where
fn import_multi<'a, S>(client: &Client, start_epoch: u64, scripts_iter: S) -> Result<(), Error>
where
S: Iterator<Item = &'a ScriptBuf>,
S: Iterator<Item = &'a Script>,
{
let requests = scripts_iter
.map(|script| ImportMultiRequest {
timestamp: Timestamp::Time(start_epoch),
timestamp: ImportMultiRescanSince::Timestamp(start_epoch),
script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(script)),
watchonly: Some(true),
..Default::default()
@@ -807,8 +800,8 @@ fn is_wallet_descriptor(client: &Client) -> Result<bool, Error> {
}
fn descriptor_from_script_pubkey(script: &Script) -> String {
let desc = format!("raw({})", script.to_hex_string());
format!("{}#{}", desc, calc_checksum(&desc).unwrap())
let desc = format!("raw({})", script.to_hex());
format!("{}#{}", desc, get_checksum(&desc).unwrap())
}
/// Factory of [`RpcBlockchain`] instances, implements [`BlockchainFactory`]
@@ -880,13 +873,15 @@ impl BlockchainFactory for RpcBlockchainFactory {
mod test {
use super::*;
use crate::{
descriptor::into_wallet_descriptor_checked, testutils::blockchain_tests::TestClient,
descriptor::{into_wallet_descriptor_checked, AsDerived},
testutils::blockchain_tests::TestClient,
wallet::utils::SecpCtx,
};
use bitcoin::{Address, Network};
use bitcoincore_rpc::RpcApi;
use log::LevelFilter;
use miniscript::DescriptorTrait;
crate::bdk_blockchain_tests! {
fn test_instance(test_client: &TestClient) -> RpcBlockchain {
@@ -963,7 +958,7 @@ mod test {
// generate scripts (1 tx per script)
let scripts = (0..TX_COUNT)
.map(|index| desc.at_derivation_index(index).unwrap().script_pubkey())
.map(|index| desc.as_derived(index, &secp).script_pubkey())
.collect::<Vec<_>>();
// import scripts and wait

View File

@@ -9,7 +9,7 @@ use crate::{
wallet::time::Instant,
BlockTime, Error, KeychainKind, LocalUtxo, TransactionDetails,
};
use bitcoin::{hashes::Hash, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid};
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
use log::*;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
@@ -53,7 +53,7 @@ pub struct ScriptReq<'a, D: BatchDatabase> {
state: State<'a, D>,
script_index: usize,
initial_scripts_needed: usize, // if this is 1, we assume the descriptor is not derivable
scripts_needed: VecDeque<ScriptBuf>,
scripts_needed: VecDeque<Script>,
stop_gap: usize,
keychain: KeychainKind,
next_keychains: Vec<KeychainKind>,
@@ -62,7 +62,7 @@ pub struct ScriptReq<'a, D: BatchDatabase> {
/// The sync starts by returning script pubkeys we are interested in.
impl<'a, D: BatchDatabase> ScriptReq<'a, D> {
pub fn request(&self) -> impl Iterator<Item = &Script> + Clone {
self.scripts_needed.iter().map(|s| s.as_script())
self.scripts_needed.iter()
}
pub fn satisfy(
@@ -444,14 +444,8 @@ impl<'a, D: BatchDatabase> State<'a, D> {
/// Remove conflicting transactions -- tie breaking them by fee.
fn make_txs_consistent(txs: &[TransactionDetails]) -> Vec<&TransactionDetails> {
let mut utxo_index: HashMap<OutPoint, &TransactionDetails> = HashMap::default();
let mut coinbase_txs = vec![];
for tx in txs {
for input in &tx.transaction.as_ref().unwrap().input {
if input.previous_output.txid == Txid::all_zeros() {
coinbase_txs.push(tx);
break;
}
utxo_index
.entry(input.previous_output)
.and_modify(|existing| match (tx.fee, existing.fee) {
@@ -469,6 +463,5 @@ fn make_txs_consistent(txs: &[TransactionDetails]) -> Vec<&TransactionDetails> {
.collect::<HashMap<_, _>>()
.into_iter()
.map(|(_, tx)| tx)
.chain(coinbase_txs)
.collect()
}

View File

@@ -153,7 +153,7 @@ impl BatchOperations for AnyDatabase {
&mut self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
impl_inner_method!(
AnyDatabase,
self,
@@ -204,7 +204,7 @@ impl Database for AnyDatabase {
)
}
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
impl_inner_method!(AnyDatabase, self, iter_script_pubkeys, keychain)
}
fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error> {
@@ -221,7 +221,7 @@ impl Database for AnyDatabase {
&self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
impl_inner_method!(
AnyDatabase,
self,
@@ -286,7 +286,7 @@ impl BatchOperations for AnyBatch {
&mut self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
impl_inner_method!(AnyBatch, self, del_script_pubkey_from_path, keychain, child)
}
fn del_path_from_script_pubkey(

View File

@@ -15,7 +15,7 @@ use sled::{Batch, Tree};
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction};
use bitcoin::{OutPoint, Script, Transaction};
use crate::database::memory::MapKey;
use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
@@ -90,7 +90,7 @@ macro_rules! impl_batch_operations {
Ok(())
}
fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result<Option<ScriptBuf>, Error> {
fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result<Option<Script>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
let res = self.remove(key);
let res = $process_delete!(res);
@@ -221,7 +221,7 @@ impl Database for Tree {
}
}
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
let key = MapKey::Path((keychain, None)).as_map_key();
self.scan_prefix(key)
.map(|x| -> Result<_, Error> {
@@ -286,7 +286,7 @@ impl Database for Tree {
&self,
keychain: KeychainKind,
path: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?)
}

View File

@@ -20,7 +20,7 @@ use std::ops::Bound::{Excluded, Included};
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction};
use bitcoin::{OutPoint, Script, Transaction};
use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database, SyncTime};
use crate::error::Error;
@@ -136,7 +136,7 @@ impl BatchOperations for MemoryDatabase {
path: u32,
) -> Result<(), Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
self.map.insert(key, Box::new(ScriptBuf::from(script)));
self.map.insert(key, Box::new(script.clone()));
let key = MapKey::Script(Some(script)).as_map_key();
let value = json!({
@@ -196,7 +196,7 @@ impl BatchOperations for MemoryDatabase {
&mut self,
keychain: KeychainKind,
path: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
let res = self.map.remove(&key);
self.deleted_keys.push(key);
@@ -315,7 +315,7 @@ impl Database for MemoryDatabase {
}
}
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
let key = MapKey::Path((keychain, None)).as_map_key();
self.map
.range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
@@ -368,7 +368,7 @@ impl Database for MemoryDatabase {
&self,
keychain: KeychainKind,
path: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
Ok(self
.map
@@ -485,6 +485,7 @@ macro_rules! populate_test_db {
$crate::populate_test_db!($db, $tx_meta, $current_height, (@coinbase false))
}};
($db:expr, $tx_meta:expr, $current_height:expr, (@coinbase $is_coinbase:expr)$(,)?) => {{
use std::str::FromStr;
use $crate::database::SyncTime;
use $crate::database::{BatchOperations, Database};
let mut db = $db;
@@ -496,7 +497,7 @@ macro_rules! populate_test_db {
}
let tx = $crate::bitcoin::Transaction {
version: 1,
lock_time: bitcoin::absolute::LockTime::ZERO,
lock_time: 0,
input,
output: tx_meta
.output
@@ -505,7 +506,6 @@ macro_rules! populate_test_db {
value: out_meta.value,
script_pubkey: $crate::bitcoin::Address::from_str(&out_meta.to_address)
.unwrap()
.assume_checked()
.script_pubkey(),
})
.collect(),

View File

@@ -27,7 +27,7 @@
use serde::{Deserialize, Serialize};
use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction, TxOut};
use bitcoin::{OutPoint, Script, Transaction, TxOut};
use crate::error::Error;
use crate::types::*;
@@ -49,7 +49,7 @@ pub use memory::MemoryDatabase;
/// Blockchain state at the time of syncing
///
/// Contains only the block time and height at the moment
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SyncTime {
/// Block timestamp and height at the time of sync
pub block_time: BlockTime,
@@ -83,7 +83,7 @@ pub trait BatchOperations {
&mut self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<ScriptBuf>, Error>;
) -> Result<Option<Script>, Error>;
/// Delete the data related to a specific script_pubkey, meaning the keychain and the child
/// number.
fn del_path_from_script_pubkey(
@@ -124,7 +124,7 @@ pub trait Database: BatchOperations {
) -> Result<(), Error>;
/// Return the list of script_pubkeys
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, Error>;
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error>;
/// Return the list of [`LocalUtxo`]s
fn iter_utxos(&self) -> Result<Vec<LocalUtxo>, Error>;
/// Return the list of raw transactions
@@ -137,7 +137,7 @@ pub trait Database: BatchOperations {
&self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<ScriptBuf>, Error>;
) -> Result<Option<Script>, Error>;
/// Fetch the keychain and child number of a given script_pubkey
fn get_path_from_script_pubkey(
&self,
@@ -214,17 +214,17 @@ impl<T: Database> DatabaseUtils for T {}
#[cfg(test)]
pub mod test {
use std::str::FromStr;
use bitcoin::consensus::encode::deserialize;
use bitcoin::consensus::serialize;
use bitcoin::hashes::hex::*;
use bitcoin::Witness;
use bitcoin::*;
use std::str::FromStr;
use super::*;
pub fn test_script_pubkey<D: Database>(mut db: D) {
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
@@ -245,7 +245,7 @@ pub mod test {
pub fn test_batch_script_pubkey<D: BatchDatabase>(mut db: D) {
let mut batch = db.begin_batch();
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
@@ -272,7 +272,7 @@ pub mod test {
}
pub fn test_iter_script_pubkey<D: Database>(mut db: D) {
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
@@ -284,7 +284,7 @@ pub mod test {
}
pub fn test_del_script_pubkey<D: Database>(mut db: D) {
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
@@ -302,7 +302,7 @@ pub mod test {
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
)
.unwrap();
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let txout = TxOut {
@@ -478,7 +478,7 @@ pub mod test {
pub fn test_del_path_from_script_pubkey<D: Database>(mut db: D) {
let keychain = KeychainKind::External;
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
@@ -502,14 +502,14 @@ pub mod test {
let scripts = db.iter_script_pubkeys(Some(keychain)).unwrap();
assert!(scripts.is_empty());
let first_script = ScriptBuf::from(
let first_script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
db.set_script_pubkey(&first_script, keychain, path).unwrap();
let second_script = ScriptBuf::from(
let second_script = Script::from(
Vec::<u8>::from_hex("00145c9a1816d38db5cbdd4b067b689dc19eb7d930e2").unwrap(),
);
let path = 57;
@@ -528,7 +528,7 @@ pub mod test {
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
)
.unwrap();
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let txout = TxOut {

View File

@@ -13,7 +13,7 @@ use std::path::PathBuf;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction, TxOut};
use bitcoin::{OutPoint, Script, Transaction, TxOut};
use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
use crate::error::Error;
@@ -57,17 +57,7 @@ static MIGRATIONS: &[&str] = &[
"CREATE TABLE utxos (value INTEGER, keychain TEXT, vout INTEGER, txid BLOB, script BLOB, is_spent BOOLEAN DEFAULT 0);",
"INSERT INTO utxos SELECT value, keychain, vout, txid, script, is_spent FROM utxos_old;",
"DROP TABLE utxos_old;",
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);",
// Fix issue https://github.com/bitcoindevkit/bdk/issues/801: drop duplicated script_pubkeys
"ALTER TABLE script_pubkeys RENAME TO script_pubkeys_old;",
"DROP INDEX idx_keychain_child;",
"DROP INDEX idx_script;",
"CREATE TABLE script_pubkeys (keychain TEXT, child INTEGER, script BLOB);",
"CREATE INDEX idx_keychain_child ON script_pubkeys(keychain, child);",
"CREATE INDEX idx_script ON script_pubkeys(script);",
"CREATE UNIQUE INDEX idx_script_pks_unique ON script_pubkeys(keychain, child);",
"INSERT OR REPLACE INTO script_pubkeys SELECT keychain, child, script FROM script_pubkeys_old;",
"DROP TABLE script_pubkeys_old;"
"CREATE UNIQUE INDEX idx_utxos_txid_vout ON utxos(txid, vout);"
];
/// Sqlite database stored on filesystem
@@ -98,7 +88,7 @@ impl SqliteDatabase {
child: u32,
script: &[u8],
) -> Result<i64, Error> {
let mut statement = self.connection.prepare_cached("INSERT OR REPLACE INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
let mut statement = self.connection.prepare_cached("INSERT INTO script_pubkeys (keychain, child, script) VALUES (:keychain, :child, :script)")?;
statement.execute(named_params! {
":keychain": keychain,
":child": child,
@@ -162,7 +152,7 @@ impl SqliteDatabase {
None => (None, None),
};
let txid: &[u8] = transaction.txid.as_ref();
let txid: &[u8] = &transaction.txid;
let mut statement = self.connection.prepare_cached("INSERT INTO transaction_details (txid, timestamp, received, sent, fee, height) VALUES (:txid, :timestamp, :received, :sent, :fee, :height)")?;
@@ -187,7 +177,7 @@ impl SqliteDatabase {
None => (None, None),
};
let txid: &[u8] = transaction.txid.as_ref();
let txid: &[u8] = &transaction.txid;
let mut statement = self.connection.prepare_cached("UPDATE transaction_details SET timestamp=:timestamp, received=:received, sent=:sent, fee=:fee, height=:height WHERE txid=:txid")?;
@@ -254,11 +244,11 @@ impl SqliteDatabase {
Ok(self.connection.last_insert_rowid())
}
fn select_script_pubkeys(&self) -> Result<Vec<ScriptBuf>, Error> {
fn select_script_pubkeys(&self) -> Result<Vec<Script>, Error> {
let mut statement = self
.connection
.prepare_cached("SELECT script FROM script_pubkeys")?;
let mut scripts: Vec<ScriptBuf> = vec![];
let mut scripts: Vec<Script> = vec![];
let mut rows = statement.query([])?;
while let Some(row) = rows.next()? {
let raw_script: Vec<u8> = row.get(0)?;
@@ -268,11 +258,11 @@ impl SqliteDatabase {
Ok(scripts)
}
fn select_script_pubkeys_by_keychain(&self, keychain: String) -> Result<Vec<ScriptBuf>, Error> {
fn select_script_pubkeys_by_keychain(&self, keychain: String) -> Result<Vec<Script>, Error> {
let mut statement = self
.connection
.prepare_cached("SELECT script FROM script_pubkeys WHERE keychain=:keychain")?;
let mut scripts: Vec<ScriptBuf> = vec![];
let mut scripts: Vec<Script> = vec![];
let mut rows = statement.query(named_params! {":keychain": keychain})?;
while let Some(row) = rows.next()? {
let raw_script: Vec<u8> = row.get(0)?;
@@ -286,7 +276,7 @@ impl SqliteDatabase {
&self,
keychain: String,
child: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
let mut statement = self.connection.prepare_cached(
"SELECT script FROM script_pubkeys WHERE keychain=:keychain AND child=:child",
)?;
@@ -295,7 +285,7 @@ impl SqliteDatabase {
match rows.next()? {
Some(row) => {
let script: Vec<u8> = row.get(0)?;
let script: ScriptBuf = script.into();
let script: Script = script.into();
Ok(Some(script))
}
None => Ok(None),
@@ -362,7 +352,7 @@ impl SqliteDatabase {
let keychain: String = row.get(1)?;
let keychain: KeychainKind = serde_json::from_str(&keychain)?;
let script: Vec<u8> = row.get(2)?;
let script_pubkey: ScriptBuf = script.into();
let script_pubkey: Script = script.into();
let is_spent: bool = row.get(3)?;
Ok(Some(LocalUtxo {
@@ -658,7 +648,7 @@ impl BatchOperations for SqliteDatabase {
utxo.txout.value,
serde_json::to_string(&utxo.keychain)?,
utxo.outpoint.vout,
utxo.outpoint.txid.as_ref(),
&utxo.outpoint.txid,
utxo.txout.script_pubkey.as_bytes(),
utxo.is_spent,
)?;
@@ -666,19 +656,19 @@ impl BatchOperations for SqliteDatabase {
}
fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> {
match self.select_transaction_by_txid(transaction.txid().as_ref())? {
match self.select_transaction_by_txid(&transaction.txid())? {
Some(_) => {
self.update_transaction(transaction.txid().as_ref(), &serialize(transaction))?;
self.update_transaction(&transaction.txid(), &serialize(transaction))?;
}
None => {
self.insert_transaction(transaction.txid().as_ref(), &serialize(transaction))?;
self.insert_transaction(&transaction.txid(), &serialize(transaction))?;
}
}
Ok(())
}
fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
match self.select_transaction_details_by_txid(transaction.txid.as_ref())? {
match self.select_transaction_details_by_txid(&transaction.txid)? {
Some(_) => {
self.update_transaction_details(transaction)?;
}
@@ -708,7 +698,7 @@ impl BatchOperations for SqliteDatabase {
&mut self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
let keychain = serde_json::to_string(&keychain)?;
let script = self.select_script_pubkey_by_path(keychain.clone(), child)?;
match script {
@@ -734,9 +724,9 @@ impl BatchOperations for SqliteDatabase {
}
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
match self.select_utxo_by_outpoint(outpoint.txid.as_ref(), outpoint.vout)? {
match self.select_utxo_by_outpoint(&outpoint.txid, outpoint.vout)? {
Some(local_utxo) => {
self.delete_utxo_by_outpoint(outpoint.txid.as_ref(), outpoint.vout)?;
self.delete_utxo_by_outpoint(&outpoint.txid, outpoint.vout)?;
Ok(Some(local_utxo))
}
None => Ok(None),
@@ -744,9 +734,9 @@ impl BatchOperations for SqliteDatabase {
}
fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> {
match self.select_transaction_by_txid(txid.as_ref())? {
match self.select_transaction_by_txid(txid)? {
Some(tx) => {
self.delete_transaction_by_txid(txid.as_ref())?;
self.delete_transaction_by_txid(txid)?;
Ok(Some(tx))
}
None => Ok(None),
@@ -758,12 +748,12 @@ impl BatchOperations for SqliteDatabase {
txid: &Txid,
include_raw: bool,
) -> Result<Option<TransactionDetails>, Error> {
match self.select_transaction_details_by_txid(txid.as_ref())? {
match self.select_transaction_details_by_txid(txid)? {
Some(mut transaction_details) => {
self.delete_transaction_details_by_txid(txid.as_ref())?;
self.delete_transaction_details_by_txid(txid)?;
if include_raw {
self.delete_transaction_by_txid(txid.as_ref())?;
self.delete_transaction_by_txid(txid)?;
} else {
transaction_details.transaction = None;
}
@@ -820,7 +810,7 @@ impl Database for SqliteDatabase {
}
}
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
match keychain {
Some(keychain) => {
let keychain = serde_json::to_string(&keychain)?;
@@ -849,7 +839,7 @@ impl Database for SqliteDatabase {
&self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<ScriptBuf>, Error> {
) -> Result<Option<Script>, Error> {
let keychain = serde_json::to_string(&keychain)?;
match self.select_script_pubkey_by_path(keychain, child)? {
Some(script) => Ok(Some(script)),
@@ -868,18 +858,18 @@ impl Database for SqliteDatabase {
}
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> {
self.select_utxo_by_outpoint(outpoint.txid.as_ref(), outpoint.vout)
self.select_utxo_by_outpoint(&outpoint.txid, outpoint.vout)
}
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
match self.select_transaction_by_txid(txid.as_ref())? {
match self.select_transaction_by_txid(txid)? {
Some(tx) => Ok(Some(tx)),
None => Ok(None),
}
}
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> {
match self.select_transaction_details_by_txid(txid.as_ref())? {
match self.select_transaction_details_by_txid(txid)? {
Some(mut transaction_details) => {
if !include_raw {
transaction_details.transaction = None;
@@ -1106,42 +1096,4 @@ pub mod test {
fn test_check_descriptor_checksum() {
crate::database::test::test_check_descriptor_checksum(get_database());
}
// Issue 801: https://github.com/bitcoindevkit/bdk/issues/801
#[test]
fn test_unique_spks() {
use crate::bitcoin::hashes::hex::FromHex;
use crate::database::*;
let mut db = get_database();
let script = ScriptBuf::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
let keychain = KeychainKind::External;
for _ in 0..100 {
db.set_script_pubkey(&script, keychain, path).unwrap();
}
let mut statement = db
.connection
.prepare_cached(
"select keychain,child,count(child) from script_pubkeys group by keychain,child;",
)
.unwrap();
let mut rows = statement.query([]).unwrap();
while let Some(row) = rows.next().unwrap() {
let keychain: String = row.get(0).unwrap();
let child: u32 = row.get(1).unwrap();
let count: usize = row.get(2).unwrap();
assert_eq!(
count, 1,
"keychain={}, child={}, count={}",
keychain, child, count
);
}
}
}

View File

@@ -41,24 +41,12 @@ fn poly_mod(mut c: u64, val: u64) -> u64 {
c
}
/// Computes the checksum bytes of a descriptor.
/// `exclude_hash = true` ignores all data after the first '#' (inclusive).
pub(crate) fn calc_checksum_bytes_internal(
mut desc: &str,
exclude_hash: bool,
) -> Result<[u8; 8], DescriptorError> {
/// Computes the checksum bytes of a descriptor
pub fn get_checksum_bytes(desc: &str) -> Result<[u8; 8], DescriptorError> {
let mut c = 1;
let mut cls = 0;
let mut clscount = 0;
let mut original_checksum = None;
if exclude_hash {
if let Some(split) = desc.split_once('#') {
desc = split.0;
original_checksum = Some(split.1);
}
}
for ch in desc.as_bytes() {
let pos = INPUT_CHARSET
.iter()
@@ -84,98 +72,38 @@ pub(crate) fn calc_checksum_bytes_internal(
checksum[j] = CHECKSUM_CHARSET[((c >> (5 * (7 - j))) & 31) as usize];
}
// if input data already had a checksum, check calculated checksum against original checksum
if let Some(original_checksum) = original_checksum {
if original_checksum.as_bytes() != checksum {
return Err(DescriptorError::InvalidDescriptorChecksum);
}
}
Ok(checksum)
}
/// Compute the checksum bytes of a descriptor, excludes any existing checksum in the descriptor string from the calculation
pub fn calc_checksum_bytes(desc: &str) -> Result<[u8; 8], DescriptorError> {
calc_checksum_bytes_internal(desc, true)
}
/// Compute the checksum of a descriptor, excludes any existing checksum in the descriptor string from the calculation
pub fn calc_checksum(desc: &str) -> Result<String, DescriptorError> {
// unsafe is okay here as the checksum only uses bytes in `CHECKSUM_CHARSET`
calc_checksum_bytes_internal(desc, true)
.map(|b| unsafe { String::from_utf8_unchecked(b.to_vec()) })
}
// TODO in release 0.25.0, remove get_checksum_bytes and get_checksum
// TODO in release 0.25.0, consolidate calc_checksum_bytes_internal into calc_checksum_bytes
/// Compute the checksum bytes of a descriptor
#[deprecated(
since = "0.24.0",
note = "Use new `calc_checksum_bytes` function which excludes any existing checksum in the descriptor string before calculating the checksum hash bytes. See https://github.com/bitcoindevkit/bdk/pull/765."
)]
pub fn get_checksum_bytes(desc: &str) -> Result<[u8; 8], DescriptorError> {
calc_checksum_bytes_internal(desc, false)
}
/// Compute the checksum of a descriptor
#[deprecated(
since = "0.24.0",
note = "Use new `calc_checksum` function which excludes any existing checksum in the descriptor string before calculating the checksum hash. See https://github.com/bitcoindevkit/bdk/pull/765."
)]
pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
// unsafe is okay here as the checksum only uses bytes in `CHECKSUM_CHARSET`
calc_checksum_bytes_internal(desc, false)
.map(|b| unsafe { String::from_utf8_unchecked(b.to_vec()) })
get_checksum_bytes(desc).map(|b| unsafe { String::from_utf8_unchecked(b.to_vec()) })
}
#[cfg(test)]
mod test {
use super::*;
use crate::descriptor::calc_checksum;
use assert_matches::assert_matches;
use crate::descriptor::get_checksum;
// test calc_checksum() function; it should return the same value as Bitcoin Core
// test get_checksum() function; it should return the same value as Bitcoin Core
#[test]
fn test_calc_checksum() {
fn test_get_checksum() {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)";
assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
assert_eq!(get_checksum(desc).unwrap(), "tqz0nc62");
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)";
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
}
// test calc_checksum() function; it should return the same value as Bitcoin Core even if the
// descriptor string includes a checksum hash
#[test]
fn test_calc_checksum_with_checksum_hash() {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc62";
assert_eq!(calc_checksum(desc).unwrap(), "tqz0nc62");
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmfs";
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
assert_matches!(
calc_checksum(desc),
Err(DescriptorError::InvalidDescriptorChecksum)
);
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
assert_matches!(
calc_checksum(desc),
Err(DescriptorError::InvalidDescriptorChecksum)
);
assert_eq!(get_checksum(desc).unwrap(), "lasegmfs");
}
#[test]
fn test_calc_checksum_invalid_character() {
fn test_get_checksum_invalid_character() {
let sparkle_heart = unsafe { std::str::from_utf8_unchecked(&[240, 159, 146, 150]) };
let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart);
assert_matches!(
calc_checksum(&invalid_desc),
Err(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
);
assert!(matches!(
get_checksum(&invalid_desc).err(),
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
));
}
}

210
src/descriptor/derived.rs Normal file
View File

@@ -0,0 +1,210 @@
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
//! Derived descriptor keys
//!
//! The [`DerivedDescriptorKey`] type is a wrapper over the standard [`DescriptorPublicKey`] which
//! guarantees that all the extended keys have a fixed derivation path, i.e. all the wildcards have
//! been replaced by actual derivation indexes.
//!
//! The [`AsDerived`] trait provides a quick way to derive descriptors to obtain a
//! `Descriptor<DerivedDescriptorKey>` type. This, in turn, can be used to derive public
//! keys for arbitrary derivation indexes.
//!
//! Combining this with [`Wallet::get_signers`], secret keys can also be derived.
//!
//! # Example
//!
//! ```
//! # use std::str::FromStr;
//! # use bitcoin::secp256k1::Secp256k1;
//! use bdk::descriptor::{AsDerived, DescriptorPublicKey};
//! use bdk::miniscript::{ToPublicKey, TranslatePk, MiniscriptKey};
//!
//! let secp = Secp256k1::gen_new();
//!
//! let key = DescriptorPublicKey::from_str("[aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/*")?;
//! let (descriptor, _, _) = bdk::descriptor!(wpkh(key))?;
//!
//! // derived: wpkh([aa600a45/84'/0'/0']tpubDCbDXFKoLTQp44wQuC12JgSn5g9CWGjZdpBHeTqyypZ4VvgYjTJmK9CkyR5bFvG9f4PutvwmvpYCLkFx2rpx25hiMs4sUgxJveW8ZzSAVAc/0/42)#3ladd0t2
//! let derived = descriptor.as_derived(42, &secp);
//! println!("derived: {}", derived);
//!
//! // with_pks: wpkh(02373ecb54c5e83bd7e0d40adf78b65efaf12fafb13571f0261fc90364eee22e1e)#p4jjgvll
//! let with_pks = derived.translate_pk_infallible(|pk| pk.to_public_key(), |pkh| pkh.to_public_key().to_pubkeyhash());
//! println!("with_pks: {}", with_pks);
//! # Ok::<(), Box<dyn std::error::Error>>(())
//! ```
//!
//! [`Wallet::get_signers`]: crate::wallet::Wallet::get_signers
use std::cmp::Ordering;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use bitcoin::hashes::hash160;
use bitcoin::{PublicKey, XOnlyPublicKey};
use miniscript::descriptor::{DescriptorSinglePub, SinglePubKey, Wildcard};
use miniscript::{Descriptor, DescriptorPublicKey, MiniscriptKey, ToPublicKey, TranslatePk};
use crate::wallet::utils::SecpCtx;
/// Extended [`DescriptorPublicKey`] that has been derived
///
/// Derived keys are guaranteed to never contain wildcards of any kind
#[derive(Debug, Clone)]
pub struct DerivedDescriptorKey<'s>(DescriptorPublicKey, &'s SecpCtx);
impl<'s> DerivedDescriptorKey<'s> {
/// Construct a new derived key
///
/// Panics if the key is wildcard
pub fn new(key: DescriptorPublicKey, secp: &'s SecpCtx) -> DerivedDescriptorKey<'s> {
if let DescriptorPublicKey::XPub(xpub) = &key {
assert!(xpub.wildcard == Wildcard::None)
}
DerivedDescriptorKey(key, secp)
}
}
impl<'s> Deref for DerivedDescriptorKey<'s> {
type Target = DescriptorPublicKey;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'s> PartialEq for DerivedDescriptorKey<'s> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<'s> Eq for DerivedDescriptorKey<'s> {}
impl<'s> PartialOrd for DerivedDescriptorKey<'s> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<'s> Ord for DerivedDescriptorKey<'s> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl<'s> fmt::Display for DerivedDescriptorKey<'s> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'s> Hash for DerivedDescriptorKey<'s> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl<'s> MiniscriptKey for DerivedDescriptorKey<'s> {
type Hash = Self;
fn to_pubkeyhash(&self) -> Self::Hash {
DerivedDescriptorKey(self.0.to_pubkeyhash(), self.1)
}
fn is_uncompressed(&self) -> bool {
self.0.is_uncompressed()
}
}
impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
fn to_public_key(&self) -> PublicKey {
match &self.0 {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(_),
..
}) => panic!("Found x-only public key in non-tr descriptor"),
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(ref pk),
..
}) => *pk,
DescriptorPublicKey::XPub(ref xpub) => PublicKey::new(
xpub.xkey
.derive_pub(self.1, &xpub.derivation_path)
.expect("Shouldn't fail, only normal derivations")
.public_key,
),
}
}
fn to_x_only_pubkey(&self) -> XOnlyPublicKey {
match &self.0 {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(ref pk),
..
}) => *pk,
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(ref pk),
..
}) => XOnlyPublicKey::from(pk.inner),
DescriptorPublicKey::XPub(ref xpub) => XOnlyPublicKey::from(
xpub.xkey
.derive_pub(self.1, &xpub.derivation_path)
.expect("Shouldn't fail, only normal derivations")
.public_key,
),
}
}
fn hash_to_hash160(hash: &Self::Hash) -> hash160::Hash {
hash.to_public_key().to_pubkeyhash()
}
}
/// Utilities to derive descriptors
///
/// Check out the [module level] documentation for more.
///
/// [module level]: crate::descriptor::derived
pub trait AsDerived {
/// Derive a descriptor and transform all of its keys to `DerivedDescriptorKey`
fn as_derived<'s>(&self, index: u32, secp: &'s SecpCtx)
-> Descriptor<DerivedDescriptorKey<'s>>;
/// Transform the keys into `DerivedDescriptorKey`.
///
/// Panics if the descriptor is not "fixed", i.e. if it's derivable
fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>>;
}
impl AsDerived for Descriptor<DescriptorPublicKey> {
fn as_derived<'s>(
&self,
index: u32,
secp: &'s SecpCtx,
) -> Descriptor<DerivedDescriptorKey<'s>> {
self.derive(index).translate_pk_infallible(
|key| DerivedDescriptorKey::new(key.clone(), secp),
|key| DerivedDescriptorKey::new(key.clone(), secp),
)
}
fn as_derived_fixed<'s>(&self, secp: &'s SecpCtx) -> Descriptor<DerivedDescriptorKey<'s>> {
assert!(!self.is_deriveable());
self.as_derived(0, secp)
}
}

View File

@@ -514,14 +514,13 @@ macro_rules! descriptor {
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
$crate::impl_top_level_pk!(Pkh, $crate::miniscript::Legacy, $key)
.and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
.map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Pkh(a), b, c))
});
( wpkh ( $key:expr ) ) => ({
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
$crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
.and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
.and_then(|(a, b, c)| Ok((a?, b, c)))
.map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Wpkh(a), b, c))
});
( sh ( wpkh ( $key:expr ) ) ) => ({
@@ -531,7 +530,7 @@ macro_rules! descriptor {
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh};
$crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key)
.and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
.and_then(|(a, b, c)| Ok((a?, b, c)))
.and_then(|(a, b, c)| Ok((Descriptor::<DescriptorPublicKey>::Sh(Sh::new_wpkh(a.into_inner())?), b, c)))
});
( sh ( $( $minisc:tt )* ) ) => ({
@@ -701,10 +700,10 @@ macro_rules! fragment {
$crate::keys::make_pkh($key, &secp)
});
( after ( $value:expr ) ) => ({
$crate::impl_leaf_opcode_value!(After, $crate::miniscript::AbsLockTime::from_consensus($value))
$crate::impl_leaf_opcode_value!(After, $value)
});
( older ( $value:expr ) ) => ({
$crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!!
$crate::impl_leaf_opcode_value!(Older, $value)
});
( sha256 ( $hash:expr ) ) => ({
$crate::impl_leaf_opcode_value!(Sha256, $hash)
@@ -794,18 +793,21 @@ macro_rules! fragment {
#[cfg(test)]
mod test {
use bitcoin::hashes::hex::ToHex;
use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
use miniscript::{Descriptor, Legacy, Segwitv0};
use std::str::FromStr;
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
use bitcoin::bip32;
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Signet, Testnet};
use bitcoin::util::bip32;
use bitcoin::PrivateKey;
use crate::descriptor::derived::AsDerived;
// test the descriptor!() macro
// verify descriptor generates expected script(s) (if bare or pk) or address(es)
@@ -815,19 +817,24 @@ mod test {
is_fixed: bool,
expected: &[&str],
) {
let secp = Secp256k1::new();
let (desc, _key_map, _networks) = desc.unwrap();
assert_eq!(desc.is_witness(), is_witness);
assert_eq!(!desc.has_wildcard(), is_fixed);
assert_eq!(!desc.is_deriveable(), is_fixed);
for i in 0..expected.len() {
let child_desc = desc
.at_derivation_index(i as u32)
.expect("i is not hardened");
let index = i as u32;
let child_desc = if !desc.is_deriveable() {
desc.as_derived_fixed(&secp)
} else {
desc.as_derived(index, &secp)
};
let address = child_desc.address(Regtest);
if let Ok(address) = address {
assert_eq!(address.to_string(), *expected.get(i).unwrap());
} else {
let script = child_desc.script_pubkey();
assert_eq!(script.to_hex_string(), *expected.get(i).unwrap());
assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap());
}
}
}
@@ -1172,7 +1179,9 @@ mod test {
}
#[test]
#[should_panic(expected = "Miniscript(ContextError(UncompressedKeysNotAllowed))")]
#[should_panic(
expected = "Miniscript(ContextError(CompressedOnly(\"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4\")))"
)]
fn test_dsl_miniscript_checks() {
let mut uncompressed_pk =
PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();

View File

@@ -20,8 +20,6 @@ pub enum Error {
InvalidDescriptorChecksum,
/// The descriptor contains hardened derivation steps on public extended keys
HardenedDerivationXpub,
/// The descriptor contains multipath keys
MultiPath,
/// Error thrown while working with [`keys`](crate::keys)
Key(crate::keys::KeyError),
@@ -32,11 +30,11 @@ pub enum Error {
InvalidDescriptorCharacter(u8),
/// BIP32 error
Bip32(bitcoin::bip32::Error),
Bip32(bitcoin::util::bip32::Error),
/// Error during base58 decoding
Base58(bitcoin::base58::Error),
Base58(bitcoin::util::base58::Error),
/// Key-related error
Pk(bitcoin::key::Error),
Pk(bitcoin::util::key::Error),
/// Miniscript error
Miniscript(miniscript::Error),
/// Hex decoding error
@@ -55,38 +53,15 @@ impl From<crate::keys::KeyError> for Error {
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidHdKeyPath => write!(f, "Invalid HD key path"),
Self::InvalidDescriptorChecksum => {
write!(f, "The provided descriptor doesn't match its checksum")
}
Self::HardenedDerivationXpub => write!(
f,
"The descriptor contains hardened derivation steps on public extended keys"
),
Self::MultiPath => write!(
f,
"The descriptor contains multipath keys, which are not supported yet"
),
Self::Key(err) => write!(f, "Key error: {}", err),
Self::Policy(err) => write!(f, "Policy error: {}", err),
Self::InvalidDescriptorCharacter(char) => {
write!(f, "Invalid descriptor character: {}", char)
}
Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
Self::Base58(err) => write!(f, "Base58 error: {}", err),
Self::Pk(err) => write!(f, "Key-related error: {}", err),
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
}
write!(f, "{:?}", self)
}
}
impl std::error::Error for Error {}
impl_error!(bitcoin::bip32::Error, Bip32);
impl_error!(bitcoin::base58::Error, Base58);
impl_error!(bitcoin::key::Error, Pk);
impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::util::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, Pk);
impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(crate::descriptor::policy::PolicyError, Policy);

View File

@@ -15,32 +15,32 @@
//! from [`miniscript`].
use std::collections::BTreeMap;
use std::ops::Deref;
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey};
use bitcoin::{psbt, taproot};
use bitcoin::{Network, TxOut};
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::util::{psbt, taproot};
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
use bitcoin::{Network, Script, TxOut};
use miniscript::descriptor::{
DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType,
DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard,
};
use miniscript::descriptor::{DescriptorType, InnerXKey, SinglePubKey};
pub use miniscript::{
Descriptor, DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
};
use miniscript::{ForEachKey, MiniscriptKey, TranslatePk};
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
use crate::descriptor::policy::BuildSatisfaction;
pub mod checksum;
pub mod derived;
#[doc(hidden)]
pub mod dsl;
pub mod error;
pub mod policy;
pub mod template;
pub use self::checksum::calc_checksum;
use self::checksum::calc_checksum_bytes;
pub use self::checksum::get_checksum;
pub use self::derived::{AsDerived, DerivedDescriptorKey};
pub use self::error::Error as DescriptorError;
pub use self::policy::Policy;
use self::template::DescriptorTemplateOut;
@@ -52,21 +52,21 @@ use crate::wallet::utils::SecpCtx;
pub type ExtendedDescriptor = Descriptor<DescriptorPublicKey>;
/// Alias for a [`Descriptor`] that contains extended **derived** keys
pub type DerivedDescriptor = Descriptor<DefiniteDescriptorKey>;
pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
/// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or
/// [`psbt::Output`]
///
/// [`psbt::Input`]: bitcoin::psbt::Input
/// [`psbt::Output`]: bitcoin::psbt::Output
/// [`psbt::Input`]: bitcoin::util::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output
pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>;
/// Alias for the type of maps that represent taproot key origins in a [`psbt::Input`] or
/// [`psbt::Output`]
///
/// [`psbt::Input`]: bitcoin::psbt::Input
/// [`psbt::Output`]: bitcoin::psbt::Output
pub type TapKeyOrigins = BTreeMap<XOnlyPublicKey, (Vec<taproot::TapLeafHash>, KeySource)>;
/// [`psbt::Input`]: bitcoin::util::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output
pub type TapKeyOrigins = BTreeMap<bitcoin::XOnlyPublicKey, (Vec<taproot::TapLeafHash>, KeySource)>;
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
pub trait IntoWalletDescriptor {
@@ -84,15 +84,19 @@ impl IntoWalletDescriptor for &str {
secp: &SecpCtx,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
let descriptor = match self.split_once('#') {
Some((desc, original_checksum)) => {
let checksum = calc_checksum_bytes(desc)?;
if original_checksum.as_bytes() != checksum {
return Err(DescriptorError::InvalidDescriptorChecksum);
}
desc
let descriptor = if self.contains('#') {
let parts: Vec<&str> = self.splitn(2, '#').collect();
if !get_checksum(parts[0])
.ok()
.map(|computed| computed == parts[1])
.unwrap_or(false)
{
return Err(DescriptorError::InvalidDescriptorChecksum);
}
None => self,
parts[0]
} else {
self
};
ExtendedDescriptor::parse_descriptor(secp, descriptor)?
@@ -128,77 +132,28 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
use crate::keys::DescriptorKey;
struct Translator<'s, 'd> {
secp: &'s SecpCtx,
descriptor: &'d ExtendedDescriptor,
network: Network,
}
let check_key = |pk: &DescriptorPublicKey| {
let (pk, _, networks) = if self.0.is_witness() {
let descriptor_key: DescriptorKey<miniscript::Segwitv0> =
pk.clone().into_descriptor_key()?;
descriptor_key.extract(secp)?
} else {
let descriptor_key: DescriptorKey<miniscript::Legacy> =
pk.clone().into_descriptor_key()?;
descriptor_key.extract(secp)?
};
impl<'s, 'd> miniscript::Translator<DescriptorPublicKey, String, DescriptorError>
for Translator<'s, 'd>
{
fn pk(&mut self, pk: &DescriptorPublicKey) -> Result<String, DescriptorError> {
let secp = &self.secp;
let (_, _, networks) = if self.descriptor.is_taproot() {
let descriptor_key: DescriptorKey<miniscript::Tap> =
pk.clone().into_descriptor_key()?;
descriptor_key.extract(secp)?
} else if self.descriptor.is_witness() {
let descriptor_key: DescriptorKey<miniscript::Segwitv0> =
pk.clone().into_descriptor_key()?;
descriptor_key.extract(secp)?
} else {
let descriptor_key: DescriptorKey<miniscript::Legacy> =
pk.clone().into_descriptor_key()?;
descriptor_key.extract(secp)?
};
if networks.contains(&self.network) {
Ok(Default::default())
} else {
Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
if networks.contains(&network) {
Ok(pk)
} else {
Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
fn sha256(
&mut self,
_sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256,
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
fn hash256(
&mut self,
_hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256,
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
fn ripemd160(
&mut self,
_ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160,
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
fn hash160(
&mut self,
_hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160,
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
}
};
// check the network for the keys
use miniscript::TranslateErr;
match self.0.translate_pk(&mut Translator {
secp,
network,
descriptor: &self.0,
}) {
Ok(_) => {}
Err(TranslateErr::TranslatorErr(e)) => return Err(e),
Err(TranslateErr::OuterError(e)) => return Err(e.into()),
}
let translated = self.0.translate_pk(check_key, check_key)?;
Ok(self)
Ok((translated, self.1))
}
}
@@ -208,17 +163,10 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
_secp: &SecpCtx,
network: Network,
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
struct Translator {
network: Network,
}
let valid_networks = &self.2;
impl miniscript::Translator<DescriptorPublicKey, DescriptorPublicKey, DescriptorError>
for Translator
{
fn pk(
&mut self,
pk: &DescriptorPublicKey,
) -> Result<DescriptorPublicKey, DescriptorError> {
let fix_key = |pk: &DescriptorPublicKey| {
if valid_networks.contains(&network) {
// workaround for xpubs generated by other key types, like bip39: since when the
// conversion is made one network has to be chosen, what we generally choose
// "mainnet", but then override the set of valid networks to specify that all of
@@ -227,7 +175,7 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
let pk = match pk {
DescriptorPublicKey::XPub(ref xpub) => {
let mut xpub = xpub.clone();
xpub.xkey.network = self.network;
xpub.xkey.network = network;
DescriptorPublicKey::XPub(xpub)
}
@@ -235,47 +183,15 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
};
Ok(pk)
} else {
Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
miniscript::translate_hash_clone!(
DescriptorPublicKey,
DescriptorPublicKey,
DescriptorError
);
}
let (desc, keymap, networks) = self;
if !networks.contains(&network) {
return Err(DescriptorError::Key(KeyError::InvalidNetwork));
}
// fixup the network for keys that need it in the descriptor
use miniscript::TranslateErr;
let translated = match desc.translate_pk(&mut Translator { network }) {
Ok(descriptor) => descriptor,
Err(TranslateErr::TranslatorErr(e)) => return Err(e),
Err(TranslateErr::OuterError(e)) => return Err(e.into()),
};
// ...and in the key map
let fixed_keymap = keymap
.into_iter()
.map(|(mut k, mut v)| {
match (&mut k, &mut v) {
(DescriptorPublicKey::XPub(xpub), DescriptorSecretKey::XPrv(xprv)) => {
xpub.xkey.network = network;
xprv.xkey.network = network;
}
(_, DescriptorSecretKey::Single(key)) => {
key.key.network = network;
}
_ => {}
}
(k, v)
})
.collect();
// fixup the network for keys that need it
let translated = self.0.translate_pk(fix_key, fix_key)?;
Ok((translated, fixed_keymap))
Ok((translated, self.1))
}
}
@@ -294,7 +210,7 @@ pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
derivation_path,
wildcard,
..
}) = k
}) = k.as_key()
{
return *wildcard == Wildcard::Hardened
|| derivation_path.into_iter().any(ChildNumber::is_hardened);
@@ -306,10 +222,6 @@ pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
return Err(DescriptorError::HardenedDerivationXpub);
}
if descriptor.is_multipath() {
return Err(DescriptorError::MultiPath);
}
// Run miniscript's sanity check, which will look for duplicated keys and other potential
// issues
descriptor.sanity_check()?;
@@ -345,25 +257,35 @@ pub trait ExtractPolicy {
}
pub(crate) trait XKeyUtils {
fn full_path(&self, append: &[ChildNumber]) -> DerivationPath;
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint;
}
impl<T> XKeyUtils for DescriptorMultiXKey<T>
where
T: InnerXKey,
{
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
match self.origin {
Some((fingerprint, _)) => fingerprint,
None => self.xkey.xkey_fingerprint(secp),
}
}
}
impl<T> XKeyUtils for DescriptorXKey<T>
where
T: InnerXKey,
{
fn full_path(&self, append: &[ChildNumber]) -> DerivationPath {
let full_path = match self.origin {
Some((_, ref path)) => path
.into_iter()
.chain(self.derivation_path.into_iter())
.cloned()
.collect(),
None => self.derivation_path.clone(),
};
if self.wildcard != Wildcard::None {
full_path
.into_iter()
.chain(append.iter())
.cloned()
.collect()
} else {
full_path
}
}
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint {
match self.origin {
Some((fingerprint, _)) => fingerprint,
@@ -372,6 +294,11 @@ where
}
}
pub(crate) trait DerivedDescriptorMeta {
fn get_hd_keypaths(&self, secp: &SecpCtx) -> HdKeyPaths;
fn get_tap_key_origins(&self, secp: &SecpCtx) -> TapKeyOrigins;
}
pub(crate) trait DescriptorMeta {
fn is_witness(&self) -> bool;
fn is_taproot(&self) -> bool;
@@ -380,23 +307,63 @@ pub(crate) trait DescriptorMeta {
&self,
hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor>;
) -> Option<DerivedDescriptor<'s>>;
fn derive_from_tap_key_origins<'s>(
&self,
tap_key_origins: &TapKeyOrigins,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor>;
) -> Option<DerivedDescriptor<'s>>;
fn derive_from_psbt_key_origins<'s>(
&self,
key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor>;
) -> Option<DerivedDescriptor<'s>>;
fn derive_from_psbt_input<'s>(
&self,
psbt_input: &psbt::Input,
utxo: Option<TxOut>,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor>;
) -> Option<DerivedDescriptor<'s>>;
}
pub(crate) trait DescriptorScripts {
fn psbt_redeem_script(&self) -> Option<Script>;
fn psbt_witness_script(&self) -> Option<Script>;
}
impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
fn psbt_redeem_script(&self) -> Option<Script> {
match self.desc_type() {
DescriptorType::ShWpkh => Some(self.explicit_script().unwrap()),
DescriptorType::ShWsh => Some(self.explicit_script().unwrap().to_v0_p2wsh()),
DescriptorType::Sh => Some(self.explicit_script().unwrap()),
DescriptorType::Bare => Some(self.explicit_script().unwrap()),
DescriptorType::ShSortedMulti => Some(self.explicit_script().unwrap()),
DescriptorType::ShWshSortedMulti => Some(self.explicit_script().unwrap().to_v0_p2wsh()),
DescriptorType::Pkh
| DescriptorType::Wpkh
| DescriptorType::Tr
| DescriptorType::Wsh
| DescriptorType::WshSortedMulti => None,
}
}
fn psbt_witness_script(&self) -> Option<Script> {
match self.desc_type() {
DescriptorType::Wsh => Some(self.explicit_script().unwrap()),
DescriptorType::ShWsh => Some(self.explicit_script().unwrap()),
DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
Some(self.explicit_script().unwrap())
}
DescriptorType::Bare
| DescriptorType::Sh
| DescriptorType::Pkh
| DescriptorType::Wpkh
| DescriptorType::ShSortedMulti
| DescriptorType::Tr
| DescriptorType::ShWpkh => None,
}
}
}
impl DescriptorMeta for ExtendedDescriptor {
@@ -420,7 +387,7 @@ impl DescriptorMeta for ExtendedDescriptor {
let mut answer = Vec::new();
self.for_each_key(|pk| {
if let DescriptorPublicKey::XPub(xpub) = pk {
if let DescriptorPublicKey::XPub(xpub) = pk.as_key() {
answer.push(xpub.clone());
}
@@ -434,7 +401,7 @@ impl DescriptorMeta for ExtendedDescriptor {
&self,
key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor> {
) -> Option<DerivedDescriptor<'s>> {
// Ensure that deriving `xpub` with `path` yields `expected`
let verify_key = |xpub: &DescriptorXKey<ExtendedPubKey>,
path: &DerivationPath,
@@ -456,7 +423,7 @@ impl DescriptorMeta for ExtendedDescriptor {
// using `for_any_key` should make this stop as soon as we return `true`
self.for_any_key(|key| {
if let DescriptorPublicKey::XPub(xpub) = key {
if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
// Check if the key matches one entry in our `key_origins`. If it does, `matches()` will
// return the "prefix" that matched, so we remove that prefix from the full path
// found in `key_origins` and save it in `derive_path`. We expect this to be a derivation
@@ -514,17 +481,14 @@ impl DescriptorMeta for ExtendedDescriptor {
false
});
path_found.map(|path| {
self.at_derivation_index(path)
.expect("We ignore hardened wildcards")
})
path_found.map(|path| self.as_derived(path, secp))
}
fn derive_from_hd_keypaths<'s>(
&self,
hd_keypaths: &HdKeyPaths,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor> {
) -> Option<DerivedDescriptor<'s>> {
// "Convert" an hd_keypaths map to the format required by `derive_from_psbt_key_origins`
let key_origins = hd_keypaths
.iter()
@@ -542,7 +506,7 @@ impl DescriptorMeta for ExtendedDescriptor {
&self,
tap_key_origins: &TapKeyOrigins,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor> {
) -> Option<DerivedDescriptor<'s>> {
// "Convert" a tap_key_origins map to the format required by `derive_from_psbt_key_origins`
let key_origins = tap_key_origins
.iter()
@@ -556,19 +520,19 @@ impl DescriptorMeta for ExtendedDescriptor {
psbt_input: &psbt::Input,
utxo: Option<TxOut>,
secp: &'s SecpCtx,
) -> Option<DerivedDescriptor> {
) -> Option<DerivedDescriptor<'s>> {
if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.bip32_derivation, secp) {
return Some(derived);
}
if let Some(derived) = self.derive_from_tap_key_origins(&psbt_input.tap_key_origins, secp) {
return Some(derived);
}
if self.has_wildcard() {
if self.is_deriveable() {
// We can't try to bruteforce the derivation index, exit here
return None;
}
let descriptor = self.at_derivation_index(0).expect("0 is not hardened");
let descriptor = self.as_derived_fixed(secp);
match descriptor.desc_type() {
// TODO: add pk() here
DescriptorType::Pkh
@@ -602,15 +566,95 @@ impl DescriptorMeta for ExtendedDescriptor {
}
}
impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
fn get_hd_keypaths(&self, secp: &SecpCtx) -> HdKeyPaths {
let mut answer = BTreeMap::new();
self.for_each_key(|key| {
if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
let derived_pubkey = xpub
.xkey
.derive_pub(secp, &xpub.derivation_path)
.expect("Derivation can't fail");
answer.insert(
derived_pubkey.public_key,
(xpub.root_fingerprint(secp), xpub.full_path(&[])),
);
}
true
});
answer
}
fn get_tap_key_origins(&self, secp: &SecpCtx) -> TapKeyOrigins {
use miniscript::ToPublicKey;
let mut answer = BTreeMap::new();
let mut insert_path = |pk: &DerivedDescriptorKey<'_>, lh| {
let key_origin = match pk.deref() {
DescriptorPublicKey::XPub(xpub) => {
Some((xpub.root_fingerprint(secp), xpub.full_path(&[])))
}
DescriptorPublicKey::SinglePub(_) => None,
};
// If this is the internal key, we only insert the key origin if it's not None.
// For keys found in the tap tree we always insert a key origin (because the signer
// looks for it to know which leaves to sign for), even though it may be None
match (lh, key_origin) {
(None, Some(ko)) => {
answer
.entry(pk.to_x_only_pubkey())
.or_insert_with(|| (vec![], ko));
}
(Some(lh), origin) => {
answer
.entry(pk.to_x_only_pubkey())
.or_insert_with(|| (vec![], origin.unwrap_or_default()))
.0
.push(lh);
}
_ => {}
}
};
if let Descriptor::Tr(tr) = &self {
// Internal key first, then iterate the scripts
insert_path(tr.internal_key(), None);
for (_, ms) in tr.iter_scripts() {
// Assume always the same leaf version
let leaf_hash = taproot::TapLeafHash::from_script(
&ms.encode(),
taproot::LeafVersion::TapScript,
);
for key in ms.iter_pk_pkh() {
let key = match key {
miniscript::miniscript::iter::PkPkh::PlainPubkey(pk) => pk,
miniscript::miniscript::iter::PkPkh::HashedPubkey(pk) => pk,
};
insert_path(&key, Some(leaf_hash));
}
}
}
answer
}
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use assert_matches::assert_matches;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::ScriptBuf;
use bitcoin::{bip32, psbt::Psbt};
use bitcoin::util::{bip32, psbt};
use bitcoin::Script;
use super::*;
use crate::psbt::PsbtUtils;
@@ -621,7 +665,7 @@ mod test {
"wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
)
.unwrap();
let psbt = Psbt::deserialize(
let psbt: psbt::PartiallySignedTransaction = deserialize(
&Vec::<u8>::from_hex(
"70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\
11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\
@@ -644,7 +688,7 @@ mod test {
"pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)",
)
.unwrap();
let psbt = Psbt::deserialize(
let psbt: psbt::PartiallySignedTransaction = deserialize(
&Vec::<u8>::from_hex(
"70736274ff010053010000000145843b86be54a3cd8c9e38444e1162676c00df\
e7964122a70df491ea12fd67090100000000ffffffff01c19598000000000017\
@@ -675,7 +719,7 @@ mod test {
"wsh(and_v(v:pk(03b6633fef2397a0a9de9d7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14),older(6)))",
)
.unwrap();
let psbt = Psbt::deserialize(
let psbt: psbt::PartiallySignedTransaction = deserialize(
&Vec::<u8>::from_hex(
"70736274ff01005302000000011c8116eea34408ab6529223c9a176606742207\
67a1ff1d46a6e3c4a88243ea6e01000000000600000001109698000000000017\
@@ -699,7 +743,7 @@ mod test {
"sh(and_v(v:pk(021403881a5587297818fcaf17d239cefca22fce84a45b3b1d23e836c4af671dbb),after(630000)))",
)
.unwrap();
let psbt = Psbt::deserialize(
let psbt: psbt::PartiallySignedTransaction = deserialize(
&Vec::<u8>::from_hex(
"70736274ff0100530100000001bc8c13df445dfadcc42afa6dc841f85d22b01d\
a6270ebf981740f4b7b1d800390000000000feffffff01ba9598000000000017\
@@ -729,40 +773,23 @@ mod test {
let secp = Secp256k1::new();
let xprv = bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3c3gF1DUWpWNr2SG2XrG8oYPpqYh7hoWsJy9NjabErnzriJPpnGHyKz5NgdXmq1KVbqS1r4NXdCoKitWg5e86zqXHa8kxyB").unwrap();
let xpub = bip32::ExtendedPubKey::from_str("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL").unwrap();
let path = bip32::DerivationPath::from_str("m/0").unwrap();
// here `to_descriptor_key` will set the valid networks for the key to only mainnet, since
// we are using an "xpub"
let key = (xprv, path.clone()).into_descriptor_key().unwrap();
let key = (xpub, path).into_descriptor_key().unwrap();
// override it with any. this happens in some key conversions, like bip39
let key = key.override_valid_networks(any_network());
// make a descriptor out of it
let desc = crate::descriptor!(wpkh(key)).unwrap();
// this should convert the key that supports "any_network" to the right network (testnet)
let (wallet_desc, keymap) = desc
let (wallet_desc, _) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let mut xprv_testnet = xprv;
xprv_testnet.network = Network::Testnet;
let xpub_testnet = bip32::ExtendedPubKey::from_priv(&secp, &xprv_testnet);
let desc_pubkey = DescriptorPublicKey::XPub(DescriptorXKey {
xkey: xpub_testnet,
origin: None,
derivation_path: path,
wildcard: Wildcard::Unhardened,
});
assert_eq!(wallet_desc.to_string(), "wpkh(tpubD6NzVbkrYhZ4XtJzoDja5snUjBNQRP5B3f4Hyn1T1x6PVPxzzVjvw6nJx2D8RBCxog9GEVjZoyStfepTz7TtKoBVdkCtnc7VCJh9dD4RAU9/0/*)#a3svx0ha");
assert_eq!(
keymap
.get(&desc_pubkey)
.map(|key| key.to_public(&secp).unwrap()),
Some(desc_pubkey)
);
assert_eq!(wallet_desc.to_string(), "wpkh(tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)#y8p7e8kk");
}
// test IntoWalletDescriptor trait from &str with and without checksum appended
@@ -788,11 +815,17 @@ mod test {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.into_wallet_descriptor(&secp, Network::Testnet);
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
assert!(matches!(
desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#67ju93jw"
.into_wallet_descriptor(&secp, Network::Testnet);
assert_matches!(desc, Err(DescriptorError::InvalidDescriptorChecksum));
assert!(matches!(
desc.err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
}
// test IntoWalletDescriptor trait from &str with keys from right and wrong network
@@ -826,11 +859,17 @@ mod test {
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)"
.into_wallet_descriptor(&secp, Network::Bitcoin);
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
assert!(matches!(
desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork))
));
let desc = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/1/2/*)"
.into_wallet_descriptor(&secp, Network::Bitcoin);
assert_matches!(desc, Err(DescriptorError::Key(KeyError::InvalidNetwork)));
assert!(matches!(
desc.err(),
Some(DescriptorError::Key(KeyError::InvalidNetwork))
));
}
// test IntoWalletDescriptor trait from the output of the descriptor!() macro
@@ -864,14 +903,12 @@ mod test {
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
DescriptorError::HardenedDerivationXpub
));
let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)";
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
assert_matches!(result, Err(DescriptorError::MultiPath));
// repeated pubkeys
let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
@@ -880,7 +917,7 @@ mod test {
#[test]
fn test_sh_wsh_sortedmulti_redeemscript() {
use miniscript::psbt::PsbtInputExt;
use super::{AsDerived, DescriptorScripts};
let secp = Secp256k1::new();
@@ -888,16 +925,11 @@ mod test {
let (descriptor, _) =
into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
let descriptor = descriptor.at_derivation_index(0).unwrap();
let descriptor = descriptor.as_derived(0, &secp);
let script = ScriptBuf::from_hex("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
let script = Script::from_str("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
let mut psbt_input = psbt::Input::default();
psbt_input
.update_with_descriptor_unchecked(&descriptor)
.unwrap();
assert_eq!(psbt_input.redeem_script, Some(script.to_v0_p2wsh()));
assert_eq!(psbt_input.witness_script, Some(script));
assert_eq!(descriptor.psbt_redeem_script(), Some(script.to_v0_p2wsh()));
assert_eq!(descriptor.psbt_witness_script(), Some(script));
}
}

View File

@@ -43,17 +43,14 @@ use std::fmt;
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use bitcoin::bip32::Fingerprint;
use bitcoin::hashes::{hash160, ripemd160, sha256};
use bitcoin::{absolute, key::XOnlyPublicKey, PublicKey, Sequence};
use bitcoin::hashes::*;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::{PublicKey, XOnlyPublicKey};
use miniscript::descriptor::{
DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
};
use miniscript::hash256;
use miniscript::{
Descriptor, Miniscript, Satisfier, ScriptContext, SigType, Terminal, ToPublicKey,
DescriptorPublicKey, DescriptorSinglePub, ShInner, SinglePubKey, SortedMultiVec, WshInner,
};
use miniscript::{Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
@@ -61,12 +58,12 @@ use log::{debug, error, info, trace};
use crate::descriptor::ExtractPolicy;
use crate::keys::ExtScriptContext;
use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{After, Older, SecpCtx};
use crate::wallet::utils::{self, After, Older, SecpCtx};
use super::checksum::calc_checksum;
use super::checksum::get_checksum;
use super::error::Error;
use super::XKeyUtils;
use bitcoin::psbt::{self, Psbt};
use bitcoin::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt};
use miniscript::psbt::PsbtInputSatisfier;
/// A unique identifier for a key
@@ -84,18 +81,15 @@ pub enum PkOrF {
impl PkOrF {
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
match k {
DescriptorPublicKey::Single(SinglePub {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(pk),
..
}) => PkOrF::Pubkey(*pk),
DescriptorPublicKey::Single(SinglePub {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(pk),
..
}) => PkOrF::XOnlyPubkey(*pk),
DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)),
DescriptorPublicKey::MultiXPub(multi) => {
PkOrF::Fingerprint(multi.root_fingerprint(secp))
}
}
}
}
@@ -117,7 +111,7 @@ pub enum SatisfiableItem {
/// Double SHA256 preimage hash
Hash256Preimage {
/// The digest value
hash: hash256::Hash,
hash: sha256d::Hash,
},
/// RIPEMD160 preimage hash
Ripemd160Preimage {
@@ -131,13 +125,13 @@ pub enum SatisfiableItem {
},
/// Absolute timeclock timestamp
AbsoluteTimelock {
/// The timelock value
value: absolute::LockTime,
/// The timestamp value
value: u32,
},
/// Relative timelock locktime
RelativeTimelock {
/// The timelock value
value: Sequence,
/// The locktime value
value: u32,
},
/// Multi-signature public keys with threshold count
Multisig {
@@ -171,7 +165,7 @@ impl SatisfiableItem {
/// Returns a unique id for the [`SatisfiableItem`]
pub fn id(&self) -> String {
calc_checksum(&serde_json::to_string(self).expect("Failed to serialize a SatisfiableItem"))
get_checksum(&serde_json::to_string(self).expect("Failed to serialize a SatisfiableItem"))
.expect("Failed to compute a SatisfiableItem id")
}
}
@@ -444,33 +438,32 @@ pub struct Policy {
}
/// An extra condition that must be satisfied but that is out of control of the user
/// TODO: use `bitcoin::LockTime` and `bitcoin::Sequence`
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Default, Serialize)]
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Default, Serialize)]
pub struct Condition {
/// Optional CheckSequenceVerify condition
#[serde(skip_serializing_if = "Option::is_none")]
pub csv: Option<Sequence>,
pub csv: Option<u32>,
/// Optional timelock condition
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<absolute::LockTime>,
pub timelock: Option<u32>,
}
impl Condition {
fn merge_nlocktime(
a: absolute::LockTime,
b: absolute::LockTime,
) -> Result<absolute::LockTime, PolicyError> {
if !a.is_same_unit(b) {
fn merge_nlocktime(a: u32, b: u32) -> Result<u32, PolicyError> {
if (a < utils::BLOCKS_TIMELOCK_THRESHOLD) != (b < utils::BLOCKS_TIMELOCK_THRESHOLD) {
Err(PolicyError::MixedTimelockUnits)
} else if a > b {
Ok(a)
} else {
Ok(b)
Ok(max(a, b))
}
}
fn merge_nsequence(a: Sequence, b: Sequence) -> Result<Sequence, PolicyError> {
if a.is_time_locked() != b.is_time_locked() {
fn merge_nsequence(a: u32, b: u32) -> Result<u32, PolicyError> {
let mask = utils::SEQUENCE_LOCKTIME_TYPE_FLAG | utils::SEQUENCE_LOCKTIME_MASK;
let a = a & mask;
let b = b & mask;
if (a < utils::SEQUENCE_LOCKTIME_TYPE_FLAG) != (b < utils::SEQUENCE_LOCKTIME_TYPE_FLAG) {
Err(PolicyError::MixedTimelockUnits)
} else {
Ok(max(a, b))
@@ -518,14 +511,7 @@ pub enum PolicyError {
impl fmt::Display for PolicyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NotEnoughItemsSelected(err) => write!(f, "Not enought items selected: {}", err),
Self::IndexOutOfRange(index) => write!(f, "Index out of range: {}", index),
Self::AddOnLeaf => write!(f, "Add on leaf"),
Self::AddOnPartialComplete => write!(f, "Add on partial complete"),
Self::MixedTimelockUnits => write!(f, "Mixed timelock units"),
Self::IncompatibleConditions => write!(f, "Incompatible conditions"),
}
write!(f, "{:?}", self)
}
}
@@ -665,11 +651,11 @@ impl Policy {
(0..*threshold).collect()
}
SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
_ => HashSet::new(),
_ => vec![],
};
let selected: HashSet<_> = match path.get(&self.id) {
Some(arr) => arr.iter().copied().collect(),
_ => default,
let selected = match path.get(&self.id) {
Some(arr) => arr,
_ => &default,
};
match &self.item {
@@ -677,24 +663,14 @@ impl Policy {
let mapped_req = items
.iter()
.map(|i| i.get_condition(path))
.collect::<Vec<_>>();
.collect::<Result<Vec<_>, _>>()?;
// if all the requirements are null we don't care about `selected` because there
// are no requirements
if mapped_req
.iter()
.all(|cond| matches!(cond, Ok(c) if c.is_null()))
{
if mapped_req.iter().all(Condition::is_null) {
return Ok(Condition::default());
}
// make sure all the indexes in the `selected` list are within range
for index in &selected {
if *index >= items.len() {
return Err(PolicyError::IndexOutOfRange(*index));
}
}
// if we have something, make sure we have enough items. note that the user can set
// an empty value for this step in case of n-of-n, because `selected` is set to all
// the elements above
@@ -703,18 +679,23 @@ impl Policy {
}
// check the selected items, see if there are conflicting requirements
mapped_req
.into_iter()
.enumerate()
.filter(|(index, _)| selected.contains(index))
.try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
let mut requirements = Condition::default();
for item_index in selected {
requirements = requirements.merge(
mapped_req
.get(*item_index)
.ok_or(PolicyError::IndexOutOfRange(*item_index))?,
)?;
}
Ok(requirements)
}
SatisfiableItem::Multisig { keys, threshold } => {
if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
}
if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
return Err(PolicyError::IndexOutOfRange(item));
if let Some(item) = selected.iter().find(|i| **i >= keys.len()) {
return Err(PolicyError::IndexOutOfRange(*item));
}
Ok(Condition::default())
@@ -739,20 +720,16 @@ impl From<SatisfiableItem> for Policy {
}
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
// For consistency we always compute the key hash in "ecdsa" form (with the leading sign
// prefix) even if we are in a taproot descriptor. We just want some kind of unique identifier
// for a key, so it doesn't really matter how the identifier is computed.
match key {
DescriptorPublicKey::Single(SinglePub {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(pk),
..
}) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
DescriptorPublicKey::Single(SinglePub {
}) => pk.to_pubkeyhash().into(),
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(pk),
..
}) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
}) => pk.to_pubkeyhash().into(),
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
DescriptorPublicKey::MultiXPub(xpub) => xpub.root_fingerprint(secp).into(),
}
}
@@ -790,9 +767,9 @@ fn make_generic_signature<M: Fn() -> SatisfiableItem, F: Fn(&Psbt) -> bool>(
fn generic_sig_in_psbt<
// C is for "check", it's a closure we use to *check* if a psbt input contains the signature
// for a specific key
C: Fn(&psbt::Input, &SinglePubKey) -> bool,
C: Fn(&PsbtInput, &SinglePubKey) -> bool,
// E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
E: Fn(&psbt::Input, Fingerprint) -> Option<SinglePubKey>,
E: Fn(&PsbtInput, Fingerprint) -> Option<SinglePubKey>,
>(
psbt: &Psbt,
key: &DescriptorPublicKey,
@@ -802,7 +779,7 @@ fn generic_sig_in_psbt<
) -> bool {
//TODO check signature validity
psbt.inputs.iter().all(|input| match key {
DescriptorPublicKey::Single(SinglePub { key, .. }) => check(input, key),
DescriptorPublicKey::SinglePub(DescriptorSinglePub { key, .. }) => check(input, key),
DescriptorPublicKey::XPub(xpub) => {
//TODO check actual derivation matches
match extract(input, xpub.root_fingerprint(secp)) {
@@ -810,13 +787,6 @@ fn generic_sig_in_psbt<
None => false,
}
}
DescriptorPublicKey::MultiXPub(xpub) => {
//TODO check actual derivation matches
match extract(input, xpub.root_fingerprint(secp)) {
Some(pubkey) => check(input, &pubkey),
None => false,
}
}
})
}
@@ -921,13 +891,10 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
}
Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
value: (*value).into(),
}
.into();
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
policy.contribution = Satisfaction::Complete {
condition: Condition {
timelock: Some((*value).into()),
timelock: Some(*value),
csv: None,
},
};
@@ -938,11 +905,9 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
} = build_sat
{
let after = After::new(Some(current_height), false);
let after_sat =
Satisfier::<bitcoin::PublicKey>::check_after(&after, (*value).into());
let inputs_sat = psbt_inputs_sat(psbt).all(|sat| {
Satisfier::<bitcoin::PublicKey>::check_after(&sat, (*value).into())
});
let after_sat = Satisfier::<bitcoin::PublicKey>::check_after(&after, *value);
let inputs_sat = psbt_inputs_sat(psbt)
.all(|sat| Satisfier::<bitcoin::PublicKey>::check_after(&sat, *value));
if after_sat && inputs_sat {
policy.satisfaction = policy.contribution.clone();
}
@@ -1034,9 +999,6 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
Policy::make_thresh(mapped, threshold)?
}
// Unsupported
Terminal::RawPkH(_) => None,
})
}
}
@@ -1162,13 +1124,14 @@ mod test {
use crate::descriptor::{ExtractPolicy, IntoWalletDescriptor};
use super::*;
use crate::descriptor::derived::AsDerived;
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer;
use assert_matches::assert_matches;
use bitcoin::bip32;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32;
use bitcoin::Network;
use miniscript::DescriptorTrait;
use std::str::FromStr;
use std::sync::Arc;
@@ -1209,8 +1172,8 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
assert_matches!(&policy.contribution, Satisfaction::None);
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc
@@ -1222,8 +1185,10 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
assert_matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
assert!(
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
);
}
// 2 pub keys descriptor, required 2 prv keys
@@ -1242,16 +1207,19 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
// TODO should this be "Satisfaction::None" since we have no prv keys?
// TODO should items and conditions not be empty?
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
assert!(
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
&& m == &2usize
&& items.is_empty()
&& conditions.is_empty()
)
);
}
@@ -1270,15 +1238,18 @@ mod test {
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
assert!(
matches!(&policy.contribution, Satisfaction::Partial { n, m, items, conditions, ..} if n == &2usize
&& m == &2usize
&& items.len() == 1
&& conditions.contains_key(&0)
)
);
}
@@ -1300,15 +1271,18 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &1
&& items.len() == 2
&& conditions.contains_key(&vec![0])
&& conditions.contains_key(&vec![1])
)
);
}
@@ -1329,15 +1303,18 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &2
&& items.len() == 2
&& conditions.contains_key(&vec![0,1])
)
);
}
@@ -1352,27 +1329,31 @@ mod test {
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
let policy = wallet_desc
let policy = single_key
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint);
assert_matches!(&policy.contribution, Satisfaction::None);
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
assert!(matches!(&policy.contribution, Satisfaction::None));
let desc = descriptor!(wpkh(prvkey)).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
let policy = wallet_desc
let policy = single_key
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
assert_matches!(policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == fingerprint);
assert_matches!(policy.contribution, Satisfaction::Complete {condition} if condition.csv.is_none() && condition.timelock.is_none());
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
assert!(
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
);
}
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
@@ -1387,21 +1368,25 @@ mod test {
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
let policy = wallet_desc
let policy = single_key
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap()
.unwrap();
assert_matches!(policy.item, Multisig { keys, threshold } if threshold == 1
assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
&& keys[1] == PkOrF::Fingerprint(fingerprint1)
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
);
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == 2
&& m == 1
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
&& m == &1
&& items.len() == 2
&& conditions.contains_key(&vec![0])
&& conditions.contains_key(&vec![1])
)
);
}
@@ -1433,14 +1418,18 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2);
assert!(
matches!(&policy.item, Thresh { items, threshold } if items.len() == 3 && threshold == &2)
);
assert_matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
assert!(
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &3
&& m == &2
&& items.len() == 3
&& conditions.get(&vec![0,1]).unwrap().iter().next().unwrap().csv.is_none()
&& conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(Sequence(sequence))
&& conditions.get(&vec![0,2]).unwrap().iter().next().unwrap().csv == Some(sequence)
&& conditions.get(&vec![1,2]).unwrap().iter().next().unwrap().csv == Some(sequence)
)
);
}
@@ -1585,8 +1574,7 @@ mod test {
.unwrap();
let addr = wallet_desc
.at_derivation_index(0)
.unwrap()
.as_derived(0, &secp)
.address(Network::Testnet)
.unwrap();
assert_eq!(
@@ -1604,9 +1592,11 @@ mod test {
.unwrap();
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
assert_matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
assert!(
matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0]
)
);
let psbt = Psbt::from_str(BOB_SIGNED_PSBT).unwrap();
@@ -1616,9 +1606,11 @@ mod test {
.unwrap();
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
assert_matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
assert!(
matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![1]
)
);
let psbt = Psbt::from_str(ALICE_BOB_SIGNED_PSBT).unwrap();
@@ -1626,9 +1618,11 @@ mod test {
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
.unwrap()
.unwrap();
assert_matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
assert!(
matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
&& m == &2
&& items == &vec![0, 1]
)
);
}
@@ -1652,8 +1646,7 @@ mod test {
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
let addr = wallet_desc
.at_derivation_index(0)
.unwrap()
.as_derived(0, &secp)
.address(Network::Testnet)
.unwrap();
assert_eq!(
@@ -1673,9 +1666,11 @@ mod test {
.extract_policy(&signers_container, build_sat, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
assert!(
matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
&& m == &2
&& items.is_empty()
)
);
//println!("{}", serde_json::to_string(&policy).unwrap());
@@ -1689,9 +1684,11 @@ mod test {
.extract_policy(&signers_container, build_sat_expired, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
assert!(
matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
&& m == &2
&& items == &vec![0]
)
);
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
@@ -1707,9 +1704,11 @@ mod test {
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
.unwrap()
.unwrap();
assert_matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
assert!(
matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
&& m == &2
&& items == &vec![0, 1]
)
);
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
}
@@ -1784,8 +1783,12 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
assert_matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1]);
assert!(
matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
);
assert!(
matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1])
);
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
@@ -1877,11 +1880,19 @@ mod test {
.unwrap()
.unwrap();
assert_matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
assert_matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty());
assert!(
matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
);
assert!(
matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty())
);
assert_matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2);
assert_matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1]);
assert!(
matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
);
assert!(
matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1])
);
let satisfied_items = match policy_signed.item {
SatisfiableItem::Thresh { items, .. } => items,

View File

@@ -14,10 +14,10 @@
//! This module contains the definition of various common script templates that are ready to be
//! used. See the documentation of each template for an example.
use bitcoin::bip32;
use bitcoin::util::bip32;
use bitcoin::Network;
use miniscript::{Legacy, Segwitv0, Tap};
use miniscript::{Legacy, Segwitv0};
use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
use crate::descriptor::DescriptorError;
@@ -170,35 +170,6 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
}
}
/// P2TR template. Expands to a descriptor `tr(key)`
///
/// ## Example
///
/// ```
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::Wallet;
/// # use bdk::database::MemoryDatabase;
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::P2TR;
///
/// let key =
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
/// let mut wallet = Wallet::new(P2TR(key), None, Network::Testnet, MemoryDatabase::default())?;
///
/// assert_eq!(
/// wallet.get_address(New)?.to_string(),
/// "tb1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xq7hps46"
/// );
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct P2TR<K: IntoDescriptorKey<Tap>>(pub K);
impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
fn build(self, _network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
descriptor!(tr(self.0))
}
}
/// BIP44 template. Expands to `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
///
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
@@ -215,7 +186,7 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new(
/// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)),
@@ -254,8 +225,8 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
@@ -264,17 +235,14 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "pkh([c55b303f/44'/0'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#xgaaevjx");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2Pkh(legacy::make_bipxx_public(
44, self.0, self.1, self.2, network,
)?)
.build(network)
P2Pkh(legacy::make_bipxx_public(44, self.0, self.1, self.2)?).build(network)
}
}
@@ -294,7 +262,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new(
/// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)),
@@ -316,7 +284,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// BIP49 public template. Expands to `sh(wpkh(key/{0,1}/*))`
///
/// This assumes that the key used has already been derived with `m/49'/0'/0'` for Mainnet or `m/49'/1'/0'` for Testnet.
/// This assumes that the key used has already been derived with `m/49'/0'/0'`.
///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
///
@@ -333,8 +301,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
@@ -343,17 +311,14 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "sh(wpkh([c55b303f/49'/0'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#gsmdv4xr");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(
49, self.0, self.1, self.2, network,
)?)
.build(network)
P2Wpkh_P2Sh(segwit_v0::make_bipxx_public(49, self.0, self.1, self.2)?).build(network)
}
}
@@ -373,7 +338,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new(
/// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)),
@@ -395,7 +360,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// BIP84 public template. Expands to `wpkh(key/{0,1}/*)`
///
/// This assumes that the key used has already been derived with `m/84'/0'/0'` for Mainnet or `m/84'/1'/0'` for Testnet.
/// This assumes that the key used has already been derived with `m/84'/0'/0'`.
///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
///
@@ -412,8 +377,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
@@ -422,96 +387,14 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "wpkh([c55b303f/84\'/0\'/0\']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#nkk5dtkg");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2Wpkh(segwit_v0::make_bipxx_public(
84, self.0, self.1, self.2, network,
)?)
.build(network)
}
}
/// BIP86 template. Expands to `tr(key/86'/{0,1}'/0'/{0,1}/*)`
///
/// Since there are hardened derivation steps, this template requires a private derivable key (generally a `xprv`/`tprv`).
///
/// See [`Bip86Public`] for a template that can work with a `xpub`/`tpub`.
///
/// ## Example
///
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip86;
///
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let mut wallet = Wallet::new(
/// Bip86(key.clone(), KeychainKind::External),
/// Some(Bip86(key, KeychainKind::Internal)),
/// Network::Testnet,
/// MemoryDatabase::default()
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip86<K: DerivableKey<Tap>>(pub K, pub KeychainKind);
impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2TR(segwit_v1::make_bipxx_private(86, self.0, self.1, network)?).build(network)
}
}
/// BIP86 public template. Expands to `tr(key/{0,1}/*)`
///
/// This assumes that the key used has already been derived with `m/86'/0'/0'` for Mainnet or `m/86'/1'/0'` for Testnet.
///
/// This template requires the parent fingerprint to populate correctly the metadata of PSBTs.
///
/// See [`Bip86`] for a template that does the full derivation, but requires private data
/// for the key.
///
/// ## Example
///
/// ```
/// # use std::str::FromStr;
/// # use bdk::bitcoin::{PrivateKey, Network};
/// # use bdk::{Wallet, KeychainKind};
/// # use bdk::database::MemoryDatabase;
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip86Public;
///
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let mut wallet = Wallet::new(
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)),
/// Network::Testnet,
/// MemoryDatabase::default()
/// )?;
///
/// assert_eq!(wallet.get_address(New)?.to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
/// assert_eq!(wallet.public_descriptor(KeychainKind::External)?.unwrap().to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
/// # Ok::<_, Box<dyn std::error::Error>>(())
/// ```
pub struct Bip86Public<K: DerivableKey<Tap>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86Public<K> {
fn build(self, network: Network) -> Result<DescriptorTemplateOut, DescriptorError> {
P2TR(segwit_v1::make_bipxx_public(
86, self.0, self.1, self.2, network,
)?)
.build(network)
P2Wpkh(segwit_v0::make_bipxx_public(84, self.0, self.1, self.2)?).build(network)
}
}
@@ -557,7 +440,6 @@ macro_rules! expand_make_bipxx {
key: K,
parent_fingerprint: bip32::Fingerprint,
keychain: KeychainKind,
network: Network,
) -> Result<impl IntoDescriptorKey<$ctx>, DescriptorError> {
let derivation_path: bip32::DerivationPath = match keychain {
KeychainKind::External => vec![bip32::ChildNumber::from_normal_idx(0)?].into(),
@@ -566,10 +448,7 @@ macro_rules! expand_make_bipxx {
let source_path = bip32::DerivationPath::from(vec![
bip32::ChildNumber::from_hardened_idx(bip)?,
match network {
Network::Bitcoin => bip32::ChildNumber::from_hardened_idx(0)?,
_ => bip32::ChildNumber::from_hardened_idx(1)?,
},
bip32::ChildNumber::from_hardened_idx(0)?,
bip32::ChildNumber::from_hardened_idx(0)?,
]);
@@ -581,7 +460,6 @@ macro_rules! expand_make_bipxx {
expand_make_bipxx!(legacy, Legacy);
expand_make_bipxx!(segwit_v0, Segwitv0);
expand_make_bipxx!(segwit_v1, Tap);
#[cfg(test)]
mod test {
@@ -590,43 +468,45 @@ mod test {
use std::str::FromStr;
use super::*;
use crate::descriptor::derived::AsDerived;
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks;
use assert_matches::assert_matches;
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use bitcoin::network::constants::Network::Regtest;
use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};
use miniscript::Descriptor;
// BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
#[test]
fn test_bip44_template_cointype() {
use bitcoin::bip32::ChildNumber::{self, Hardened};
use bitcoin::util::bip32::ChildNumber::{self, Hardened};
let xprvkey = bitcoin::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
let xprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
assert_eq!(Network::Bitcoin, xprvkey.network);
let xdesc = Bip44(xprvkey, KeychainKind::Internal)
.build(Network::Bitcoin)
.unwrap();
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 });
assert!(matches!(purpose, Hardened { index: 44 }));
let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 0 });
assert!(matches!(coin_type, Hardened { index: 0 }));
}
let tprvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
assert_eq!(Network::Testnet, tprvkey.network);
let tdesc = Bip44(tprvkey, KeychainKind::Internal)
.build(Network::Testnet)
.unwrap();
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().into();
let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 });
assert!(matches!(purpose, Hardened { index: 44 }));
let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 1 });
assert!(matches!(coin_type, Hardened { index: 1 }));
}
}
@@ -634,23 +514,22 @@ mod test {
fn check(
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
is_witness: bool,
is_taproot: bool,
is_fixed: bool,
network: Network,
expected: &[&str],
) {
let secp = Secp256k1::new();
let (desc, _key_map, _networks) = desc.unwrap();
assert_eq!(desc.is_witness(), is_witness);
assert_eq!(desc.is_taproot(), is_taproot);
assert_eq!(!desc.has_wildcard(), is_fixed);
assert_eq!(!desc.is_deriveable(), is_fixed);
for i in 0..expected.len() {
let index = i as u32;
let child_desc = if !desc.has_wildcard() {
desc.at_derivation_index(0).unwrap()
let child_desc = if !desc.is_deriveable() {
desc.as_derived_fixed(&secp)
} else {
desc.at_derivation_index(index).unwrap()
desc.as_derived(index, &secp)
};
let address = child_desc.address(network).unwrap();
let address = child_desc.address(Regtest).unwrap();
assert_eq!(address.to_string(), *expected.get(i).unwrap());
}
}
@@ -664,9 +543,7 @@ mod test {
check(
P2Pkh(prvkey).build(Network::Bitcoin),
false,
false,
true,
Network::Regtest,
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
);
@@ -677,9 +554,7 @@ mod test {
check(
P2Pkh(pubkey).build(Network::Bitcoin),
false,
false,
true,
Network::Regtest,
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
);
}
@@ -693,9 +568,7 @@ mod test {
check(
P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
);
@@ -706,9 +579,7 @@ mod test {
check(
P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
);
}
@@ -722,9 +593,7 @@ mod test {
check(
P2Wpkh(prvkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
);
@@ -735,52 +604,19 @@ mod test {
check(
P2Wpkh(pubkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"],
);
}
// P2TR `tr(key)`
#[test]
fn test_p2tr_template() {
let prvkey =
bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")
.unwrap();
check(
P2TR(prvkey).build(Network::Bitcoin),
false,
true,
true,
Network::Regtest,
&["bcrt1pvjf9t34fznr53u5tqhejz4nr69luzkhlvsdsdfq9pglutrpve2xqnwtkqq"],
);
let pubkey = bitcoin::PublicKey::from_str(
"03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd",
)
.unwrap();
check(
P2TR(pubkey).build(Network::Bitcoin),
false,
true,
true,
Network::Regtest,
&["bcrt1pw74tdcrxlzn5r8z6ku2vztr86fgq0m245s72mjktf4afwzsf8ugs4evwdf"],
);
}
// BIP44 `pkh(key/44'/0'/0'/{0,1}/*)`
#[test]
fn test_bip44_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5",
"mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP",
@@ -791,8 +627,6 @@ mod test {
Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
"n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
@@ -804,14 +638,12 @@ mod test {
// BIP44 public `pkh(key/{0,1}/*)`
#[test]
fn test_bip44_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"miNG7dJTzJqNbFS19svRdTCisC65dsubtR",
"n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg",
@@ -822,8 +654,6 @@ mod test {
Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
"ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
@@ -835,13 +665,11 @@ mod test {
// BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))`
#[test]
fn test_bip49_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV",
"2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS",
@@ -852,8 +680,6 @@ mod test {
Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
"2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
@@ -865,14 +691,12 @@ mod test {
// BIP49 public `sh(wpkh(key/{0,1}/*))`
#[test]
fn test_bip49_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt",
"2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX",
@@ -883,8 +707,6 @@ mod test {
Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
"2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
@@ -896,13 +718,11 @@ mod test {
// BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)`
#[test]
fn test_bip84_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s",
"bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp",
@@ -913,8 +733,6 @@ mod test {
Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
"bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
@@ -926,14 +744,12 @@ mod test {
// BIP84 public `wpkh(key/{0,1}/*)`
#[test]
fn test_bip84_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h",
"bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana",
@@ -944,8 +760,6 @@ mod test {
Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
"bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp",
@@ -953,67 +767,4 @@ mod test {
],
);
}
// BIP86 `tr(key/86'/0'/0'/{0,1}/*)`
// Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
#[test]
fn test_bip86_template() {
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap();
check(
Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin),
false,
true,
false,
Network::Bitcoin,
&[
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
"bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
"bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
],
);
check(
Bip86(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
false,
true,
false,
Network::Bitcoin,
&[
"bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
"bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
"bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
],
);
}
// BIP86 public `tr(key/{0,1}/*)`
// Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
#[test]
fn test_bip86_public_template() {
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap();
check(
Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
false,
true,
false,
Network::Bitcoin,
&[
"bc1p5cyxnuxmeuwuvkwfem96lqzszd02n6xdcjrs20cac6yqjjwudpxqkedrcr",
"bc1p4qhjn9zdvkux4e44uhx8tc55attvtyu358kutcqkudyccelu0was9fqzwh",
"bc1p0d0rhyynq0awa9m8cqrcr8f5nxqx3aw29w4ru5u9my3h0sfygnzs9khxz8",
],
);
check(
Bip86Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
false,
true,
false,
Network::Bitcoin,
&[
"bc1p3qkhfews2uk44qtvauqyr2ttdsw7svhkl9nkm9s9c3x4ax5h60wqwruhk7",
"bc1ptdg60grjk9t3qqcqczp4tlyy3z47yrx9nhlrjsmw36q5a72lhdrs9f00nj",
"bc1pgcwgsu8naxp7xlp5p7ufzs7emtfza2las7r2e7krzjhe5qj5xz2q88kmk5",
],
);
}
}

View File

@@ -12,7 +12,7 @@
use std::fmt;
use crate::bitcoin::Network;
use crate::{descriptor, wallet};
use crate::{descriptor, wallet, wallet::address_validator};
use bitcoin::{OutPoint, Txid};
/// Errors that can be thrown by the [`Wallet`](crate::wallet::Wallet)
@@ -86,8 +86,6 @@ pub enum Error {
/// found network, for example the network of the bitcoin node
found: Network,
},
/// The address requested comes from an hardened index
HardenedIndex,
#[cfg(feature = "verify")]
/// Transaction verification error
Verification(crate::wallet::verify::VerifyError),
@@ -101,24 +99,24 @@ pub enum Error {
/// Error related to the parsing and usage of descriptors
Descriptor(crate::descriptor::error::Error),
/// Error that can be returned to fail the validation of an address
AddressValidator(crate::wallet::address_validator::AddressValidatorError),
/// Encoding error
Encode(bitcoin::consensus::encode::Error),
/// Miniscript error
Miniscript(miniscript::Error),
/// Miniscript PSBT error
MiniscriptPsbt(MiniscriptPsbtError),
/// BIP32 error
Bip32(bitcoin::bip32::Error),
/// A secp256k1 error
Bip32(bitcoin::util::bip32::Error),
/// An ECDSA error
Secp256k1(bitcoin::secp256k1::Error),
/// Error serializing or deserializing JSON data
Json(serde_json::Error),
/// Hex decoding error
Hex(bitcoin::hashes::hex::Error),
/// Partially signed bitcoin transaction error
Psbt(bitcoin::psbt::Error),
Psbt(bitcoin::util::psbt::Error),
/// Partially signed bitcoin transaction parse error
PsbtParse(bitcoin::psbt::PsbtParseError),
PsbtParse(bitcoin::util::psbt::PsbtParseError),
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
//MissingInputUTXO(usize),
@@ -151,26 +149,6 @@ pub enum Error {
Rusqlite(rusqlite::Error),
}
/// Errors returned by miniscript when updating inconsistent PSBTs
#[derive(Debug, Clone)]
pub enum MiniscriptPsbtError {
Conversion(miniscript::descriptor::ConversionError),
UtxoUpdate(miniscript::psbt::UtxoUpdateError),
OutputUpdate(miniscript::psbt::OutputUpdateError),
}
impl fmt::Display for MiniscriptPsbtError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Conversion(err) => write!(f, "Conversion error: {}", err),
Self::UtxoUpdate(err) => write!(f, "UTXO update error: {}", err),
Self::OutputUpdate(err) => write!(f, "Output update error: {}", err),
}
}
}
impl std::error::Error for MiniscriptPsbtError {}
/// Represents the last failed [`crate::blockchain::WalletSync`] sync attempt in which we were short
/// on cached `scriptPubKey`s.
#[derive(Debug)]
@@ -183,94 +161,7 @@ pub struct MissingCachedScripts {
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidU32Bytes(_) => write!(
f,
"Wrong number of bytes found when trying to convert to u32"
),
Self::Generic(err) => write!(f, "Generic error: {}", err),
Self::ScriptDoesntHaveAddressForm => write!(f, "Script doesn't have address form"),
Self::NoRecipients => write!(f, "Cannot build tx without recipients"),
Self::NoUtxosSelected => write!(f, "No UTXO selected"),
Self::OutputBelowDustLimit(limit) => {
write!(f, "Output below the dust limit: {}", limit)
}
Self::InsufficientFunds { needed, available } => write!(
f,
"Insufficient funds: {} sat available of {} sat needed",
available, needed
),
Self::BnBTotalTriesExceeded => {
write!(f, "Branch and bound coin selection: total tries exceeded")
}
Self::BnBNoExactMatch => write!(f, "Branch and bound coin selection: not exact match"),
Self::UnknownUtxo => write!(f, "UTXO not found in the internal database"),
Self::TransactionNotFound => {
write!(f, "Transaction not found in the internal database")
}
Self::TransactionConfirmed => write!(f, "Transaction already confirmed"),
Self::IrreplaceableTransaction => write!(f, "Transaction can't be replaced"),
Self::FeeRateTooLow { required } => write!(
f,
"Fee rate too low: required {} sat/vbyte",
required.as_sat_per_vb()
),
Self::FeeTooLow { required } => write!(f, "Fee to low: required {} sat", required),
Self::FeeRateUnavailable => write!(f, "Fee rate unavailable"),
Self::MissingKeyOrigin(err) => write!(f, "Missing key origin: {}", err),
Self::Key(err) => write!(f, "Key error: {}", err),
Self::ChecksumMismatch => write!(f, "Descriptor checksum mismatch"),
Self::SpendingPolicyRequired(keychain_kind) => {
write!(f, "Spending policy required: {:?}", keychain_kind)
}
Self::InvalidPolicyPathError(err) => write!(f, "Invalid policy path: {}", err),
Self::Signer(err) => write!(f, "Signer error: {}", err),
Self::InvalidNetwork { requested, found } => write!(
f,
"Invalid network: requested {} but found {}",
requested, found
),
Self::HardenedIndex => write!(f, "Requested address from an hardened index"),
#[cfg(feature = "verify")]
Self::Verification(err) => write!(f, "Transaction verification error: {}", err),
Self::InvalidProgressValue(progress) => {
write!(f, "Invalid progress value: {}", progress)
}
Self::ProgressUpdateError => write!(
f,
"Progress update error (maybe the channel has been closed)"
),
Self::InvalidOutpoint(outpoint) => write!(
f,
"Requested outpoint doesn't exist in the tx: {}",
outpoint
),
Self::Descriptor(err) => write!(f, "Descriptor error: {}", err),
Self::Encode(err) => write!(f, "Encoding error: {}", err),
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
Self::MiniscriptPsbt(err) => write!(f, "Miniscript PSBT error: {}", err),
Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
Self::Secp256k1(err) => write!(f, "Secp256k1 error: {}", err),
Self::Json(err) => write!(f, "Serialize/Deserialize JSON error: {}", err),
Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
Self::Psbt(err) => write!(f, "PSBT error: {}", err),
Self::PsbtParse(err) => write!(f, "Impossible to parse PSBT: {}", err),
Self::MissingCachedScripts(missing_cached_scripts) => {
write!(f, "Missing cached scripts: {:?}", missing_cached_scripts)
}
#[cfg(feature = "electrum")]
Self::Electrum(err) => write!(f, "Electrum client error: {}", err),
#[cfg(feature = "esplora")]
Self::Esplora(err) => write!(f, "Esplora client error: {}", err),
#[cfg(feature = "compact_filters")]
Self::CompactFilters(err) => write!(f, "Compact filters client error: {}", err),
#[cfg(feature = "key-value-db")]
Self::Sled(err) => write!(f, "Sled database error: {}", err),
#[cfg(feature = "rpc")]
Self::Rpc(err) => write!(f, "RPC client error: {}", err),
#[cfg(feature = "sqlite")]
Self::Rusqlite(err) => write!(f, "SQLite error: {}", err),
}
write!(f, "{:?}", self)
}
}
@@ -290,6 +181,7 @@ macro_rules! impl_error {
}
impl_error!(descriptor::error::Error, Descriptor);
impl_error!(address_validator::AddressValidatorError, AddressValidator);
impl_error!(descriptor::policy::PolicyError, InvalidPolicyPathError);
impl_error!(wallet::signer::SignerError, Signer);
@@ -306,13 +198,12 @@ impl From<crate::keys::KeyError> for Error {
impl_error!(bitcoin::consensus::encode::Error, Encode);
impl_error!(miniscript::Error, Miniscript);
impl_error!(MiniscriptPsbtError, MiniscriptPsbt);
impl_error!(bitcoin::bip32::Error, Bip32);
impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, Json);
impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(bitcoin::psbt::Error, Psbt);
impl_error!(bitcoin::psbt::PsbtParseError, PsbtParse);
impl_error!(bitcoin::util::psbt::Error, Psbt);
impl_error!(bitcoin::util::psbt::PsbtParseError, PsbtParse);
#[cfg(feature = "electrum")]
impl_error!(electrum_client::Error, Electrum);

View File

@@ -14,7 +14,7 @@
// TODO: maybe write our own implementation of bip39? Seems stupid to have an extra dependency for
// something that should be fairly simple to re-implement.
use bitcoin::bip32;
use bitcoin::util::bip32;
use bitcoin::Network;
use miniscript::ScriptContext;
@@ -141,7 +141,7 @@ impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
(word_count, language): Self::Options,
entropy: Self::Entropy,
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
let entropy = &entropy[..(word_count as usize / 8)];
let entropy = &entropy.as_ref()[..(word_count as usize / 8)];
let mnemonic = Mnemonic::from_entropy_in(language, entropy)?;
Ok(GeneratedKey::new(mnemonic, any_network()))
@@ -152,7 +152,7 @@ impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
mod test {
use std::str::FromStr;
use bitcoin::bip32;
use bitcoin::util::bip32;
use bip39::{Language, Mnemonic};

View File

@@ -19,13 +19,13 @@ use std::str::FromStr;
use bitcoin::secp256k1::{self, Secp256k1, Signing};
use bitcoin::bip32;
use bitcoin::{key::XOnlyPublicKey, Network, PrivateKey, PublicKey};
use bitcoin::util::bip32;
use bitcoin::{Network, PrivateKey, PublicKey, XOnlyPublicKey};
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
pub use miniscript::descriptor::{
DescriptorPublicKey, DescriptorSecretKey, KeyMap, SinglePriv, SinglePub, SinglePubKey,
SortedMultiVec,
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
SinglePubKey, SortedMultiVec,
};
pub use miniscript::ScriptContext;
use miniscript::{Miniscript, Terminal};
@@ -40,7 +40,7 @@ pub mod bip39;
/// Set of valid networks for a key
pub type ValidNetworks = HashSet<Network>;
/// Create a set containing mainnet, testnet, signet, and regtest
/// Create a set containing mainnet, testnet and regtest
pub fn any_network() -> ValidNetworks {
vec![
Network::Bitcoin,
@@ -95,7 +95,7 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
}
// This method is used internally by `bdk::fragment!` and `bdk::descriptor!`. It has to be
// public because it is effectively called by external crates once the macros are expanded,
// public because it is effectively called by external crates, once the macros are expanded,
// but since it is not meant to be part of the public api we hide it from the docs.
#[doc(hidden)]
pub fn extract(
@@ -110,7 +110,7 @@ impl<Ctx: ScriptContext> DescriptorKey<Ctx> {
let mut key_map = KeyMap::with_capacity(1);
let public = secret
.to_public(secp)
.as_public(secp)
.map_err(|e| miniscript::Error::Unexpected(e.to_string()))?;
key_map.insert(public.clone(), secret);
@@ -224,8 +224,8 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
/// use bdk::bitcoin::PublicKey;
///
/// use bdk::keys::{
/// mainnet_network, DescriptorKey, DescriptorPublicKey, IntoDescriptorKey, KeyError,
/// ScriptContext, SinglePub, SinglePubKey,
/// mainnet_network, DescriptorKey, DescriptorPublicKey, DescriptorSinglePub,
/// IntoDescriptorKey, KeyError, ScriptContext, SinglePubKey,
/// };
///
/// pub struct MyKeyType {
@@ -235,7 +235,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
/// impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for MyKeyType {
/// fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
/// Ok(DescriptorKey::from_public(
/// DescriptorPublicKey::Single(SinglePub {
/// DescriptorPublicKey::SinglePub(DescriptorSinglePub {
/// origin: None,
/// key: SinglePubKey::FullKey(self.pubkey),
/// }),
@@ -375,7 +375,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
/// `(DerivableKey, KeySource, DerivationPath)` tuples.
///
/// For key types that don't encode any indication about the path to use (like bip39), it's
/// generally recommended to implement this trait instead of [`IntoDescriptorKey`]. The same
/// generally recommended to implemented this trait instead of [`IntoDescriptorKey`]. The same
/// rules regarding script context and valid networks apply.
///
/// ## Examples
@@ -385,12 +385,12 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
///
/// ```
/// use bdk::bitcoin;
/// use bdk::bitcoin::bip32;
/// use bdk::bitcoin::util::bip32;
/// use bdk::keys::{DerivableKey, ExtendedKey, KeyError, ScriptContext};
///
/// struct MyCustomKeyType {
/// key_data: bitcoin::PrivateKey,
/// chain_code: [u8; 32],
/// chain_code: Vec<u8>,
/// network: bitcoin::Network,
/// }
///
@@ -401,7 +401,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
/// depth: 0,
/// parent_fingerprint: bip32::Fingerprint::default(),
/// private_key: self.key_data.inner,
/// chain_code: bip32::ChainCode::from(&self.chain_code),
/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()),
/// child_number: bip32::ChildNumber::Normal { index: 0 },
/// };
///
@@ -416,14 +416,14 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
///
/// ```
/// use bdk::bitcoin;
/// use bdk::bitcoin::bip32;
/// use bdk::bitcoin::util::bip32;
/// use bdk::keys::{
/// any_network, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext,
/// };
///
/// struct MyCustomKeyType {
/// key_data: bitcoin::PrivateKey,
/// chain_code: [u8; 32],
/// chain_code: Vec<u8>,
/// }
///
/// impl<Ctx: ScriptContext> DerivableKey<Ctx> for MyCustomKeyType {
@@ -433,7 +433,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
/// depth: 0,
/// parent_fingerprint: bip32::Fingerprint::default(),
/// private_key: self.key_data.inner,
/// chain_code: bip32::ChainCode::from(&self.chain_code),
/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()),
/// child_number: bip32::ChildNumber::Normal { index: 0 },
/// };
///
@@ -842,7 +842,7 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorKey<Ctx> {
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorPublicKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
let networks = match self {
DescriptorPublicKey::Single(_) => any_network(),
DescriptorPublicKey::SinglePub(_) => any_network(),
DescriptorPublicKey::XPub(DescriptorXKey { xkey, .. })
if xkey.network == Network::Bitcoin =>
{
@@ -857,7 +857,7 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorPublicKey {
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
DescriptorPublicKey::Single(SinglePub {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::FullKey(self),
origin: None,
})
@@ -867,7 +867,7 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for XOnlyPublicKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
DescriptorPublicKey::Single(SinglePub {
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
key: SinglePubKey::XOnly(self),
origin: None,
})
@@ -878,7 +878,7 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for XOnlyPublicKey {
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorSecretKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
let networks = match &self {
DescriptorSecretKey::Single(sk) if sk.key.network == Network::Bitcoin => {
DescriptorSecretKey::SinglePriv(sk) if sk.key.network == Network::Bitcoin => {
mainnet_network()
}
DescriptorSecretKey::XPrv(DescriptorXKey { xkey, .. })
@@ -903,7 +903,7 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for &'_ str {
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PrivateKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
DescriptorSecretKey::Single(SinglePriv {
DescriptorSecretKey::SinglePriv(DescriptorSinglePriv {
key: self,
origin: None,
})
@@ -925,24 +925,17 @@ pub enum KeyError {
Message(String),
/// BIP32 error
Bip32(bitcoin::bip32::Error),
Bip32(bitcoin::util::bip32::Error),
/// Miniscript error
Miniscript(miniscript::Error),
}
impl_error!(miniscript::Error, Miniscript, KeyError);
impl_error!(bitcoin::bip32::Error, Bip32, KeyError);
impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError);
impl std::fmt::Display for KeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::InvalidScriptContext => write!(f, "Invalid script context"),
Self::InvalidNetwork => write!(f, "Invalid network"),
Self::InvalidChecksum => write!(f, "Invalid checksum"),
Self::Message(err) => write!(f, "{}", err),
Self::Bip32(err) => write!(f, "BIP32 error: {}", err),
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
}
write!(f, "{:?}", self)
}
}
@@ -950,7 +943,7 @@ impl std::error::Error for KeyError {}
#[cfg(test)]
pub mod test {
use bitcoin::bip32;
use bitcoin::util::bip32;
use super::*;

View File

@@ -149,7 +149,7 @@ fn main() -> Result<(), bdk::Error> {
//! ```no_run
//! use std::str::FromStr;
//!
//! use bitcoin::psbt::PartiallySignedTransaction as Psbt;
//! use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
//!
//! use bdk::{Wallet, SignOptions};
//! use bdk::database::MemoryDatabase;
@@ -203,8 +203,6 @@ pub extern crate miniscript;
extern crate serde;
#[macro_use]
extern crate serde_json;
#[cfg(feature = "hardware-signer")]
pub extern crate hwi;
#[cfg(all(feature = "reqwest", feature = "ureq"))]
compile_error!("Features reqwest and ureq are mutually exclusive and cannot be enabled together");
@@ -227,7 +225,7 @@ compile_error!(
#[cfg(feature = "keys-bip39")]
extern crate bip39;
#[cfg(feature = "async-interface")]
#[cfg(any(target_arch = "wasm32", feature = "async-interface"))]
#[macro_use]
extern crate async_trait;
#[macro_use]
@@ -256,9 +254,6 @@ pub extern crate rusqlite;
#[macro_use]
pub mod testutils;
#[cfg(test)]
extern crate assert_matches;
#[allow(unused_imports)]
#[macro_use]
pub(crate) mod error;
@@ -268,7 +263,7 @@ pub mod descriptor;
#[cfg(feature = "test-md-docs")]
mod doctest;
pub mod keys;
pub mod psbt;
pub(crate) mod psbt;
pub(crate) mod types;
pub mod wallet;
@@ -276,6 +271,7 @@ pub use descriptor::template;
pub use descriptor::HdKeyPaths;
pub use error::Error;
pub use types::*;
pub use wallet::address_validator;
pub use wallet::signer;
pub use wallet::signer::SignOptions;
pub use wallet::tx_builder::TxBuilder;

View File

@@ -9,20 +9,14 @@
// You may not use this file except in accordance with one or both of these
// licenses.
//! Additional functions on the `rust-bitcoin` `PartiallySignedTransaction` structure.
use crate::FeeRate;
use bitcoin::psbt::PartiallySignedTransaction as Psbt;
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
use bitcoin::TxOut;
// TODO upstream the functions here to `rust-bitcoin`?
/// Trait to add functions to extract utxos and calculate fees.
pub trait PsbtUtils {
/// Get the `TxOut` for the specified input index, if it doesn't exist in the PSBT `None` is returned.
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut>;
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in sats.
/// The total transaction fee amount, sum of input amounts minus sum of output amounts, in Sats.
/// If the PSBT is missing a TxOut for an input returns None.
fn fee_amount(&self) -> Option<u64>;

View File

@@ -1,22 +1,12 @@
// Bitcoin Dev Kit
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
use crate::testutils::TestIncomingTx;
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::sha256d;
use bitcoin::{absolute, Address, Amount, Script, ScriptBuf, Sequence, Transaction, Txid, Witness};
pub use bitcoincore_rpc::json::AddressType;
use bitcoin::{Address, Amount, Script, Transaction, Txid, Witness};
pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
use core::str::FromStr;
use electrsd::bitcoind::BitcoinD;
use electrsd::electrum_client::ElectrumApi as _;
use electrsd::{bitcoind, ElectrsD};
pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
#[allow(unused_imports)]
@@ -45,11 +35,7 @@ impl TestClient {
let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap();
let node_address = bitcoind
.client
.get_new_address(None, None)
.unwrap()
.assume_checked();
let node_address = bitcoind.client.get_new_address(None, None).unwrap();
bitcoind
.client
.generate_to_address(101, &node_address)
@@ -111,7 +97,7 @@ impl TestClient {
.collect();
if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) {
panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap().assume_checked());
panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap());
}
// FIXME: core can't create a tx with two outputs to the same address
@@ -124,7 +110,7 @@ impl TestClient {
if let Some(true) = meta_tx.replaceable {
// for some reason core doesn't set this field right
for input in &mut tx.input {
input.sequence = Sequence(0xFFFFFFFD);
input.sequence = 0xFFFFFFFD;
}
}
@@ -147,7 +133,6 @@ impl TestClient {
let monitor_script = Address::from_str(&meta_tx.output[0].to_address)
.unwrap()
.assume_checked()
.script_pubkey();
self.wait_for_tx(txid, &monitor_script);
@@ -166,7 +151,7 @@ impl TestClient {
let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap();
let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap();
let monitor_script = ScriptBuf::from_bytes(tx.vout[0].script_pub_key.hex.clone());
let monitor_script = Script::from_hex(&mut tx.vout[0].script_pub_key.hex.to_hex()).unwrap();
self.wait_for_tx(new_txid, &monitor_script);
debug!("Bumped {}, new txid {}", txid, new_txid);
@@ -175,44 +160,40 @@ impl TestClient {
}
pub fn generate_manually(&mut self, txs: Vec<Transaction>) -> String {
use bitcoin::blockdata::block::{Block, Header, Version};
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::script::Builder;
use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
use bitcoin::hash_types::{BlockHash, TxMerkleNode};
use bitcoin::hashes::Hash;
use bitcoin::pow::CompactTarget;
let block_template: serde_json::Value = self
.call("getblocktemplate", &[json!({"rules": ["segwit"]})])
.unwrap();
trace!("getblocktemplate: {:#?}", block_template);
let header = Header {
version: Version::from_consensus(block_template["version"].as_i64().unwrap() as i32),
prev_blockhash: BlockHash::from_str(
let header = BlockHeader {
version: block_template["version"].as_i64().unwrap() as i32,
prev_blockhash: BlockHash::from_hex(
block_template["previousblockhash"].as_str().unwrap(),
)
.unwrap(),
merkle_root: TxMerkleNode::all_zeros(),
merkle_root: TxMerkleNode::default(),
time: block_template["curtime"].as_u64().unwrap() as u32,
bits: CompactTarget::from_consensus(
u32::from_str_radix(block_template["bits"].as_str().unwrap(), 16).unwrap(),
),
bits: u32::from_str_radix(block_template["bits"].as_str().unwrap(), 16).unwrap(),
nonce: 0,
};
debug!("header: {:#?}", header);
let height = block_template["height"].as_u64().unwrap() as i64;
let witness_reserved_value = sha256d::Hash::all_zeros().as_byte_array().to_vec();
let witness_reserved_value: Vec<u8> = sha256d::Hash::default().as_ref().into();
// burn block subsidy and fees, not a big deal
let mut coinbase_tx = Transaction {
version: 1,
lock_time: absolute::LockTime::ZERO,
lock_time: 0,
input: vec![TxIn {
previous_output: OutPoint::null(),
script_sig: Builder::new().push_int(height).into_script(),
sequence: Sequence(0xFFFFFFFF),
witness: Witness::from_slice(&vec![witness_reserved_value]),
sequence: 0xFFFFFFFF,
witness: Witness::from_vec(vec![witness_reserved_value]),
}],
output: vec![],
};
@@ -233,7 +214,7 @@ impl TestClient {
// now update and replace the coinbase tx
let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
coinbase_witness_commitment_script.extend_from_slice(witness_commitment.as_ref());
coinbase_witness_commitment_script.extend_from_slice(&witness_commitment);
coinbase_tx.output.push(TxOut {
value: 0,
@@ -253,11 +234,11 @@ impl TestClient {
// now do PoW :)
let target = block.header.target();
while block.header.validate_pow(target).is_err() {
while block.header.validate_pow(&target).is_err() {
block.header.nonce = block.header.nonce.checked_add(1).unwrap(); // panic if we run out of nonces
}
let block_hex: String = bitcoin::consensus::encode::serialize_hex(&block);
let block_hex: String = serialize(&block).to_hex();
debug!("generated block hex: {}", block_hex);
self.electrsd.client.block_headers_subscribe().unwrap();
@@ -273,12 +254,11 @@ impl TestClient {
self.wait_for_block(height as usize);
block.header.block_hash().to_string()
block.header.block_hash().to_hex()
}
pub fn generate(&mut self, num_blocks: u64, address: Option<Address>) {
let address =
address.unwrap_or_else(|| self.get_new_address(None, None).unwrap().assume_checked());
let address = address.unwrap_or_else(|| self.get_new_address(None, None).unwrap());
let hashes = self.generate_to_address(num_blocks, &address).unwrap();
let best_hash = hashes.last().unwrap();
let height = self.get_block_info(best_hash).unwrap().height;
@@ -326,11 +306,9 @@ impl TestClient {
&self
.get_new_address(None, address_type)
.unwrap()
.assume_checked()
.to_string(),
)
.unwrap()
.assume_checked()
}
}
@@ -389,7 +367,7 @@ macro_rules! bdk_blockchain_tests {
fn $_fn_name:ident ( $( $test_client:ident : &TestClient )? $(,)? ) -> $blockchain:ty $block:block) => {
#[cfg(test)]
mod bdk_blockchain_tests {
use $crate::bitcoin::{Transaction, Network, blockdata::script::PushBytesBuf};
use $crate::bitcoin::{Transaction, Network};
use $crate::testutils::blockchain_tests::TestClient;
use $crate::blockchain::Blockchain;
use $crate::database::MemoryDatabase;
@@ -397,7 +375,6 @@ macro_rules! bdk_blockchain_tests {
use $crate::wallet::AddressIndex;
use $crate::{Wallet, FeeRate, SyncOptions};
use $crate::testutils;
use std::convert::TryFrom;
use super::*;
@@ -1070,7 +1047,7 @@ macro_rules! bdk_blockchain_tests {
assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
let data = PushBytesBuf::try_from(vec![42u8;80]).unwrap();
let data = [42u8;80];
builder.add_data(&data);
let (mut psbt, details) = builder.finish().unwrap();
@@ -1078,7 +1055,7 @@ macro_rules! bdk_blockchain_tests {
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
let serialized_tx = bitcoin::consensus::encode::serialize(&tx);
assert!(serialized_tx.windows(data.len()).any(|e| e==data.as_bytes()), "cannot find op_return data in transaction");
assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction");
blockchain.broadcast(&tx).unwrap();
test_client.generate(1, Some(node_addr));
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
@@ -1098,18 +1075,18 @@ macro_rules! bdk_blockchain_tests {
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap().immature, 0, "incorrect balance");
test_client.generate(2, Some(wallet_addr));
test_client.generate(1, Some(wallet_addr));
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap().immature, 5000000000*2, "incorrect balance after receiving coinbase");
assert!(wallet.get_balance().unwrap().immature > 0, "incorrect balance after receiving coinbase");
// make coinbase mature (100 blocks)
let node_addr = test_client.get_node_address(None);
test_client.generate(100, Some(node_addr));
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap().confirmed, 5000000000 * 2, "incorrect balance after maturing coinbase");
assert!(wallet.get_balance().unwrap().confirmed > 0, "incorrect balance after maturing coinbase");
}
@@ -1177,9 +1154,8 @@ macro_rules! bdk_blockchain_tests {
// 2. Get a new bech32m address from test bitcoind node taproot wallet
// TODO replace once rust-bitcoincore-rpc with PR 199 released
let node_addr: bitcoin::Address<bitcoin::address::NetworkUnchecked> = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap();
let node_addr = node_addr.assume_checked();
assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap().assume_checked());
let node_addr: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap();
assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap());
// 3. Send 50_000 sats from test bitcoind node to test BDK wallet
@@ -1208,7 +1184,7 @@ macro_rules! bdk_blockchain_tests {
// 5. Verify 25_000 sats are received by test bitcoind node taproot wallet
let taproot_balance = taproot_wallet_client.get_balance(None, None).unwrap();
assert_eq!(taproot_balance.to_sat(), 25_000, "node has incorrect taproot wallet balance");
assert_eq!(taproot_balance.as_sat(), 25_000, "node has incorrect taproot wallet balance");
}
#[test]
@@ -1228,7 +1204,7 @@ macro_rules! bdk_blockchain_tests {
let (wallet, blockchain, _, mut test_client) = init_single_sig();
let bdk_address = wallet.get_address(AddressIndex::New).unwrap().address;
let core_address = test_client.get_new_address(None, None).unwrap().assume_checked();
let core_address = test_client.get_new_address(None, None).unwrap();
let tx = testutils! {
@tx ( (@addr bdk_address.clone()) => 50_000, (@addr core_address.clone()) => 40_000 )
};
@@ -1420,8 +1396,8 @@ macro_rules! bdk_blockchain_tests {
"label":"taproot key spend",
}]);
let _importdescriptors_result: Value = taproot_wallet_client.call("importdescriptors", &[import_descriptor_args]).expect("import wallet");
let generate_to_address: bitcoin::Address<bitcoin::address::NetworkUnchecked> = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).expect("new address");
let _generatetoaddress_result = taproot_wallet_client.generate_to_address(101, &generate_to_address.assume_checked()).expect("generated to address");
let generate_to_address: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).expect("new address");
let _generatetoaddress_result = taproot_wallet_client.generate_to_address(101, &generate_to_address).expect("generated to address");
let send_to_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string();
let change_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string();
let send_addr_amounts = json!([{
@@ -1434,7 +1410,7 @@ macro_rules! bdk_blockchain_tests {
let send_result: Value = taproot_wallet_client.call("send", &[send_addr_amounts, Value::Null, "unset".into(), Value::Null, send_options]).expect("send psbt");
let core_psbt = send_result["psbt"].as_str().expect("core psbt str");
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::util::psbt::PartiallySignedTransaction;
// Test parsing core created PSBT
let mut psbt = PartiallySignedTransaction::from_str(&core_psbt).expect("core taproot psbt");

View File

@@ -23,7 +23,7 @@ pub trait ConfigurableBlockchainTester<B: ConfigurableBlockchain>: Sized {
None
}
/// Runs all available tests.
/// Runs all avaliable tests.
fn run(&self) {
let test_client = &mut TestClient::default();

View File

@@ -101,21 +101,25 @@ impl TestIncomingTx {
macro_rules! testutils {
( @external $descriptors:expr, $child:expr ) => ({
use $crate::bitcoin::secp256k1::Secp256k1;
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
use $crate::descriptor::AsDerived;
let secp = Secp256k1::new();
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
parsed.at_derivation_index($child).unwrap().address(bitcoin::Network::Regtest).expect("No address form")
parsed.as_derived($child, &secp).address(bitcoin::Network::Regtest).expect("No address form")
});
( @internal $descriptors:expr, $child:expr ) => ({
use $crate::bitcoin::secp256k1::Secp256k1;
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
use $crate::descriptor::AsDerived;
let secp = Secp256k1::new();
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
parsed.at_derivation_index($child).address($crate::bitcoin::Network::Regtest).expect("No address form")
parsed.as_derived($child, &secp).address($crate::bitcoin::Network::Regtest).expect("No address form")
});
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
@@ -146,7 +150,7 @@ macro_rules! testutils {
let mut seed = [0u8; 32];
rand::thread_rng().fill(&mut seed[..]);
let key = $crate::bitcoin::bip32::ExtendedPrivKey::new_master(
let key = $crate::bitcoin::util::bip32::ExtendedPrivKey::new_master(
$crate::bitcoin::Network::Testnet,
&seed,
);
@@ -182,50 +186,49 @@ macro_rules! testutils {
( @descriptors ( $external_descriptor:expr ) $( ( $internal_descriptor:expr ) )? $( ( @keys $( $keys:tt )* ) )* ) => ({
use std::str::FromStr;
use std::collections::HashMap;
use std::convert::Infallible;
use $crate::miniscript::descriptor::Descriptor;
use $crate::miniscript::TranslatePk;
struct Translator {
keys: HashMap<&'static str, (String, Option<String>, Option<String>)>,
is_internal: bool,
}
impl $crate::miniscript::Translator<String, String, Infallible> for Translator {
fn pk(&mut self, pk: &String) -> Result<String, Infallible> {
match self.keys.get(pk.as_str()) {
Some((key, ext_path, int_path)) => {
let path = if self.is_internal { int_path } else { ext_path };
Ok(format!("{}{}", key, path.clone().unwrap_or_default()))
}
None => Ok(pk.clone()),
}
}
fn sha256(&mut self, sha256: &String) -> Result<String, Infallible> { Ok(sha256.clone()) }
fn hash256(&mut self, hash256: &String) -> Result<String, Infallible> { Ok(hash256.clone()) }
fn ripemd160(&mut self, ripemd160: &String) -> Result<String, Infallible> { Ok(ripemd160.clone()) }
fn hash160(&mut self, hash160: &String) -> Result<String, Infallible> { Ok(hash160.clone()) }
}
#[allow(unused_assignments, unused_mut)]
let mut keys = HashMap::new();
let mut keys: HashMap<&'static str, (String, Option<String>, Option<String>)> = HashMap::new();
$(
keys = testutils!{ @keys $( $keys )* };
)*
let mut translator = Translator { keys, is_internal: false };
let external: Descriptor<String> = FromStr::from_str($external_descriptor).unwrap();
let external = external.translate_pk(&mut translator).expect("Infallible conversion");
let external: Descriptor<String> = external.translate_pk_infallible::<_, _>(|k| {
if let Some((key, ext_path, _)) = keys.get(&k.as_str()) {
format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
} else {
k.clone()
}
}, |kh| {
if let Some((key, ext_path, _)) = keys.get(&kh.as_str()) {
format!("{}{}", key, ext_path.as_ref().unwrap_or(&"".into()))
} else {
kh.clone()
}
});
let external = external.to_string();
translator.is_internal = true;
let internal = None::<String>$(.or({
let internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
let internal = internal.translate_pk(&mut translator).expect("Infallible conversion");
Some(internal.to_string())
let string_internal: Descriptor<String> = FromStr::from_str($internal_descriptor).unwrap();
let string_internal: Descriptor<String> = string_internal.translate_pk_infallible::<_, _>(|k| {
if let Some((key, _, int_path)) = keys.get(&k.as_str()) {
format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
} else {
k.clone()
}
}, |kh| {
if let Some((key, _, int_path)) = keys.get(&kh.as_str()) {
format!("{}{}", key, int_path.as_ref().unwrap_or(&"".into()))
} else {
kh.clone()
}
});
Some(string_internal.to_string())
}))?;
(external, internal)

View File

@@ -13,7 +13,7 @@ use std::convert::AsRef;
use std::ops::Sub;
use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut};
use bitcoin::{hash_types::Txid, psbt, Weight};
use bitcoin::{hash_types::Txid, util::psbt};
use serde::{Deserialize, Serialize};
@@ -97,8 +97,8 @@ impl FeeRate {
}
/// Calculate fee rate from `fee` and weight units (`wu`).
pub fn from_wu(fee: u64, wu: Weight) -> FeeRate {
Self::from_vb(fee, wu.to_vbytes_ceil() as usize)
pub fn from_wu(fee: u64, wu: usize) -> FeeRate {
Self::from_vb(fee, wu.vbytes())
}
/// Calculate fee rate from `fee` and `vbytes`.
@@ -113,8 +113,8 @@ impl FeeRate {
}
/// Calculate absolute fee in Satoshis using size in weight units.
pub fn fee_wu(&self, wu: Weight) -> u64 {
self.fee_vb(wu.to_vbytes_ceil() as usize)
pub fn fee_wu(&self, wu: usize) -> u64 {
self.fee_vb(wu.vbytes())
}
/// Calculate absolute fee in Satoshis using size in virtual bytes.
@@ -166,7 +166,7 @@ pub struct LocalUtxo {
}
/// A [`Utxo`] with its `satisfaction_weight`.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub struct WeightedUtxo {
/// The weight of the witness data and `scriptSig` expressed in [weight units]. This is used to
/// properly maintain the feerate when adding this input to a transaction during coin selection.
@@ -177,7 +177,7 @@ pub struct WeightedUtxo {
pub utxo: Utxo,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
/// An unspent transaction output (UTXO).
pub enum Utxo {
/// A UTXO owned by the local wallet.
@@ -224,12 +224,13 @@ impl Utxo {
}
/// A wallet transaction
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct TransactionDetails {
/// Optional transaction
pub transaction: Option<Transaction>,
/// Transaction id
pub txid: Txid,
/// Received value (sats)
/// Sum of owned outputs of this transaction.
pub received: u64,
@@ -241,25 +242,11 @@ pub struct TransactionDetails {
/// Server backend, but it could be `None` with a Bitcoin RPC node without txindex that receive
/// funds while offline.
pub fee: Option<u64>,
/// If the transaction is confirmed, contains height and Unix timestamp of the block containing the
/// If the transaction is confirmed, contains height and timestamp of the block containing the
/// transaction, unconfirmed transaction contains `None`.
pub confirmation_time: Option<BlockTime>,
}
impl PartialOrd for TransactionDetails {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for TransactionDetails {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.confirmation_time
.cmp(&other.confirmation_time)
.then_with(|| self.txid.cmp(&other.txid))
}
}
/// Block height and timestamp of a block
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct BlockTime {
@@ -269,20 +256,6 @@ pub struct BlockTime {
pub timestamp: u64,
}
impl PartialOrd for BlockTime {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for BlockTime {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.height
.cmp(&other.height)
.then_with(|| self.timestamp.cmp(&other.timestamp))
}
}
/// **DEPRECATED**: Confirmation time of a transaction
///
/// The structure has been renamed to `BlockTime`
@@ -361,95 +334,6 @@ impl std::iter::Sum for Balance {
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::hashes::Hash;
#[test]
fn sort_block_time() {
let block_time_a = BlockTime {
height: 100,
timestamp: 100,
};
let block_time_b = BlockTime {
height: 100,
timestamp: 110,
};
let block_time_c = BlockTime {
height: 0,
timestamp: 0,
};
let mut vec = vec![
block_time_a.clone(),
block_time_b.clone(),
block_time_c.clone(),
];
vec.sort();
let expected = vec![block_time_c, block_time_a, block_time_b];
assert_eq!(vec, expected)
}
#[test]
fn sort_tx_details() {
let block_time_a = BlockTime {
height: 100,
timestamp: 100,
};
let block_time_b = BlockTime {
height: 0,
timestamp: 0,
};
let tx_details_a = TransactionDetails {
transaction: None,
txid: Txid::all_zeros(),
received: 0,
sent: 0,
fee: None,
confirmation_time: None,
};
let tx_details_b = TransactionDetails {
transaction: None,
txid: Txid::all_zeros(),
received: 0,
sent: 0,
fee: None,
confirmation_time: Some(block_time_a),
};
let tx_details_c = TransactionDetails {
transaction: None,
txid: Txid::all_zeros(),
received: 0,
sent: 0,
fee: None,
confirmation_time: Some(block_time_b.clone()),
};
let tx_details_d = TransactionDetails {
transaction: None,
txid: Txid::from_byte_array([1; Txid::LEN]),
received: 0,
sent: 0,
fee: None,
confirmation_time: Some(block_time_b),
};
let mut vec = vec![
tx_details_a.clone(),
tx_details_b.clone(),
tx_details_c.clone(),
tx_details_d.clone(),
];
vec.sort();
let expected = vec![tx_details_a, tx_details_c, tx_details_d, tx_details_b];
assert_eq!(vec, expected)
}
#[test]
fn can_store_feerate_in_const() {

View File

@@ -0,0 +1,158 @@
// Bitcoin Dev Kit
// Written in 2020 by Alekos Filini <alekos.filini@gmail.com>
//
// Copyright (c) 2020-2021 Bitcoin Dev Kit Developers
//
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE
// or http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your option.
// You may not use this file except in accordance with one or both of these
// licenses.
//! Address validation callbacks
//!
//! The typical usage of those callbacks is for displaying the newly-generated address on a
//! hardware wallet, so that the user can cross-check its correctness.
//!
//! More generally speaking though, these callbacks can also be used to "do something" every time
//! an address is generated, without necessarily checking or validating it.
//!
//! An address validator can be attached to a [`Wallet`](super::Wallet) by using the
//! [`Wallet::add_address_validator`](super::Wallet::add_address_validator) method, and
//! whenever a new address is generated (either explicitly by the user with
//! [`Wallet::get_address`](super::Wallet::get_address) or internally to create a change
//! address) all the attached validators will be polled, in sequence. All of them must complete
//! successfully to continue.
//!
//! ## Example
//!
//! ```
//! # use std::sync::Arc;
//! # use bitcoin::*;
//! # use bdk::address_validator::*;
//! # use bdk::database::*;
//! # use bdk::*;
//! # use bdk::wallet::AddressIndex::New;
//! #[derive(Debug)]
//! struct PrintAddressAndContinue;
//!
//! impl AddressValidator for PrintAddressAndContinue {
//! fn validate(
//! &self,
//! keychain: KeychainKind,
//! hd_keypaths: &HdKeyPaths,
//! script: &Script
//! ) -> Result<(), AddressValidatorError> {
//! let address = Address::from_script(script, Network::Testnet)
//! .as_ref()
//! .map(Address::to_string)
//! .unwrap_or(script.to_string());
//! println!("New address of type {:?}: {}", keychain, address);
//! println!("HD keypaths: {:#?}", hd_keypaths);
//!
//! Ok(())
//! }
//! }
//!
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
//! let mut wallet = Wallet::new(descriptor, None, Network::Testnet, MemoryDatabase::default())?;
//! wallet.add_address_validator(Arc::new(PrintAddressAndContinue));
//!
//! let address = wallet.get_address(New)?;
//! println!("Address: {}", address);
//! # Ok::<(), bdk::Error>(())
//! ```
use std::fmt;
use bitcoin::Script;
use crate::descriptor::HdKeyPaths;
use crate::types::KeychainKind;
/// Errors that can be returned to fail the validation of an address
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AddressValidatorError {
/// User rejected the address
UserRejected,
/// Network connection error
ConnectionError,
/// Network request timeout error
TimeoutError,
/// Invalid script
InvalidScript,
/// A custom error message
Message(String),
}
impl fmt::Display for AddressValidatorError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self)
}
}
impl std::error::Error for AddressValidatorError {}
/// Trait to build address validators
///
/// All the address validators attached to a wallet with [`Wallet::add_address_validator`](super::Wallet::add_address_validator) will be polled
/// every time an address (external or internal) is generated by the wallet. Errors returned in the
/// validator will be propagated up to the original caller that triggered the address generation.
///
/// For a usage example see [this module](crate::address_validator)'s documentation.
#[deprecated = "AddressValidator was rarely used. Address validation can occur outside of BDK"]
pub trait AddressValidator: Send + Sync + fmt::Debug {
/// Validate or inspect an address
fn validate(
&self,
keychain: KeychainKind,
hd_keypaths: &HdKeyPaths,
script: &Script,
) -> Result<(), AddressValidatorError>;
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use super::*;
use crate::wallet::AddressIndex::New;
use crate::wallet::{get_funded_wallet, test::get_test_wpkh};
#[derive(Debug)]
struct TestValidator;
#[allow(deprecated)]
impl AddressValidator for TestValidator {
fn validate(
&self,
_keychain: KeychainKind,
_hd_keypaths: &HdKeyPaths,
_script: &bitcoin::Script,
) -> Result<(), AddressValidatorError> {
Err(AddressValidatorError::InvalidScript)
}
}
#[test]
#[should_panic(expected = "InvalidScript")]
fn test_address_validator_external() {
let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh());
#[allow(deprecated)]
wallet.add_address_validator(Arc::new(TestValidator));
wallet.get_address(New).unwrap();
}
#[test]
#[should_panic(expected = "InvalidScript")]
fn test_address_validator_internal() {
let (mut wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
#[allow(deprecated)]
wallet.add_address_validator(Arc::new(TestValidator));
let addr = crate::testutils!(@external descriptors, 10);
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
builder.finish().unwrap();
}
}

View File

@@ -40,12 +40,12 @@
//! database: &D,
//! required_utxos: Vec<WeightedUtxo>,
//! optional_utxos: Vec<WeightedUtxo>,
//! fee_rate: bdk::FeeRate,
//! fee_rate: FeeRate,
//! target_amount: u64,
//! drain_script: &Script,
//! ) -> Result<CoinSelectionResult, bdk::Error> {
//! let mut selected_amount = 0;
//! let mut additional_weight = Weight::ZERO;
//! let mut additional_weight = 0;
//! let all_utxos_selected = required_utxos
//! .into_iter()
//! .chain(optional_utxos)
@@ -53,9 +53,7 @@
//! (&mut selected_amount, &mut additional_weight),
//! |(selected_amount, additional_weight), weighted_utxo| {
//! **selected_amount += weighted_utxo.utxo.txout().value;
//! **additional_weight += Weight::from_wu(
//! (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
//! );
//! **additional_weight += TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight;
//! Some(weighted_utxo.utxo)
//! },
//! )
@@ -84,10 +82,7 @@
//! # let wallet = doctest_wallet!();
//! // create wallet, sync, ...
//!
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
//! .unwrap()
//! .require_network(Network::Testnet)
//! .unwrap();
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! let (psbt, details) = {
//! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything);
//! builder.add_recipient(to_address.script_pubkey(), 50_000);
@@ -105,13 +100,13 @@ use crate::{database::Database, WeightedUtxo};
use crate::{error::Error, Utxo};
use bitcoin::consensus::encode::serialize;
use bitcoin::{Script, Weight};
use bitcoin::Script;
#[cfg(test)]
use assert_matches::assert_matches;
use rand::seq::SliceRandom;
#[cfg(not(test))]
use rand::thread_rng;
#[cfg(test)]
use rand::{rngs::StdRng, SeedableRng};
use std::collections::HashMap;
use std::convert::TryInto;
@@ -315,7 +310,7 @@ pub fn decide_change(remaining_amount: u64, fee_rate: FeeRate, drain_script: &Sc
let drain_val = remaining_amount.saturating_sub(change_fee);
if drain_val.is_dust(drain_script) {
let dust_threshold = drain_script.dust_value().to_sat();
let dust_threshold = drain_script.dust_value().as_sat();
Excess::NoChange {
dust_threshold,
change_fee,
@@ -342,9 +337,8 @@ fn select_sorted_utxos(
(&mut selected_amount, &mut fee_amount),
|(selected_amount, fee_amount), (must_use, weighted_utxo)| {
if must_use || **selected_amount < target_amount + **fee_amount {
**fee_amount += fee_rate.fee_wu(Weight::from_wu(
(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
));
**fee_amount +=
fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
**selected_amount += weighted_utxo.utxo.txout().value;
log::debug!(
@@ -392,9 +386,7 @@ struct OutputGroup {
impl OutputGroup {
fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
let fee = fee_rate.fee_wu(Weight::from_wu(
(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
));
let fee = fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64;
OutputGroup {
weighted_utxo,
@@ -679,7 +671,6 @@ impl BranchAndBoundCoinSelection {
optional_utxos.shuffle(&mut thread_rng());
#[cfg(test)]
{
use rand::{rngs::StdRng, SeedableRng};
let seed = [0; 32];
let mut rng: StdRng = SeedableRng::from_seed(seed);
optional_utxos.shuffle(&mut rng);
@@ -732,7 +723,7 @@ impl BranchAndBoundCoinSelection {
mod test {
use std::str::FromStr;
use bitcoin::{OutPoint, ScriptBuf, TxOut};
use bitcoin::{OutPoint, Script, TxOut};
use super::*;
use crate::database::{BatchOperations, MemoryDatabase};
@@ -762,7 +753,7 @@ mod test {
outpoint,
txout: TxOut {
value,
script_pubkey: ScriptBuf::new(),
script_pubkey: Script::new(),
},
keychain: KeychainKind::External,
is_spent: false,
@@ -844,8 +835,8 @@ mod test {
)
.unwrap(),
txout: TxOut {
value: rng.gen_range(0..200000000),
script_pubkey: ScriptBuf::new(),
value: rng.gen_range(0, 200000000),
script_pubkey: Script::new(),
},
keychain: KeychainKind::External,
is_spent: false,
@@ -865,7 +856,7 @@ mod test {
.unwrap(),
txout: TxOut {
value: utxos_value,
script_pubkey: ScriptBuf::new(),
script_pubkey: Script::new(),
},
keychain: KeychainKind::External,
is_spent: false,
@@ -875,7 +866,7 @@ mod test {
}
fn sum_random_utxos(mut rng: &mut StdRng, utxos: &mut Vec<WeightedUtxo>) -> u64 {
let utxos_picked_len = rng.gen_range(2..utxos.len() / 2);
let utxos_picked_len = rng.gen_range(2, utxos.len() / 2);
utxos.shuffle(&mut rng);
utxos[..utxos_picked_len]
.iter()
@@ -887,7 +878,7 @@ mod test {
fn test_largest_first_coin_selection_success() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 250_000 + FEE_AMOUNT;
let result = LargestFirstCoinSelection::default()
@@ -910,7 +901,7 @@ mod test {
fn test_largest_first_coin_selection_use_all() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = LargestFirstCoinSelection::default()
@@ -933,7 +924,7 @@ mod test {
fn test_largest_first_coin_selection_use_only_necessary() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = LargestFirstCoinSelection::default()
@@ -957,7 +948,7 @@ mod test {
fn test_largest_first_coin_selection_insufficient_funds() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 500_000 + FEE_AMOUNT;
LargestFirstCoinSelection::default()
@@ -977,7 +968,7 @@ mod test {
fn test_largest_first_coin_selection_insufficient_funds_high_fees() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 250_000 + FEE_AMOUNT;
LargestFirstCoinSelection::default()
@@ -996,7 +987,7 @@ mod test {
fn test_oldest_first_coin_selection_success() {
let mut database = MemoryDatabase::default();
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 180_000 + FEE_AMOUNT;
let result = OldestFirstCoinSelection::default()
@@ -1021,7 +1012,7 @@ mod test {
let utxo1 = utxo(120_000, 1);
let utxo2 = utxo(80_000, 2);
let utxo3 = utxo(300_000, 3);
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let mut database = MemoryDatabase::default();
@@ -1078,7 +1069,7 @@ mod test {
fn test_oldest_first_coin_selection_use_all() {
let mut database = MemoryDatabase::default();
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = OldestFirstCoinSelection::default()
@@ -1101,7 +1092,7 @@ mod test {
fn test_oldest_first_coin_selection_use_only_necessary() {
let mut database = MemoryDatabase::default();
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = OldestFirstCoinSelection::default()
@@ -1125,7 +1116,7 @@ mod test {
fn test_oldest_first_coin_selection_insufficient_funds() {
let mut database = MemoryDatabase::default();
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 600_000 + FEE_AMOUNT;
OldestFirstCoinSelection::default()
@@ -1147,7 +1138,7 @@ mod test {
let utxos = setup_database_and_get_oldest_first_test_utxos(&mut database);
let target_amount: u64 = utxos.iter().map(|wu| wu.utxo.txout().value).sum::<u64>() - 50;
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
OldestFirstCoinSelection::default()
.coin_select(
@@ -1168,7 +1159,7 @@ mod test {
let utxos = generate_same_value_utxos(100_000, 20);
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 250_000 + FEE_AMOUNT;
@@ -1192,7 +1183,7 @@ mod test {
fn test_bnb_coin_selection_required_are_enough() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = BranchAndBoundCoinSelection::default()
@@ -1215,7 +1206,7 @@ mod test {
fn test_bnb_coin_selection_optional_are_enough() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 299756 + FEE_AMOUNT;
let result = BranchAndBoundCoinSelection::default()
@@ -1235,7 +1226,6 @@ mod test {
}
#[test]
#[ignore]
fn test_bnb_coin_selection_required_not_enough() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
@@ -1249,7 +1239,7 @@ mod test {
assert_eq!(amount, 100_000);
let amount: u64 = optional.iter().map(|u| u.utxo.txout().value).sum();
assert!(amount > 150_000);
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 150_000 + FEE_AMOUNT;
@@ -1274,7 +1264,7 @@ mod test {
fn test_bnb_coin_selection_insufficient_funds() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 500_000 + FEE_AMOUNT;
BranchAndBoundCoinSelection::default()
@@ -1294,7 +1284,7 @@ mod test {
fn test_bnb_coin_selection_insufficient_funds_high_fees() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 250_000 + FEE_AMOUNT;
BranchAndBoundCoinSelection::default()
@@ -1313,7 +1303,7 @@ mod test {
fn test_bnb_coin_selection_check_fee_rate() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 99932; // first utxo's effective value
let result = BranchAndBoundCoinSelection::new(0)
@@ -1343,7 +1333,7 @@ mod test {
for _i in 0..200 {
let mut optional_utxos = generate_random_utxos(&mut rng, 16);
let target_amount = sum_random_utxos(&mut rng, &mut optional_utxos);
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let result = BranchAndBoundCoinSelection::new(0)
.coin_select(
&database,
@@ -1372,7 +1362,7 @@ mod test {
let size_of_change = 31;
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let target_amount = 20_000 + FEE_AMOUNT;
BranchAndBoundCoinSelection::new(size_of_change)
.bnb(
@@ -1403,7 +1393,7 @@ mod test {
let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb();
let target_amount = 20_000 + FEE_AMOUNT;
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
BranchAndBoundCoinSelection::new(size_of_change)
.bnb(
@@ -1439,7 +1429,7 @@ mod test {
// cost_of_change + 5.
let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as i64 + 5;
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let result = BranchAndBoundCoinSelection::new(size_of_change)
.bnb(
@@ -1479,7 +1469,7 @@ mod test {
let target_amount =
optional_utxos[3].effective_value + optional_utxos[23].effective_value;
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let result = BranchAndBoundCoinSelection::new(0)
.bnb(
@@ -1510,7 +1500,7 @@ mod test {
.map(|u| OutputGroup::new(u, fee_rate))
.collect();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let result = BranchAndBoundCoinSelection::default().single_random_draw(
vec![],
@@ -1529,75 +1519,81 @@ mod test {
fn test_bnb_exclude_negative_effective_value() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,
vec![],
utxos,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
);
let err = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
vec![],
utxos,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
)
.unwrap_err();
assert_matches!(
selection,
Err(Error::InsufficientFunds {
assert!(matches!(
err,
Error::InsufficientFunds {
available: 300_000,
..
})
);
}
));
}
#[test]
fn test_bnb_include_negative_effective_value_when_required() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let (required, optional) = utxos
.into_iter()
.partition(|u| matches!(u, WeightedUtxo { utxo, .. } if utxo.txout().value < 1000));
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,
required,
optional,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
);
let err = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
required,
optional,
FeeRate::from_sat_per_vb(10.0),
500_000,
&drain_script,
)
.unwrap_err();
assert_matches!(
selection,
Err(Error::InsufficientFunds {
assert!(matches!(
err,
Error::InsufficientFunds {
available: 300_010,
..
})
);
}
));
}
#[test]
fn test_bnb_sum_of_effective_value_negative() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = ScriptBuf::default();
let drain_script = Script::default();
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,
utxos,
vec![],
FeeRate::from_sat_per_vb(10_000.0),
500_000,
&drain_script,
);
let err = BranchAndBoundCoinSelection::default()
.coin_select(
&database,
utxos,
vec![],
FeeRate::from_sat_per_vb(10_000.0),
500_000,
&drain_script,
)
.unwrap_err();
assert_matches!(
selection,
Err(Error::InsufficientFunds {
assert!(matches!(
err,
Error::InsufficientFunds {
available: 300_010,
..
})
);
}
));
}
}

View File

@@ -134,11 +134,15 @@ impl FullyNodedExport {
let blockheight = match wallet.database.borrow().iter_txs(false) {
_ if !include_blockheight => 0,
Err(_) => 0,
Ok(txs) => txs
.into_iter()
.filter_map(|tx| tx.confirmation_time.map(|c| c.height))
.min()
.unwrap_or(0),
Ok(txs) => {
let mut heights = txs
.into_iter()
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(0))
.collect::<Vec<_>>();
heights.sort_unstable();
*heights.last().unwrap_or(&0)
}
};
let export = FullyNodedExport {
@@ -245,22 +249,6 @@ mod test {
fee: Some(500),
confirmation_time: Some(BlockTime {
timestamp: 12345678,
height: 5001,
}),
})
.unwrap();
db.set_tx(&TransactionDetails {
transaction: None,
txid: Txid::from_str(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
)
.unwrap(),
received: 25_000,
sent: 0,
fee: Some(300),
confirmation_time: Some(BlockTime {
timestamp: 12345677,
height: 5000,
}),
})

View File

@@ -11,47 +11,11 @@
//! HWI Signer
//!
//! This module contains HWISigner, an implementation of a [TransactionSigner] to be
//! used with hardware wallets.
//! ```no_run
//! # use bdk::bitcoin::Network;
//! # use bdk::database::MemoryDatabase;
//! # use bdk::signer::SignerOrdering;
//! # use bdk::wallet::hardwaresigner::HWISigner;
//! # use bdk::wallet::AddressIndex::New;
//! # use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
//! # use hwi::HWIClient;
//! # use std::sync::Arc;
//! #
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut devices = HWIClient::enumerate()?;
//! if devices.is_empty() {
//! panic!("No devices found!");
//! }
//! let first_device = devices.remove(0)?;
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
//!
//! # let mut wallet = Wallet::new(
//! # "",
//! # None,
//! # Network::Testnet,
//! # MemoryDatabase::default(),
//! # )?;
//! #
//! // Adding the hardware signer to the BDK wallet
//! wallet.add_signer(
//! KeychainKind::External,
//! SignerOrdering(200),
//! Arc::new(custom_signer),
//! );
//!
//! # Ok(())
//! # }
//! ```
//! This module contains a simple implementation of a Custom signer for rust-hwi
use bitcoin::bip32::Fingerprint;
use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32::Fingerprint;
use hwi::error::Error;
use hwi::types::{HWIChain, HWIDevice};

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@
//! # use std::str::FromStr;
//! # use bitcoin::secp256k1::{Secp256k1, All};
//! # use bitcoin::*;
//! # use bitcoin::psbt;
//! # use bitcoin::util::psbt;
//! # use bdk::signer::*;
//! # use bdk::database::*;
//! # use bdk::*;
@@ -86,19 +86,20 @@ use std::fmt;
use std::ops::{Bound::Included, Deref};
use std::sync::Arc;
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
use bitcoin::hashes::hash160;
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::script::Builder as ScriptBuilder;
use bitcoin::hashes::{hash160, Hash};
use bitcoin::secp256k1::Message;
use bitcoin::sighash::{EcdsaSighashType, TapSighash, TapSighashType};
use bitcoin::{ecdsa, psbt, sighash, taproot};
use bitcoin::{key::TapTweak, key::XOnlyPublicKey, secp256k1};
use bitcoin::{PrivateKey, PublicKey};
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
use bitcoin::util::{ecdsa, psbt, schnorr, sighash, taproot};
use bitcoin::{secp256k1, XOnlyPublicKey};
use bitcoin::{EcdsaSighashType, PrivateKey, PublicKey, SchnorrSighashType, Script};
use miniscript::descriptor::{
Descriptor, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey,
InnerXKey, KeyMap, SinglePriv, SinglePubKey,
Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey,
KeyMap, SinglePubKey,
};
use miniscript::{Legacy, Segwitv0, SigType, Tap, ToPublicKey};
use miniscript::{Legacy, MiniscriptKey, Segwitv0, Tap};
use super::utils::SecpCtx;
use crate::descriptor::{DescriptorMeta, XKeyUtils};
@@ -129,7 +130,7 @@ impl From<Fingerprint> for SignerId {
}
/// Signing error
#[derive(Debug)]
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum SignerError {
/// The private key is missing for the required public key
MissingKey,
@@ -179,22 +180,7 @@ impl From<sighash::Error> for SignerError {
impl fmt::Display for SignerError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingKey => write!(f, "Missing private key"),
Self::InvalidKey => write!(f, "The private key in use has the right fingerprint but derives differently than expected"),
Self::UserCanceled => write!(f, "The user canceled the operation"),
Self::InputIndexOutOfRange => write!(f, "Input index out of range"),
Self::MissingNonWitnessUtxo => write!(f, "Missing non-witness UTXO"),
Self::InvalidNonWitnessUtxo => write!(f, "Invalid non-witness UTXO"),
Self::MissingWitnessUtxo => write!(f, "Missing witness UTXO"),
Self::MissingWitnessScript => write!(f, "Missing witness script"),
Self::MissingHdKeypath => write!(f, "Missing fingerprint and derivation path"),
Self::NonStandardSighash => write!(f, "The psbt contains a non standard sighash"),
Self::InvalidSighash => write!(f, "Invalid SIGHASH for the signing context in use"),
Self::SighashError(err) => write!(f, "Error while computing the hash to sign: {}", err),
#[cfg(feature = "hardware-signer")]
Self::HWIError(err) => write!(f, "Error while signing using hardware wallets: {}", err),
}
write!(f, "{:?}", self)
}
}
@@ -381,56 +367,13 @@ impl InputSigner for SignerWrapper<DescriptorXKey<ExtendedPrivKey>> {
}
}
fn multikey_to_xkeys<K: InnerXKey + Clone>(
multikey: DescriptorMultiXKey<K>,
) -> Vec<DescriptorXKey<K>> {
multikey
.derivation_paths
.clone()
.into_paths()
.into_iter()
.map(|derivation_path| DescriptorXKey {
origin: multikey.origin.clone(),
xkey: multikey.xkey.clone(),
derivation_path,
wildcard: multikey.wildcard,
})
.collect()
}
impl SignerCommon for SignerWrapper<DescriptorMultiXKey<ExtendedPrivKey>> {
fn id(&self, secp: &SecpCtx) -> SignerId {
SignerId::from(self.root_fingerprint(secp))
}
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
Some(DescriptorSecretKey::MultiXPrv(self.signer.clone()))
}
}
impl InputSigner for SignerWrapper<DescriptorMultiXKey<ExtendedPrivKey>> {
fn sign_input(
&self,
psbt: &mut psbt::PartiallySignedTransaction,
input_index: usize,
sign_options: &SignOptions,
secp: &SecpCtx,
) -> Result<(), SignerError> {
let xkeys = multikey_to_xkeys(self.signer.clone());
for xkey in xkeys {
SignerWrapper::new(xkey, self.ctx).sign_input(psbt, input_index, sign_options, secp)?
}
Ok(())
}
}
impl SignerCommon for SignerWrapper<PrivateKey> {
fn id(&self, secp: &SecpCtx) -> SignerId {
SignerId::from(self.public_key(secp).to_pubkeyhash(SigType::Ecdsa))
SignerId::from(self.public_key(secp).to_pubkeyhash())
}
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
Some(DescriptorSecretKey::Single(SinglePriv {
Some(DescriptorSecretKey::SinglePriv(DescriptorSinglePriv {
key: self.signer,
origin: None,
}))
@@ -518,16 +461,8 @@ impl InputSigner for SignerWrapper<PrivateKey> {
}
let (hash, hash_ty) = match self.ctx {
SignerContext::Segwitv0 => {
let (h, t) = Segwitv0::sighash(psbt, input_index, ())?;
let h = h.to_raw_hash();
(h, t)
}
SignerContext::Legacy => {
let (h, t) = Legacy::sighash(psbt, input_index, ())?;
let h = h.to_raw_hash();
(h, t)
}
SignerContext::Segwitv0 => Segwitv0::sighash(psbt, input_index, ())?,
SignerContext::Legacy => Legacy::sighash(psbt, input_index, ())?,
_ => return Ok(()), // handled above
};
sign_psbt_ecdsa(
@@ -537,7 +472,6 @@ impl InputSigner for SignerWrapper<PrivateKey> {
hash,
hash_ty,
secp,
sign_options.allow_grinding,
);
Ok(())
@@ -548,21 +482,16 @@ fn sign_psbt_ecdsa(
secret_key: &secp256k1::SecretKey,
pubkey: PublicKey,
psbt_input: &mut psbt::Input,
hash: impl bitcoin::hashes::Hash + bitcoin::secp256k1::ThirtyTwoByteHash,
hash: bitcoin::Sighash,
hash_ty: EcdsaSighashType,
secp: &SecpCtx,
allow_grinding: bool,
) {
let msg = &Message::from(hash);
let sig = if allow_grinding {
secp.sign_ecdsa_low_r(msg, secret_key)
} else {
secp.sign_ecdsa(msg, secret_key)
};
let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
let sig = secp.sign_ecdsa(msg, secret_key);
secp.verify_ecdsa(msg, &sig, &pubkey.inner)
.expect("invalid or corrupted ecdsa signature");
let final_signature = ecdsa::Signature { sig, hash_ty };
let final_signature = ecdsa::EcdsaSig { sig, hash_ty };
psbt_input.partial_sigs.insert(pubkey, final_signature);
}
@@ -572,24 +501,26 @@ fn sign_psbt_schnorr(
pubkey: XOnlyPublicKey,
leaf_hash: Option<taproot::TapLeafHash>,
psbt_input: &mut psbt::Input,
hash: TapSighash,
hash_ty: TapSighashType,
hash: taproot::TapSighashHash,
hash_ty: SchnorrSighashType,
secp: &SecpCtx,
) {
use schnorr::TapTweak;
let keypair = secp256k1::KeyPair::from_seckey_slice(secp, secret_key.as_ref()).unwrap();
let keypair = match leaf_hash {
None => keypair
.tap_tweak(secp, psbt_input.tap_merkle_root)
.to_inner(),
.into_inner(),
Some(_) => keypair, // no tweak for script spend
};
let msg = &Message::from(hash);
let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
let sig = secp.sign_schnorr(msg, &keypair);
secp.verify_schnorr(&sig, msg, &XOnlyPublicKey::from_keypair(&keypair).0)
secp.verify_schnorr(&sig, msg, &XOnlyPublicKey::from_keypair(&keypair))
.expect("invalid or corrupted schnorr signature");
let final_signature = taproot::Signature { sig, hash_ty };
let final_signature = schnorr::SchnorrSig { sig, hash_ty };
if let Some(lh) = leaf_hash {
psbt_input
@@ -639,7 +570,7 @@ impl SignersContainer {
self.0
.values()
.filter_map(|signer| signer.descriptor_secret_key())
.filter_map(|secret| secret.to_public(secp).ok().map(|public| (public, secret)))
.filter_map(|secret| secret.as_public(secp).ok().map(|public| (public, secret)))
.collect()
}
@@ -664,13 +595,8 @@ impl SignersContainer {
};
match secret {
DescriptorSecretKey::Single(private_key) => container.add_external(
SignerId::from(
private_key
.key
.public_key(secp)
.to_pubkeyhash(SigType::Ecdsa),
),
DescriptorSecretKey::SinglePriv(private_key) => container.add_external(
SignerId::from(private_key.key.public_key(secp).to_pubkeyhash()),
SignerOrdering::default(),
Arc::new(SignerWrapper::new(private_key.key, ctx)),
),
@@ -679,11 +605,6 @@ impl SignersContainer {
SignerOrdering::default(),
Arc::new(SignerWrapper::new(xprv, ctx)),
),
DescriptorSecretKey::MultiXPrv(xprv) => container.add_external(
SignerId::from(xprv.root_fingerprint(secp)),
SignerOrdering::default(),
Arc::new(SignerWrapper::new(xprv, ctx)),
),
};
}
@@ -797,15 +718,10 @@ pub struct SignOptions {
///
/// Defaults to `true`, i.e., we always try to sign with the taproot internal key.
pub sign_with_tap_internal_key: bool,
/// Whether we should grind ECDSA signature to ensure signing with low r
/// or not.
/// Defaults to `true`, i.e., we always grind ECDSA signature to sign with low r.
pub allow_grinding: bool,
}
/// Customize which taproot script-path leaves the signer should sign.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, PartialEq)]
pub enum TapLeavesOptions {
/// The signer will sign all the leaves it has a key for.
All,
@@ -835,7 +751,6 @@ impl Default for SignOptions {
try_finalize: true,
tap_leaves_options: TapLeavesOptions::default(),
sign_with_tap_internal_key: true,
allow_grinding: true,
}
}
}
@@ -854,7 +769,7 @@ pub(crate) trait ComputeSighash {
impl ComputeSighash for Legacy {
type Extra = ();
type Sighash = sighash::LegacySighash;
type Sighash = bitcoin::Sighash;
type SighashType = EcdsaSighashType;
fn sighash(
@@ -901,9 +816,19 @@ impl ComputeSighash for Legacy {
}
}
fn p2wpkh_script_code(script: &Script) -> Script {
ScriptBuilder::new()
.push_opcode(opcodes::all::OP_DUP)
.push_opcode(opcodes::all::OP_HASH160)
.push_slice(&script[2..])
.push_opcode(opcodes::all::OP_EQUALVERIFY)
.push_opcode(opcodes::all::OP_CHECKSIG)
.into_script()
}
impl ComputeSighash for Segwitv0 {
type Extra = ();
type Sighash = sighash::SegwitV0Sighash;
type Sighash = bitcoin::Sighash;
type SighashType = EcdsaSighashType;
fn sighash(
@@ -950,21 +875,14 @@ impl ComputeSighash for Segwitv0 {
Some(ref witness_script) => witness_script.clone(),
None => {
if utxo.script_pubkey.is_v0_p2wpkh() {
utxo.script_pubkey
.p2wpkh_script_code()
.expect("We check above that the spk is a p2wpkh")
p2wpkh_script_code(&utxo.script_pubkey)
} else if psbt_input
.redeem_script
.as_ref()
.map(|s| s.is_v0_p2wpkh())
.map(Script::is_v0_p2wpkh)
.unwrap_or(false)
{
psbt_input
.redeem_script
.as_ref()
.unwrap()
.p2wpkh_script_code()
.expect("We check above that the spk is a p2wpkh")
p2wpkh_script_code(psbt_input.redeem_script.as_ref().unwrap())
} else {
return Err(SignerError::MissingWitnessScript);
}
@@ -985,14 +903,14 @@ impl ComputeSighash for Segwitv0 {
impl ComputeSighash for Tap {
type Extra = Option<taproot::TapLeafHash>;
type Sighash = TapSighash;
type SighashType = TapSighashType;
type Sighash = taproot::TapSighashHash;
type SighashType = SchnorrSighashType;
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
input_index: usize,
extra: Self::Extra,
) -> Result<(Self::Sighash, TapSighashType), SignerError> {
) -> Result<(Self::Sighash, SchnorrSighashType), SignerError> {
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange);
}
@@ -1001,8 +919,8 @@ impl ComputeSighash for Tap {
let sighash_type = psbt_input
.sighash_type
.unwrap_or_else(|| TapSighashType::Default.into())
.taproot_hash_ty()
.unwrap_or_else(|| SchnorrSighashType::Default.into())
.schnorr_hash_ty()
.map_err(|_| SignerError::InvalidSighash)?;
let witness_utxos = (0..psbt.inputs.len())
.map(|i| psbt.get_utxo_for(i))
@@ -1063,9 +981,8 @@ mod signers_container_tests {
use crate::descriptor;
use crate::descriptor::IntoWalletDescriptor;
use crate::keys::{DescriptorKey, IntoDescriptorKey};
use assert_matches::assert_matches;
use bitcoin::bip32;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32;
use bitcoin::Network;
use miniscript::ScriptContext;
use std::str::FromStr;
@@ -1133,17 +1050,17 @@ mod signers_container_tests {
signers.add_external(id2.clone(), SignerOrdering(2), signer2.clone());
signers.add_external(id3.clone(), SignerOrdering(3), signer3.clone());
assert_matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1));
assert_matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2));
assert_matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3));
assert!(matches!(signers.find(id1), Some(signer) if is_equal(signer, &signer1)));
assert!(matches!(signers.find(id2), Some(signer) if is_equal(signer, &signer2)));
assert!(matches!(signers.find(id3.clone()), Some(signer) if is_equal(signer, &signer3)));
// The `signer4` has the same ID as `signer3` but lower ordering.
// It should be found by `id3` instead of `signer3`.
signers.add_external(id3.clone(), SignerOrdering(2), signer4.clone());
assert_matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4));
assert!(matches!(signers.find(id3), Some(signer) if is_equal(signer, &signer4)));
// Can't find anything with ID that doesn't exist
assert_matches!(signers.find(id_nonexistent), None);
assert!(matches!(signers.find(id_nonexistent), None));
}
#[derive(Debug, Clone, Copy)]

View File

@@ -18,7 +18,7 @@
//! # use bitcoin::*;
//! # use bdk::*;
//! # use bdk::wallet::tx_builder::CreateTx;
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! # let wallet = doctest_wallet!();
//! // create a TxBuilder from a wallet
//! let mut tx_builder = wallet.build_tx();
@@ -27,7 +27,7 @@
//! // Create a transaction with one output to `to_address` of 50_000 satoshi
//! .add_recipient(to_address.script_pubkey(), 50_000)
//! // With a custom fee rate of 5.0 satoshi/vbyte
//! .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0))
//! .fee_rate(FeeRate::from_sat_per_vb(5.0))
//! // Only spend non-change outputs
//! .do_not_spend_change()
//! // Turn on RBF signaling
@@ -41,8 +41,10 @@ use std::collections::HashSet;
use std::default::Default;
use std::marker::PhantomData;
use bitcoin::psbt::{self, PartiallySignedTransaction as Psbt};
use bitcoin::{absolute, script::PushBytes, OutPoint, ScriptBuf, Sequence, Transaction};
use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt};
use bitcoin::{OutPoint, Script, Transaction};
use miniscript::descriptor::DescriptorTrait;
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use crate::{database::BatchDatabase, Error, Utxo, Wallet};
@@ -79,7 +81,7 @@ impl TxBuilderContext for BumpFee {}
/// # use bitcoin::*;
/// # use core::str::FromStr;
/// # let wallet = doctest_wallet!();
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let addr2 = addr1.clone();
/// // chaining
/// let (psbt1, details) = {
@@ -126,9 +128,9 @@ pub struct TxBuilder<'a, D, Cs, Ctx> {
//TODO: TxParams should eventually be exposed publicly.
#[derive(Default, Debug, Clone)]
pub(crate) struct TxParams {
pub(crate) recipients: Vec<(ScriptBuf, u64)>,
pub(crate) recipients: Vec<(Script, u64)>,
pub(crate) drain_wallet: bool,
pub(crate) drain_to: Option<ScriptBuf>,
pub(crate) drain_to: Option<Script>,
pub(crate) fee_policy: Option<FeePolicy>,
pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) external_policy_path: Option<BTreeMap<String, Vec<usize>>>,
@@ -137,7 +139,7 @@ pub(crate) struct TxParams {
pub(crate) manually_selected_only: bool,
pub(crate) sighash: Option<psbt::PsbtSighashType>,
pub(crate) ordering: TxOrdering,
pub(crate) locktime: Option<absolute::LockTime>,
pub(crate) locktime: Option<u32>,
pub(crate) rbf: Option<RbfValue>,
pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy,
@@ -145,7 +147,7 @@ pub(crate) struct TxParams {
pub(crate) add_global_xpubs: bool,
pub(crate) include_output_redeem_witness_script: bool,
pub(crate) bumping_fee: Option<PreviousFee>,
pub(crate) current_height: Option<absolute::LockTime>,
pub(crate) current_height: Option<u32>,
pub(crate) allow_dust: bool,
}
@@ -241,10 +243,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
/// # use std::collections::BTreeMap;
/// # use bitcoin::*;
/// # use bdk::*;
/// # let to_address =
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
/// .unwrap()
/// .assume_checked();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let wallet = doctest_wallet!();
/// let mut path = BTreeMap::new();
/// path.insert("aabbccdd".to_string(), vec![0, 1]);
@@ -284,7 +283,6 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
for utxo in utxos {
let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
#[allow(deprecated)]
let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
self.params.utxos.push(WeightedUtxo {
satisfaction_weight,
@@ -428,7 +426,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
/// Use a specific nLockTime while creating the transaction
///
/// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator.
pub fn nlocktime(&mut self, locktime: absolute::LockTime) -> &mut Self {
pub fn nlocktime(&mut self, locktime: u32) -> &mut Self {
self.params.locktime = Some(locktime);
self
}
@@ -467,7 +465,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
self
}
/// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::psbt::Input::witness_utxo) field when spending from
/// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::util::psbt::Input::witness_utxo) field when spending from
/// SegWit descriptors.
///
/// This reduces the size of the PSBT, but some signers might reject them due to the lack of
@@ -477,8 +475,8 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
self
}
/// Fill-in the [`psbt::Output::redeem_script`](bitcoin::psbt::Output::redeem_script) and
/// [`psbt::Output::witness_script`](bitcoin::psbt::Output::witness_script) fields.
/// Fill-in the [`psbt::Output::redeem_script`](bitcoin::util::psbt::Output::redeem_script) and
/// [`psbt::Output::witness_script`](bitcoin::util::psbt::Output::witness_script) fields.
///
/// This is useful for signers which always require it, like ColdCard hardware wallets.
pub fn include_output_redeem_witness_script(&mut self) -> &mut Self {
@@ -543,7 +541,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
///
/// If the `nsequence` is higher than `0xFFFFFFFD` an error will be thrown, since it would not
/// be a valid nSequence to signal RBF.
pub fn enable_rbf_with_sequence(&mut self, nsequence: Sequence) -> &mut Self {
pub fn enable_rbf_with_sequence(&mut self, nsequence: u32) -> &mut Self {
self.params.rbf = Some(RbfValue::Value(nsequence));
self
}
@@ -560,8 +558,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
///
/// In both cases, if you don't provide a current height, we use the last sync height.
pub fn current_height(&mut self, height: u32) -> &mut Self {
self.params.current_height =
Some(absolute::LockTime::from_height(height).expect("Invalid height"));
self.params.current_height = Some(height);
self
}
@@ -576,20 +573,20 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
/// Replace the recipients already added with a new list
pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, u64)>) -> &mut Self {
pub fn set_recipients(&mut self, recipients: Vec<(Script, u64)>) -> &mut Self {
self.params.recipients = recipients;
self
}
/// Add a recipient to the internal list
pub fn add_recipient(&mut self, script_pubkey: ScriptBuf, amount: u64) -> &mut Self {
pub fn add_recipient(&mut self, script_pubkey: Script, amount: u64) -> &mut Self {
self.params.recipients.push((script_pubkey, amount));
self
}
/// Add data as an output, using OP_RETURN
pub fn add_data<T: AsRef<PushBytes>>(&mut self, data: &T) -> &mut Self {
let script = ScriptBuf::new_op_return(data);
pub fn add_data(&mut self, data: &[u8]) -> &mut Self {
let script = Script::new_op_return(data);
self.add_recipient(script, 0u64);
self
}
@@ -619,10 +616,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, C
/// # use bitcoin::*;
/// # use bdk::*;
/// # use bdk::wallet::tx_builder::CreateTx;
/// # let to_address =
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
/// .unwrap()
/// .assume_checked();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let wallet = doctest_wallet!();
/// let mut tx_builder = wallet.build_tx();
///
@@ -631,7 +625,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, C
/// .drain_wallet()
/// // Send the excess (which is all the coins minus the fee) to this address.
/// .drain_to(to_address.script_pubkey())
/// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0))
/// .fee_rate(FeeRate::from_sat_per_vb(5.0))
/// .enable_rbf();
/// let (psbt, tx_details) = tx_builder.finish()?;
/// # Ok::<(), bdk::Error>(())
@@ -641,7 +635,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, C
/// [`add_recipient`]: Self::add_recipient
/// [`add_utxos`]: Self::add_utxos
/// [`drain_wallet`]: Self::drain_wallet
pub fn drain_to(&mut self, script_pubkey: ScriptBuf) -> &mut Self {
pub fn drain_to(&mut self, script_pubkey: Script) -> &mut Self {
self.params.drain_to = Some(script_pubkey);
self
}
@@ -659,7 +653,7 @@ impl<'a, D: BatchDatabase> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpF
///
/// Returns an `Err` if `script_pubkey` can't be found among the recipients of the
/// transaction we are bumping.
pub fn allow_shrinking(&mut self, script_pubkey: ScriptBuf) -> Result<&mut Self, Error> {
pub fn allow_shrinking(&mut self, script_pubkey: Script) -> Result<&mut Self, Error> {
match self
.params
.recipients
@@ -709,7 +703,7 @@ impl TxOrdering {
#[cfg(not(test))]
let mut rng = rand::thread_rng();
#[cfg(test)]
let mut rng = rand::rngs::StdRng::seed_from_u64(12345);
let mut rng = rand::rngs::StdRng::seed_from_u64(0);
tx.output.shuffle(&mut rng);
}
@@ -742,13 +736,13 @@ impl Default for Version {
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub(crate) enum RbfValue {
Default,
Value(Sequence),
Value(u32),
}
impl RbfValue {
pub(crate) fn get_value(&self) -> Sequence {
pub(crate) fn get_value(&self) -> u32 {
match self {
RbfValue::Default => Sequence::ENABLE_RBF_NO_LOCKTIME,
RbfValue::Default => 0xFFFFFFFD,
RbfValue::Value(v) => *v,
}
}
@@ -798,7 +792,6 @@ mod test {
use bitcoin::consensus::deserialize;
use bitcoin::hashes::hex::FromHex;
use std::str::FromStr;
use super::*;
@@ -830,6 +823,8 @@ mod test {
#[test]
fn test_output_ordering_bip69() {
use std::str::FromStr;
let original_tx = ordering_test_tx!();
let mut tx = original_tx;
@@ -858,20 +853,15 @@ mod test {
);
assert_eq!(tx.output[0].value, 800);
assert_eq!(tx.output[1].script_pubkey, ScriptBuf::from(vec![0xAA]));
assert_eq!(
tx.output[2].script_pubkey,
ScriptBuf::from(vec![0xAA, 0xEE])
);
assert_eq!(tx.output[1].script_pubkey, From::from(vec![0xAA]));
assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE]));
}
fn get_test_utxos() -> Vec<LocalUtxo> {
use bitcoin::hashes::Hash;
vec![
LocalUtxo {
outpoint: OutPoint {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
txid: Default::default(),
vout: 0,
},
txout: Default::default(),
@@ -880,7 +870,7 @@ mod test {
},
LocalUtxo {
outpoint: OutPoint {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
txid: Default::default(),
vout: 1,
},
txout: Default::default(),

View File

@@ -9,11 +9,23 @@
// You may not use this file except in accordance with one or both of these
// licenses.
use bitcoin::blockdata::script::Script;
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::{absolute, Script, Sequence};
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
// MSB of the nSequence. If set there's no consensus-constraint, so it must be disabled when
// spending using CSV in order to enforce CSV rules
pub(crate) const SEQUENCE_LOCKTIME_DISABLE_FLAG: u32 = 1 << 31;
// When nSequence is lower than this flag the timelock is interpreted as block-height-based,
// otherwise it's time-based
pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
// Mask for the bits used to express the timelock
pub(crate) const SEQUENCE_LOCKTIME_MASK: u32 = 0x0000FFFF;
// Threshold for nLockTime to be considered a block-height-based timelock rather than time-based
pub(crate) const BLOCKS_TIMELOCK_THRESHOLD: u32 = 500000000;
/// Trait to check if a value is below the dust limit.
/// We are performing dust value calculation for a given script public key using rust-bitcoin to
/// keep it compatible with network dust rate
@@ -26,7 +38,7 @@ pub trait IsDust {
impl IsDust for u64 {
fn is_dust(&self, script: &Script) -> bool {
*self < script.dust_value().to_sat()
*self < script.dust_value().as_sat()
}
}
@@ -44,15 +56,19 @@ impl After {
}
}
pub(crate) fn check_nsequence_rbf(rbf: Sequence, csv: Sequence) -> bool {
// The RBF value must enable relative timelocks
if !rbf.is_relative_lock_time() {
pub(crate) fn check_nsequence_rbf(rbf: u32, csv: u32) -> bool {
// This flag cannot be set in the nSequence when spending using OP_CSV
if rbf & SEQUENCE_LOCKTIME_DISABLE_FLAG != 0 {
return false;
}
let mask = SEQUENCE_LOCKTIME_TYPE_FLAG | SEQUENCE_LOCKTIME_MASK;
let rbf = rbf & mask;
let csv = csv & mask;
// Both values should be represented in the same unit (either time-based or
// block-height based)
if rbf.is_time_locked() != csv.is_time_locked() {
if (rbf < SEQUENCE_LOCKTIME_TYPE_FLAG) != (csv < SEQUENCE_LOCKTIME_TYPE_FLAG) {
return false;
}
@@ -64,10 +80,24 @@ pub(crate) fn check_nsequence_rbf(rbf: Sequence, csv: Sequence) -> bool {
true
}
pub(crate) fn check_nlocktime(nlocktime: u32, required: u32) -> bool {
// Both values should be expressed in the same unit
if (nlocktime < BLOCKS_TIMELOCK_THRESHOLD) != (required < BLOCKS_TIMELOCK_THRESHOLD) {
return false;
}
// The value should be at least `required`
if nlocktime < required {
return false;
}
true
}
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
fn check_after(&self, n: absolute::LockTime) -> bool {
fn check_after(&self, n: u32) -> bool {
if let Some(current_height) = self.current_height {
current_height >= n.to_consensus_u32()
current_height >= n
} else {
self.assume_height_reached
}
@@ -95,15 +125,10 @@ impl Older {
}
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for Older {
fn check_older(&self, n: Sequence) -> bool {
fn check_older(&self, n: u32) -> bool {
if let Some(current_height) = self.current_height {
// TODO: test >= / >
current_height
>= self
.create_height
.unwrap_or(0)
.checked_add(n.to_consensus_u32())
.expect("Overflowing addition")
current_height as u64 >= self.create_height.unwrap_or(0) as u64 + n as u64
} else {
self.assume_height_reached
}
@@ -114,20 +139,16 @@ pub(crate) type SecpCtx = Secp256k1<All>;
#[cfg(test)]
mod test {
use super::{
check_nlocktime, check_nsequence_rbf, IsDust, BLOCKS_TIMELOCK_THRESHOLD,
SEQUENCE_LOCKTIME_TYPE_FLAG,
};
use crate::bitcoin::Address;
use std::str::FromStr;
// When nSequence is lower than this flag the timelock is interpreted as block-height-based,
// otherwise it's time-based
pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
use super::{check_nsequence_rbf, IsDust};
use crate::bitcoin::{Address, Network, Sequence};
#[test]
fn test_is_dust() {
let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe")
.unwrap()
.require_network(Network::Bitcoin)
.unwrap()
.script_pubkey();
assert!(script_p2pkh.is_p2pkh());
@@ -135,8 +156,6 @@ mod test {
assert!(!546.is_dust(&script_p2pkh));
let script_p2wpkh = Address::from_str("bc1qxlh2mnc0yqwas76gqq665qkggee5m98t8yskd8")
.unwrap()
.require_network(Network::Bitcoin)
.unwrap()
.script_pubkey();
assert!(script_p2wpkh.is_v0_p2wpkh());
@@ -146,40 +165,66 @@ mod test {
#[test]
fn test_check_nsequence_rbf_msb_set() {
let result = check_nsequence_rbf(Sequence(0x80000000), Sequence(5000));
let result = check_nsequence_rbf(0x80000000, 5000);
assert!(!result);
}
#[test]
fn test_check_nsequence_rbf_lt_csv() {
let result = check_nsequence_rbf(Sequence(4000), Sequence(5000));
let result = check_nsequence_rbf(4000, 5000);
assert!(!result);
}
#[test]
fn test_check_nsequence_rbf_different_unit() {
let result =
check_nsequence_rbf(Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000), Sequence(5000));
let result = check_nsequence_rbf(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000, 5000);
assert!(!result);
}
#[test]
fn test_check_nsequence_rbf_mask() {
let result = check_nsequence_rbf(Sequence(0x3f + 10_000), Sequence(5000));
let result = check_nsequence_rbf(0x3f + 10_000, 5000);
assert!(result);
}
#[test]
fn test_check_nsequence_rbf_same_unit_blocks() {
let result = check_nsequence_rbf(Sequence(10_000), Sequence(5000));
let result = check_nsequence_rbf(10_000, 5000);
assert!(result);
}
#[test]
fn test_check_nsequence_rbf_same_unit_time() {
let result = check_nsequence_rbf(
Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 10_000),
Sequence(SEQUENCE_LOCKTIME_TYPE_FLAG + 5000),
SEQUENCE_LOCKTIME_TYPE_FLAG + 10_000,
SEQUENCE_LOCKTIME_TYPE_FLAG + 5000,
);
assert!(result);
}
#[test]
fn test_check_nlocktime_lt_cltv() {
let result = check_nlocktime(4000, 5000);
assert!(!result);
}
#[test]
fn test_check_nlocktime_different_unit() {
let result = check_nlocktime(BLOCKS_TIMELOCK_THRESHOLD + 5000, 5000);
assert!(!result);
}
#[test]
fn test_check_nlocktime_same_unit_blocks() {
let result = check_nlocktime(10_000, 5000);
assert!(result);
}
#[test]
fn test_check_nlocktime_same_unit_time() {
let result = check_nlocktime(
BLOCKS_TIMELOCK_THRESHOLD + 10_000,
BLOCKS_TIMELOCK_THRESHOLD + 5000,
);
assert!(result);
}

View File

@@ -91,12 +91,7 @@ pub enum VerifyError {
impl fmt::Display for VerifyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::MissingInputTx(txid) => write!(f, "The transaction being spent is not available in the database or the blockchain client: {}", txid),
Self::InvalidInput(outpoint) => write!(f, "The transaction being spent doesn't have the requested output: {}", outpoint),
Self::Consensus(err) => write!(f, "Consensus error: {:?}", err),
Self::Global(err) => write!(f, "Generic error: {}", err),
}
write!(f, "{:?}", self)
}
}
@@ -113,7 +108,6 @@ impl_error!(bitcoinconsensus::Error, Consensus, VerifyError);
mod test {
use super::*;
use crate::database::{BatchOperations, MemoryDatabase};
use assert_matches::assert_matches;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex;
use bitcoin::{Transaction, Txid};
@@ -143,7 +137,9 @@ mod test {
}
let result = verify_tx(&signed_tx, &database, &blockchain);
assert_matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid(),
assert!(result.is_err(), "Should fail with missing input tx");
assert!(
matches!(result, Err(VerifyError::MissingInputTx(txid)) if txid == prev_tx.txid()),
"Error should be a `MissingInputTx` error"
);
@@ -151,9 +147,9 @@ mod test {
database.set_raw_tx(&prev_tx).unwrap();
let result = verify_tx(&unsigned_tx, &database, &blockchain);
assert_matches!(
result,
Err(VerifyError::Consensus(_)),
assert!(result.is_err(), "Should fail since the TX is unsigned");
assert!(
matches!(result, Err(VerifyError::Consensus(_))),
"Error should be a `Consensus` error"
);