Compare commits

...

45 Commits

Author SHA1 Message Date
Daniela Brozzoni
2a8c8c2bb6
Merge bitcoindevkit/bdk#1160: ci (maintenance): Pin byteorder, webpki to keep the MSRV
67b083fa03f116effe787fd7c1ba17c739d963ce ci: Pin byteorder, webpki to keep the MSRV (Daniela Brozzoni)

Pull request description:

ACKs for top commit:
  evanlinjin:
    ACK 67b083fa03f116effe787fd7c1ba17c739d963ce

Tree-SHA512: d3aac6be55d664147e1c31e31dedd38b8e0683a5c55858df7aa9d4e3300f87e74ade8b9c638a3e505bd943c4cb849e2f0f6b385ab83df430d971df3a8ac61370
2023-10-11 13:15:52 +02:00
Daniela Brozzoni
67b083fa03
ci: Pin byteorder, webpki to keep the MSRV 2023-10-10 13:41:27 +02:00
Steve Myers
213c270ab4
ci: update MSRV version pinning in workflow and README 2023-10-05 12:00:49 -05:00
Steve Myers
7914ff03d3
Bump version to 0.29.0 2023-10-05 10:42:36 -05:00
Steve Myers
e3ca356cae
Merge bitcoindevkit/bdk#1090: fix(electrum): Don't ignore multiple coinbase txs
530ba36b07f2cddad90b844ef1a56cb27ee1cf74 ci: fix msrv dependency versions for reqest and h2 (Daniela Brozzoni)
7a359d5eef66e60e0ab604fb0d043c2db260252d fix(electrum): Don't ignore multiple coinbase txs (Daniela Brozzoni)

Pull request description:

  We would previously insert just one coinbase transaction in the database if we caught multiple in the same sync.
  When we sync with electrum, before committing to the database, we remove from the update conflicting transactions, using the `make_txs_consistent` function. This function considers two txs to be conflicting if they spend from the same outpoint - but every coinbase transaction spends from the same outpoint!
  Here we make sure to avoid filtering out coinbase transactions, by adding a check on the txid just before we do the filtering.

  Fixes #1051

  ### Changelog notice

  - Fix a bug when syncing coinbase utxos on electrum

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### Bugfixes:

  * [x] I've added tests to reproduce the issue which are now passing
  * [x] I'm linking the issue being fixed by this PR

ACKs for top commit:
  notmandatory:
    tACK 530ba36b07f2cddad90b844ef1a56cb27ee1cf74

Tree-SHA512: ebbc6af86d4433ac4083212033a23f183d109641db345cc06ab4d506995ab71657761351c03772462ab4ff0d081226ecc97f1042490194aaf8661914cbeb72cb
2023-08-22 12:29:46 -05:00
Daniela Brozzoni
530ba36b07
ci: fix msrv dependency versions for reqest and h2
- reqwest 0.11.19 has MSRV 1.63.0+, pin to 0.11.18
- h2 0.3.21 has MSRV 1.63.0+, pin to 0.3.20
- rustls-webpki has MSRV 1.60.0+, pin to 0.100.1
2023-08-22 17:11:36 +02:00
Daniela Brozzoni
7a359d5eef
fix(electrum): Don't ignore multiple coinbase txs
We would previously insert just one coinbase transaction in the database
if we caught multiple in the same sync.
When we sync with electrum, before committing to the database, we remove
from the update conflicting transactions, using the
`make_txs_consistent` function. This function considers two txs to be
conflicting if they spend from the same outpoint - but every coinbase
transaction spends from the same outpoint!
Here we make sure to avoid filtering out coinbase transactions, by
adding a check on the txid just before we do the filtering.

Fixes #1051
2023-08-22 16:42:37 +02:00
Steve Myers
d1af252ac4
docs: add new README.md MSRV update instructions 2023-08-16 14:01:16 -05:00
Daniela Brozzoni
dcc46362dc
ci: Pin even more dependencies for MSRV
- Specify which version of zip needs to be updated
- Pin rustls
- Pin flate2
2023-08-16 16:09:07 +02:00
Daniela Brozzoni
4d48a07717
fix: Explicitly deny multipath keys
Although there is *some* code to handle multipath keys inside bdk,
it's all untested, and from a few quick tests it
seems that it's pretty easy to find buggy edge cases.
Better to deny multipath descs for now, and revisit the
decision once we work on supporting multidescriptor wallets.
2023-08-16 15:02:52 +02:00
Daniela Brozzoni
958e72877c
build!: Update bdk to rust-bitcoin 0.30.0 2023-08-16 15:02:51 +02:00
Steve Myers
0ba6bbe114
release: bump version to 0.28.2 2023-08-09 14:42:46 -05:00
Steve Myers
361f925526
ci: pin tokio to 1.29.1 and cc to 1.0.81 to build with MSRV 1.57.0 2023-08-09 14:14:12 -05:00
Steve Myers
7231039e81
release: bump version to 0.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
2023-08-03 14:08:27 -05:00
Steve Myers
1d840e09f8
Merge bitcoindevkit/bdk#1054: ci: pin dependencies with Cargo update instead of in Cargo.toml
b6fecc8bc04e51c7f8d5a81580bd34c8ba6ab8cc ci: pin dependencies with Cargo update instead of in Cargo.toml (Steve Myers)

Pull request description:

  ### Description

  Remove 1.57.0 MSRV dependency pinning from Cargo.toml and use `cargo update --precise` method instead. This is how bdk 1.0.0 and other rust projects are doing it now.

  ### Notes to the reviewers

  This is needed to get the release/0.28 branch to build again. After it's merged I'll be able to start making the `0.28.1` release.

  ### Changelog notice

  none.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  thunderbiscuit:
    ACK b6fecc8bc04e51c7f8d5a81580bd34c8ba6ab8cc. Neat fix.

Tree-SHA512: 67b78a29eefb247500b7f583ef7e928222fe0fe58464427739e7dcf83ec02ab9703f5cf5e760d60a2681ea0b11ae013aae291dc51b352d109e2100adb020a031
2023-08-03 13:17:01 -05:00
Steve Myers
b6fecc8bc0
ci: pin dependencies with Cargo update instead of in Cargo.toml 2023-08-02 16:23:19 -05:00
Steve Myers
d0f7543f69
Merge bitcoindevkit/bdk#1033: Backport new taproot descriptor template (BIP86)
7587f1603d66d17906378cc7c9227227931fd9cc feat(descriptor): backport from master branch new taproot descriptor template (BIP86) (Steve Myers)
177c96db5a2a8416f5ae8a14d7a3086f05dd0b10 Create taproot descriptor template (Vladimir Fomene)

Pull request description:

  ### Description

  This PR solves #836 for the release/0.28 branch. This PR adds a P2TR descriptor template and a BIP86 taproot descriptor template. With this, users can now create a taproot descriptor with templates.

  ### Notes to the reviewers

  The commit from #840 is cherry-picked from the `master` branch to the `release/0.28` branch without any changes.

  ### Changelog notice

  Add taproot descriptor template (BIP-86).

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [x] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  danielabrozzoni:
    utACK 7587f1603d66d17906378cc7c9227227931fd9cc

Tree-SHA512: e5b07473e27bba8ca5ec58854fa318c5a82cb67ce751d352ef17e9926dd08f8e6b7a720a77170b6f6f018c58ed9f8741cee396dc8e8721f4022c33ef2904815f
2023-08-02 11:52:42 -05:00
Steve Myers
7587f1603d
feat(descriptor): backport from master branch new taproot descriptor template (BIP86) 2023-07-18 22:37:23 -05:00
Vladimir Fomene
177c96db5a
Create taproot descriptor template
This PR solves #836. This PR adds a P2TR
descriptor template and a BIP86 taproot
descriptor template. With this, users
can now create a taproot descriptor with templates.
2023-07-18 22:08:34 -05:00
Steve Myers
07c1ce9c85
Merge commit 'refs/pull/1007/head' of github.com:bitcoindevkit/bdk into release/0.28 2023-07-18 18:59:12 -05:00
Steve Myers
6097957650
Merge bitcoindevkit/bdk#1008: Backporting "Fix policy condition calculation" onto release/0.28
9cffaad71f3d090196a3e35e84ad714118ea2846 Fix build errors (junderw)
3ccdb84523997d3a9582b1ebd2cf2fe373ddaa55 Fix policy condition calculation When constructing the `Condition` struct we recursively call `get_condition` on all the items in a threshold and short-circuit if there's an error somewhere (for example, because the policy-path hasn't been provided for a specific threshold). (Alekos Filini)

Pull request description:

  Fixes #942

  ### Description

  This backports "Fix policy condition calculation" (#932) onto release/0.28

  ### Notes to the reviewers

  Currently kept the Author the same and the committer is myself. Let me know if this is not the desired outcome of the history. (I have made no modifications myself)

  ### Changelog notice

  Backported "Fix policy condition calculation"

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  notmandatory:
    ACK 9cffaad71f3d090196a3e35e84ad714118ea2846

Tree-SHA512: c505bc9c8efd75cfa23db7a0e77f010f1758a1b6725661a1af1bcb40c6fd606a13e50ca005c7752fe28c3b7cd748c182fdb5870693e299adaf230adeef02517a
2023-06-26 12:35:02 -05:00
junderw
9cffaad71f
Fix build errors 2023-06-19 21:20:15 -07:00
Alekos Filini
3ccdb84523
Fix policy condition calculation
When constructing the `Condition` struct we recursively call
`get_condition` on all the items in a threshold and short-circuit if
there's an error somewhere (for example, because the policy-path hasn't
been provided for a specific threshold).

This can cause issues when the user doesn't care about a subtree, because
we still try to call `get_condition` on all the items and fail if
something is missing, even if the specific subtree isn't selected and
won't be used later on.

This commit changes the logic so that we first filter only the `selected`
items, and then unwrap the error using the question mark. If errors
happened somewhere else they will be ignored, as it should.
2023-06-16 13:52:48 -07:00
Steve Myers
f7d0852e92
Ignore rocksdb RUSTSEC-2022-0046 audit error 2023-06-15 22:55:29 -05:00
Steve Myers
15079ee4db
Pin log dependency to 0.4.18 to keep the MSRV to 1.57.0 2023-06-14 20:03:16 -05:00
Steve Myers
0f25c6ab29
Pin regex to 1.7.3 for compact_filters feature to keep the MSRV to 1.57.0 2023-06-06 18:05:22 -05:00
Steve Myers
0bf9a0ee2c
Pin hashlink to 0.8.1 for sqlite feature to keep the MSRV to 1.57.0 2023-06-06 18:05:14 -05:00
Steve Myers
973cd9a286
Bump esplora-client to 0.5 to keep the MSRV to 1.57.0 2023-05-25 11:52:30 -05:00
Steve Myers
78529b6a42
Bump version to 0.28.0 2023-04-11 12:44:11 -05:00
Steve Myers
0ad65c7776
Merge bitcoindevkit/bdk#930: Add default std feature, prep for 0.28.0 release
cbcbdd120d48742ed70fcb47c159266ae6c5b1ae Update CHANGELOG for 0.28.0 release (Steve Myers)
f5071857294f3a0002aa3e2774f86a588ac4af3e Change workflows to run for release branches (Steve Myers)
573bf52578d0f3e93b9dae7f17faffc3aad3839b Add default feature std (Steve Myers)

Pull request description:

  ### Description

  Add new `std` feature which enables `bitcoin/std` and `miniscript/std`, and enable `std` feature in `default` feature set.

  ### Notes to the reviewers

  When using `bdk` in a wasm environment then `default` features should be disabled and `bitcoin/no-std` and `miniscript/no-std` must be enabled.  This is a breaking change for anyone using `bdk` with `--no-default-features`.

  I also updated the workflows to also run for `release/*` branches in addition to `master`.

  ### Changelog notice

  Changed

  - Added new `std` feature as part of `default` features, `std` must be enabled unless building for wasm.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

  #### New Features:

  * [ ] I've added tests for the new feature
  * [x] I've added docs for the new feature

ACKs for top commit:
  rajarshimaitra:
    ACK cbcbdd120d48742ed70fcb47c159266ae6c5b1ae

Tree-SHA512: fc0e1495d2f4e65c015d771366ac8d9589736b4464cf496de5093cdef145abfed037c0701c3c210f70a7bde5128b681492202cecfca7cd94771d498e8fb8e565
2023-04-05 16:16:56 -05:00
Steve Myers
cbcbdd120d
Update CHANGELOG for 0.28.0 release 2023-03-28 15:34:41 -05:00
Steve Myers
f507185729
Change workflows to run for release branches 2023-03-28 14:44:55 -05:00
Steve Myers
573bf52578
Add default feature std 2023-03-28 14:17:32 -05:00
Steve Myers
10608afb76
Merge bitcoindevkit/bdk#875: Bump bip39 crate to v2.0.0
a4647cfa98c300a196cafc5303cc08221d0e23bb Bump bip39 crate to v2.0.0 (Elias Rohrer)

Pull request description:

  <!-- You can erase any parts of this template not applicable to your Pull Request. -->

  ### Description

  The updated version of `rust-bip39` was just released. It also [bumped the pinned version](https://github.com/rust-bitcoin/rust-bip39/pull/41) of `unicode-normalization`, which previously had lead to some incompatibilities.

  ### Notes to the reviewers

  <!-- In this section you can include notes directed to the reviewers, like explaining why some parts
  of the PR were done in a specific way -->

  ### Changelog notice

  <!-- Notice the release manager should include in the release tag message changelog -->
  <!-- See https://keepachangelog.com/en/1.0.0/ for examples -->

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  rajarshimaitra:
    tACK a4647cfa98c300a196cafc5303cc08221d0e23bb
  notmandatory:
    tACK a4647cfa98c300a196cafc5303cc08221d0e23bb

Tree-SHA512: c13f2c5081cd1cf0477ed41717b09b4854651ee43434760b12a460c19657ec6f437d6401b0b6d32c8315491301d7c9848f0575d427f027adc8390d649fe898a9
2023-03-25 21:05:59 -05:00
Steve Myers
de46a51208
Bump version to 0.27.2 2023-03-15 11:35:10 -05:00
Steve Myers
e8acafce8e
Merge bitcoindevkit/bdk#884: Update esplora client dependency to version 0.4
bb2b2d6dd8949a84675554c05b9880199c460fcf Update esplora-client dependency to version 0.4 (Steve Myers)

Pull request description:

  ### Description

  Update the `esplora-client` to the new `0.4` release.

  ### Notes to the reviewers

  The `esplora-client` was updated in bitcoindevkit/rust-esplora-client#39 to not include default `rust-bitcoin` dependencies.

  ### Changelog notice

  - Update the `esplora-client` optional dependency to version `0.4`.

  ### Checklists

  #### All Submissions:

  * [x] I've signed all my commits
  * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md)
  * [x] I ran `cargo fmt` and `cargo clippy` before committing

