Compare commits

..

3 Commits

Author SHA1 Message Date
Steve Myers
d288cbbbbc Bump version to 0.25.0 2022-12-05 14:28:30 -06:00
Steve Myers
005447c81e Downgrade ubuntu to 20.04 for test_hardware_wallet CI job 2022-12-05 13:54:05 -06:00
Steve Myers
5168d41a93 Bump version to 0.25.0-rc.1 2022-11-30 09:08:37 -08:00
59 changed files with 904 additions and 2004 deletions

View File

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

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,27 +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 tokio-util --precise "0.7.8"
cargo update -p cc --precise "1.0.81"
cargo update -p rustls:0.20.9 --precise "0.20.8"
cargo update -p rustls:0.21.7 --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:0.100.3 --precise "0.100.1"
cargo update -p rustls-webpki:0.101.6 --precise "0.101.1"
cargo update -p byteorder --precise "1.4.3"
cargo update -p webpki --precise "0.22.2"
- name: Build
run: cargo build --features ${{ matrix.features }} --no-default-features
- name: Clippy
@@ -175,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
@@ -183,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,dev-getrandom-wasm --no-default-features
fmt:
name: Rust fmt
@@ -207,8 +178,8 @@ jobs:
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
@@ -228,26 +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 tokio-util --precise "0.7.8"
cargo update -p cc --precise "1.0.81"
cargo update -p rustls:0.20.9 --precise "0.20.8"
cargo update -p rustls:0.21.7 --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:0.100.3 --precise "0.100.1"
cargo update -p rustls-webpki:0.101.6 --precise "0.101.1"
cargo update -p byteorder --precise "1.4.3"
cargo update -p webpki --precise "0.22.2"
- 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,202 +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.29.0]
### Summary
This maintenance release updates our `rust-bitcoin` dependency to 0.30.x and fixes a wallet balance bug when a wallet has more than one coinbase transaction.
### Changed
- Update rust-bitcoin to 0.30 #1071
### Fixed
- Fix a bug when syncing coinbase utxos on electrum #1090
## [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.
@@ -209,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)
@@ -220,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`
@@ -236,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.
@@ -246,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`.
@@ -267,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`.
@@ -319,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
@@ -348,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
@@ -372,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
@@ -388,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
@@ -417,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
@@ -454,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
@@ -682,15 +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
[v0.29.0]: https://github.com/bitcoindevkit/bdk/compare/v0.28.2...v0.29.0
[Unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.29.0...HEAD

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk"
version = "0.29.0"
version = "0.25.0"
edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org"
@@ -13,31 +13,31 @@ 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 = "8.0", features = ["serde"] }
bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
rand = "^0.8"
# 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.12", optional = true }
esplora-client = { version = "0.2", 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.3.0", 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.16", optional = true }
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@@ -52,10 +52,7 @@ js-sys = "0.3"
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"]
@@ -107,12 +104,11 @@ test-hardware-signer = ["hardware-signer"]
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.21"
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
base64 = "^0.13"
[[example]]
name = "compact_filters_balance"

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(),
)?;
@@ -96,16 +95,15 @@ use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
use bitcoin::base64;
use bdk::bitcoin::consensus::serialize;
use bdk::bitcoin::Network;
use base64;
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(())
}
@@ -134,22 +132,21 @@ fn main() -> Result<(), bdk::Error> {
```rust,no_run
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use bitcoin::base64;
use bdk::bitcoin::consensus::deserialize;
use bdk::bitcoin::{psbt::Psbt, Network};
use base64;
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(())
}
@@ -201,48 +198,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 1.30 has MSRV 1.63.0+
cargo update -p tokio --precise "1.29.1"
# tokio-util 0.7.9 doesn't build with MSRV 1.57.0
cargo update -p tokio-util --precise "0.7.8"
# 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.20.9 has MSRV 1.60.0+
cargo update -p rustls:0.20.9 --precise "0.20.8"
# rustls 0.21.2 has MSRV 1.60.0+
cargo update -p rustls:0.21.7 --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:0.100.3 --precise "0.100.1"
# rustls-webpki 0.101.6 has MSRV 1.60+
cargo update -p rustls-webpki:0.101.6 --precise "0.101.1"
# byteorder 1.5.0 has MSRV 1.60.0+
cargo update -p byteorder --precise "1.4.3"
# webpki 0.22.4 requires `ring:0.17.2` which has MSRV 1.61.0+
cargo update -p webpki --precise "0.22.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

@@ -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,6 +1,6 @@
use std::str::FromStr;
use bdk::bitcoin::bip32::ExtendedPrivKey;
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
use bdk::bitcoin::Network;
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase;
@@ -10,7 +10,7 @@ use bdk::{KeychainKind, SyncOptions, Wallet};
use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex;
use bitcoin::bip32;
use bitcoin::util::bip32;
pub mod utils;

View File

@@ -9,7 +9,7 @@ use bdk::{
KeychainKind, SyncOptions, Wallet,
};
use bitcoin::{
bip32::{self, ExtendedPrivKey},
util::bip32::{self, ExtendedPrivKey},
Network,
};

View File

@@ -9,7 +9,7 @@ use bdk::{
KeychainKind, SyncOptions, Wallet,
};
use bitcoin::{
bip32::{self, ExtendedPrivKey},
util::bip32::{self, ExtendedPrivKey},
Network,
};

View File

@@ -1,8 +1,7 @@
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::hwi::{types::HWIChain, HWIClient};
use bdk::signer::SignerOrdering;
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
@@ -24,27 +23,26 @@ 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)?;
let devices = HWIClient::enumerate()?;
let first_device = devices
.first()
.expect("No devices found. Either plug in a hardware wallet, or start a simulator.");
// ...and creating a client out of the first one
let client = HWIClient::get_client(&first_device, true, Network::Testnet.into())?;
let client = HWIClient::get_client(first_device, true, HWIChain::Test)?;
println!("Look what I found, a {}!", first_device.model);
// Getting the HW's public descriptors
let descriptors = client.get_descriptors::<Descriptor<DescriptorPublicKey>>(None)?;
let descriptors = client.get_descriptors(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 custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
let mut wallet = Wallet::new(
descriptors.receive[0].clone(),
Some(descriptors.internal[0].clone()),
&descriptors.receive[0],
Some(&descriptors.internal[0]),
Network::Testnet,
MemoryDatabase::default(),
)?;
@@ -77,8 +75,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(());
}
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?
.require_network(Network::Testnet)?;
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?;
let (mut psbt, _details) = {
let mut builder = wallet.build_tx();
builder

View File

@@ -6,8 +6,8 @@
// 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::util::bip32::DerivationPath;
use bdk::bitcoin::Network;
use bdk::descriptor;
use bdk::descriptor::IntoWalletDescriptor;

View File

@@ -92,8 +92,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

@@ -13,10 +13,7 @@ pub(crate) mod tx {
// 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();
let to_address = Address::from_str(recipient_address).unwrap();
// Set recipient of the transaction
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);

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,7 +190,6 @@ impl_from!(boxed rpc::RpcBlockchain, AnyBlockchain, Rpc, #[cfg(feature = "rpc")]
/// socks5: None,
/// timeout: None,
/// stop_gap: 20,
/// validate_domain: true,
/// })
/// );
/// # }

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);
}
@@ -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;
@@ -242,7 +242,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);
@@ -229,12 +228,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 +260,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(),
@@ -293,23 +292,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 +381,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 +433,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 +496,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 +506,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 +546,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 +556,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 +566,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 +584,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 +641,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 +653,7 @@ impl CfStore {
&first_key,
(
BundleStatus::Init,
filter.filter_header(&FilterHeader::from_raw_hash(Hash::all_zeros())),
filter.filter_header(&FilterHeader::from_hash(Hash::all_zeros())),
)
.serialize(),
)?;

View File