ACKs for top commit:
  vladimirfomene:
    ACK bb2b2d6dd8949a84675554c05b9880199c460fcf
  rajarshimaitra:
    tACK bb2b2d6dd8949a84675554c05b9880199c460fcf

Tree-SHA512: 0f277106166ab4a8144a641ac9507f1a32d735f99c0b73a026ff6e0b82589110a71fe81f880caa01dab247572f3146b771cc28bdab5b22bcd87ff628e15b2e1a
2023-03-15 11:11:21 -05:00
Steve Myers
bb2b2d6dd8
Update esplora-client dependency to version 0.4 2023-03-14 23:14:28 -05:00
benthecarman
87c558c9cf
Set default-features = false for rust-bitcoin 2023-03-07 20:45:53 -06:00
Elias Rohrer
a4647cfa98
Bump bip39 crate to v2.0.0 2023-03-07 19:56:34 +01:00
Steve Myers
b111f97c58
Set dev-dependency base64ct version to <1.6.0 2023-03-06 20:43:31 -06:00
Steve Myers
7a8e6609b1
Bump version to 0.27.1 2023-02-16 11:52:05 -06:00
Steve Myers
4ec6f3272e
Update rusqlite from 0.27.0 to 0.28.0 2023-02-15 14:47:51 -06:00
Steve Myers
553df318ff
Bump version to 0.27.0 2023-02-10 22:37:53 -06:00
Steve Myers
9e2e6411f2
Fix ci Dockerfile.ledger 2023-02-10 22:37:52 -06:00
Steve Myers
5d48e37926
Bump version to 0.27.0-rc.1 2023-02-03 16:06:57 -06:00
49 changed files with 1262 additions and 555 deletions

2
.cargo/audit.toml Normal file
View File

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

View File

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

View File

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

View File

@ -1,4 +1,12 @@
on: [push, pull_request]
on:
push:
branches:
- 'master'
- 'release/*'
pull_request:
branches:
- 'master'
- 'release/*'
name: CI
@ -51,6 +59,27 @@ 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
@ -199,5 +228,26 @@ 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,6 +1,14 @@
name: Publish Nightly Docs
on: [push, pull_request]
on:
push:
branches:
- 'master'
- 'release/*'
pull_request:
branches:
- 'master'
- 'release/*'
jobs:
build_docs:

View File