@@ -13,11 +13,11 @@ 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 +136,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)?;

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);
}
}
@@ -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};
@@ -135,12 +135,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};
@@ -118,12 +117,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();
let batch_update = loop {
@@ -132,7 +129,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

@@ -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::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;
@@ -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>,
@@ -667,7 +665,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 +693,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,7 +805,7 @@ 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());
let desc = format!("raw({})", script.to_hex());
format!("{}#{}", desc, calc_checksum(&desc).unwrap())
}
@@ -963,7 +961,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.at_derivation_index(index).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: bitcoin::PackedLockTime(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;
@@ -162,7 +162,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 +187,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 +254,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 +268,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 +286,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 +295,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 +362,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 +658,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 +666,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 +708,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 +734,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 +744,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 +758,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 +820,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 +849,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 +868,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;
@@ -1115,7 +1115,7 @@ pub mod test {
let mut db = get_database();
let script = ScriptBuf::from(
let script = Script::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
@@ -1137,10 +1137,12 @@ pub mod test {
let child: u32 = row.get(1).unwrap();
let count: usize = row.get(2).unwrap();
assert_eq!(
count, 1,
assert!(
count == 1,
"keychain={}, child={}, count={}",
keychain, child, count
keychain,
child,
count
);
}
}

View File

@@ -133,7 +133,6 @@ pub fn get_checksum(desc: &str) -> Result<String, DescriptorError> {
mod test {
use super::*;
use crate::descriptor::calc_checksum;
use assert_matches::assert_matches;
// test calc_checksum() function; it should return the same value as Bitcoin Core
#[test]
@@ -156,16 +155,16 @@ mod test {
assert_eq!(calc_checksum(desc).unwrap(), "lasegmfs");
let desc = "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)#tqz0nc26";
assert_matches!(
calc_checksum(desc),
Err(DescriptorError::InvalidDescriptorChecksum)
);
assert!(matches!(
calc_checksum(desc).err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
let desc = "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)#lasegmsf";
assert_matches!(
calc_checksum(desc),
Err(DescriptorError::InvalidDescriptorChecksum)
);
assert!(matches!(
calc_checksum(desc).err(),
Some(DescriptorError::InvalidDescriptorChecksum)
));
}
#[test]
@@ -173,9 +172,9 @@ mod test {
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!(
calc_checksum(&invalid_desc).err(),
Some(DescriptorError::InvalidDescriptorCharacter(invalid_char)) if invalid_char == sparkle_heart.as_bytes()[0]
));
}
}

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,7 +700,7 @@ 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, $crate::bitcoin::PackedLockTime($value)) // TODO!! https://github.com/rust-bitcoin/rust-bitcoin/issues/1302
});
( older ( $value:expr ) ) => ({
$crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!!
@@ -794,6 +793,7 @@ macro_rules! fragment {
#[cfg(test)]
mod test {
use bitcoin::hashes::hex::ToHex;
use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use miniscript::{Descriptor, Legacy, Segwitv0};
@@ -802,8 +802,8 @@ mod test {
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;
// test the descriptor!() macro
@@ -819,15 +819,18 @@ mod test {
assert_eq!(desc.is_witness(), is_witness);
assert_eq!(!desc.has_wildcard(), 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.has_wildcard() {
desc.at_derivation_index(0)
} else {
desc.at_derivation_index(index)
};
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 +1175,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

@@ -16,17 +16,17 @@
use std::collections::BTreeMap;
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey};
use bitcoin::{psbt, taproot};
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::util::{psbt, taproot};
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
use bitcoin::{Network, TxOut};
use miniscript::descriptor::{
DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType,
DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard,
DefiniteDescriptorKey, DescriptorSecretKey, 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};
@@ -57,16 +57,16 @@ pub type DerivedDescriptor = Descriptor<DefiniteDescriptorKey>;
/// 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 {
@@ -134,10 +134,14 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
network: Network,
}
impl<'s, 'd> miniscript::Translator<DescriptorPublicKey, String, DescriptorError>
impl<'s, 'd>
miniscript::Translator<DescriptorPublicKey, miniscript::DummyKey, DescriptorError>
for Translator<'s, 'd>
{
fn pk(&mut self, pk: &DescriptorPublicKey) -> Result<String, DescriptorError> {
fn pk(
&mut self,
pk: &DescriptorPublicKey,
) -> Result<miniscript::DummyKey, DescriptorError> {
let secp = &self.secp;
let (_, _, networks) = if self.descriptor.is_taproot() {
@@ -155,7 +159,7 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
};
if networks.contains(&self.network) {
Ok(Default::default())
Ok(miniscript::DummyKey)
} else {
Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
@@ -163,40 +167,35 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
fn sha256(
&mut self,
_sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256,
) -> Result<String, DescriptorError> {
) -> Result<miniscript::DummySha256Hash, DescriptorError> {
Ok(Default::default())
}
fn hash256(
&mut self,
_hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256,
) -> Result<String, DescriptorError> {
) -> Result<miniscript::DummyHash256Hash, DescriptorError> {
Ok(Default::default())
}
fn ripemd160(
&mut self,
_ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160,
) -> Result<String, DescriptorError> {
) -> Result<miniscript::DummyRipemd160Hash, DescriptorError> {
Ok(Default::default())
}
fn hash160(
&mut self,
_hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160,
) -> Result<String, DescriptorError> {
) -> Result<miniscript::DummyHash160Hash, DescriptorError> {
Ok(Default::default())
}
}
// check the network for the keys
use miniscript::TranslateErr;
match self.0.translate_pk(&mut Translator {
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()),
}
})?;
Ok(self)
}
@@ -250,12 +249,7 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
}
// 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()),
};
let translated = desc.translate_pk(&mut Translator { network })?;
// ...and in the key map
let fixed_keymap = keymap
.into_iter()
@@ -306,10 +300,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()?;
@@ -348,18 +338,6 @@ pub(crate) trait XKeyUtils {
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,
@@ -514,10 +492,7 @@ impl DescriptorMeta for ExtendedDescriptor {
false
});
path_found.map(|path| {
self.at_derivation_index(path)
.expect("We ignore hardened wildcards")
})
path_found.map(|path| self.at_derivation_index(path))
}
fn derive_from_hd_keypaths<'s>(
@@ -568,7 +543,7 @@ impl DescriptorMeta for ExtendedDescriptor {
return None;
}
let descriptor = self.at_derivation_index(0).expect("0 is not hardened");
let descriptor = self.at_derivation_index(0);
match descriptor.desc_type() {
// TODO: add pk() here
DescriptorType::Pkh
@@ -606,11 +581,11 @@ impl DescriptorMeta for ExtendedDescriptor {
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 +596,7 @@ mod test {
"wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
)
.unwrap();
let psbt = Psbt::deserialize(
let psbt: psbt::PartiallySignedTransaction = deserialize(
&Vec::<u8>::from_hex(
"70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\
11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\
@@ -644,7 +619,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 +650,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 +674,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\
@@ -788,11 +763,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 +807,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 +851,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);
@@ -888,9 +873,9 @@ mod test {
let (descriptor, _) =
into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
let descriptor = descriptor.at_derivation_index(0).unwrap();
let descriptor = descriptor.at_derivation_index(0);
let script = ScriptBuf::from_hex("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
let script = Script::from_str("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
let mut psbt_input = psbt::Input::default();
psbt_input

View File

@@ -43,9 +43,9 @@ 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::util::bip32::Fingerprint;
use bitcoin::{LockTime, PublicKey, Sequence, XOnlyPublicKey};
use miniscript::descriptor::{
DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
@@ -66,7 +66,7 @@ use crate::wallet::utils::{After, Older, SecpCtx};
use super::checksum::calc_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
@@ -93,9 +93,6 @@ impl PkOrF {
..
}) => PkOrF::XOnlyPubkey(*pk),
DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)),
DescriptorPublicKey::MultiXPub(multi) => {
PkOrF::Fingerprint(multi.root_fingerprint(secp))
}
}
}
}
@@ -132,7 +129,7 @@ pub enum SatisfiableItem {
/// Absolute timeclock timestamp
AbsoluteTimelock {
/// The timelock value
value: absolute::LockTime,
value: LockTime,
},
/// Relative timelock locktime
RelativeTimelock {
@@ -452,14 +449,11 @@ pub struct Condition {
pub csv: Option<Sequence>,
/// Optional timelock condition
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<absolute::LockTime>,
pub timelock: Option<LockTime>,
}
impl Condition {
fn merge_nlocktime(
a: absolute::LockTime,
b: absolute::LockTime,
) -> Result<absolute::LockTime, PolicyError> {
fn merge_nlocktime(a: LockTime, b: LockTime) -> Result<LockTime, PolicyError> {
if !a.is_same_unit(b) {
Err(PolicyError::MixedTimelockUnits)
} else if a > b {
@@ -518,14 +512,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 +652,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 +664,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 +680,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())
@@ -752,7 +734,6 @@ fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
..
}) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
DescriptorPublicKey::MultiXPub(xpub) => xpub.root_fingerprint(secp).into(),
}
}
@@ -790,9 +771,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,
@@ -810,13 +791,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,
}
}
})
}
@@ -922,12 +896,12 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
}
Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
value: (*value).into(),
value: value.into(),
}
.into();
policy.contribution = Satisfaction::Complete {
condition: Condition {
timelock: Some((*value).into()),
timelock: Some(value.into()),
csv: None,
},
};
@@ -939,9 +913,9 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
{
let after = After::new(Some(current_height), false);
let after_sat =
Satisfier::<bitcoin::PublicKey>::check_after(&after, (*value).into());
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())
Satisfier::<bitcoin::PublicKey>::check_after(&sat, value.into())
});
if after_sat && inputs_sat {
policy.satisfaction = policy.contribution.clone();
@@ -1165,9 +1139,8 @@ mod test {
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 std::str::FromStr;
use std::sync::Arc;
@@ -1209,8 +1182,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 +1195,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 +1217,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 +1248,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 +1281,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 +1313,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])
)
);
}
@@ -1358,8 +1345,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
@@ -1371,8 +1358,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)
);
}
// single key, 1 prv and 1 pub key descriptor, required 1 prv keys
@@ -1393,15 +1382,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])
)
);
}
@@ -1433,14 +1425,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))
)
);
}
@@ -1586,7 +1582,6 @@ mod test {
let addr = wallet_desc
.at_derivation_index(0)
.unwrap()
.address(Network::Testnet)
.unwrap();
assert_eq!(
@@ -1604,9 +1599,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 +1613,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 +1625,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]
)
);
}
@@ -1653,7 +1654,6 @@ mod test {
let addr = wallet_desc
.at_derivation_index(0)
.unwrap()
.address(Network::Testnet)
.unwrap();
assert_eq!(
@@ -1673,9 +1673,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 +1691,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 +1711,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 +1790,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 +1887,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 {
@@ -592,41 +470,41 @@ mod test {
use super::*;
use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks;
use assert_matches::assert_matches;
use bitcoin::network::constants::Network::Regtest;
use miniscript::descriptor::{DescriptorPublicKey, 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 +512,20 @@ 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 (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);
for i in 0..expected.len() {
let index = i as u32;
let child_desc = if !desc.has_wildcard() {
desc.at_derivation_index(0).unwrap()
desc.at_derivation_index(0)
} else {
desc.at_derivation_index(index).unwrap()
desc.at_derivation_index(index)
};
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 +539,7 @@ mod test {
check(
P2Pkh(prvkey).build(Network::Bitcoin),
false,
false,
true,
Network::Regtest,
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
);
@@ -677,9 +550,7 @@ mod test {
check(
P2Pkh(pubkey).build(Network::Bitcoin),
false,
false,
true,
Network::Regtest,
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
);
}
@@ -693,9 +564,7 @@ mod test {
check(
P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
);
@@ -706,9 +575,7 @@ mod test {
check(
P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
);
}
@@ -722,9 +589,7 @@ mod test {
check(
P2Wpkh(prvkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
);
@@ -735,52 +600,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 +623,6 @@ mod test {
Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
"n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
@@ -804,14 +634,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 +650,6 @@ mod test {
Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
"ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
@@ -835,13 +661,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 +676,6 @@ mod test {
Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
"2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
@@ -865,14 +687,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 +703,6 @@ mod test {
Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
"2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
@@ -896,13 +714,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 +729,6 @@ mod test {
Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
"bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
@@ -926,14 +740,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 +756,6 @@ mod test {
Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
"bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp",
@@ -953,67 +763,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

@@ -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),
@@ -108,17 +106,17 @@ pub enum 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),
@@ -159,18 +157,6 @@ pub enum MiniscriptPsbtError {
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 +169,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)
}
}
@@ -307,12 +206,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,8 +19,8 @@ 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::{
@@ -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(
@@ -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 },
/// };
///
@@ -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;
@@ -227,7 +227,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 +256,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;

View File

@@ -12,7 +12,7 @@
//! 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`?
@@ -22,7 +22,7 @@ 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

@@ -10,13 +10,13 @@
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, PackedLockTime, Script, Sequence, 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 +45,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 +107,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
@@ -147,7 +143,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 +161,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 +170,41 @@ 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(),
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::all_zeros().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: PackedLockTime(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]),
witness: Witness::from_vec(vec![witness_reserved_value]),
}],
output: vec![],
};
@@ -233,7 +225,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 +245,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 +265,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 +317,9 @@ impl TestClient {
&self
.get_new_address(None, address_type)
.unwrap()
.assume_checked()
.to_string(),
)
.unwrap()
.assume_checked()
}
}
@@ -389,7 +378,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 +386,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 +1058,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 +1066,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 +1086,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 +1165,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
@@ -1228,7 +1215,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 +1407,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 +1421,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

@@ -106,7 +106,7 @@ macro_rules! testutils {
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.at_derivation_index($child).address(bitcoin::Network::Regtest).expect("No address form")
});
( @internal $descriptors:expr, $child:expr ) => ({
use $crate::bitcoin::secp256k1::Secp256k1;
@@ -146,7 +146,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,
);

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.
@@ -230,6 +230,7 @@ pub struct TransactionDetails {
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

@@ -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;
@@ -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,
@@ -845,7 +836,7 @@ mod test {
.unwrap(),
txout: TxOut {
value: rng.gen_range(0..200000000),
script_pubkey: ScriptBuf::new(),
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,
@@ -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()
@@ -1249,7 +1240,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 +1265,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 +1285,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 +1304,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 +1334,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 +1363,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 +1394,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 +1430,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 +1470,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 +1501,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 +1520,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

@@ -20,16 +20,13 @@
//! # use bdk::wallet::hardwaresigner::HWISigner;
//! # use bdk::wallet::AddressIndex::New;
//! # use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
//! # use hwi::HWIClient;
//! # use hwi::{types::HWIChain, 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 devices = HWIClient::enumerate()?;
//! let first_device = devices.first().expect("No devices found!");
//! let custom_signer = HWISigner::from_device(first_device, HWIChain::Test)?;
//!
//! # let mut wallet = Wallet::new(
//! # "",
@@ -49,9 +46,9 @@
//! # }
//! ```
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,17 +86,18 @@ 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, DescriptorXKey, KeyMap, SinglePriv,
SinglePubKey,
};
use miniscript::{Legacy, Segwitv0, SigType, Tap, ToPublicKey};
@@ -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,49 +367,6 @@ 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))
@@ -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(
@@ -548,12 +483,12 @@ 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 msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
let sig = if allow_grinding {
secp.sign_ecdsa_low_r(msg, secret_key)
} else {
@@ -562,7 +497,7 @@ fn sign_psbt_ecdsa(
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,10 +507,12 @@ 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
@@ -584,12 +521,12 @@ fn sign_psbt_schnorr(
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)
.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
@@ -679,11 +616,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)),
),
};
}
@@ -854,7 +786,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 +833,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 +892,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 +920,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 +936,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 +998,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 +1067,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,8 @@ 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::{LockTime, OutPoint, Script, Sequence, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use crate::{database::BatchDatabase, Error, Utxo, Wallet};
@@ -79,7 +79,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 +126,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 +137,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<LockTime>,
pub(crate) rbf: Option<RbfValue>,
pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy,
@@ -145,7 +145,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<LockTime>,
pub(crate) allow_dust: bool,
}
@@ -241,10 +241,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 +281,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 +424,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: LockTime) -> &mut Self {
self.params.locktime = Some(locktime);
self
}
@@ -467,7 +463,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 +473,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 {
@@ -560,8 +556,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(LockTime::from_height(height).expect("Invalid height"));
self
}
@@ -576,20 +571,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 +614,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 +623,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 +633,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 +651,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
@@ -798,7 +790,6 @@ mod test {
use bitcoin::consensus::deserialize;
use bitcoin::hashes::hex::FromHex;
use std::str::FromStr;
use super::*;
@@ -830,6 +821,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,11 +851,8 @@ 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> {
@@ -871,7 +861,7 @@ mod test {
vec![
LocalUtxo {
outpoint: OutPoint {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
txid: bitcoin::Txid::from_inner([0; 32]),
vout: 0,
},
txout: Default::default(),
@@ -880,7 +870,7 @@ mod test {
},
LocalUtxo {
outpoint: OutPoint {
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
txid: bitcoin::Txid::from_inner([0; 32]),
vout: 1,
},
txout: Default::default(),

View File

@@ -10,7 +10,7 @@
// licenses.
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::{absolute, Script, Sequence};
use bitcoin::{LockTime, Script, Sequence};
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey};
@@ -65,7 +65,7 @@ pub(crate) fn check_nsequence_rbf(rbf: Sequence, csv: Sequence) -> bool {
}
impl<Pk: MiniscriptKey + ToPublicKey> Satisfier<Pk> for After {
fn check_after(&self, n: absolute::LockTime) -> bool {
fn check_after(&self, n: LockTime) -> bool {
if let Some(current_height) = self.current_height {
current_height >= n.to_consensus_u32()
} else {
@@ -114,20 +114,17 @@ pub(crate) type SecpCtx = Secp256k1<All>;
#[cfg(test)]
mod test {
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};
use crate::bitcoin::{Address, Sequence};
use std::str::FromStr;
#[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 +132,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());

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"
);