@ -9,6 +9,65 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [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
@ -629,4 +688,9 @@ final transaction is created by calling `finish` on the builder.
[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
[Unreleased]: https://github.com/bitcoindevkit/bdk/compare/v0.27.0...HEAD
[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.27.0"
version = "0.29.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 = "9.0", features = ["serde"] }
bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] }
log = "0.4"
miniscript = { version = "10.0", default-features = false, features = ["serde"] }
bitcoin = { version = "0.30", default-features = false, features = ["serde", "base64", "rand-std"] }
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.12", optional = true }
esplora-client = { version = "0.3", default-features = false, optional = true }
rusqlite = { version = "0.27.0", 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 }
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.5", optional = true, features = [ "use-miniscript"] }
hwi = { version = "0.7", optional = true, features = ["miniscript"] }
bip39 = { version = "1.0.1", optional = true }
bip39 = { version = "2.0.0", optional = true }
bitcoinconsensus = { version = "0.19.0-3", optional = true }
# Needed by bdk_blockchain_tests macro and the `rpc` feature
bitcoincore-rpc = { version = "0.16", optional = true }
bitcoincore-rpc = { package="core-rpc", version = "0.17", optional = true }
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@ -52,7 +52,10 @@ js-sys = "0.3"
minimal = []
compiler = ["miniscript/compiler"]
verify = ["bitcoinconsensus"]
default = ["key-value-db", "electrum"]
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"]
sqlite = ["rusqlite", "ahash"]
sqlite-bundled = ["sqlite", "rusqlite/bundled"]
compact_filters = ["rocksdb", "socks", "cc"]
@ -104,14 +107,12 @@ 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 = "0.7"
electrsd = "0.22"
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
base64 = "^0.13"
env_logger = { version = "0.7", default-features = false }
electrsd = "0.24"
assert_matches = "1.5.0"
# zip versions after 0.6.3 don't work with our MSRV 1.57.0
zip = "=0.6.3"
[[example]]
name = "compact_filters_balance"

View File

@ -96,7 +96,7 @@ use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New;
use base64;
use bitcoin::base64;
use bdk::bitcoin::consensus::serialize;
use bdk::bitcoin::Network;
@ -123,7 +123,7 @@ fn main() -> Result<(), bdk::Error> {
};
println!("Transaction details: {:#?}", details);
println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt)));
println!("Unsigned PSBT: {}", base64::encode(psbt.serialize()));
Ok(())
}
@ -134,9 +134,9 @@ fn main() -> Result<(), bdk::Error> {
```rust,no_run
use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use base64;
use bitcoin::base64;
use bdk::bitcoin::consensus::deserialize;
use bdk::bitcoin::Network;
use bdk::bitcoin::{psbt::Psbt, Network};
fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new(
@ -147,7 +147,7 @@ fn main() -> Result<(), bdk::Error> {
)?;
let psbt = "...";
let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
let mut psbt = Psbt::deserialize(&base64::decode(psbt).unwrap())?;
let _finalized = wallet.sign(&mut psbt, SignOptions::default())?;
@ -201,3 +201,48 @@ 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", "--display", "headless", "--vnc-port", "41000", "btc.elf"]
ENTRYPOINT ["python", "./speculos.py", "--automation", "file:automation.json", "--model", "nanos", "--display", "headless", "--vnc-port", "41000", "btc.elf"]

View File

@ -1,6 +1,6 @@
use std::str::FromStr;
use bdk::bitcoin::util::bip32::ExtendedPrivKey;
use bdk::bitcoin::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::util::bip32;
use bitcoin::bip32;
pub mod utils;

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use bdk::bitcoin::{Address, Network};
use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase;
use bdk::hwi::{types::HWIChain, HWIClient};
use bdk::hwi::HWIClient;
use bdk::miniscript::{Descriptor, DescriptorPublicKey};
use bdk::signer::SignerOrdering;
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
@ -30,7 +30,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
let first_device = devices.remove(0)?;
// ...and creating a client out of the first one
let client = HWIClient::get_client(&first_device, true, HWIChain::Test)?;
let client = HWIClient::get_client(&first_device, true, Network::Testnet.into())?;
println!("Look what I found, a {}!", first_device.model);
// Getting the HW's public descriptors
@ -41,7 +41,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
);
// Creating a custom signer from the device
let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
let mut wallet = Wallet::new(
descriptors.receive[0].clone(),
Some(descriptors.internal[0].clone()),
@ -77,7 +77,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(());
}
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?;
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?
.require_network(Network::Testnet)?;
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,7 +92,8 @@ 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")?;
let return_address = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")?
.require_network(Network::Testnet)?;
let mut builder = watch_only_wallet.build_tx();
builder
.add_recipient(return_address.script_pubkey(), 9_800)

View File

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

View File

@ -13,7 +13,10 @@ pub(crate) mod tx {
// Create a transaction builder
let mut tx_builder = wallet.build_tx();
let to_address = Address::from_str(recipient_address).unwrap();
let to_address = Address::from_str(recipient_address)
.unwrap()
.require_network(wallet.network())
.unwrap();
// Set recipient of the transaction
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);

View File

@ -355,7 +355,7 @@ impl WalletSync for CompactFiltersBlockchain {
peer,
|block_hash, filter| {
if !filter
.match_any(block_hash, &mut all_scripts.iter().map(AsRef::as_ref))?
.match_any(block_hash, all_scripts.iter().map(|s| s.as_slice()))?
{
return Ok(false);
}
@ -570,7 +570,7 @@ pub enum CompactFiltersError {
/// Internal I/O error
Io(std::io::Error),
/// Invalid BIP158 filter
Bip158(bitcoin::util::bip158::Error),
Bip158(bitcoin::bip158::Error),
/// Internal system time error
Time(std::time::SystemTimeError),
@ -608,7 +608,7 @@ impl std::error::Error for CompactFiltersError {}
impl_error!(rocksdb::Error, Db, CompactFiltersError);
impl_error!(std::io::Error, Io, CompactFiltersError);
impl_error!(bitcoin::util::bip158::Error, Bip158, CompactFiltersError);
impl_error!(bitcoin::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;
use bitcoin::network::{Address, Magic};
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: u32,
magic: Magic,
payload: NetworkMessage,
) -> Result<(), CompactFiltersError> {
log::trace!("==> {:?}", payload);

View File

@ -21,16 +21,17 @@ 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::util::bip158::BlockFilter;
use bitcoin::util::uint::Uint256;
use bitcoin::pow::Work;
use bitcoin::Block;
use bitcoin::BlockHash;
use bitcoin::BlockHeader;
use bitcoin::Network;
use bitcoin::ScriptBuf;
use super::CompactFiltersError;
@ -69,7 +70,7 @@ impl StoreEntry {
}
StoreEntry::Block(Some(height)) => prefix.extend_from_slice(&height.to_be_bytes()),
StoreEntry::BlockHeaderIndex(Some(hash)) => {
prefix.extend_from_slice(&hash.into_inner())
prefix.extend_from_slice(hash.to_raw_hash().as_ref())
}
StoreEntry::CFilterTable((filter_type, bundle_index)) => {
prefix.push(*filter_type);
@ -228,7 +229,7 @@ impl ChainStore<Full> {
batch.put_cf(
cf_handle,
genesis_key,
(genesis.header, genesis.header.work()).serialize(),
(genesis.header, genesis.header.work().to_be_bytes()).serialize(),
);
batch.put_cf(
cf_handle,
@ -260,7 +261,7 @@ impl ChainStore<Full> {
step *= 2;
}
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(
let (header, _): (Header, [u8; 32]) = SerializeDb::deserialize(
&store_read
.get_pinned_cf(cf_handle, StoreEntry::BlockHeader(Some(index)).get_key())?
.unwrap(),
@ -292,11 +293,12 @@ 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): (BlockHeader, Uint256) = SerializeDb::deserialize(
let (header, work): (Header, [u8; 32]) = 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(
@ -307,7 +309,7 @@ impl ChainStore<Full> {
batch.put_cf(
new_cf_handle,
StoreEntry::BlockHeader(Some(from)).get_key(),
(header, work).serialize(),
(header, work.to_be_bytes()).serialize(),
);
write_store.write(batch)?;
@ -381,7 +383,7 @@ impl ChainStore<Full> {
opts,
IteratorMode::From(&from_key, Direction::Forward),
) {
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
let (header, _): (Header, [u8; 32]) = SerializeDb::deserialize(&v)?;
batch.delete_cf(
cf_handle,
@ -433,7 +435,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, _): (BlockHeader, Uint256) =
let (header, _): (Header, [u8; 32]) =
deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
Ok::<_, CompactFiltersError>(header.block_hash())
})
@ -496,7 +498,7 @@ impl ChainStore<Full> {
}
impl<T: StoreType> ChainStore<T> {
pub fn work(&self) -> Result<Uint256, CompactFiltersError> {
pub fn work(&self) -> Result<Work, CompactFiltersError> {
let read_store = self.store.read().unwrap();
let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();
@ -506,12 +508,13 @@ impl<T: StoreType> ChainStore<T> {
Ok(iterator
.last()
.map(|(_, v)| -> Result<_, CompactFiltersError> {
let (_, work): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
let (_, work): (Header, [u8; 32]) = SerializeDb::deserialize(&v)?;
let work = Work::from_be_bytes(work);
Ok(work)
})
.transpose()?
.unwrap_or_default())
.unwrap_or_else(|| Work::from_be_bytes([0; 32])))
}
pub fn get_height(&self) -> Result<usize, CompactFiltersError> {
@ -546,7 +549,7 @@ impl<T: StoreType> ChainStore<T> {
iterator
.last()
.map(|(_, v)| -> Result<_, CompactFiltersError> {
let (header, _): (BlockHeader, Uint256) = SerializeDb::deserialize(&v)?;
let (header, _): (Header, [u8; 32]) = SerializeDb::deserialize(&v)?;
Ok(header.block_hash())
})
@ -556,7 +559,7 @@ impl<T: StoreType> ChainStore<T> {
pub fn apply(
&mut self,
from: usize,
headers: Vec<BlockHeader>,
headers: Vec<Header>,
) -> Result<BlockHash, CompactFiltersError> {
let mut batch = WriteBatch::default();
@ -566,7 +569,8 @@ 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): (BlockHeader, Uint256) = SerializeDb::deserialize(&result)?;
let (header, work): (Header, [u8; 32]) = SerializeDb::deserialize(&result)?;
let work = Work::from_be_bytes(work);
Ok::<_, CompactFiltersError>((header.block_hash(), work))
})
.transpose()?
@ -589,7 +593,7 @@ impl<T: StoreType> ChainStore<T> {
batch.put_cf(
cf_handle,
StoreEntry::BlockHeader(Some(height)).get_key(),
(header, accumulated_work).serialize(),
(header, accumulated_work.to_be_bytes()).serialize(),
);
}
@ -641,7 +645,7 @@ impl CfStore {
let genesis = genesis_block(headers_store.network);
let filter = BlockFilter::new_script_filter(&genesis, |utxo| {
Err(bitcoin::util::bip158::Error::UtxoMissing(*utxo))
Err::<ScriptBuf, _>(bitcoin::bip158::Error::UtxoMissing(*utxo))
})?;
let first_key = StoreEntry::CFilterTable((filter_type, Some(0))).get_key();
@ -653,7 +657,7 @@ impl CfStore {
&first_key,
(
BundleStatus::Init,
filter.filter_header(&FilterHeader::from_hash(Hash::all_zeros())),
filter.filter_header(&FilterHeader::from_raw_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::*;

View File

@ -325,8 +325,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)?
.timeout(config.timeout)
.socks5(socks5)
.validate_domain(config.validate_domain)
.build();

View File

@ -132,7 +132,7 @@ impl WalletSync for EsploraBlockchain {
let scripts = script_req
.request()
.take(self.concurrency as usize)
.cloned();
.map(bitcoin::ScriptBuf::from);
let mut handles = vec![];
for script in scripts {

View File

@ -31,18 +31,17 @@
//! 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;
use bitcoin::{Script, ScriptBuf};
use bitcoincore_rpc::json::{
GetTransactionResultDetailCategory, ImportMultiOptions, ImportMultiRequest,
ImportMultiRequestScriptPubkey, ImportMultiRescanSince, ListTransactionResult,
ListUnspentResultEntry, ScanningDetails,
ImportMultiRequestScriptPubkey, ListTransactionResult, ListUnspentResultEntry, ScanningDetails,
Timestamp,
};
use bitcoincore_rpc::jsonrpc::serde_json::{json, Value};
use bitcoincore_rpc::Auth as RpcAuth;
@ -302,8 +301,8 @@ struct DbState<'a, D> {
params: &'a RpcSyncParams,
prog: &'a dyn Progress,
ext_spks: Vec<Script>,
int_spks: Vec<Script>,
ext_spks: Vec<ScriptBuf>,
int_spks: Vec<ScriptBuf>,
txs: HashMap<Txid, TransactionDetails>,
utxos: HashSet<LocalUtxo>,
last_indexes: HashMap<KeychainKind, u32>,
@ -668,7 +667,7 @@ fn import_descriptors<'a, S>(
scripts_iter: S,
) -> Result<(), Error>
where
S: Iterator<Item = &'a Script>,
S: Iterator<Item = &'a ScriptBuf>,
{
let requests = Value::Array(
scripts_iter
@ -696,11 +695,11 @@ where
fn import_multi<'a, S>(client: &Client, start_epoch: u64, scripts_iter: S) -> Result<(), Error>
where
S: Iterator<Item = &'a Script>,
S: Iterator<Item = &'a ScriptBuf>,
{
let requests = scripts_iter
.map(|script| ImportMultiRequest {
timestamp: ImportMultiRescanSince::Timestamp(start_epoch),
timestamp: Timestamp::Time(start_epoch),
script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(script)),
watchonly: Some(true),
..Default::default()
@ -808,7 +807,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());
let desc = format!("raw({})", script.to_hex_string());
format!("{}#{}", desc, calc_checksum(&desc).unwrap())
}
@ -964,7 +963,7 @@ mod test {
// generate scripts (1 tx per script)
let scripts = (0..TX_COUNT)
.map(|index| desc.at_derivation_index(index).script_pubkey())
.map(|index| desc.at_derivation_index(index).unwrap().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::{OutPoint, Script, Transaction, TxOut, Txid};
use bitcoin::{hashes::Hash, OutPoint, Script, ScriptBuf, 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<Script>,
scripts_needed: VecDeque<ScriptBuf>,
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()
self.scripts_needed.iter().map(|s| s.as_script())
}
pub fn satisfy(
@ -444,8 +444,14 @@ 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) {
@ -463,5 +469,6 @@ 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<Script>, Error> {
) -> Result<Option<ScriptBuf>, Error> {
impl_inner_method!(
AnyDatabase,
self,
@ -204,7 +204,7 @@ impl Database for AnyDatabase {
)
}
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, 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<Script>, Error> {
) -> Result<Option<ScriptBuf>, Error> {
impl_inner_method!(
AnyDatabase,
self,
@ -286,7 +286,7 @@ impl BatchOperations for AnyBatch {
&mut self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<Script>, Error> {
) -> Result<Option<ScriptBuf>, 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, Transaction};
use bitcoin::{OutPoint, Script, ScriptBuf, 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<Script>, Error> {
fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result<Option<ScriptBuf>, 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<Script>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, 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<Script>, Error> {
) -> Result<Option<ScriptBuf>, 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, Transaction};
use bitcoin::{OutPoint, Script, ScriptBuf, 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(script.clone()));
self.map.insert(key, Box::new(ScriptBuf::from(script)));
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<Script>, Error> {
) -> Result<Option<ScriptBuf>, 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<Script>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, 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<Script>, Error> {
) -> Result<Option<ScriptBuf>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
Ok(self
.map
@ -485,7 +485,6 @@ 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;
@ -497,7 +496,7 @@ macro_rules! populate_test_db {
}
let tx = $crate::bitcoin::Transaction {
version: 1,
lock_time: bitcoin::PackedLockTime(0),
lock_time: bitcoin::absolute::LockTime::ZERO,
input,
output: tx_meta
.output
@ -506,6 +505,7 @@ 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, Transaction, TxOut};
use bitcoin::{OutPoint, Script, ScriptBuf, Transaction, TxOut};
use crate::error::Error;
use crate::types::*;
@ -83,7 +83,7 @@ pub trait BatchOperations {
&mut self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<Script>, Error>;
) -> Result<Option<ScriptBuf>, 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<Script>, Error>;
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, 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<Script>, Error>;
) -> Result<Option<ScriptBuf>, 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 = Script::from(
let script = ScriptBuf::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 = Script::from(
let script = ScriptBuf::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 = Script::from(
let script = ScriptBuf::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 = Script::from(
let script = ScriptBuf::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
@ -302,7 +302,7 @@ pub mod test {
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
)
.unwrap();
let script = Script::from(
let script = ScriptBuf::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 = Script::from(
let script = ScriptBuf::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 = Script::from(
let first_script = ScriptBuf::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;
db.set_script_pubkey(&first_script, keychain, path).unwrap();
let second_script = Script::from(
let second_script = ScriptBuf::from(
Vec::<u8>::from_hex("00145c9a1816d38db5cbdd4b067b689dc19eb7d930e2").unwrap(),
);
let path = 57;
@ -528,7 +528,7 @@ pub mod test {
"5df6e0e2761359d30a8275058e299fcc0381534545f55cf43e41983f5d4c9456:0",
)
.unwrap();
let script = Script::from(
let script = ScriptBuf::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, Transaction, TxOut};
use bitcoin::{OutPoint, Script, ScriptBuf, 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;
let txid: &[u8] = transaction.txid.as_ref();
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;
let txid: &[u8] = transaction.txid.as_ref();
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<Script>, Error> {
fn select_script_pubkeys(&self) -> Result<Vec<ScriptBuf>, Error> {
let mut statement = self
.connection
.prepare_cached("SELECT script FROM script_pubkeys")?;
let mut scripts: Vec<Script> = vec![];
let mut scripts: Vec<ScriptBuf> = 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<Script>, Error> {
fn select_script_pubkeys_by_keychain(&self, keychain: String) -> Result<Vec<ScriptBuf>, Error> {
let mut statement = self
.connection
.prepare_cached("SELECT script FROM script_pubkeys WHERE keychain=:keychain")?;
let mut scripts: Vec<Script> = vec![];
let mut scripts: Vec<ScriptBuf> = 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<Script>, Error> {
) -> Result<Option<ScriptBuf>, 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: Script = script.into();
let script: ScriptBuf = 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: Script = script.into();
let script_pubkey: ScriptBuf = 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,
utxo.outpoint.txid.as_ref(),
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())? {
match self.select_transaction_by_txid(transaction.txid().as_ref())? {
Some(_) => {
self.update_transaction(&transaction.txid(), &serialize(transaction))?;
self.update_transaction(transaction.txid().as_ref(), &serialize(transaction))?;
}
None => {
self.insert_transaction(&transaction.txid(), &serialize(transaction))?;
self.insert_transaction(transaction.txid().as_ref(), &serialize(transaction))?;
}
}
Ok(())
}
fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> {
match self.select_transaction_details_by_txid(&transaction.txid)? {
match self.select_transaction_details_by_txid(transaction.txid.as_ref())? {
Some(_) => {
self.update_transaction_details(transaction)?;
}
@ -708,7 +708,7 @@ impl BatchOperations for SqliteDatabase {
&mut self,
keychain: KeychainKind,
child: u32,
) -> Result<Option<Script>, Error> {
) -> Result<Option<ScriptBuf>, 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, outpoint.vout)? {
match self.select_utxo_by_outpoint(outpoint.txid.as_ref(), outpoint.vout)? {
Some(local_utxo) => {
self.delete_utxo_by_outpoint(&outpoint.txid, outpoint.vout)?;
self.delete_utxo_by_outpoint(outpoint.txid.as_ref(), 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)? {
match self.select_transaction_by_txid(txid.as_ref())? {
Some(tx) => {
self.delete_transaction_by_txid(txid)?;
self.delete_transaction_by_txid(txid.as_ref())?;
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)? {
match self.select_transaction_details_by_txid(txid.as_ref())? {
Some(mut transaction_details) => {
self.delete_transaction_details_by_txid(txid)?;
self.delete_transaction_details_by_txid(txid.as_ref())?;
if include_raw {
self.delete_transaction_by_txid(txid)?;
self.delete_transaction_by_txid(txid.as_ref())?;
} else {
transaction_details.transaction = None;
}
@ -820,7 +820,7 @@ impl Database for SqliteDatabase {
}
}
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<Script>, Error> {
fn iter_script_pubkeys(&self, keychain: Option<KeychainKind>) -> Result<Vec<ScriptBuf>, 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<Script>, Error> {
) -> Result<Option<ScriptBuf>, 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, outpoint.vout)
self.select_utxo_by_outpoint(outpoint.txid.as_ref(), outpoint.vout)
}
fn get_raw_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
match self.select_transaction_by_txid(txid)? {
match self.select_transaction_by_txid(txid.as_ref())? {
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)? {
match self.select_transaction_details_by_txid(txid.as_ref())? {
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 = Script::from(
let script = ScriptBuf::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
);
let path = 42;

View File

@ -514,13 +514,14 @@ 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?, b, c)))
.and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
.map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Wpkh(a), b, c))
});
( sh ( wpkh ( $key:expr ) ) ) => ({
@ -530,7 +531,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?, b, c)))
.and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c)))
.and_then(|(a, b, c)| Ok((Descriptor::<DescriptorPublicKey>::Sh(Sh::new_wpkh(a.into_inner())?), b, c)))
});
( sh ( $( $minisc:tt )* ) ) => ({
@ -700,7 +701,7 @@ macro_rules! fragment {
$crate::keys::make_pkh($key, &secp)
});
( after ( $value:expr ) ) => ({
$crate::impl_leaf_opcode_value!(After, $crate::bitcoin::PackedLockTime($value)) // TODO!! https://github.com/rust-bitcoin/rust-bitcoin/issues/1302
$crate::impl_leaf_opcode_value!(After, $crate::miniscript::AbsLockTime::from_consensus($value))
});
( older ( $value:expr ) ) => ({
$crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!!
@ -793,7 +794,6 @@ 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,18 +819,15 @@ mod test {
assert_eq!(desc.is_witness(), is_witness);
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)
} else {
desc.at_derivation_index(index)
};
let child_desc = desc
.at_derivation_index(i as u32)
.expect("i is not hardened");
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().as_str(), *expected.get(i).unwrap());
assert_eq!(script.to_hex_string(), *expected.get(i).unwrap());
}
}
}
@ -1175,9 +1172,7 @@ mod test {
}
#[test]
#[should_panic(
expected = "Miniscript(ContextError(CompressedOnly(\"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4\")))"
)]
#[should_panic(expected = "Miniscript(ContextError(UncompressedKeysNotAllowed))")]
fn test_dsl_miniscript_checks() {
let mut uncompressed_pk =
PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();

View File

@ -20,6 +20,8 @@ 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),
@ -30,11 +32,11 @@ pub enum Error {
InvalidDescriptorCharacter(u8),
/// BIP32 error
Bip32(bitcoin::util::bip32::Error),
Bip32(bitcoin::bip32::Error),
/// Error during base58 decoding
Base58(bitcoin::util::base58::Error),
Base58(bitcoin::base58::Error),
/// Key-related error
Pk(bitcoin::util::key::Error),
Pk(bitcoin::key::Error),
/// Miniscript error
Miniscript(miniscript::Error),
/// Hex decoding error
@ -62,6 +64,10 @@ impl std::fmt::Display for Error {
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) => {
@ -78,9 +84,9 @@ impl std::fmt::Display for Error {
impl std::error::Error for Error {}
impl_error!(bitcoin::util::bip32::Error, Bip32);
impl_error!(bitcoin::util::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, Pk);
impl_error!(bitcoin::bip32::Error, Bip32);
impl_error!(bitcoin::base58::Error, Base58);
impl_error!(bitcoin::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::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::util::{psbt, taproot};
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey};
use bitcoin::{psbt, taproot};
use bitcoin::{Network, TxOut};
use miniscript::descriptor::{
DefiniteDescriptorKey, DescriptorSecretKey, DescriptorType, InnerXKey, SinglePubKey,
DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType,
DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard,
};
pub use miniscript::{
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
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::util::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output
/// [`psbt::Input`]: bitcoin::psbt::Input
/// [`psbt::Output`]: bitcoin::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::util::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output
pub type TapKeyOrigins = BTreeMap<bitcoin::XOnlyPublicKey, (Vec<taproot::TapLeafHash>, KeySource)>;
/// [`psbt::Input`]: bitcoin::psbt::Input
/// [`psbt::Output`]: bitcoin::psbt::Output
pub type TapKeyOrigins = BTreeMap<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,14 +134,10 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
network: Network,
}
impl<'s, 'd>
miniscript::Translator<DescriptorPublicKey, miniscript::DummyKey, DescriptorError>
impl<'s, 'd> miniscript::Translator<DescriptorPublicKey, String, DescriptorError>
for Translator<'s, 'd>
{
fn pk(
&mut self,
pk: &DescriptorPublicKey,
) -> Result<miniscript::DummyKey, DescriptorError> {
fn pk(&mut self, pk: &DescriptorPublicKey) -> Result<String, DescriptorError> {
let secp = &self.secp;
let (_, _, networks) = if self.descriptor.is_taproot() {
@ -159,7 +155,7 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
};
if networks.contains(&self.network) {
Ok(miniscript::DummyKey)
Ok(Default::default())
} else {
Err(DescriptorError::Key(KeyError::InvalidNetwork))
}
@ -167,35 +163,40 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
fn sha256(
&mut self,
_sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256,
) -> Result<miniscript::DummySha256Hash, DescriptorError> {
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
fn hash256(
&mut self,
_hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256,
) -> Result<miniscript::DummyHash256Hash, DescriptorError> {
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
fn ripemd160(
&mut self,
_ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160,
) -> Result<miniscript::DummyRipemd160Hash, DescriptorError> {
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
fn hash160(
&mut self,
_hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160,
) -> Result<miniscript::DummyHash160Hash, DescriptorError> {
) -> Result<String, DescriptorError> {
Ok(Default::default())
}
}
// check the network for the keys
self.0.translate_pk(&mut Translator {
use miniscript::TranslateErr;
match self.0.translate_pk(&mut Translator {
secp,
network,
descriptor: &self.0,
})?;
}) {
Ok(_) => {}
Err(TranslateErr::TranslatorErr(e)) => return Err(e),
Err(TranslateErr::OuterError(e)) => return Err(e.into()),
}
Ok(self)
}
@ -249,7 +250,12 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
}
// fixup the network for keys that need it in the descriptor
let translated = desc.translate_pk(&mut Translator { network })?;
use miniscript::TranslateErr;
let translated = match desc.translate_pk(&mut Translator { network }) {
Ok(descriptor) => descriptor,
Err(TranslateErr::TranslatorErr(e)) => return Err(e),
Err(TranslateErr::OuterError(e)) => return Err(e.into()),
};
// ...and in the key map
let fixed_keymap = keymap
.into_iter()
@ -300,6 +306,10 @@ 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()?;
@ -338,6 +348,18 @@ 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,
@ -492,7 +514,10 @@ impl DescriptorMeta for ExtendedDescriptor {
false
});
path_found.map(|path| self.at_derivation_index(path))
path_found.map(|path| {
self.at_derivation_index(path)
.expect("We ignore hardened wildcards")
})
}
fn derive_from_hd_keypaths<'s>(
@ -543,7 +568,7 @@ impl DescriptorMeta for ExtendedDescriptor {
return None;
}
let descriptor = self.at_derivation_index(0);
let descriptor = self.at_derivation_index(0).expect("0 is not hardened");
match descriptor.desc_type() {
// TODO: add pk() here
DescriptorType::Pkh
@ -582,11 +607,10 @@ 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::util::{bip32, psbt};
use bitcoin::Script;
use bitcoin::ScriptBuf;
use bitcoin::{bip32, psbt::Psbt};
use super::*;
use crate::psbt::PsbtUtils;
@ -597,7 +621,7 @@ mod test {
"wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
)
.unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize(
let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex(
"70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\
11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\
@ -620,7 +644,7 @@ mod test {
"pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)",
)
.unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize(
let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex(
"70736274ff010053010000000145843b86be54a3cd8c9e38444e1162676c00df\
e7964122a70df491ea12fd67090100000000ffffffff01c19598000000000017\
@ -651,7 +675,7 @@ mod test {
"wsh(and_v(v:pk(03b6633fef2397a0a9de9d7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14),older(6)))",
)
.unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize(
let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex(
"70736274ff01005302000000011c8116eea34408ab6529223c9a176606742207\
67a1ff1d46a6e3c4a88243ea6e01000000000600000001109698000000000017\
@ -675,7 +699,7 @@ mod test {
"sh(and_v(v:pk(021403881a5587297818fcaf17d239cefca22fce84a45b3b1d23e836c4af671dbb),after(630000)))",
)
.unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize(
let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex(
"70736274ff0100530100000001bc8c13df445dfadcc42afa6dc841f85d22b01d\
a6270ebf981740f4b7b1d800390000000000feffffff01ba9598000000000017\
@ -842,6 +866,12 @@ mod test {
assert_matches!(result, 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);
@ -858,9 +888,9 @@ mod test {
let (descriptor, _) =
into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
let descriptor = descriptor.at_derivation_index(0);
let descriptor = descriptor.at_derivation_index(0).unwrap();
let script = Script::from_str("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap();
let script = ScriptBuf::from_hex("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::util::bip32::Fingerprint;
use bitcoin::{LockTime, PublicKey, Sequence, XOnlyPublicKey};
use bitcoin::{absolute, key::XOnlyPublicKey, PublicKey, Sequence};
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::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt};
use bitcoin::psbt::{self, Psbt};
use miniscript::psbt::PsbtInputSatisfier;
/// A unique identifier for a key
@ -93,6 +93,9 @@ impl PkOrF {
..
}) => PkOrF::XOnlyPubkey(*pk),
DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)),
DescriptorPublicKey::MultiXPub(multi) => {
PkOrF::Fingerprint(multi.root_fingerprint(secp))
}
}
}
}
@ -129,7 +132,7 @@ pub enum SatisfiableItem {
/// Absolute timeclock timestamp
AbsoluteTimelock {
/// The timelock value
value: LockTime,
value: absolute::LockTime,
},
/// Relative timelock locktime
RelativeTimelock {
@ -449,11 +452,14 @@ pub struct Condition {
pub csv: Option<Sequence>,
/// Optional timelock condition
#[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<LockTime>,
pub timelock: Option<absolute::LockTime>,
}
impl Condition {
fn merge_nlocktime(a: LockTime, b: LockTime) -> Result<LockTime, PolicyError> {
fn merge_nlocktime(
a: absolute::LockTime,
b: absolute::LockTime,
) -> Result<absolute::LockTime, PolicyError> {
if !a.is_same_unit(b) {
Err(PolicyError::MixedTimelockUnits)
} else if a > b {
@ -659,11 +665,11 @@ impl Policy {
(0..*threshold).collect()
}
SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
_ => vec![],
_ => HashSet::new(),
};
let selected = match path.get(&self.id) {
Some(arr) => arr,
_ => &default,
let selected: HashSet<_> = match path.get(&self.id) {
Some(arr) => arr.iter().copied().collect(),
_ => default,
};
match &self.item {
@ -671,14 +677,24 @@ impl Policy {
let mapped_req = items
.iter()
.map(|i| i.get_condition(path))
.collect::<Result<Vec<_>, _>>()?;
.collect::<Vec<_>>();
// if all the requirements are null we don't care about `selected` because there
// are no requirements
if mapped_req.iter().all(Condition::is_null) {
if mapped_req
.iter()
.all(|cond| matches!(cond, Ok(c) if c.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
@ -687,23 +703,18 @@ impl Policy {
}
// check the selected items, see if there are conflicting requirements
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)
mapped_req
.into_iter()
.enumerate()
.filter(|(index, _)| selected.contains(index))
.try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
}
SatisfiableItem::Multisig { keys, threshold } => {
if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
}
if let Some(item) = selected.iter().find(|i| **i >= keys.len()) {
return Err(PolicyError::IndexOutOfRange(*item));
if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
return Err(PolicyError::IndexOutOfRange(item));
}
Ok(Condition::default())
@ -741,6 +752,7 @@ 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(),
}
}
@ -778,9 +790,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(&PsbtInput, &SinglePubKey) -> bool,
C: Fn(&psbt::Input, &SinglePubKey) -> bool,
// E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
E: Fn(&PsbtInput, Fingerprint) -> Option<SinglePubKey>,
E: Fn(&psbt::Input, Fingerprint) -> Option<SinglePubKey>,
>(
psbt: &Psbt,
key: &DescriptorPublicKey,
@ -798,6 +810,13 @@ 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,
}
}
})
}
@ -903,12 +922,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,
},
};
@ -920,9 +939,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();
@ -1147,8 +1166,8 @@ mod test {
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;
@ -1567,6 +1586,7 @@ mod test {
let addr = wallet_desc
.at_derivation_index(0)
.unwrap()
.address(Network::Testnet)
.unwrap();
assert_eq!(
@ -1633,6 +1653,7 @@ mod test {
let addr = wallet_desc
.at_derivation_index(0)
.unwrap()
.address(Network::Testnet)
.unwrap();
assert_eq!(

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::util::bip32;
use bitcoin::bip32;
use bitcoin::Network;
use miniscript::{Legacy, Segwitv0};
use miniscript::{Legacy, Segwitv0, Tap};
use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
use crate::descriptor::DescriptorError;
@ -170,6 +170,35 @@ 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`).
@ -186,7 +215,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new(
/// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)),
@ -225,8 +254,8 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44Public;
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
@ -265,7 +294,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new(
/// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)),
@ -304,8 +333,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49Public;
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
@ -344,7 +373,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84;
///
/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
/// let wallet = Wallet::new(
/// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)),
@ -383,8 +412,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84Public;
///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?;
/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
@ -407,6 +436,85 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
}
}
/// 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)
}
}
macro_rules! expand_make_bipxx {
( $mod_name:ident, $ctx:ty ) => {
mod $mod_name {
@ -473,6 +581,7 @@ 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 {
@ -484,37 +593,36 @@ mod test {
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::util::bip32::ChildNumber::{self, Hardened};
use bitcoin::bip32::ChildNumber::{self, Hardened};
let xprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap();
let xprvkey = bitcoin::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().into();
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 });
let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 0 });
}
let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let tprvkey = bitcoin::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().into();
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 });
let coin_type = path.get(1).unwrap();
@ -526,20 +634,23 @@ 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)
desc.at_derivation_index(0).unwrap()
} else {
desc.at_derivation_index(index)
desc.at_derivation_index(index).unwrap()
};
let address = child_desc.address(Regtest).unwrap();
let address = child_desc.address(network).unwrap();
assert_eq!(address.to_string(), *expected.get(i).unwrap());
}
}
@ -553,7 +664,9 @@ mod test {
check(
P2Pkh(prvkey).build(Network::Bitcoin),
false,
false,
true,
Network::Regtest,
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
);
@ -564,7 +677,9 @@ mod test {
check(
P2Pkh(pubkey).build(Network::Bitcoin),
false,
false,
true,
Network::Regtest,
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
);
}
@ -578,7 +693,9 @@ mod test {
check(
P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
);
@ -589,7 +706,9 @@ mod test {
check(
P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
);
}
@ -603,7 +722,9 @@ mod test {
check(
P2Wpkh(prvkey).build(Network::Bitcoin),
true,
false,
true,
Network::Regtest,
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
);
@ -614,19 +735,52 @@ 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::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5",
"mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP",
@ -637,6 +791,8 @@ mod test {
Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
"n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
@ -648,12 +804,14 @@ mod test {
// BIP44 public `pkh(key/{0,1}/*)`
#[test]
fn test_bip44_public_template() {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"miNG7dJTzJqNbFS19svRdTCisC65dsubtR",
"n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg",
@ -664,6 +822,8 @@ mod test {
Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
false,
false,
false,
Network::Regtest,
&[
"moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
"ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
@ -675,11 +835,13 @@ mod test {
// BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))`
#[test]
fn test_bip49_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV",
"2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS",
@ -690,6 +852,8 @@ mod test {
Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
"2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
@ -701,12 +865,14 @@ mod test {
// BIP49 public `sh(wpkh(key/{0,1}/*))`
#[test]
fn test_bip49_public_template() {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt",
"2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX",
@ -717,6 +883,8 @@ mod test {
Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
"2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
@ -728,11 +896,13 @@ mod test {
// BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)`
#[test]
fn test_bip84_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check(
Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s",
"bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp",
@ -743,6 +913,8 @@ mod test {
Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
"bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
@ -754,12 +926,14 @@ mod test {
// BIP84 public `wpkh(key/{0,1}/*)`
#[test]
fn test_bip84_public_template() {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap();
let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check(
Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h",
"bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana",
@ -770,6 +944,8 @@ mod test {
Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true,
false,
false,
Network::Regtest,
&[
"bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
"bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp",
@ -777,4 +953,67 @@ 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,6 +86,8 @@ 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),
@ -106,7 +108,7 @@ pub enum Error {
/// Miniscript PSBT error
MiniscriptPsbt(MiniscriptPsbtError),
/// BIP32 error
Bip32(bitcoin::util::bip32::Error),
Bip32(bitcoin::bip32::Error),
/// A secp256k1 error
Secp256k1(bitcoin::secp256k1::Error),
/// Error serializing or deserializing JSON data
@ -114,9 +116,9 @@ pub enum Error {
/// Hex decoding error
Hex(bitcoin::hashes::hex::Error),
/// Partially signed bitcoin transaction error
Psbt(bitcoin::util::psbt::Error),
Psbt(bitcoin::psbt::Error),
/// Partially signed bitcoin transaction parse error
PsbtParse(bitcoin::util::psbt::PsbtParseError),
PsbtParse(bitcoin::psbt::PsbtParseError),
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
//MissingInputUTXO(usize),
@ -228,6 +230,7 @@ impl fmt::Display for Error {
"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) => {
@ -304,12 +307,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::util::bip32::Error, Bip32);
impl_error!(bitcoin::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::util::psbt::Error, Psbt);
impl_error!(bitcoin::util::psbt::PsbtParseError, PsbtParse);
impl_error!(bitcoin::psbt::Error, Psbt);
impl_error!(bitcoin::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::util::bip32;
use bitcoin::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.as_ref()[..(word_count as usize / 8)];
let entropy = &entropy[..(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::util::bip32;
use bitcoin::bip32;
use bip39::{Language, Mnemonic};

View File

@ -19,8 +19,8 @@ use std::str::FromStr;
use bitcoin::secp256k1::{self, Secp256k1, Signing};
use bitcoin::util::bip32;
use bitcoin::{Network, PrivateKey, PublicKey, XOnlyPublicKey};
use bitcoin::bip32;
use bitcoin::{key::XOnlyPublicKey, Network, PrivateKey, PublicKey};
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
pub use miniscript::descriptor::{
@ -385,12 +385,12 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
///
/// ```
/// use bdk::bitcoin;
/// use bdk::bitcoin::util::bip32;
/// use bdk::bitcoin::bip32;
/// use bdk::keys::{DerivableKey, ExtendedKey, KeyError, ScriptContext};
///
/// struct MyCustomKeyType {
/// key_data: bitcoin::PrivateKey,
/// chain_code: Vec<u8>,
/// chain_code: [u8; 32],
/// 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.as_ref()),
/// chain_code: bip32::ChainCode::from(&self.chain_code),
/// 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::util::bip32;
/// use bdk::bitcoin::bip32;
/// use bdk::keys::{
/// any_network, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext,
/// };
///
/// struct MyCustomKeyType {
/// key_data: bitcoin::PrivateKey,
/// chain_code: Vec<u8>,
/// chain_code: [u8; 32],
/// }
///
/// 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.as_ref()),
/// chain_code: bip32::ChainCode::from(&self.chain_code),
/// child_number: bip32::ChildNumber::Normal { index: 0 },
/// };
///
@ -925,13 +925,13 @@ pub enum KeyError {
Message(String),
/// BIP32 error
Bip32(bitcoin::util::bip32::Error),
Bip32(bitcoin::bip32::Error),
/// Miniscript error
Miniscript(miniscript::Error),
}
impl_error!(miniscript::Error, Miniscript, KeyError);
impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError);
impl_error!(bitcoin::bip32::Error, Bip32, KeyError);
impl std::fmt::Display for KeyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -950,7 +950,7 @@ impl std::error::Error for KeyError {}
#[cfg(test)]
pub mod test {
use bitcoin::util::bip32;
use bitcoin::bip32;
use super::*;

View File

@ -149,7 +149,7 @@ fn main() -> Result<(), bdk::Error> {
//! ```no_run
//! use std::str::FromStr;
//!
//! use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
//! use bitcoin::psbt::PartiallySignedTransaction as Psbt;
//!
//! use bdk::{Wallet, SignOptions};
//! use bdk::database::MemoryDatabase;

View File

@ -12,7 +12,7 @@
//! Additional functions on the `rust-bitcoin` `PartiallySignedTransaction` structure.
use crate::FeeRate;
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
use bitcoin::psbt::PartiallySignedTransaction as Psbt;
use bitcoin::TxOut;
// TODO upstream the functions here to `rust-bitcoin`?

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::{Address, Amount, PackedLockTime, Script, Sequence, Transaction, Txid, Witness};
pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
use bitcoin::{absolute, Address, Amount, Script, ScriptBuf, Sequence, Transaction, Txid, Witness};
pub use 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,7 +45,11 @@ impl TestClient {
let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap();
let node_address = bitcoind.client.get_new_address(None, None).unwrap();
let node_address = bitcoind
.client
.get_new_address(None, None)
.unwrap()
.assume_checked();
bitcoind
.client
.generate_to_address(101, &node_address)
@ -107,7 +111,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());
panic!("Insufficient funds in bitcoind. Please generate a few blocks with: `bitcoin-cli generatetoaddress 10 {}`", self.get_new_address(None, None).unwrap().assume_checked());
}
// FIXME: core can't create a tx with two outputs to the same address
@ -143,6 +147,7 @@ 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);
@ -161,7 +166,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 = Script::from_hex(&mut tx.vout[0].script_pub_key.hex.to_hex()).unwrap();
let monitor_script = ScriptBuf::from_bytes(tx.vout[0].script_pub_key.hex.clone());
self.wait_for_tx(new_txid, &monitor_script);
debug!("Bumped {}, new txid {}", txid, new_txid);
@ -170,41 +175,44 @@ impl TestClient {
}
pub fn generate_manually(&mut self, txs: Vec<Transaction>) -> String {
use bitcoin::blockdata::block::{Block, BlockHeader};
use bitcoin::blockdata::block::{Block, Header, Version};
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 = BlockHeader {
version: block_template["version"].as_i64().unwrap() as i32,
prev_blockhash: BlockHash::from_hex(
let header = Header {
version: Version::from_consensus(block_template["version"].as_i64().unwrap() as i32),
prev_blockhash: BlockHash::from_str(
block_template["previousblockhash"].as_str().unwrap(),
)
.unwrap(),
merkle_root: TxMerkleNode::all_zeros(),
time: block_template["curtime"].as_u64().unwrap() as u32,
bits: u32::from_str_radix(block_template["bits"].as_str().unwrap(), 16).unwrap(),
bits: CompactTarget::from_consensus(
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: Vec<u8> = sha256d::Hash::all_zeros().as_ref().into();
let witness_reserved_value = sha256d::Hash::all_zeros().as_byte_array().to_vec();
// burn block subsidy and fees, not a big deal
let mut coinbase_tx = Transaction {
version: 1,
lock_time: PackedLockTime(0),
lock_time: absolute::LockTime::ZERO,
input: vec![TxIn {
previous_output: OutPoint::null(),
script_sig: Builder::new().push_int(height).into_script(),
sequence: Sequence(0xFFFFFFFF),
witness: Witness::from_vec(vec![witness_reserved_value]),
witness: Witness::from_slice(&vec![witness_reserved_value]),
}],
output: vec![],
};
@ -225,7 +233,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);
coinbase_witness_commitment_script.extend_from_slice(witness_commitment.as_ref());
coinbase_tx.output.push(TxOut {
value: 0,
@ -245,11 +253,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 = serialize(&block).to_hex();
let block_hex: String = bitcoin::consensus::encode::serialize_hex(&block);
debug!("generated block hex: {}", block_hex);
self.electrsd.client.block_headers_subscribe().unwrap();
@ -265,11 +273,12 @@ impl TestClient {
self.wait_for_block(height as usize);
block.header.block_hash().to_hex()
block.header.block_hash().to_string()
}
pub fn generate(&mut self, num_blocks: u64, address: Option<Address>) {
let address = address.unwrap_or_else(|| self.get_new_address(None, None).unwrap());
let address =
address.unwrap_or_else(|| self.get_new_address(None, None).unwrap().assume_checked());
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;
@ -317,9 +326,11 @@ impl TestClient {
&self
.get_new_address(None, address_type)
.unwrap()
.assume_checked()
.to_string(),
)
.unwrap()
.assume_checked()
}
}
@ -378,7 +389,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};
use $crate::bitcoin::{Transaction, Network, blockdata::script::PushBytesBuf};
use $crate::testutils::blockchain_tests::TestClient;
use $crate::blockchain::Blockchain;
use $crate::database::MemoryDatabase;
@ -386,6 +397,7 @@ macro_rules! bdk_blockchain_tests {
use $crate::wallet::AddressIndex;
use $crate::{Wallet, FeeRate, SyncOptions};
use $crate::testutils;
use std::convert::TryFrom;
use super::*;
@ -1058,7 +1070,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 = [42u8;80];
let data = PushBytesBuf::try_from(vec![42u8;80]).unwrap();
builder.add_data(&data);
let (mut psbt, details) = builder.finish().unwrap();
@ -1066,7 +1078,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), "cannot find op_return data in transaction");
assert!(serialized_tx.windows(data.len()).any(|e| e==data.as_bytes()), "cannot find op_return data in transaction");
blockchain.broadcast(&tx).unwrap();
test_client.generate(1, Some(node_addr));
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
@ -1086,18 +1098,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(1, Some(wallet_addr));
test_client.generate(2, Some(wallet_addr));
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert!(wallet.get_balance().unwrap().immature > 0, "incorrect balance after receiving coinbase");
assert_eq!(wallet.get_balance().unwrap().immature, 5000000000*2, "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!(wallet.get_balance().unwrap().confirmed > 0, "incorrect balance after maturing coinbase");
assert_eq!(wallet.get_balance().unwrap().confirmed, 5000000000 * 2, "incorrect balance after maturing coinbase");
}
@ -1165,8 +1177,9 @@ 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 = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap();
assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap());
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());
// 3. Send 50_000 sats from test bitcoind node to test BDK wallet
@ -1215,7 +1228,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();
let core_address = test_client.get_new_address(None, None).unwrap().assume_checked();
let tx = testutils! {
@tx ( (@addr bdk_address.clone()) => 50_000, (@addr core_address.clone()) => 40_000 )
};
@ -1407,8 +1420,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 = 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 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 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!([{
@ -1421,7 +1434,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::util::psbt::PartiallySignedTransaction;
use bitcoin::psbt::PartiallySignedTransaction;
// Test parsing core created PSBT
let mut psbt = PartiallySignedTransaction::from_str(&core_psbt).expect("core taproot psbt");

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).address(bitcoin::Network::Regtest).expect("No address form")
parsed.at_derivation_index($child).unwrap().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::util::bip32::ExtendedPrivKey::new_master(
let key = $crate::bitcoin::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, util::psbt};
use bitcoin::{hash_types::Txid, psbt, Weight};
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: usize) -> FeeRate {
Self::from_vb(fee, wu.vbytes())
pub fn from_wu(fee: u64, wu: Weight) -> FeeRate {
Self::from_vb(fee, wu.to_vbytes_ceil() as usize)
}
/// 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: usize) -> u64 {
self.fee_vb(wu.vbytes())
pub fn fee_wu(&self, wu: Weight) -> u64 {
self.fee_vb(wu.to_vbytes_ceil() as usize)
}
/// Calculate absolute fee in Satoshis using size in virtual bytes.
@ -405,7 +405,7 @@ mod tests {
let tx_details_a = TransactionDetails {
transaction: None,
txid: Txid::from_inner([0; 32]),
txid: Txid::all_zeros(),
received: 0,
sent: 0,
fee: None,
@ -414,7 +414,7 @@ mod tests {
let tx_details_b = TransactionDetails {
transaction: None,
txid: Txid::from_inner([0; 32]),
txid: Txid::all_zeros(),
received: 0,
sent: 0,
fee: None,
@ -423,7 +423,7 @@ mod tests {
let tx_details_c = TransactionDetails {
transaction: None,
txid: Txid::from_inner([0; 32]),
txid: Txid::all_zeros(),
received: 0,
sent: 0,
fee: None,
@ -432,7 +432,7 @@ mod tests {
let tx_details_d = TransactionDetails {
transaction: None,
txid: Txid::from_inner([1; 32]),
txid: Txid::from_byte_array([1; Txid::LEN]),
received: 0,
sent: 0,
fee: None,

View File

@ -40,12 +40,12 @@
//! database: &D,
//! required_utxos: Vec<WeightedUtxo>,
//! optional_utxos: Vec<WeightedUtxo>,
//! fee_rate: FeeRate,
//! fee_rate: bdk::FeeRate,
//! target_amount: u64,
//! drain_script: &Script,
//! ) -> Result<CoinSelectionResult, bdk::Error> {
//! let mut selected_amount = 0;
//! let mut additional_weight = 0;
//! let mut additional_weight = Weight::ZERO;
//! let all_utxos_selected = required_utxos
//! .into_iter()
//! .chain(optional_utxos)
@ -53,7 +53,9 @@
//! (&mut selected_amount, &mut additional_weight),
//! |(selected_amount, additional_weight), weighted_utxo| {
//! **selected_amount += weighted_utxo.utxo.txout().value;
//! **additional_weight += TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight;
//! **additional_weight += Weight::from_wu(
//! (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
//! );
//! Some(weighted_utxo.utxo)
//! },
//! )
@ -82,7 +84,10 @@
//! # let wallet = doctest_wallet!();
//! // create wallet, sync, ...
//!
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
//! .unwrap()
//! .require_network(Network::Testnet)
//! .unwrap();
//! let (psbt, details) = {
//! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything);
//! builder.add_recipient(to_address.script_pubkey(), 50_000);
@ -100,7 +105,7 @@ use crate::{database::Database, WeightedUtxo};
use crate::{error::Error, Utxo};
use bitcoin::consensus::encode::serialize;
use bitcoin::Script;
use bitcoin::{Script, Weight};
#[cfg(test)]
use assert_matches::assert_matches;
@ -337,8 +342,9 @@ 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(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
**fee_amount += fee_rate.fee_wu(Weight::from_wu(
(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
));
**selected_amount += weighted_utxo.utxo.txout().value;
log::debug!(
@ -386,7 +392,9 @@ struct OutputGroup {
impl OutputGroup {
fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self {
let fee = fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight);
let fee = fee_rate.fee_wu(Weight::from_wu(
(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64,
));
let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64;
OutputGroup {
weighted_utxo,
@ -724,7 +732,7 @@ impl BranchAndBoundCoinSelection {
mod test {
use std::str::FromStr;
use bitcoin::{OutPoint, Script, TxOut};
use bitcoin::{OutPoint, ScriptBuf, TxOut};
use super::*;
use crate::database::{BatchOperations, MemoryDatabase};
@ -754,7 +762,7 @@ mod test {
outpoint,
txout: TxOut {
value,
script_pubkey: Script::new(),
script_pubkey: ScriptBuf::new(),
},
keychain: KeychainKind::External,
is_spent: false,
@ -837,7 +845,7 @@ mod test {
.unwrap(),
txout: TxOut {
value: rng.gen_range(0..200000000),
script_pubkey: Script::new(),
script_pubkey: ScriptBuf::new(),
},
keychain: KeychainKind::External,
is_spent: false,
@ -857,7 +865,7 @@ mod test {
.unwrap(),
txout: TxOut {
value: utxos_value,
script_pubkey: Script::new(),
script_pubkey: ScriptBuf::new(),
},
keychain: KeychainKind::External,
is_spent: false,
@ -879,7 +887,7 @@ mod test {
fn test_largest_first_coin_selection_success() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 250_000 + FEE_AMOUNT;
let result = LargestFirstCoinSelection::default()
@ -902,7 +910,7 @@ mod test {
fn test_largest_first_coin_selection_use_all() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = LargestFirstCoinSelection::default()
@ -925,7 +933,7 @@ mod test {
fn test_largest_first_coin_selection_use_only_necessary() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = LargestFirstCoinSelection::default()
@ -949,7 +957,7 @@ mod test {
fn test_largest_first_coin_selection_insufficient_funds() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 500_000 + FEE_AMOUNT;
LargestFirstCoinSelection::default()
@ -969,7 +977,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 = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 250_000 + FEE_AMOUNT;
LargestFirstCoinSelection::default()
@ -988,7 +996,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 = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 180_000 + FEE_AMOUNT;
let result = OldestFirstCoinSelection::default()
@ -1013,7 +1021,7 @@ mod test {
let utxo1 = utxo(120_000, 1);
let utxo2 = utxo(80_000, 2);
let utxo3 = utxo(300_000, 3);
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let mut database = MemoryDatabase::default();
@ -1070,7 +1078,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 = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = OldestFirstCoinSelection::default()
@ -1093,7 +1101,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 = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = OldestFirstCoinSelection::default()
@ -1117,7 +1125,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 = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 600_000 + FEE_AMOUNT;
OldestFirstCoinSelection::default()
@ -1139,7 +1147,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 = Script::default();
let drain_script = ScriptBuf::default();
OldestFirstCoinSelection::default()
.coin_select(
@ -1160,7 +1168,7 @@ mod test {
let utxos = generate_same_value_utxos(100_000, 20);
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 250_000 + FEE_AMOUNT;
@ -1184,7 +1192,7 @@ mod test {
fn test_bnb_coin_selection_required_are_enough() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 20_000 + FEE_AMOUNT;
let result = BranchAndBoundCoinSelection::default()
@ -1207,7 +1215,7 @@ mod test {
fn test_bnb_coin_selection_optional_are_enough() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 299756 + FEE_AMOUNT;
let result = BranchAndBoundCoinSelection::default()
@ -1241,7 +1249,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 = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 150_000 + FEE_AMOUNT;
@ -1266,7 +1274,7 @@ mod test {
fn test_bnb_coin_selection_insufficient_funds() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 500_000 + FEE_AMOUNT;
BranchAndBoundCoinSelection::default()
@ -1286,7 +1294,7 @@ mod test {
fn test_bnb_coin_selection_insufficient_funds_high_fees() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 250_000 + FEE_AMOUNT;
BranchAndBoundCoinSelection::default()
@ -1305,7 +1313,7 @@ mod test {
fn test_bnb_coin_selection_check_fee_rate() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 99932; // first utxo's effective value
let result = BranchAndBoundCoinSelection::new(0)
@ -1335,7 +1343,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 = Script::default();
let drain_script = ScriptBuf::default();
let result = BranchAndBoundCoinSelection::new(0)
.coin_select(
&database,
@ -1364,7 +1372,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 = Script::default();
let drain_script = ScriptBuf::default();
let target_amount = 20_000 + FEE_AMOUNT;
BranchAndBoundCoinSelection::new(size_of_change)
.bnb(
@ -1395,7 +1403,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 = Script::default();
let drain_script = ScriptBuf::default();
BranchAndBoundCoinSelection::new(size_of_change)
.bnb(
@ -1431,7 +1439,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 = Script::default();
let drain_script = ScriptBuf::default();
let result = BranchAndBoundCoinSelection::new(size_of_change)
.bnb(
@ -1471,7 +1479,7 @@ mod test {
let target_amount =
optional_utxos[3].effective_value + optional_utxos[23].effective_value;
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let result = BranchAndBoundCoinSelection::new(0)
.bnb(
@ -1502,7 +1510,7 @@ mod test {
.map(|u| OutputGroup::new(u, fee_rate))
.collect();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let result = BranchAndBoundCoinSelection::default().single_random_draw(
vec![],
@ -1521,7 +1529,7 @@ mod test {
fn test_bnb_exclude_negative_effective_value() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,
@ -1545,7 +1553,7 @@ mod test {
fn test_bnb_include_negative_effective_value_when_required() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let (required, optional) = utxos
.into_iter()
@ -1573,7 +1581,7 @@ mod test {
fn test_bnb_sum_of_effective_value_negative() {
let utxos = get_test_utxos();
let database = MemoryDatabase::default();
let drain_script = Script::default();
let drain_script = ScriptBuf::default();
let selection = BranchAndBoundCoinSelection::default().coin_select(
&database,

View File

@ -20,7 +20,7 @@
//! # use bdk::wallet::hardwaresigner::HWISigner;
//! # use bdk::wallet::AddressIndex::New;
//! # use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
//! # use hwi::{types::HWIChain, HWIClient};
//! # use hwi::HWIClient;
//! # use std::sync::Arc;
//! #
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -29,7 +29,7 @@
//! panic!("No devices found!");
//! }
//! let first_device = devices.remove(0)?;
//! let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?;
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
//!
//! # let mut wallet = Wallet::new(
//! # "",
@ -49,9 +49,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};

View File

@ -24,10 +24,11 @@ use std::sync::Arc;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::consensus::encode::serialize;
use bitcoin::util::psbt;
use bitcoin::psbt;
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
use bitcoin::{
Address, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script, Sequence,
Transaction, TxOut, Txid, Witness,
absolute, Address, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxOut, Txid,
Weight, Witness,
};
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
@ -116,13 +117,15 @@ pub enum AddressIndex {
/// web page.
LastUnused,
/// Return the address for a specific descriptor index. Does not change the current descriptor
/// index used by `AddressIndex::New` and `AddressIndex::LastUsed`.
/// index used by `AddressIndex::New` and `AddressIndex::LastUsed`. The index must be non-hardened,
/// i.e., < 2**31.
///
/// Use with caution, if an index is given that is less than the current descriptor index
/// then the returned address may have already been used.
Peek(u32),
/// Return the address for a specific descriptor index and reset the current descriptor index
/// used by `AddressIndex::New` and `AddressIndex::LastUsed` to this value.
/// used by `AddressIndex::New` and `AddressIndex::LastUsed` to this value. The index must be
/// non-hardened, i.e. < 2**31
///
/// Use with caution, if an index is given that is less than the current descriptor index
/// then the returned address and subsequent addresses returned by calls to `AddressIndex::New`
@ -257,6 +260,7 @@ where
let address_result = self
.get_descriptor_for_keychain(keychain)
.at_derivation_index(incremented_index)
.expect("can't be hardened")
.address(self.network);
address_result
@ -275,7 +279,8 @@ where
let derived_key = self
.get_descriptor_for_keychain(keychain)
.at_derivation_index(current_index);
.at_derivation_index(current_index)
.expect("can't be hardened");
let script_pubkey = derived_key.script_pubkey();
@ -304,6 +309,7 @@ where
fn peek_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
self.get_descriptor_for_keychain(keychain)
.at_derivation_index(index)
.map_err(|_| Error::HardenedIndex)?
.address(self.network)
.map(|address| AddressInfo {
index,
@ -320,6 +326,7 @@ where
self.get_descriptor_for_keychain(keychain)
.at_derivation_index(index)
.map_err(|_| Error::HardenedIndex)?
.address(self.network)
.map(|address| AddressInfo {
index,
@ -567,7 +574,7 @@ where
/// # use bdk::database::*;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = doctest_wallet!();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
/// let (psbt, details) = {
/// let mut builder = wallet.build_tx();
/// builder
@ -669,7 +676,8 @@ where
let current_height = match params.current_height {
// If they didn't tell us the current height, we assume it's the latest sync height.
None => self.database().get_sync_time()?.map(|sync_time| {
LockTime::from_height(sync_time.block_time.height).expect("Invalid height")
absolute::LockTime::from_height(sync_time.block_time.height)
.expect("Invalid height")
}),
h => h,
};
@ -680,7 +688,7 @@ where
// Fee sniping can be partially prevented by setting the timelock
// to current_height. If we don't know the current_height,
// we default to 0.
let fee_sniping_height = current_height.unwrap_or(LockTime::ZERO);
let fee_sniping_height = current_height.unwrap_or(absolute::LockTime::ZERO);
// We choose the biggest between the required nlocktime and the fee sniping
// height
@ -688,7 +696,7 @@ where
// No requirement, just use the fee_sniping_height
None => fee_sniping_height,
// There's a block-based requirement, but the value is lower than the fee_sniping_height
Some(value @ LockTime::Blocks(_)) if value < fee_sniping_height => fee_sniping_height,
Some(value @ absolute::LockTime::Blocks(_)) if value < fee_sniping_height => fee_sniping_height,
// There's a time-based requirement or a block-based requirement greater
// than the fee_sniping_height use that value
Some(value) => value,
@ -704,7 +712,9 @@ where
let n_sequence = match (params.rbf, requirements.csv) {
// No RBF or CSV but there's an nLockTime, so the nSequence cannot be final
(None, None) if lock_time != LockTime::ZERO => Sequence::ENABLE_LOCKTIME_NO_RBF,
(None, None) if lock_time != absolute::LockTime::ZERO => {
Sequence::ENABLE_LOCKTIME_NO_RBF
}
// No RBF, CSV or nLockTime, make the transaction final
(None, None) => Sequence::MAX,
@ -767,7 +777,7 @@ where
let mut tx = Transaction {
version,
lock_time: lock_time.into(),
lock_time,
input: vec![],
output: vec![],
};
@ -815,7 +825,7 @@ where
// end up with a transaction with a slightly higher fee rate than the requested one.
// If, instead, we undershoot, we may end up with a feerate lower than the requested one
// - we might come up with non broadcastable txs!
fee_amount += fee_rate.fee_wu(2);
fee_amount += fee_rate.fee_wu(Weight::from_wu(2));
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
&& self.change_descriptor.is_none()
@ -832,7 +842,7 @@ where
params.drain_wallet,
params.manually_selected_only,
params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee
current_height.map(LockTime::to_consensus_u32),
current_height.map(absolute::LockTime::to_consensus_u32),
)?;
// get drain script
@ -860,7 +870,7 @@ where
.iter()
.map(|u| bitcoin::TxIn {
previous_output: u.outpoint(),
script_sig: Script::default(),
script_sig: ScriptBuf::default(),
sequence: n_sequence,
witness: Witness::new(),
})
@ -949,7 +959,8 @@ where
/// # use bdk::database::*;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = doctest_wallet!();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let to_address =
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
/// let (mut psbt, _) = {
/// let mut builder = wallet.build_tx();
/// builder
@ -963,7 +974,7 @@ where
/// let (mut psbt, _) = {
/// let mut builder = wallet.build_fee_bump(tx.txid())?;
/// builder
/// .fee_rate(FeeRate::from_sat_per_vb(5.0));
/// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0));
/// builder.finish()?
/// };
///
@ -1010,6 +1021,7 @@ where
.borrow()
.get_path_from_script_pubkey(&txout.script_pubkey)?
{
#[allow(deprecated)]
Some((keychain, _)) => (
self._get_descriptor_for_keychain(keychain)
.0
@ -1100,7 +1112,7 @@ where
/// # use bdk::database::*;
/// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
/// # let wallet = doctest_wallet!();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
/// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
/// let (mut psbt, _) = {
/// let mut builder = wallet.build_tx();
/// builder.add_recipient(to_address.script_pubkey(), 50_000);
@ -1137,8 +1149,8 @@ where
&& !psbt.inputs.iter().all(|i| {
i.sighash_type.is_none()
|| i.sighash_type == Some(EcdsaSighashType::All.into())
|| i.sighash_type == Some(SchnorrSighashType::All.into())
|| i.sighash_type == Some(SchnorrSighashType::Default.into())
|| i.sighash_type == Some(TapSighashType::All.into())
|| i.sighash_type == Some(TapSighashType::Default.into())
})
{
return Err(Error::Signer(signer::SignerError::NonStandardSighash));
@ -1323,7 +1335,10 @@ where
.borrow()
.get_path_from_script_pubkey(&txout.script_pubkey)?
.map(|(keychain, child)| (self.get_descriptor_for_keychain(keychain), child))
.map(|(desc, child)| desc.at_derivation_index(child)))
.map(|(desc, child)| {
desc.at_derivation_index(child)
.expect("child is not hardened")
}))
}
fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
@ -1384,7 +1399,10 @@ where
let start_time = time::Instant::new();
for i in from..(from + count) {
address_batch.set_script_pubkey(
&descriptor.at_derivation_index(i).script_pubkey(),
&descriptor
.at_derivation_index(i)
.expect("i is not hardened")
.script_pubkey(),
keychain,
i,
)?;
@ -1410,6 +1428,7 @@ where
let keychain = utxo.keychain;
(
utxo,
#[allow(deprecated)]
self.get_descriptor_for_keychain(keychain)
.max_satisfaction_weight()
.unwrap(),
@ -1614,7 +1633,9 @@ where
};
let desc = self.get_descriptor_for_keychain(keychain);
let derived_descriptor = desc.at_derivation_index(child);
let derived_descriptor = desc
.at_derivation_index(child)
.expect("child can't be hardened");
psbt_input
.update_with_descriptor_unchecked(&derived_descriptor)
@ -1665,7 +1686,9 @@ where
);
let desc = self.get_descriptor_for_keychain(keychain);
let desc = desc.at_derivation_index(child);
let desc = desc
.at_derivation_index(child)
.expect("child can't be hardened");
if is_input {
psbt.update_input_with_descriptor(index, &desc)
@ -1830,6 +1853,7 @@ pub fn get_funded_wallet(
.set_script_pubkey(
&bitcoin::Address::from_str(&tx_meta.output.get(0).unwrap().to_address)
.unwrap()
.assume_checked()
.script_pubkey(),
KeychainKind::External,
funding_address_kix,
@ -1849,7 +1873,7 @@ pub fn get_funded_wallet(
#[cfg(test)]
pub(crate) mod test {
use assert_matches::assert_matches;
use bitcoin::{util::psbt, Network, PackedLockTime, Sequence};
use bitcoin::{absolute, blockdata::script::PushBytes, psbt, Network, Sequence};
use crate::database::Database;
use crate::types::KeychainKind;
@ -2185,7 +2209,7 @@ pub(crate) mod test {
// Since we never synced the wallet we don't have a last_sync_height
// we could use to try to prevent fee sniping. We default to 0.
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(0));
assert_eq!(psbt.unsigned_tx.lock_time, absolute::LockTime::ZERO);
}
#[test]
@ -2210,7 +2234,10 @@ pub(crate) mod test {
let (psbt, _) = builder.finish().unwrap();
// current_height will override the last sync height
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(current_height));
assert_eq!(
psbt.unsigned_tx.lock_time,
absolute::LockTime::from_height(current_height).unwrap()
);
}
#[test]
@ -2235,7 +2262,7 @@ pub(crate) mod test {
// If there's no current_height we're left with using the last sync height
assert_eq!(
psbt.unsigned_tx.lock_time,
PackedLockTime(sync_time.block_time.height)
absolute::LockTime::from_height(sync_time.block_time.height).unwrap()
);
}
@ -2247,7 +2274,10 @@ pub(crate) mod test {
builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(100_000));
assert_eq!(
psbt.unsigned_tx.lock_time,
absolute::LockTime::from_height(100_000).unwrap()
);
}
#[test]
@ -2258,13 +2288,16 @@ pub(crate) mod test {
builder
.add_recipient(addr.script_pubkey(), 25_000)
.current_height(630_001)
.nlocktime(LockTime::from_height(630_000).unwrap());
.nlocktime(absolute::LockTime::from_height(630_000).unwrap());
let (psbt, _) = builder.finish().unwrap();
// When we explicitly specify a nlocktime
// we don't try any fee sniping prevention trick
// (we ignore the current_height)
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(630_000));
assert_eq!(
psbt.unsigned_tx.lock_time,
absolute::LockTime::from_height(630_000).unwrap()
);
}
#[test]
@ -2274,10 +2307,13 @@ pub(crate) mod test {
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
.nlocktime(LockTime::from_height(630_000).unwrap());
.nlocktime(absolute::LockTime::from_height(630_000).unwrap());
let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.unsigned_tx.lock_time, PackedLockTime(630_000));
assert_eq!(
psbt.unsigned_tx.lock_time,
absolute::LockTime::from_height(630_000).unwrap()
);
}
#[test]
@ -2290,7 +2326,7 @@ pub(crate) mod test {
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
.nlocktime(LockTime::from_height(50000).unwrap());
.nlocktime(absolute::LockTime::from_height(50000).unwrap());
builder.finish().unwrap();
}
@ -2428,7 +2464,9 @@ pub(crate) mod test {
#[test]
fn test_create_tx_drain_wallet_and_drain_to_and_with_recipient() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
let addr = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
.unwrap()
.assume_checked();
let drain_addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder
@ -2645,19 +2683,18 @@ pub(crate) mod test {
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
.sighash(bitcoin::EcdsaSighashType::Single.into());
.sighash(bitcoin::sighash::EcdsaSighashType::Single.into());
let (psbt, _) = builder.finish().unwrap();
assert_eq!(
psbt.inputs[0].sighash_type,
Some(bitcoin::EcdsaSighashType::Single.into())
Some(bitcoin::sighash::EcdsaSighashType::Single.into())
);
}
#[test]
fn test_create_tx_input_hd_keypaths() {
use bitcoin::util::bip32::{DerivationPath, Fingerprint};
use std::str::FromStr;
use bitcoin::bip32::{DerivationPath, Fingerprint};
let (wallet, _, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
let addr = wallet.get_address(New).unwrap();
@ -2677,8 +2714,7 @@ pub(crate) mod test {
#[test]
fn test_create_tx_output_hd_keypaths() {
use bitcoin::util::bip32::{DerivationPath, Fingerprint};
use std::str::FromStr;
use bitcoin::bip32::{DerivationPath, Fingerprint};
let (wallet, descriptors, _) = get_funded_wallet("wpkh([d34db33f/44'/0'/0']tpubDEnoLuPdBep9bzw5LoGYpsxUQYheRQ9gcgrJhJEcdKFB9cWQRyYmkCyRoTqeD4tJYiVVgt6A3rN6rWn9RYhR9sBsGxji29LYWHuKKbdb1ev/0/*)");
// cache some addresses
@ -2712,7 +2748,7 @@ pub(crate) mod test {
assert_eq!(
psbt.inputs[0].redeem_script,
Some(Script::from(
Some(ScriptBuf::from(
Vec::<u8>::from_hex(
"21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
)
@ -2736,7 +2772,7 @@ pub(crate) mod test {
assert_eq!(psbt.inputs[0].redeem_script, None);
assert_eq!(
psbt.inputs[0].witness_script,
Some(Script::from(
Some(ScriptBuf::from(
Vec::<u8>::from_hex(
"21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac"
)
@ -2756,7 +2792,7 @@ pub(crate) mod test {
builder.drain_to(addr.script_pubkey()).drain_wallet();
let (psbt, _) = builder.finish().unwrap();
let script = Script::from(
let script = ScriptBuf::from(
Vec::<u8>::from_hex(
"21032b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3ac",
)
@ -2830,7 +2866,9 @@ pub(crate) mod test {
Some(100),
);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
@ -2859,7 +2897,9 @@ pub(crate) mod test {
Some(100),
);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
@ -2877,7 +2917,9 @@ pub(crate) mod test {
fn test_create_tx_policy_path_required() {
let (wallet, _, _) = get_funded_wallet(get_test_a_or_b_plus_csv());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 30_000);
builder.finish().unwrap();
@ -2907,7 +2949,9 @@ pub(crate) mod test {
// child #0 is just the key "A"
let path = vec![(root_id, vec![0])].into_iter().collect();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
@ -2926,7 +2970,9 @@ pub(crate) mod test {
// child #1 is or(pk(B),older(144))
let path = vec![(root_id, vec![1])].into_iter().collect();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
@ -2936,10 +2982,31 @@ pub(crate) mod test {
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(144));
}
#[test]
fn test_create_tx_policy_path_ignored_subtree_with_csv() {
let (wallet, _, _) = get_funded_wallet("wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),or_i(and_v(v:pkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(30)),and_v(v:pkh(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(90)))))");
let external_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
let root_id = external_policy.id;
// child #0 is pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)
let path = vec![(root_id, vec![0])].into_iter().collect();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
.policy_path(path, KeychainKind::External);
let (psbt, _) = builder.finish().unwrap();
assert_eq!(psbt.unsigned_tx.input[0].sequence, Sequence(0xFFFFFFFE));
}
#[test]
fn test_create_tx_global_xpubs_with_origin() {
use bitcoin::bip32;
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::bip32;
let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_address(New).unwrap();
@ -2963,8 +3030,11 @@ pub(crate) mod test {
let (wallet2, _, _) =
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let utxo = wallet2.list_unspent().unwrap().remove(0);
#[allow(deprecated)]
let foreign_utxo_satisfaction = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
@ -3030,6 +3100,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let mut builder = wallet.build_tx();
let outpoint = wallet.list_unspent().unwrap()[0].outpoint;
#[allow(deprecated)]
let foreign_utxo_satisfaction = wallet
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
@ -3063,6 +3134,7 @@ pub(crate) mod test {
.transaction
.unwrap();
#[allow(deprecated)]
let satisfaction_weight = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
@ -3102,9 +3174,12 @@ pub(crate) mod test {
let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
let (wallet2, _, txid2) =
get_funded_wallet("wpkh(cVbZ8ovhye9AoAHFsqobCf7LxbXDAECy9Kb8TZdfsDYMZGBUyCnm)");
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let utxo2 = wallet2.list_unspent().unwrap().remove(0);
#[allow(deprecated)]
let satisfaction_weight = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
@ -3194,8 +3269,8 @@ pub(crate) mod test {
#[test]
fn test_create_tx_global_xpubs_master_without_origin() {
use bitcoin::bip32;
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::bip32;
let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
let addr = wallet.get_address(New).unwrap();
@ -3324,7 +3399,9 @@ pub(crate) mod test {
#[test]
fn test_bump_fee_reduce_change() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -3384,7 +3461,9 @@ pub(crate) mod test {
#[test]
fn test_bump_fee_absolute_reduce_change() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 25_000)
@ -3450,7 +3529,9 @@ pub(crate) mod test {
#[test]
fn test_bump_fee_reduce_single_recipient() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -3494,7 +3575,9 @@ pub(crate) mod test {
#[test]
fn test_bump_fee_absolute_reduce_single_recipient() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -3548,7 +3631,9 @@ pub(crate) mod test {
txid: incoming_txid,
vout: 0,
};
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -3605,7 +3690,9 @@ pub(crate) mod test {
txid: incoming_txid,
vout: 0,
};
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -3648,7 +3735,9 @@ pub(crate) mod test {
Some(100),
);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 45_000)
@ -3711,7 +3800,9 @@ pub(crate) mod test {
Some(100),
);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 45_000)
@ -3775,7 +3866,9 @@ pub(crate) mod test {
);
// initially make a tx without change by using `drain_to`
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
@ -3851,7 +3944,9 @@ pub(crate) mod test {
Some(100),
);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 45_000)
@ -3888,7 +3983,7 @@ pub(crate) mod test {
// + extra input weight: 160 WU = (32 (prevout) + 4 (vout) + 4 (nsequence)) * 4
// + input satisfaction weight: 112 WU = 106 (witness) + 2 (witness len) + (1 (script len)) * 4
// - change output weight: 124 WU = (8 (value) + 1 (script len) + 22 (script)) * 4
let new_tx_weight = original_tx_weight + 160 + 112 - 124;
let new_tx_weight = original_tx_weight + Weight::from_wu(160 + 112 - 124);
// two inputs (50k, 25k) and one output (45k) - epsilon
// We use epsilon here to avoid asking for a slightly too high feerate
let fee_abs = 50_000 + 25_000 - 45_000 - 10;
@ -3928,7 +4023,9 @@ pub(crate) mod test {
Some(100),
);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 45_000)
@ -3999,7 +4096,9 @@ pub(crate) mod test {
Some(100),
);
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 45_000)
@ -4071,7 +4170,9 @@ pub(crate) mod test {
// The replacement transaction may only include an unconfirmed input
// if that input was included in one of the original transactions.
let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.drain_wallet()
@ -4115,7 +4216,9 @@ pub(crate) mod test {
// always fee bump with an unconfirmed input if it was included in the
// original transaction)
let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
// We receive a tx with 0 confirmations, which will be used as an input
// in the drain tx.
crate::populate_test_db!(
@ -4165,7 +4268,9 @@ pub(crate) mod test {
// for a transaction.
// See https://github.com/bitcoindevkit/bdk/issues/660
let (wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt").unwrap();
let send_to = Address::from_str("tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt")
.unwrap()
.assume_checked();
let fee_rate = FeeRate::from_sat_per_vb(2.01);
let incoming_txid = crate::populate_test_db!(
wallet.database.borrow_mut(),
@ -4283,7 +4388,9 @@ pub(crate) mod test {
#[test]
fn test_include_output_redeem_witness_script() {
let (wallet, _, _) = get_funded_wallet("sh(wsh(multi(1,cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW,cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu)))");
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 45_000)
@ -4300,7 +4407,9 @@ pub(crate) mod test {
#[test]
fn test_signing_only_one_of_multiple_inputs() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 45_000)
@ -4308,7 +4417,7 @@ pub(crate) mod test {
let (mut psbt, _) = builder.finish().unwrap();
// add another input to the psbt that is at least passable.
let dud_input = bitcoin::util::psbt::Input {
let dud_input = bitcoin::psbt::Input {
witness_utxo: Some(TxOut {
value: 100_000,
script_pubkey: miniscript::Descriptor::<bitcoin::PublicKey>::from_str(
@ -4600,7 +4709,9 @@ pub(crate) mod test {
wallet.get_address(New).unwrap(),
AddressInfo {
index: 0,
address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a").unwrap(),
address: Address::from_str("tb1q6yn66vajcctph75pvylgkksgpp6nq04ppwct9a")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
}
);
@ -4610,7 +4721,9 @@ pub(crate) mod test {
wallet.get_address(New).unwrap(),
AddressInfo {
index: 1,
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
}
);
@ -4620,7 +4733,9 @@ pub(crate) mod test {
wallet.get_address(Peek(25)).unwrap(),
AddressInfo {
index: 25,
address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2").unwrap(),
address: Address::from_str("tb1qsp7qu0knx3sl6536dzs0703u2w2ag6ppl9d0c2")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
}
);
@ -4630,7 +4745,9 @@ pub(crate) mod test {
wallet.get_address(New).unwrap(),
AddressInfo {
index: 2,
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
}
);
@ -4640,7 +4757,9 @@ pub(crate) mod test {
wallet.get_address(Reset(1)).unwrap(),
AddressInfo {
index: 1,
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7").unwrap(),
address: Address::from_str("tb1q4er7kxx6sssz3q7qp7zsqsdx4erceahhax77d7")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
}
);
@ -4650,7 +4769,9 @@ pub(crate) mod test {
wallet.get_address(New).unwrap(),
AddressInfo {
index: 2,
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2").unwrap(),
address: Address::from_str("tb1qzntf2mqex4ehwkjlfdyy3ewdlk08qkvkvrz7x2")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
}
);
@ -4661,7 +4782,8 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr =
Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
.unwrap();
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 45_000);
builder.finish().unwrap();
@ -4670,7 +4792,7 @@ pub(crate) mod test {
#[test]
fn test_get_address() {
use crate::descriptor::template::Bip84;
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let wallet = Wallet::new(
Bip84(key, KeychainKind::External),
Some(Bip84(key, KeychainKind::Internal)),
@ -4683,7 +4805,9 @@ pub(crate) mod test {
wallet.get_address(AddressIndex::New).unwrap(),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
.unwrap()
.assume_checked(),
keychain: KeychainKind::External,
}
);
@ -4692,7 +4816,9 @@ pub(crate) mod test {
wallet.get_internal_address(AddressIndex::New).unwrap(),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79").unwrap(),
address: Address::from_str("bcrt1q0ue3s5y935tw7v3gmnh36c5zzsaw4n9c9smq79")
.unwrap()
.assume_checked(),
keychain: KeychainKind::Internal,
}
);
@ -4709,7 +4835,9 @@ pub(crate) mod test {
wallet.get_internal_address(AddressIndex::New).unwrap(),
AddressInfo {
index: 0,
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w").unwrap(),
address: Address::from_str("bcrt1qrhgaqu0zvf5q2d0gwwz04w0dh0cuehhqvzpp4w")
.unwrap()
.assume_checked(),
keychain: KeychainKind::Internal,
},
"when there's no internal descriptor it should just use external"
@ -4721,7 +4849,7 @@ pub(crate) mod test {
use crate::descriptor::template::Bip84;
use std::collections::HashSet;
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let wallet = Wallet::new(
Bip84(key, KeychainKind::External),
None,
@ -4784,7 +4912,7 @@ pub(crate) mod test {
let (wallet, _, _) = get_funded_wallet(get_test_tr_repeated_key());
let addr = wallet.get_address(AddressIndex::New).unwrap();
let path = vec![("e5mmg3xh".to_string(), vec![0])]
let path = vec![("rn4nre9c".to_string(), vec![0])]
.into_iter()
.collect();
@ -4804,13 +4932,6 @@ pub(crate) mod test {
assert_eq!(
input_key_origins,
vec![
(
from_str!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"),
(
vec![],
(FromStr::from_str("871fd295").unwrap(), vec![].into())
)
),
(
from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
(
@ -4824,7 +4945,14 @@ pub(crate) mod test {
],
(FromStr::from_str("ece52657").unwrap(), vec![].into())
)
)
),
(
from_str!("b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"),
(
vec![],
(FromStr::from_str("871fd295").unwrap(), vec![].into())
)
),
],
"Wrong input tap_key_origins"
);
@ -4844,10 +4972,8 @@ pub(crate) mod test {
#[test]
fn test_taproot_psbt_input_tap_tree() {
use crate::bitcoin::psbt::serialize::Deserialize;
use crate::bitcoin::psbt::TapTree;
use bitcoin::hashes::hex::FromHex;
use bitcoin::util::taproot;
use bitcoin::taproot;
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree());
let addr = wallet.get_address(AddressIndex::Peek(0)).unwrap();
@ -4859,7 +4985,7 @@ pub(crate) mod test {
assert_eq!(
psbt.inputs[0].tap_merkle_root,
Some(
FromHex::from_hex(
taproot::TapNodeHash::from_str(
"61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986"
)
.unwrap()
@ -4868,9 +4994,9 @@ pub(crate) mod test {
assert_eq!(
psbt.inputs[0].tap_scripts.clone().into_iter().collect::<Vec<_>>(),
vec![
(taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (from_str!("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac"), taproot::LeafVersion::TapScript)),
(taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (from_str!("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac"), taproot::LeafVersion::TapScript)),
],
(taproot::ControlBlock::decode(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (ScriptBuf::from_hex("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac").unwrap(), taproot::LeafVersion::TapScript)),
(taproot::ControlBlock::decode(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (ScriptBuf::from_hex("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac").unwrap(), taproot::LeafVersion::TapScript)),
],
);
assert_eq!(
psbt.inputs[0].tap_internal_key,
@ -4886,10 +5012,8 @@ pub(crate) mod test {
psbt.outputs[0].tap_internal_key
);
assert_eq!(
psbt.outputs[0].tap_tree,
Some(TapTree::deserialize(&Vec::<u8>::from_hex("01c022208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac01c0222051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",).unwrap()).unwrap())
);
let tap_tree: bitcoin::taproot::TapTree = serde_json::from_str(r#"[1,{"Script":["2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",192]},1,{"Script":["208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac",192]}]"#).unwrap();
assert_eq!(psbt.outputs[0].tap_tree, Some(tap_tree));
}
#[test]
@ -4960,9 +5084,12 @@ pub(crate) mod test {
let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
let (wallet2, _, _) = get_funded_wallet(get_test_tr_single_sig());
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let utxo = wallet2.list_unspent().unwrap().remove(0);
let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
#[allow(deprecated)]
let foreign_utxo_satisfaction = wallet2
.get_descriptor_for_keychain(KeychainKind::External)
.max_satisfaction_weight()
@ -5083,7 +5210,7 @@ pub(crate) mod test {
#[test]
fn test_taproot_script_spend_sign_include_some_leaves() {
use crate::signer::TapLeavesOptions;
use bitcoin::util::taproot::TapLeafHash;
use bitcoin::taproot::TapLeafHash;
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
let addr = wallet.get_address(AddressIndex::New).unwrap();
@ -5125,7 +5252,7 @@ pub(crate) mod test {
#[test]
fn test_taproot_script_spend_sign_exclude_some_leaves() {
use crate::signer::TapLeavesOptions;
use bitcoin::util::taproot::TapLeafHash;
use bitcoin::taproot::TapLeafHash;
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_both_priv());
let addr = wallet.get_address(AddressIndex::New).unwrap();
@ -5221,7 +5348,7 @@ pub(crate) mod test {
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
.sighash(SchnorrSighashType::All.into())
.sighash(TapSighashType::All.into())
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
@ -5234,7 +5361,7 @@ pub(crate) mod test {
#[test]
fn test_taproot_sign_non_default_sighash() {
let sighash = SchnorrSighashType::NonePlusAnyoneCanPay;
let sighash = TapSighashType::NonePlusAnyoneCanPay;
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
let addr = wallet.get_address(New).unwrap();
@ -5348,7 +5475,9 @@ pub(crate) mod test {
// We try to create a transaction, only to notice that all
// our funds are unspendable
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX")
.unwrap()
.assume_checked();
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), balance.immature / 2)
@ -5434,7 +5563,7 @@ pub(crate) mod test {
let addr = wallet.get_address(New).unwrap();
let fee_rate = FeeRate::from_sat_per_vb(1.0);
let mut builder = wallet.build_tx();
let mut data = vec![0];
let data: &PushBytes = From::<&[u8; 1]>::from(&[0; 1]);
builder
.drain_to(addr.script_pubkey())
.drain_wallet()
@ -5454,8 +5583,8 @@ pub(crate) mod test {
while sig_len < 71 {
// Changing the OP_RETURN data will make the signature change (but not the fee, until
// data[0] is small enough)
data[0] += 1;
psbt.unsigned_tx.output[op_return_vout].script_pubkey = Script::new_op_return(&data);
let data: &PushBytes = From::<&[u8; 1]>::from(&[1; 1]);
psbt.unsigned_tx.output[op_return_vout].script_pubkey = ScriptBuf::new_op_return(&data);
// Clearing the previous signature
psbt.inputs[0].partial_sigs.clear();
// Signing
@ -5526,7 +5655,6 @@ pub(crate) mod test {
#[test]
fn test_create_signer() {
use crate::wallet::hardwaresigner::HWISigner;
use hwi::types::HWIChain;
use hwi::HWIClient;
let mut devices = HWIClient::enumerate().unwrap();
@ -5534,9 +5662,9 @@ pub(crate) mod test {
panic!("No devices found!");
}
let device = devices.remove(0).unwrap();
let client = HWIClient::get_client(&device, true, HWIChain::Regtest).unwrap();
let client = HWIClient::get_client(&device, true, Network::Regtest.into()).unwrap();
let descriptors = client.get_descriptors::<String>(None).unwrap();
let custom_signer = HWISigner::from_device(&device, HWIChain::Regtest).unwrap();
let custom_signer = HWISigner::from_device(&device, Network::Regtest.into()).unwrap();
let (mut wallet, _, _) = get_funded_wallet(&descriptors.internal[0]);
wallet.add_signer(

View File

@ -19,7 +19,7 @@
//! # use std::str::FromStr;
//! # use bitcoin::secp256k1::{Secp256k1, All};
//! # use bitcoin::*;
//! # use bitcoin::util::psbt;
//! # use bitcoin::psbt;
//! # use bdk::signer::*;
//! # use bdk::database::*;
//! # use bdk::*;
@ -86,18 +86,17 @@ use std::fmt;
use std::ops::{Bound::Included, Deref};
use std::sync::Arc;
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::script::Builder as ScriptBuilder;
use bitcoin::hashes::{hash160, Hash};
use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
use bitcoin::hashes::hash160;
use bitcoin::secp256k1::Message;
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 bitcoin::sighash::{EcdsaSighashType, TapSighash, TapSighashType};
use bitcoin::{ecdsa, psbt, sighash, taproot};
use bitcoin::{key::TapTweak, key::XOnlyPublicKey, secp256k1};
use bitcoin::{PrivateKey, PublicKey};
use miniscript::descriptor::{
Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap, SinglePriv,
SinglePubKey,
Descriptor, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey,
InnerXKey, KeyMap, SinglePriv, SinglePubKey,
};
use miniscript::{Legacy, Segwitv0, SigType, Tap, ToPublicKey};
@ -130,7 +129,7 @@ impl From<Fingerprint> for SignerId {
}
/// Signing error
#[derive(Debug, PartialEq, Eq, Clone)]
#[derive(Debug)]
pub enum SignerError {
/// The private key is missing for the required public key
MissingKey,
@ -382,6 +381,49 @@ 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))
@ -476,8 +518,16 @@ impl InputSigner for SignerWrapper<PrivateKey> {
}
let (hash, hash_ty) = match self.ctx {
SignerContext::Segwitv0 => Segwitv0::sighash(psbt, input_index, ())?,
SignerContext::Legacy => Legacy::sighash(psbt, input_index, ())?,
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)
}
_ => return Ok(()), // handled above
};
sign_psbt_ecdsa(
@ -498,12 +548,12 @@ fn sign_psbt_ecdsa(
secret_key: &secp256k1::SecretKey,
pubkey: PublicKey,
psbt_input: &mut psbt::Input,
hash: bitcoin::Sighash,
hash: impl bitcoin::hashes::Hash + bitcoin::secp256k1::ThirtyTwoByteHash,
hash_ty: EcdsaSighashType,
secp: &SecpCtx,
allow_grinding: bool,
) {
let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
let msg = &Message::from(hash);
let sig = if allow_grinding {
secp.sign_ecdsa_low_r(msg, secret_key)
} else {
@ -512,7 +562,7 @@ fn sign_psbt_ecdsa(
secp.verify_ecdsa(msg, &sig, &pubkey.inner)
.expect("invalid or corrupted ecdsa signature");
let final_signature = ecdsa::EcdsaSig { sig, hash_ty };
let final_signature = ecdsa::Signature { sig, hash_ty };
psbt_input.partial_sigs.insert(pubkey, final_signature);
}
@ -522,12 +572,10 @@ fn sign_psbt_schnorr(
pubkey: XOnlyPublicKey,
leaf_hash: Option<taproot::TapLeafHash>,
psbt_input: &mut psbt::Input,
hash: taproot::TapSighashHash,
hash_ty: SchnorrSighashType,
hash: TapSighash,
hash_ty: TapSighashType,
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
@ -536,12 +584,12 @@ fn sign_psbt_schnorr(
Some(_) => keypair, // no tweak for script spend
};
let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap();
let msg = &Message::from(hash);
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 = schnorr::SchnorrSig { sig, hash_ty };
let final_signature = taproot::Signature { sig, hash_ty };
if let Some(lh) = leaf_hash {
psbt_input
@ -631,6 +679,11 @@ 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)),
),
};
}
@ -801,7 +854,7 @@ pub(crate) trait ComputeSighash {
impl ComputeSighash for Legacy {
type Extra = ();
type Sighash = bitcoin::Sighash;
type Sighash = sighash::LegacySighash;
type SighashType = EcdsaSighashType;
fn sighash(
@ -848,19 +901,9 @@ 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 = bitcoin::Sighash;
type Sighash = sighash::SegwitV0Sighash;
type SighashType = EcdsaSighashType;
fn sighash(
@ -907,14 +950,21 @@ impl ComputeSighash for Segwitv0 {
Some(ref witness_script) => witness_script.clone(),
None => {
if utxo.script_pubkey.is_v0_p2wpkh() {
p2wpkh_script_code(&utxo.script_pubkey)
utxo.script_pubkey
.p2wpkh_script_code()
.expect("We check above that the spk is a p2wpkh")
} else if psbt_input
.redeem_script
.as_ref()
.map(Script::is_v0_p2wpkh)
.map(|s| s.is_v0_p2wpkh())
.unwrap_or(false)
{
p2wpkh_script_code(psbt_input.redeem_script.as_ref().unwrap())
psbt_input
.redeem_script
.as_ref()
.unwrap()
.p2wpkh_script_code()
.expect("We check above that the spk is a p2wpkh")
} else {
return Err(SignerError::MissingWitnessScript);
}
@ -935,14 +985,14 @@ impl ComputeSighash for Segwitv0 {
impl ComputeSighash for Tap {
type Extra = Option<taproot::TapLeafHash>;
type Sighash = taproot::TapSighashHash;
type SighashType = SchnorrSighashType;
type Sighash = TapSighash;
type SighashType = TapSighashType;
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
input_index: usize,
extra: Self::Extra,
) -> Result<(Self::Sighash, SchnorrSighashType), SignerError> {
) -> Result<(Self::Sighash, TapSighashType), SignerError> {
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange);
}
@ -951,8 +1001,8 @@ impl ComputeSighash for Tap {
let sighash_type = psbt_input
.sighash_type
.unwrap_or_else(|| SchnorrSighashType::Default.into())
.schnorr_hash_ty()
.unwrap_or_else(|| TapSighashType::Default.into())
.taproot_hash_ty()
.map_err(|_| SignerError::InvalidSighash)?;
let witness_utxos = (0..psbt.inputs.len())
.map(|i| psbt.get_utxo_for(i))
@ -1014,8 +1064,8 @@ mod signers_container_tests {
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;

View File

@ -18,7 +18,7 @@
//! # use bitcoin::*;
//! # use bdk::*;
//! # use bdk::wallet::tx_builder::CreateTx;
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap();
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
//! # 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(FeeRate::from_sat_per_vb(5.0))
//! .fee_rate(bdk::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::util::psbt::{self, PartiallySignedTransaction as Psbt};
use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction};
use bitcoin::psbt::{self, PartiallySignedTransaction as Psbt};
use bitcoin::{absolute, script::PushBytes, OutPoint, ScriptBuf, 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();
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
/// # 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<(Script, u64)>,
pub(crate) recipients: Vec<(ScriptBuf, u64)>,
pub(crate) drain_wallet: bool,
pub(crate) drain_to: Option<Script>,
pub(crate) drain_to: Option<ScriptBuf>,
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<LockTime>,
pub(crate) locktime: Option<absolute::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<LockTime>,
pub(crate) current_height: Option<absolute::LockTime>,
pub(crate) allow_dust: bool,
}
@ -241,7 +241,10 @@ 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();
/// # let to_address =
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
/// .unwrap()
/// .assume_checked();
/// # let wallet = doctest_wallet!();
/// let mut path = BTreeMap::new();
/// path.insert("aabbccdd".to_string(), vec![0, 1]);
@ -281,6 +284,7 @@ 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,
@ -424,7 +428,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: LockTime) -> &mut Self {
pub fn nlocktime(&mut self, locktime: absolute::LockTime) -> &mut Self {
self.params.locktime = Some(locktime);
self
}
@ -463,7 +467,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
self
}
/// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::util::psbt::Input::witness_utxo) field when spending from
/// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::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
@ -473,8 +477,8 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
self
}
/// 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.
/// Fill-in the [`psbt::Output::redeem_script`](bitcoin::psbt::Output::redeem_script) and
/// [`psbt::Output::witness_script`](bitcoin::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 {
@ -556,7 +560,8 @@ 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(LockTime::from_height(height).expect("Invalid height"));
self.params.current_height =
Some(absolute::LockTime::from_height(height).expect("Invalid height"));
self
}
@ -571,20 +576,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<(Script, u64)>) -> &mut Self {
pub fn set_recipients(&mut self, recipients: Vec<(ScriptBuf, u64)>) -> &mut Self {
self.params.recipients = recipients;
self
}
/// Add a recipient to the internal list
pub fn add_recipient(&mut self, script_pubkey: Script, amount: u64) -> &mut Self {
pub fn add_recipient(&mut self, script_pubkey: ScriptBuf, amount: u64) -> &mut Self {
self.params.recipients.push((script_pubkey, amount));
self
}
/// Add data as an output, using OP_RETURN
pub fn add_data(&mut self, data: &[u8]) -> &mut Self {
let script = Script::new_op_return(data);
pub fn add_data<T: AsRef<PushBytes>>(&mut self, data: &T) -> &mut Self {
let script = ScriptBuf::new_op_return(data);
self.add_recipient(script, 0u64);
self
}
@ -614,7 +619,10 @@ 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();
/// # let to_address =
/// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
/// .unwrap()
/// .assume_checked();
/// # let wallet = doctest_wallet!();
/// let mut tx_builder = wallet.build_tx();
///
@ -623,7 +631,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(FeeRate::from_sat_per_vb(5.0))
/// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0))
/// .enable_rbf();
/// let (psbt, tx_details) = tx_builder.finish()?;
/// # Ok::<(), bdk::Error>(())
@ -633,7 +641,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: Script) -> &mut Self {
pub fn drain_to(&mut self, script_pubkey: ScriptBuf) -> &mut Self {
self.params.drain_to = Some(script_pubkey);
self
}
@ -651,7 +659,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: Script) -> Result<&mut Self, Error> {
pub fn allow_shrinking(&mut self, script_pubkey: ScriptBuf) -> Result<&mut Self, Error> {
match self
.params
.recipients
@ -790,6 +798,7 @@ mod test {
use bitcoin::consensus::deserialize;
use bitcoin::hashes::hex::FromHex;
use std::str::FromStr;
use super::*;
@ -821,8 +830,6 @@ mod test {
#[test]
fn test_output_ordering_bip69() {
use std::str::FromStr;
let original_tx = ordering_test_tx!();
let mut tx = original_tx;
@ -851,8 +858,11 @@ mod test {
);
assert_eq!(tx.output[0].value, 800);
assert_eq!(tx.output[1].script_pubkey, From::from(vec![0xAA]));
assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE]));
assert_eq!(tx.output[1].script_pubkey, ScriptBuf::from(vec![0xAA]));
assert_eq!(
tx.output[2].script_pubkey,
ScriptBuf::from(vec![0xAA, 0xEE])
);
}
fn get_test_utxos() -> Vec<LocalUtxo> {
@ -861,7 +871,7 @@ mod test {
vec![
LocalUtxo {
outpoint: OutPoint {
txid: bitcoin::Txid::from_inner([0; 32]),
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
vout: 0,
},
txout: Default::default(),
@ -870,7 +880,7 @@ mod test {
},
LocalUtxo {
outpoint: OutPoint {
txid: bitcoin::Txid::from_inner([0; 32]),
txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
vout: 1,
},
txout: Default::default(),

View File

@ -10,7 +10,7 @@
// licenses.
use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::{LockTime, Script, Sequence};
use bitcoin::{absolute, 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: LockTime) -> bool {
fn check_after(&self, n: absolute::LockTime) -> bool {
if let Some(current_height) = self.current_height {
current_height >= n.to_consensus_u32()
} else {
@ -114,17 +114,20 @@ 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, Sequence};
use std::str::FromStr;
use crate::bitcoin::{Address, Network, Sequence};
#[test]
fn test_is_dust() {
let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe")
.unwrap()
.require_network(Network::Bitcoin)
.unwrap()
.script_pubkey();
assert!(script_p2pkh.is_p2pkh());
@ -132,6 +135,8 @@ 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());