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: on:
push: push:
branches:
- 'master'
- 'release/*'
paths: paths:
- '**/Cargo.toml' - '**/Cargo.toml'
- '**/Cargo.lock' - '**/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 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 name: CI
@ -51,6 +59,27 @@ jobs:
run: rustup component add clippy run: rustup component add clippy
- name: Update toolchain - name: Update toolchain
run: rustup update 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 - name: Build
run: cargo build --features ${{ matrix.features }} --no-default-features run: cargo build --features ${{ matrix.features }} --no-default-features
- name: Clippy - name: Clippy
@ -199,5 +228,26 @@ jobs:
run: rustup set profile minimal run: rustup set profile minimal
- name: Update toolchain - name: Update toolchain
run: rustup update 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 - name: Test
run: cargo test --features test-hardware-signer run: cargo test --features test-hardware-signer

View File

@ -1,6 +1,14 @@
name: Publish Nightly Docs name: Publish Nightly Docs
on: [push, pull_request] on:
push:
branches:
- 'master'
- 'release/*'
pull_request:
branches:
- 'master'
- 'release/*'
jobs: jobs:
build_docs: build_docs:

View File

@ -9,6 +9,65 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [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] ## [v0.27.0]
### Summary ### 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.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.26.0]: https://github.com/bitcoindevkit/bdk/compare/v0.25.0...v0.26.0
[v0.27.0]: https://github.com/bitcoindevkit/bdk/compare/v0.26.0...v0.27.0 [v0.27.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] [package]
name = "bdk" name = "bdk"
version = "0.27.0" version = "0.29.0"
edition = "2018" edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"] authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org" homepage = "https://bitcoindevkit.org"
@ -13,31 +13,31 @@ license = "MIT OR Apache-2.0"
[dependencies] [dependencies]
bdk-macros = "^0.6" bdk-macros = "^0.6"
log = "^0.4" log = "0.4"
miniscript = { version = "9.0", features = ["serde"] } miniscript = { version = "10.0", default-features = false, features = ["serde"] }
bitcoin = { version = "0.29.1", features = ["serde", "base64", "rand"] } bitcoin = { version = "0.30", default-features = false, features = ["serde", "base64", "rand-std"] }
serde = { version = "^1.0", features = ["derive"] } serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" } serde_json = { version = "^1.0" }
rand = "^0.8" rand = "^0.8"
# Optional dependencies # Optional dependencies
sled = { version = "0.34", optional = true } sled = { version = "0.34", optional = true }
electrum-client = { version = "0.12", optional = true } electrum-client = { version = "0.18", optional = true }
esplora-client = { version = "0.3", default-features = false, optional = true } esplora-client = { version = "0.6", default-features = false, optional = true }
rusqlite = { version = "0.27.0", optional = true } rusqlite = { version = "0.28.0", optional = true }
ahash = { version = "0.7.6", optional = true } ahash = { version = "0.7.6", optional = true }
futures = { version = "0.3", optional = true } futures = { version = "0.3", optional = true }
async-trait = { version = "0.1", optional = true } async-trait = { version = "0.1", optional = true }
rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true } rocksdb = { version = "0.14", default-features = false, features = ["snappy"], optional = true }
cc = { version = ">=1.0.64", optional = true } cc = { version = ">=1.0.64", optional = true }
socks = { version = "0.3", 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 } bitcoinconsensus = { version = "0.19.0-3", optional = true }
# Needed by bdk_blockchain_tests macro and the `rpc` feature # 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 # Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies] [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
@ -52,7 +52,10 @@ js-sys = "0.3"
minimal = [] minimal = []
compiler = ["miniscript/compiler"] compiler = ["miniscript/compiler"]
verify = ["bitcoinconsensus"] 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 = ["rusqlite", "ahash"]
sqlite-bundled = ["sqlite", "rusqlite/bundled"] sqlite-bundled = ["sqlite", "rusqlite/bundled"]
compact_filters = ["rocksdb", "socks", "cc"] compact_filters = ["rocksdb", "socks", "cc"]
@ -104,14 +107,12 @@ test-hardware-signer = ["hardware-signer"]
dev-getrandom-wasm = ["getrandom/js"] dev-getrandom-wasm = ["getrandom/js"]
[dev-dependencies] [dev-dependencies]
miniscript = { version = "10.0", features = ["std"] }
bitcoin = { version = "0.30", features = ["std"] }
lazy_static = "1.4" lazy_static = "1.4"
env_logger = "0.7" env_logger = { version = "0.7", default-features = false }
electrsd = "0.22" electrsd = "0.24"
# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released
base64 = "^0.13"
assert_matches = "1.5.0" 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]] [[example]]
name = "compact_filters_balance" name = "compact_filters_balance"

View File

@ -96,7 +96,7 @@ use bdk::blockchain::ElectrumBlockchain;
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex::New; use bdk::wallet::AddressIndex::New;
use base64; use bitcoin::base64;
use bdk::bitcoin::consensus::serialize; use bdk::bitcoin::consensus::serialize;
use bdk::bitcoin::Network; use bdk::bitcoin::Network;
@ -123,7 +123,7 @@ fn main() -> Result<(), bdk::Error> {
}; };
println!("Transaction details: {:#?}", details); println!("Transaction details: {:#?}", details);
println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt))); println!("Unsigned PSBT: {}", base64::encode(psbt.serialize()));
Ok(()) Ok(())
} }
@ -134,9 +134,9 @@ fn main() -> Result<(), bdk::Error> {
```rust,no_run ```rust,no_run
use bdk::{Wallet, SignOptions, database::MemoryDatabase}; use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use base64; use bitcoin::base64;
use bdk::bitcoin::consensus::deserialize; use bdk::bitcoin::consensus::deserialize;
use bdk::bitcoin::Network; use bdk::bitcoin::{psbt::Psbt, Network};
fn main() -> Result<(), bdk::Error> { fn main() -> Result<(), bdk::Error> {
let wallet = Wallet::new( let wallet = Wallet::new(
@ -147,7 +147,7 @@ fn main() -> Result<(), bdk::Error> {
)?; )?;
let psbt = "..."; 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())?; let _finalized = wallet.sign(&mut psbt, SignOptions::default())?;
@ -201,3 +201,48 @@ at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted 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 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. 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 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 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 std::str::FromStr;
use bdk::bitcoin::util::bip32::ExtendedPrivKey; use bdk::bitcoin::bip32::ExtendedPrivKey;
use bdk::bitcoin::Network; use bdk::bitcoin::Network;
use bdk::blockchain::{Blockchain, ElectrumBlockchain}; use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
@ -10,7 +10,7 @@ use bdk::{KeychainKind, SyncOptions, Wallet};
use bdk::electrum_client::Client; use bdk::electrum_client::Client;
use bdk::wallet::AddressIndex; use bdk::wallet::AddressIndex;
use bitcoin::util::bip32; use bitcoin::bip32;
pub mod utils; pub mod utils;

View File

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

View File

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

View File

@ -1,7 +1,7 @@
use bdk::bitcoin::{Address, Network}; use bdk::bitcoin::{Address, Network};
use bdk::blockchain::{Blockchain, ElectrumBlockchain}; use bdk::blockchain::{Blockchain, ElectrumBlockchain};
use bdk::database::MemoryDatabase; use bdk::database::MemoryDatabase;
use bdk::hwi::{types::HWIChain, HWIClient}; use bdk::hwi::HWIClient;
use bdk::miniscript::{Descriptor, DescriptorPublicKey}; use bdk::miniscript::{Descriptor, DescriptorPublicKey};
use bdk::signer::SignerOrdering; use bdk::signer::SignerOrdering;
use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex}; use bdk::wallet::{hardwaresigner::HWISigner, AddressIndex};
@ -30,7 +30,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
let first_device = devices.remove(0)?; let first_device = devices.remove(0)?;
// ...and creating a client out of the first one // ...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); println!("Look what I found, a {}!", first_device.model);
// Getting the HW's public descriptors // 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 // 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( let mut wallet = Wallet::new(
descriptors.receive[0].clone(), descriptors.receive[0].clone(),
Some(descriptors.internal[0].clone()), Some(descriptors.internal[0].clone()),
@ -77,7 +77,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
return Ok(()); 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 psbt, _details) = {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder

View File

@ -6,8 +6,8 @@
// You may not use this file except in accordance with one or both of these // You may not use this file except in accordance with one or both of these
// licenses. // licenses.
use bdk::bitcoin::bip32::DerivationPath;
use bdk::bitcoin::secp256k1::Secp256k1; use bdk::bitcoin::secp256k1::Secp256k1;
use bdk::bitcoin::util::bip32::DerivationPath;
use bdk::bitcoin::Network; use bdk::bitcoin::Network;
use bdk::descriptor; use bdk::descriptor;
use bdk::descriptor::IntoWalletDescriptor; use bdk::descriptor::IntoWalletDescriptor;

View File

@ -92,7 +92,8 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
} else { } else {
println!("Creating a PSBT sending 9800 SATs plus fee to the u01.net testnet faucet return address 'tb1ql7w62elx9ucw4pj5lgw4l028hmuw80sndtntxt'."); 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(); let mut builder = watch_only_wallet.build_tx();
builder builder
.add_recipient(return_address.script_pubkey(), 9_800) .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 // 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 // Generate 101 blocks and use the above address as coinbase
bitcoind.client.generate_to_address(101, &core_address)?; bitcoind.client.generate_to_address(101, &core_address)?;

View File

@ -13,7 +13,10 @@ pub(crate) mod tx {
// Create a transaction builder // Create a transaction builder
let mut tx_builder = wallet.build_tx(); 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 // Set recipient of the transaction
tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]); tx_builder.set_recipients(vec![(to_address.script_pubkey(), amount)]);

View File

@ -355,7 +355,7 @@ impl WalletSync for CompactFiltersBlockchain {
peer, peer,
|block_hash, filter| { |block_hash, filter| {
if !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); return Ok(false);
} }
@ -570,7 +570,7 @@ pub enum CompactFiltersError {
/// Internal I/O error /// Internal I/O error
Io(std::io::Error), Io(std::io::Error),
/// Invalid BIP158 filter /// Invalid BIP158 filter
Bip158(bitcoin::util::bip158::Error), Bip158(bitcoin::bip158::Error),
/// Internal system time error /// Internal system time error
Time(std::time::SystemTimeError), Time(std::time::SystemTimeError),
@ -608,7 +608,7 @@ impl std::error::Error for CompactFiltersError {}
impl_error!(rocksdb::Error, Db, CompactFiltersError); impl_error!(rocksdb::Error, Db, CompactFiltersError);
impl_error!(std::io::Error, Io, 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_error!(std::time::SystemTimeError, Time, CompactFiltersError);
impl From<crate::error::Error> for 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_blockdata::*;
use bitcoin::network::message_filter::*; use bitcoin::network::message_filter::*;
use bitcoin::network::message_network::VersionMessage; use bitcoin::network::message_network::VersionMessage;
use bitcoin::network::Address; use bitcoin::network::{Address, Magic};
use bitcoin::{Block, Network, Transaction, Txid, Wtxid}; use bitcoin::{Block, Network, Transaction, Txid, Wtxid};
use super::CompactFiltersError; use super::CompactFiltersError;
@ -242,7 +242,7 @@ impl Peer {
/// Send a Bitcoin network message /// Send a Bitcoin network message
fn _send( fn _send(
writer: &mut TcpStream, writer: &mut TcpStream,
magic: u32, magic: Magic,
payload: NetworkMessage, payload: NetworkMessage,
) -> Result<(), CompactFiltersError> { ) -> Result<(), CompactFiltersError> {
log::trace!("==> {:?}", payload); log::trace!("==> {:?}", payload);

View File

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

View File

@ -13,11 +13,11 @@ use std::collections::{BTreeMap, HashMap, VecDeque};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::time::Duration; use std::time::Duration;
use bitcoin::bip158::BlockFilter;
use bitcoin::hash_types::{BlockHash, FilterHeader}; use bitcoin::hash_types::{BlockHash, FilterHeader};
use bitcoin::hashes::Hash; use bitcoin::hashes::Hash;
use bitcoin::network::message::NetworkMessage; use bitcoin::network::message::NetworkMessage;
use bitcoin::network::message_blockdata::GetHeadersMessage; use bitcoin::network::message_blockdata::GetHeadersMessage;
use bitcoin::util::bip158::BlockFilter;
use super::peer::*; use super::peer::*;
use super::store::*; use super::store::*;

View File

@ -325,8 +325,8 @@ impl ConfigurableBlockchain for ElectrumBlockchain {
let socks5 = config.socks5.as_ref().map(Socks5Config::new); let socks5 = config.socks5.as_ref().map(Socks5Config::new);
let electrum_config = ConfigBuilder::new() let electrum_config = ConfigBuilder::new()
.retry(config.retry) .retry(config.retry)
.timeout(config.timeout)? .timeout(config.timeout)
.socks5(socks5)? .socks5(socks5)
.validate_domain(config.validate_domain) .validate_domain(config.validate_domain)
.build(); .build();

View File

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

View File

@ -31,18 +31,17 @@
//! let blockchain = RpcBlockchain::from_config(&config); //! let blockchain = RpcBlockchain::from_config(&config);
//! ``` //! ```
use crate::bitcoin::hashes::hex::ToHex;
use crate::bitcoin::{Network, OutPoint, Transaction, TxOut, Txid}; use crate::bitcoin::{Network, OutPoint, Transaction, TxOut, Txid};
use crate::blockchain::*; use crate::blockchain::*;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::descriptor::calc_checksum; use crate::descriptor::calc_checksum;
use crate::error::MissingCachedScripts; use crate::error::MissingCachedScripts;
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails}; use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
use bitcoin::Script; use bitcoin::{Script, ScriptBuf};
use bitcoincore_rpc::json::{ use bitcoincore_rpc::json::{
GetTransactionResultDetailCategory, ImportMultiOptions, ImportMultiRequest, GetTransactionResultDetailCategory, ImportMultiOptions, ImportMultiRequest,
ImportMultiRequestScriptPubkey, ImportMultiRescanSince, ListTransactionResult, ImportMultiRequestScriptPubkey, ListTransactionResult, ListUnspentResultEntry, ScanningDetails,
ListUnspentResultEntry, ScanningDetails, Timestamp,
}; };
use bitcoincore_rpc::jsonrpc::serde_json::{json, Value}; use bitcoincore_rpc::jsonrpc::serde_json::{json, Value};
use bitcoincore_rpc::Auth as RpcAuth; use bitcoincore_rpc::Auth as RpcAuth;
@ -302,8 +301,8 @@ struct DbState<'a, D> {
params: &'a RpcSyncParams, params: &'a RpcSyncParams,
prog: &'a dyn Progress, prog: &'a dyn Progress,
ext_spks: Vec<Script>, ext_spks: Vec<ScriptBuf>,
int_spks: Vec<Script>, int_spks: Vec<ScriptBuf>,
txs: HashMap<Txid, TransactionDetails>, txs: HashMap<Txid, TransactionDetails>,
utxos: HashSet<LocalUtxo>, utxos: HashSet<LocalUtxo>,
last_indexes: HashMap<KeychainKind, u32>, last_indexes: HashMap<KeychainKind, u32>,
@ -668,7 +667,7 @@ fn import_descriptors<'a, S>(
scripts_iter: S, scripts_iter: S,
) -> Result<(), Error> ) -> Result<(), Error>
where where
S: Iterator<Item = &'a Script>, S: Iterator<Item = &'a ScriptBuf>,
{ {
let requests = Value::Array( let requests = Value::Array(
scripts_iter scripts_iter
@ -696,11 +695,11 @@ where
fn import_multi<'a, S>(client: &Client, start_epoch: u64, scripts_iter: S) -> Result<(), Error> fn import_multi<'a, S>(client: &Client, start_epoch: u64, scripts_iter: S) -> Result<(), Error>
where where
S: Iterator<Item = &'a Script>, S: Iterator<Item = &'a ScriptBuf>,
{ {
let requests = scripts_iter let requests = scripts_iter
.map(|script| ImportMultiRequest { .map(|script| ImportMultiRequest {
timestamp: ImportMultiRescanSince::Timestamp(start_epoch), timestamp: Timestamp::Time(start_epoch),
script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(script)), script_pubkey: Some(ImportMultiRequestScriptPubkey::Script(script)),
watchonly: Some(true), watchonly: Some(true),
..Default::default() ..Default::default()
@ -808,7 +807,7 @@ fn is_wallet_descriptor(client: &Client) -> Result<bool, Error> {
} }
fn descriptor_from_script_pubkey(script: &Script) -> String { 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()) format!("{}#{}", desc, calc_checksum(&desc).unwrap())
} }
@ -964,7 +963,7 @@ mod test {
// generate scripts (1 tx per script) // generate scripts (1 tx per script)
let scripts = (0..TX_COUNT) 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<_>>(); .collect::<Vec<_>>();
// import scripts and wait // import scripts and wait

View File

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

View File

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

View File

@ -15,7 +15,7 @@ use sled::{Batch, Tree};
use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid; use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, Transaction}; use bitcoin::{OutPoint, Script, ScriptBuf, Transaction};
use crate::database::memory::MapKey; use crate::database::memory::MapKey;
use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime}; use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
@ -90,7 +90,7 @@ macro_rules! impl_batch_operations {
Ok(()) 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 key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
let res = self.remove(key); let res = self.remove(key);
let res = $process_delete!(res); 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(); let key = MapKey::Path((keychain, None)).as_map_key();
self.scan_prefix(key) self.scan_prefix(key)
.map(|x| -> Result<_, Error> { .map(|x| -> Result<_, Error> {
@ -286,7 +286,7 @@ impl Database for Tree {
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
path: u32, path: u32,
) -> Result<Option<Script>, Error> { ) -> Result<Option<ScriptBuf>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key(); let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
Ok(self.get(key)?.map(|b| deserialize(&b)).transpose()?) 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::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid; 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::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database, SyncTime};
use crate::error::Error; use crate::error::Error;
@ -136,7 +136,7 @@ impl BatchOperations for MemoryDatabase {
path: u32, path: u32,
) -> Result<(), Error> { ) -> Result<(), Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key(); 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 key = MapKey::Script(Some(script)).as_map_key();
let value = json!({ let value = json!({
@ -196,7 +196,7 @@ impl BatchOperations for MemoryDatabase {
&mut self, &mut self,
keychain: KeychainKind, keychain: KeychainKind,
path: u32, path: u32,
) -> Result<Option<Script>, Error> { ) -> Result<Option<ScriptBuf>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key(); let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
let res = self.map.remove(&key); let res = self.map.remove(&key);
self.deleted_keys.push(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(); let key = MapKey::Path((keychain, None)).as_map_key();
self.map self.map
.range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key)))) .range::<Vec<u8>, _>((Included(&key), Excluded(&after(&key))))
@ -368,7 +368,7 @@ impl Database for MemoryDatabase {
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
path: u32, path: u32,
) -> Result<Option<Script>, Error> { ) -> Result<Option<ScriptBuf>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key(); let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
Ok(self Ok(self
.map .map
@ -485,7 +485,6 @@ macro_rules! populate_test_db {
$crate::populate_test_db!($db, $tx_meta, $current_height, (@coinbase false)) $crate::populate_test_db!($db, $tx_meta, $current_height, (@coinbase false))
}}; }};
($db:expr, $tx_meta:expr, $current_height:expr, (@coinbase $is_coinbase:expr)$(,)?) => {{ ($db:expr, $tx_meta:expr, $current_height:expr, (@coinbase $is_coinbase:expr)$(,)?) => {{
use std::str::FromStr;
use $crate::database::SyncTime; use $crate::database::SyncTime;
use $crate::database::{BatchOperations, Database}; use $crate::database::{BatchOperations, Database};
let mut db = $db; let mut db = $db;
@ -497,7 +496,7 @@ macro_rules! populate_test_db {
} }
let tx = $crate::bitcoin::Transaction { let tx = $crate::bitcoin::Transaction {
version: 1, version: 1,
lock_time: bitcoin::PackedLockTime(0), lock_time: bitcoin::absolute::LockTime::ZERO,
input, input,
output: tx_meta output: tx_meta
.output .output
@ -506,6 +505,7 @@ macro_rules! populate_test_db {
value: out_meta.value, value: out_meta.value,
script_pubkey: $crate::bitcoin::Address::from_str(&out_meta.to_address) script_pubkey: $crate::bitcoin::Address::from_str(&out_meta.to_address)
.unwrap() .unwrap()
.assume_checked()
.script_pubkey(), .script_pubkey(),
}) })
.collect(), .collect(),

View File

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

View File

@ -13,7 +13,7 @@ use std::path::PathBuf;
use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid; 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::database::{BatchDatabase, BatchOperations, Database, SyncTime};
use crate::error::Error; use crate::error::Error;
@ -162,7 +162,7 @@ impl SqliteDatabase {
None => (None, None), 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)")?; 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), 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")?; 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()) 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 let mut statement = self
.connection .connection
.prepare_cached("SELECT script FROM script_pubkeys")?; .prepare_cached("SELECT script FROM script_pubkeys")?;
let mut scripts: Vec<Script> = vec![]; let mut scripts: Vec<ScriptBuf> = vec![];
let mut rows = statement.query([])?; let mut rows = statement.query([])?;
while let Some(row) = rows.next()? { while let Some(row) = rows.next()? {
let raw_script: Vec<u8> = row.get(0)?; let raw_script: Vec<u8> = row.get(0)?;
@ -268,11 +268,11 @@ impl SqliteDatabase {
Ok(scripts) 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 let mut statement = self
.connection .connection
.prepare_cached("SELECT script FROM script_pubkeys WHERE keychain=:keychain")?; .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})?; let mut rows = statement.query(named_params! {":keychain": keychain})?;
while let Some(row) = rows.next()? { while let Some(row) = rows.next()? {
let raw_script: Vec<u8> = row.get(0)?; let raw_script: Vec<u8> = row.get(0)?;
@ -286,7 +286,7 @@ impl SqliteDatabase {
&self, &self,
keychain: String, keychain: String,
child: u32, child: u32,
) -> Result<Option<Script>, Error> { ) -> Result<Option<ScriptBuf>, Error> {
let mut statement = self.connection.prepare_cached( let mut statement = self.connection.prepare_cached(
"SELECT script FROM script_pubkeys WHERE keychain=:keychain AND child=:child", "SELECT script FROM script_pubkeys WHERE keychain=:keychain AND child=:child",
)?; )?;
@ -295,7 +295,7 @@ impl SqliteDatabase {
match rows.next()? { match rows.next()? {
Some(row) => { Some(row) => {
let script: Vec<u8> = row.get(0)?; let script: Vec<u8> = row.get(0)?;
let script: Script = script.into(); let script: ScriptBuf = script.into();
Ok(Some(script)) Ok(Some(script))
} }
None => Ok(None), None => Ok(None),
@ -362,7 +362,7 @@ impl SqliteDatabase {
let keychain: String = row.get(1)?; let keychain: String = row.get(1)?;
let keychain: KeychainKind = serde_json::from_str(&keychain)?; let keychain: KeychainKind = serde_json::from_str(&keychain)?;
let script: Vec<u8> = row.get(2)?; 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)?; let is_spent: bool = row.get(3)?;
Ok(Some(LocalUtxo { Ok(Some(LocalUtxo {
@ -658,7 +658,7 @@ impl BatchOperations for SqliteDatabase {
utxo.txout.value, utxo.txout.value,
serde_json::to_string(&utxo.keychain)?, serde_json::to_string(&utxo.keychain)?,
utxo.outpoint.vout, utxo.outpoint.vout,
&utxo.outpoint.txid, utxo.outpoint.txid.as_ref(),
utxo.txout.script_pubkey.as_bytes(), utxo.txout.script_pubkey.as_bytes(),
utxo.is_spent, utxo.is_spent,
)?; )?;
@ -666,19 +666,19 @@ impl BatchOperations for SqliteDatabase {
} }
fn set_raw_tx(&mut self, transaction: &Transaction) -> Result<(), Error> { 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(_) => { Some(_) => {
self.update_transaction(&transaction.txid(), &serialize(transaction))?; self.update_transaction(transaction.txid().as_ref(), &serialize(transaction))?;
} }
None => { None => {
self.insert_transaction(&transaction.txid(), &serialize(transaction))?; self.insert_transaction(transaction.txid().as_ref(), &serialize(transaction))?;
} }
} }
Ok(()) Ok(())
} }
fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error> { 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(_) => { Some(_) => {
self.update_transaction_details(transaction)?; self.update_transaction_details(transaction)?;
} }
@ -708,7 +708,7 @@ impl BatchOperations for SqliteDatabase {
&mut self, &mut self,
keychain: KeychainKind, keychain: KeychainKind,
child: u32, child: u32,
) -> Result<Option<Script>, Error> { ) -> Result<Option<ScriptBuf>, Error> {
let keychain = serde_json::to_string(&keychain)?; let keychain = serde_json::to_string(&keychain)?;
let script = self.select_script_pubkey_by_path(keychain.clone(), child)?; let script = self.select_script_pubkey_by_path(keychain.clone(), child)?;
match script { match script {
@ -734,9 +734,9 @@ impl BatchOperations for SqliteDatabase {
} }
fn del_utxo(&mut self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { 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) => { 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)) Ok(Some(local_utxo))
} }
None => Ok(None), None => Ok(None),
@ -744,9 +744,9 @@ impl BatchOperations for SqliteDatabase {
} }
fn del_raw_tx(&mut self, txid: &Txid) -> Result<Option<Transaction>, Error> { 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) => { Some(tx) => {
self.delete_transaction_by_txid(txid)?; self.delete_transaction_by_txid(txid.as_ref())?;
Ok(Some(tx)) Ok(Some(tx))
} }
None => Ok(None), None => Ok(None),
@ -758,12 +758,12 @@ impl BatchOperations for SqliteDatabase {
txid: &Txid, txid: &Txid,
include_raw: bool, include_raw: bool,
) -> Result<Option<TransactionDetails>, Error> { ) -> 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) => { Some(mut transaction_details) => {
self.delete_transaction_details_by_txid(txid)?; self.delete_transaction_details_by_txid(txid.as_ref())?;
if include_raw { if include_raw {
self.delete_transaction_by_txid(txid)?; self.delete_transaction_by_txid(txid.as_ref())?;
} else { } else {
transaction_details.transaction = None; 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 { match keychain {
Some(keychain) => { Some(keychain) => {
let keychain = serde_json::to_string(&keychain)?; let keychain = serde_json::to_string(&keychain)?;
@ -849,7 +849,7 @@ impl Database for SqliteDatabase {
&self, &self,
keychain: KeychainKind, keychain: KeychainKind,
child: u32, child: u32,
) -> Result<Option<Script>, Error> { ) -> Result<Option<ScriptBuf>, Error> {
let keychain = serde_json::to_string(&keychain)?; let keychain = serde_json::to_string(&keychain)?;
match self.select_script_pubkey_by_path(keychain, child)? { match self.select_script_pubkey_by_path(keychain, child)? {
Some(script) => Ok(Some(script)), Some(script) => Ok(Some(script)),
@ -868,18 +868,18 @@ impl Database for SqliteDatabase {
} }
fn get_utxo(&self, outpoint: &OutPoint) -> Result<Option<LocalUtxo>, Error> { 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> { 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)), Some(tx) => Ok(Some(tx)),
None => Ok(None), None => Ok(None),
} }
} }
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error> { 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) => { Some(mut transaction_details) => {
if !include_raw { if !include_raw {
transaction_details.transaction = None; transaction_details.transaction = None;
@ -1115,7 +1115,7 @@ pub mod test {
let mut db = get_database(); let mut db = get_database();
let script = Script::from( let script = ScriptBuf::from(
Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(), Vec::<u8>::from_hex("76a91402306a7c23f3e8010de41e9e591348bb83f11daa88ac").unwrap(),
); );
let path = 42; let path = 42;

View File

@ -514,13 +514,14 @@ macro_rules! descriptor {
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey}; use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
$crate::impl_top_level_pk!(Pkh, $crate::miniscript::Legacy, $key) $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)) .map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Pkh(a), b, c))
}); });
( wpkh ( $key:expr ) ) => ({ ( wpkh ( $key:expr ) ) => ({
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey}; use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
$crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) $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)) .map(|(a, b, c)| (Descriptor::<DescriptorPublicKey>::Wpkh(a), b, c))
}); });
( sh ( wpkh ( $key:expr ) ) ) => ({ ( sh ( wpkh ( $key:expr ) ) ) => ({
@ -530,7 +531,7 @@ macro_rules! descriptor {
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh}; use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh};
$crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) $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))) .and_then(|(a, b, c)| Ok((Descriptor::<DescriptorPublicKey>::Sh(Sh::new_wpkh(a.into_inner())?), b, c)))
}); });
( sh ( $( $minisc:tt )* ) ) => ({ ( sh ( $( $minisc:tt )* ) ) => ({
@ -700,7 +701,7 @@ macro_rules! fragment {
$crate::keys::make_pkh($key, &secp) $crate::keys::make_pkh($key, &secp)
}); });
( after ( $value:expr ) ) => ({ ( 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 ) ) => ({ ( older ( $value:expr ) ) => ({
$crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!! $crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!!
@ -793,7 +794,6 @@ macro_rules! fragment {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use bitcoin::hashes::hex::ToHex;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use miniscript::{Descriptor, Legacy, Segwitv0}; use miniscript::{Descriptor, Legacy, Segwitv0};
@ -802,8 +802,8 @@ mod test {
use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks}; use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
use bitcoin::bip32;
use bitcoin::network::constants::Network::{Bitcoin, Regtest, Signet, Testnet}; use bitcoin::network::constants::Network::{Bitcoin, Regtest, Signet, Testnet};
use bitcoin::util::bip32;
use bitcoin::PrivateKey; use bitcoin::PrivateKey;
// test the descriptor!() macro // test the descriptor!() macro
@ -819,18 +819,15 @@ mod test {
assert_eq!(desc.is_witness(), is_witness); assert_eq!(desc.is_witness(), is_witness);
assert_eq!(!desc.has_wildcard(), is_fixed); assert_eq!(!desc.has_wildcard(), is_fixed);
for i in 0..expected.len() { for i in 0..expected.len() {
let index = i as u32; let child_desc = desc
let child_desc = if !desc.has_wildcard() { .at_derivation_index(i as u32)
desc.at_derivation_index(0) .expect("i is not hardened");
} else {
desc.at_derivation_index(index)
};
let address = child_desc.address(Regtest); let address = child_desc.address(Regtest);
if let Ok(address) = address { if let Ok(address) = address {
assert_eq!(address.to_string(), *expected.get(i).unwrap()); assert_eq!(address.to_string(), *expected.get(i).unwrap());
} else { } else {
let script = child_desc.script_pubkey(); 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] #[test]
#[should_panic( #[should_panic(expected = "Miniscript(ContextError(UncompressedKeysNotAllowed))")]
expected = "Miniscript(ContextError(CompressedOnly(\"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4\")))"
)]
fn test_dsl_miniscript_checks() { fn test_dsl_miniscript_checks() {
let mut uncompressed_pk = let mut uncompressed_pk =
PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap(); PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();

View File

@ -20,6 +20,8 @@ pub enum Error {
InvalidDescriptorChecksum, InvalidDescriptorChecksum,
/// The descriptor contains hardened derivation steps on public extended keys /// The descriptor contains hardened derivation steps on public extended keys
HardenedDerivationXpub, HardenedDerivationXpub,
/// The descriptor contains multipath keys
MultiPath,
/// Error thrown while working with [`keys`](crate::keys) /// Error thrown while working with [`keys`](crate::keys)
Key(crate::keys::KeyError), Key(crate::keys::KeyError),
@ -30,11 +32,11 @@ pub enum Error {
InvalidDescriptorCharacter(u8), InvalidDescriptorCharacter(u8),
/// BIP32 error /// BIP32 error
Bip32(bitcoin::util::bip32::Error), Bip32(bitcoin::bip32::Error),
/// Error during base58 decoding /// Error during base58 decoding
Base58(bitcoin::util::base58::Error), Base58(bitcoin::base58::Error),
/// Key-related error /// Key-related error
Pk(bitcoin::util::key::Error), Pk(bitcoin::key::Error),
/// Miniscript error /// Miniscript error
Miniscript(miniscript::Error), Miniscript(miniscript::Error),
/// Hex decoding error /// Hex decoding error
@ -62,6 +64,10 @@ impl std::fmt::Display for Error {
f, f,
"The descriptor contains hardened derivation steps on public extended keys" "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::Key(err) => write!(f, "Key error: {}", err),
Self::Policy(err) => write!(f, "Policy error: {}", err), Self::Policy(err) => write!(f, "Policy error: {}", err),
Self::InvalidDescriptorCharacter(char) => { Self::InvalidDescriptorCharacter(char) => {
@ -78,9 +84,9 @@ impl std::fmt::Display for Error {
impl std::error::Error for Error {} impl std::error::Error for Error {}
impl_error!(bitcoin::util::bip32::Error, Bip32); impl_error!(bitcoin::bip32::Error, Bip32);
impl_error!(bitcoin::util::base58::Error, Base58); impl_error!(bitcoin::base58::Error, Base58);
impl_error!(bitcoin::util::key::Error, Pk); impl_error!(bitcoin::key::Error, Pk);
impl_error!(miniscript::Error, Miniscript); impl_error!(miniscript::Error, Miniscript);
impl_error!(bitcoin::hashes::hex::Error, Hex); impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(crate::descriptor::policy::PolicyError, Policy); impl_error!(crate::descriptor::policy::PolicyError, Policy);

View File

@ -16,17 +16,17 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource}; use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::util::{psbt, taproot}; use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey};
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey}; use bitcoin::{psbt, taproot};
use bitcoin::{Network, TxOut}; use bitcoin::{Network, TxOut};
use miniscript::descriptor::{ use miniscript::descriptor::{
DefiniteDescriptorKey, DescriptorSecretKey, DescriptorType, InnerXKey, SinglePubKey, DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType,
DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard,
}; };
pub use miniscript::{ pub use miniscript::{
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor, Descriptor, DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
}; };
use miniscript::{ForEachKey, MiniscriptKey, TranslatePk}; 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 /// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or
/// [`psbt::Output`] /// [`psbt::Output`]
/// ///
/// [`psbt::Input`]: bitcoin::util::psbt::Input /// [`psbt::Input`]: bitcoin::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output /// [`psbt::Output`]: bitcoin::psbt::Output
pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>; pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>;
/// Alias for the type of maps that represent taproot key origins in a [`psbt::Input`] or /// Alias for the type of maps that represent taproot key origins in a [`psbt::Input`] or
/// [`psbt::Output`] /// [`psbt::Output`]
/// ///
/// [`psbt::Input`]: bitcoin::util::psbt::Input /// [`psbt::Input`]: bitcoin::psbt::Input
/// [`psbt::Output`]: bitcoin::util::psbt::Output /// [`psbt::Output`]: bitcoin::psbt::Output
pub type TapKeyOrigins = BTreeMap<bitcoin::XOnlyPublicKey, (Vec<taproot::TapLeafHash>, KeySource)>; 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`] /// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
pub trait IntoWalletDescriptor { pub trait IntoWalletDescriptor {
@ -134,14 +134,10 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
network: Network, network: Network,
} }
impl<'s, 'd> impl<'s, 'd> miniscript::Translator<DescriptorPublicKey, String, DescriptorError>
miniscript::Translator<DescriptorPublicKey, miniscript::DummyKey, DescriptorError>
for Translator<'s, 'd> for Translator<'s, 'd>
{ {
fn pk( fn pk(&mut self, pk: &DescriptorPublicKey) -> Result<String, DescriptorError> {
&mut self,
pk: &DescriptorPublicKey,
) -> Result<miniscript::DummyKey, DescriptorError> {
let secp = &self.secp; let secp = &self.secp;
let (_, _, networks) = if self.descriptor.is_taproot() { let (_, _, networks) = if self.descriptor.is_taproot() {
@ -159,7 +155,7 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
}; };
if networks.contains(&self.network) { if networks.contains(&self.network) {
Ok(miniscript::DummyKey) Ok(Default::default())
} else { } else {
Err(DescriptorError::Key(KeyError::InvalidNetwork)) Err(DescriptorError::Key(KeyError::InvalidNetwork))
} }
@ -167,35 +163,40 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) {
fn sha256( fn sha256(
&mut self, &mut self,
_sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256, _sha256: &<DescriptorPublicKey as MiniscriptKey>::Sha256,
) -> Result<miniscript::DummySha256Hash, DescriptorError> { ) -> Result<String, DescriptorError> {
Ok(Default::default()) Ok(Default::default())
} }
fn hash256( fn hash256(
&mut self, &mut self,
_hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256, _hash256: &<DescriptorPublicKey as MiniscriptKey>::Hash256,
) -> Result<miniscript::DummyHash256Hash, DescriptorError> { ) -> Result<String, DescriptorError> {
Ok(Default::default()) Ok(Default::default())
} }
fn ripemd160( fn ripemd160(
&mut self, &mut self,
_ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160, _ripemd160: &<DescriptorPublicKey as MiniscriptKey>::Ripemd160,
) -> Result<miniscript::DummyRipemd160Hash, DescriptorError> { ) -> Result<String, DescriptorError> {
Ok(Default::default()) Ok(Default::default())
} }
fn hash160( fn hash160(
&mut self, &mut self,
_hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160, _hash160: &<DescriptorPublicKey as MiniscriptKey>::Hash160,
) -> Result<miniscript::DummyHash160Hash, DescriptorError> { ) -> Result<String, DescriptorError> {
Ok(Default::default()) Ok(Default::default())
} }
} }
// check the network for the keys // check the network for the keys
self.0.translate_pk(&mut Translator { use miniscript::TranslateErr;
match self.0.translate_pk(&mut Translator {
secp, secp,
network, network,
descriptor: &self.0, descriptor: &self.0,
})?; }) {
Ok(_) => {}
Err(TranslateErr::TranslatorErr(e)) => return Err(e),
Err(TranslateErr::OuterError(e)) => return Err(e.into()),
}
Ok(self) Ok(self)
} }
@ -249,7 +250,12 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
} }
// fixup the network for keys that need it in the descriptor // 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 // ...and in the key map
let fixed_keymap = keymap let fixed_keymap = keymap
.into_iter() .into_iter()
@ -300,6 +306,10 @@ pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
return Err(DescriptorError::HardenedDerivationXpub); 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 // Run miniscript's sanity check, which will look for duplicated keys and other potential
// issues // issues
descriptor.sanity_check()?; descriptor.sanity_check()?;
@ -338,6 +348,18 @@ pub(crate) trait XKeyUtils {
fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint; 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> impl<T> XKeyUtils for DescriptorXKey<T>
where where
T: InnerXKey, T: InnerXKey,
@ -492,7 +514,10 @@ impl DescriptorMeta for ExtendedDescriptor {
false 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>( fn derive_from_hd_keypaths<'s>(
@ -543,7 +568,7 @@ impl DescriptorMeta for ExtendedDescriptor {
return None; 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() { match descriptor.desc_type() {
// TODO: add pk() here // TODO: add pk() here
DescriptorType::Pkh DescriptorType::Pkh
@ -582,11 +607,10 @@ mod test {
use std::str::FromStr; use std::str::FromStr;
use assert_matches::assert_matches; use assert_matches::assert_matches;
use bitcoin::consensus::encode::deserialize;
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::{bip32, psbt}; use bitcoin::ScriptBuf;
use bitcoin::Script; use bitcoin::{bip32, psbt::Psbt};
use super::*; use super::*;
use crate::psbt::PsbtUtils; use crate::psbt::PsbtUtils;
@ -597,7 +621,7 @@ mod test {
"wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)", "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)",
) )
.unwrap(); .unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize( let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex( &Vec::<u8>::from_hex(
"70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\ "70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\
11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\ 11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\
@ -620,7 +644,7 @@ mod test {
"pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)", "pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)",
) )
.unwrap(); .unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize( let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex( &Vec::<u8>::from_hex(
"70736274ff010053010000000145843b86be54a3cd8c9e38444e1162676c00df\ "70736274ff010053010000000145843b86be54a3cd8c9e38444e1162676c00df\
e7964122a70df491ea12fd67090100000000ffffffff01c19598000000000017\ e7964122a70df491ea12fd67090100000000ffffffff01c19598000000000017\
@ -651,7 +675,7 @@ mod test {
"wsh(and_v(v:pk(03b6633fef2397a0a9de9d7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14),older(6)))", "wsh(and_v(v:pk(03b6633fef2397a0a9de9d7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14),older(6)))",
) )
.unwrap(); .unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize( let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex( &Vec::<u8>::from_hex(
"70736274ff01005302000000011c8116eea34408ab6529223c9a176606742207\ "70736274ff01005302000000011c8116eea34408ab6529223c9a176606742207\
67a1ff1d46a6e3c4a88243ea6e01000000000600000001109698000000000017\ 67a1ff1d46a6e3c4a88243ea6e01000000000600000001109698000000000017\
@ -675,7 +699,7 @@ mod test {
"sh(and_v(v:pk(021403881a5587297818fcaf17d239cefca22fce84a45b3b1d23e836c4af671dbb),after(630000)))", "sh(and_v(v:pk(021403881a5587297818fcaf17d239cefca22fce84a45b3b1d23e836c4af671dbb),after(630000)))",
) )
.unwrap(); .unwrap();
let psbt: psbt::PartiallySignedTransaction = deserialize( let psbt = Psbt::deserialize(
&Vec::<u8>::from_hex( &Vec::<u8>::from_hex(
"70736274ff0100530100000001bc8c13df445dfadcc42afa6dc841f85d22b01d\ "70736274ff0100530100000001bc8c13df445dfadcc42afa6dc841f85d22b01d\
a6270ebf981740f4b7b1d800390000000000feffffff01ba9598000000000017\ a6270ebf981740f4b7b1d800390000000000feffffff01ba9598000000000017\
@ -842,6 +866,12 @@ mod test {
assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub)); 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 descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet); let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
@ -858,9 +888,9 @@ mod test {
let (descriptor, _) = let (descriptor, _) =
into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap(); 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(); let mut psbt_input = psbt::Input::default();
psbt_input psbt_input

View File

@ -43,9 +43,9 @@ use std::fmt;
use serde::ser::SerializeMap; use serde::ser::SerializeMap;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use bitcoin::bip32::Fingerprint;
use bitcoin::hashes::{hash160, ripemd160, sha256}; use bitcoin::hashes::{hash160, ripemd160, sha256};
use bitcoin::util::bip32::Fingerprint; use bitcoin::{absolute, key::XOnlyPublicKey, PublicKey, Sequence};
use bitcoin::{LockTime, PublicKey, Sequence, XOnlyPublicKey};
use miniscript::descriptor::{ use miniscript::descriptor::{
DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner, DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner,
@ -66,7 +66,7 @@ use crate::wallet::utils::{After, Older, SecpCtx};
use super::checksum::calc_checksum; use super::checksum::calc_checksum;
use super::error::Error; use super::error::Error;
use super::XKeyUtils; use super::XKeyUtils;
use bitcoin::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt}; use bitcoin::psbt::{self, Psbt};
use miniscript::psbt::PsbtInputSatisfier; use miniscript::psbt::PsbtInputSatisfier;
/// A unique identifier for a key /// A unique identifier for a key
@ -93,6 +93,9 @@ impl PkOrF {
.. ..
}) => PkOrF::XOnlyPubkey(*pk), }) => PkOrF::XOnlyPubkey(*pk),
DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)), 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 /// Absolute timeclock timestamp
AbsoluteTimelock { AbsoluteTimelock {
/// The timelock value /// The timelock value
value: LockTime, value: absolute::LockTime,
}, },
/// Relative timelock locktime /// Relative timelock locktime
RelativeTimelock { RelativeTimelock {
@ -449,11 +452,14 @@ pub struct Condition {
pub csv: Option<Sequence>, pub csv: Option<Sequence>,
/// Optional timelock condition /// Optional timelock condition
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub timelock: Option<LockTime>, pub timelock: Option<absolute::LockTime>,
} }
impl Condition { 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) { if !a.is_same_unit(b) {
Err(PolicyError::MixedTimelockUnits) Err(PolicyError::MixedTimelockUnits)
} else if a > b { } else if a > b {
@ -659,11 +665,11 @@ impl Policy {
(0..*threshold).collect() (0..*threshold).collect()
} }
SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(), SatisfiableItem::Multisig { keys, .. } => (0..keys.len()).collect(),
_ => vec![], _ => HashSet::new(),
}; };
let selected = match path.get(&self.id) { let selected: HashSet<_> = match path.get(&self.id) {
Some(arr) => arr, Some(arr) => arr.iter().copied().collect(),
_ => &default, _ => default,
}; };
match &self.item { match &self.item {
@ -671,14 +677,24 @@ impl Policy {
let mapped_req = items let mapped_req = items
.iter() .iter()
.map(|i| i.get_condition(path)) .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 // if all the requirements are null we don't care about `selected` because there
// are no requirements // 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()); 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 // 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 // an empty value for this step in case of n-of-n, because `selected` is set to all
// the elements above // the elements above
@ -687,23 +703,18 @@ impl Policy {
} }
// check the selected items, see if there are conflicting requirements // 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 mapped_req
.get(*item_index) .into_iter()
.ok_or(PolicyError::IndexOutOfRange(*item_index))?, .enumerate()
)?; .filter(|(index, _)| selected.contains(index))
} .try_fold(Condition::default(), |acc, (_, cond)| acc.merge(&cond?))
Ok(requirements)
} }
SatisfiableItem::Multisig { keys, threshold } => { SatisfiableItem::Multisig { keys, threshold } => {
if selected.len() < *threshold { if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(self.id.clone())); return Err(PolicyError::NotEnoughItemsSelected(self.id.clone()));
} }
if let Some(item) = selected.iter().find(|i| **i >= keys.len()) { if let Some(item) = selected.into_iter().find(|&i| i >= keys.len()) {
return Err(PolicyError::IndexOutOfRange(*item)); return Err(PolicyError::IndexOutOfRange(item));
} }
Ok(Condition::default()) Ok(Condition::default())
@ -741,6 +752,7 @@ fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
.. ..
}) => pk.to_pubkeyhash(SigType::Ecdsa).into(), }) => pk.to_pubkeyhash(SigType::Ecdsa).into(),
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).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< fn generic_sig_in_psbt<
// C is for "check", it's a closure we use to *check* if a psbt input contains the signature // C is for "check", it's a closure we use to *check* if a psbt input contains the signature
// for a specific key // 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 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, psbt: &Psbt,
key: &DescriptorPublicKey, key: &DescriptorPublicKey,
@ -798,6 +810,13 @@ fn generic_sig_in_psbt<
None => false, 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) => { Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { let mut policy: Policy = SatisfiableItem::AbsoluteTimelock {
value: value.into(), value: (*value).into(),
} }
.into(); .into();
policy.contribution = Satisfaction::Complete { policy.contribution = Satisfaction::Complete {
condition: Condition { condition: Condition {
timelock: Some(value.into()), timelock: Some((*value).into()),
csv: None, csv: None,
}, },
}; };
@ -920,9 +939,9 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
{ {
let after = After::new(Some(current_height), false); let after = After::new(Some(current_height), false);
let after_sat = 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| { 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 { if after_sat && inputs_sat {
policy.satisfaction = policy.contribution.clone(); policy.satisfaction = policy.contribution.clone();
@ -1147,8 +1166,8 @@ mod test {
use crate::keys::{DescriptorKey, IntoDescriptorKey}; use crate::keys::{DescriptorKey, IntoDescriptorKey};
use crate::wallet::signer::SignersContainer; use crate::wallet::signer::SignersContainer;
use assert_matches::assert_matches; use assert_matches::assert_matches;
use bitcoin::bip32;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32;
use bitcoin::Network; use bitcoin::Network;
use std::str::FromStr; use std::str::FromStr;
use std::sync::Arc; use std::sync::Arc;
@ -1567,6 +1586,7 @@ mod test {
let addr = wallet_desc let addr = wallet_desc
.at_derivation_index(0) .at_derivation_index(0)
.unwrap()
.address(Network::Testnet) .address(Network::Testnet)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(
@ -1633,6 +1653,7 @@ mod test {
let addr = wallet_desc let addr = wallet_desc
.at_derivation_index(0) .at_derivation_index(0)
.unwrap()
.address(Network::Testnet) .address(Network::Testnet)
.unwrap(); .unwrap();
assert_eq!( assert_eq!(

View File

@ -14,10 +14,10 @@
//! This module contains the definition of various common script templates that are ready to be //! 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. //! used. See the documentation of each template for an example.
use bitcoin::util::bip32; use bitcoin::bip32;
use bitcoin::Network; use bitcoin::Network;
use miniscript::{Legacy, Segwitv0}; use miniscript::{Legacy, Segwitv0, Tap};
use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap}; use super::{ExtendedDescriptor, IntoWalletDescriptor, KeyMap};
use crate::descriptor::DescriptorError; 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}/*)` /// 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`). /// 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::wallet::AddressIndex::New;
/// use bdk::template::Bip44; /// 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( /// let wallet = Wallet::new(
/// Bip44(key.clone(), KeychainKind::External), /// Bip44(key.clone(), KeychainKind::External),
/// Some(Bip44(key, KeychainKind::Internal)), /// Some(Bip44(key, KeychainKind::Internal)),
@ -225,8 +254,8 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
/// # use bdk::wallet::AddressIndex::New; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip44Public; /// use bdk::template::Bip44Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; /// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new( /// let wallet = Wallet::new(
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), /// 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::wallet::AddressIndex::New;
/// use bdk::template::Bip49; /// 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( /// let wallet = Wallet::new(
/// Bip49(key.clone(), KeychainKind::External), /// Bip49(key.clone(), KeychainKind::External),
/// Some(Bip49(key, KeychainKind::Internal)), /// Some(Bip49(key, KeychainKind::Internal)),
@ -304,8 +333,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
/// # use bdk::wallet::AddressIndex::New; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip49Public; /// use bdk::template::Bip49Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; /// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new( /// let wallet = Wallet::new(
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), /// 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::wallet::AddressIndex::New;
/// use bdk::template::Bip84; /// 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( /// let wallet = Wallet::new(
/// Bip84(key.clone(), KeychainKind::External), /// Bip84(key.clone(), KeychainKind::External),
/// Some(Bip84(key, KeychainKind::Internal)), /// Some(Bip84(key, KeychainKind::Internal)),
@ -383,8 +412,8 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
/// # use bdk::wallet::AddressIndex::New; /// # use bdk::wallet::AddressIndex::New;
/// use bdk::template::Bip84Public; /// use bdk::template::Bip84Public;
/// ///
/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; /// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; /// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
/// let wallet = Wallet::new( /// let wallet = Wallet::new(
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), /// 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 { macro_rules! expand_make_bipxx {
( $mod_name:ident, $ctx:ty ) => { ( $mod_name:ident, $ctx:ty ) => {
mod $mod_name { mod $mod_name {
@ -473,6 +581,7 @@ macro_rules! expand_make_bipxx {
expand_make_bipxx!(legacy, Legacy); expand_make_bipxx!(legacy, Legacy);
expand_make_bipxx!(segwit_v0, Segwitv0); expand_make_bipxx!(segwit_v0, Segwitv0);
expand_make_bipxx!(segwit_v1, Tap);
#[cfg(test)] #[cfg(test)]
mod test { mod test {
@ -484,37 +593,36 @@ mod test {
use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks; use crate::keys::ValidNetworks;
use assert_matches::assert_matches; use assert_matches::assert_matches;
use bitcoin::network::constants::Network::Regtest;
use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; use miniscript::descriptor::{DescriptorPublicKey, KeyMap};
use miniscript::Descriptor; use miniscript::Descriptor;
// BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)` // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)`
#[test] #[test]
fn test_bip44_template_cointype() { 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); assert_eq!(Network::Bitcoin, xprvkey.network);
let xdesc = Bip44(xprvkey, KeychainKind::Internal) let xdesc = Bip44(xprvkey, KeychainKind::Internal)
.build(Network::Bitcoin) .build(Network::Bitcoin)
.unwrap(); .unwrap();
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 { 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(); let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 }); assert_matches!(purpose, Hardened { index: 44 });
let coin_type = path.get(1).unwrap(); let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 0 }); 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); assert_eq!(Network::Testnet, tprvkey.network);
let tdesc = Bip44(tprvkey, KeychainKind::Internal) let tdesc = Bip44(tprvkey, KeychainKind::Internal)
.build(Network::Testnet) .build(Network::Testnet)
.unwrap(); .unwrap();
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 { 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(); let purpose = path.get(0).unwrap();
assert_matches!(purpose, Hardened { index: 44 }); assert_matches!(purpose, Hardened { index: 44 });
let coin_type = path.get(1).unwrap(); let coin_type = path.get(1).unwrap();
@ -526,20 +634,23 @@ mod test {
fn check( fn check(
desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>, desc: Result<(Descriptor<DescriptorPublicKey>, KeyMap, ValidNetworks), DescriptorError>,
is_witness: bool, is_witness: bool,
is_taproot: bool,
is_fixed: bool, is_fixed: bool,
network: Network,
expected: &[&str], expected: &[&str],
) { ) {
let (desc, _key_map, _networks) = desc.unwrap(); let (desc, _key_map, _networks) = desc.unwrap();
assert_eq!(desc.is_witness(), is_witness); assert_eq!(desc.is_witness(), is_witness);
assert_eq!(desc.is_taproot(), is_taproot);
assert_eq!(!desc.has_wildcard(), is_fixed); assert_eq!(!desc.has_wildcard(), is_fixed);
for i in 0..expected.len() { for i in 0..expected.len() {
let index = i as u32; let index = i as u32;
let child_desc = if !desc.has_wildcard() { let child_desc = if !desc.has_wildcard() {
desc.at_derivation_index(0) desc.at_derivation_index(0).unwrap()
} else { } 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()); assert_eq!(address.to_string(), *expected.get(i).unwrap());
} }
} }
@ -553,7 +664,9 @@ mod test {
check( check(
P2Pkh(prvkey).build(Network::Bitcoin), P2Pkh(prvkey).build(Network::Bitcoin),
false, false,
false,
true, true,
Network::Regtest,
&["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"], &["mwJ8hxFYW19JLuc65RCTaP4v1rzVU8cVMT"],
); );
@ -564,7 +677,9 @@ mod test {
check( check(
P2Pkh(pubkey).build(Network::Bitcoin), P2Pkh(pubkey).build(Network::Bitcoin),
false, false,
false,
true, true,
Network::Regtest,
&["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"], &["muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi"],
); );
} }
@ -578,7 +693,9 @@ mod test {
check( check(
P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin), P2Wpkh_P2Sh(prvkey).build(Network::Bitcoin),
true, true,
false,
true, true,
Network::Regtest,
&["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"], &["2NB4ox5VDRw1ecUv6SnT3VQHPXveYztRqk5"],
); );
@ -589,7 +706,9 @@ mod test {
check( check(
P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin), P2Wpkh_P2Sh(pubkey).build(Network::Bitcoin),
true, true,
false,
true, true,
Network::Regtest,
&["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"], &["2N5LiC3CqzxDamRTPG1kiNv1FpNJQ7x28sb"],
); );
} }
@ -603,7 +722,9 @@ mod test {
check( check(
P2Wpkh(prvkey).build(Network::Bitcoin), P2Wpkh(prvkey).build(Network::Bitcoin),
true, true,
false,
true, true,
Network::Regtest,
&["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"], &["bcrt1q4525hmgw265tl3drrl8jjta7ayffu6jfcwxx9y"],
); );
@ -614,19 +735,52 @@ mod test {
check( check(
P2Wpkh(pubkey).build(Network::Bitcoin), P2Wpkh(pubkey).build(Network::Bitcoin),
true, true,
false,
true, true,
Network::Regtest,
&["bcrt1qngw83fg8dz0k749cg7k3emc7v98wy0c7azaa6h"], &["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}/*)` // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)`
#[test] #[test]
fn test_bip44_template() { fn test_bip44_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin), Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin),
false, false,
false, false,
false,
Network::Regtest,
&[ &[
"n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5", "n453VtnjDHPyDt2fDstKSu7A3YCJoHZ5g5",
"mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP", "mvfrrumXgTtwFPWDNUecBBgzuMXhYM7KRP",
@ -637,6 +791,8 @@ mod test {
Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin), Bip44(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
false, false,
false, false,
false,
Network::Regtest,
&[ &[
"muHF98X9KxEzdKrnFAX85KeHv96eXopaip", "muHF98X9KxEzdKrnFAX85KeHv96eXopaip",
"n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR", "n4hpyLJE5ub6B5Bymv4eqFxS5KjrewSmYR",
@ -648,12 +804,14 @@ mod test {
// BIP44 public `pkh(key/{0,1}/*)` // BIP44 public `pkh(key/{0,1}/*)`
#[test] #[test]
fn test_bip44_public_template() { fn test_bip44_public_template() {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
false, false,
false, false,
false,
Network::Regtest,
&[ &[
"miNG7dJTzJqNbFS19svRdTCisC65dsubtR", "miNG7dJTzJqNbFS19svRdTCisC65dsubtR",
"n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg", "n2UqaDbCjWSFJvpC84m3FjUk5UaeibCzYg",
@ -664,6 +822,8 @@ mod test {
Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), Bip44Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
false, false,
false, false,
false,
Network::Regtest,
&[ &[
"moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H", "moDr3vJ8wpt5nNxSK55MPq797nXJb2Ru9H",
"ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG", "ms7A1Yt4uTezT2XkefW12AvLoko8WfNJMG",
@ -675,11 +835,13 @@ mod test {
// BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))` // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))`
#[test] #[test]
fn test_bip49_template() { fn test_bip49_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin), Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV", "2N9bCAJXGm168MjVwpkBdNt6ucka3PKVoUV",
"2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS", "2NDckYkqrYyDMtttEav5hB3Bfw9EGAW5HtS",
@ -690,6 +852,8 @@ mod test {
Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin), Bip49(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG", "2NB3pA8PnzJLGV8YEKNDFpbViZv3Bm1K6CG",
"2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p", "2NBiX2Wzxngb5rPiWpUiJQ2uLVB4HBjFD4p",
@ -701,12 +865,14 @@ mod test {
// BIP49 public `sh(wpkh(key/{0,1}/*))` // BIP49 public `sh(wpkh(key/{0,1}/*))`
#[test] #[test]
fn test_bip49_public_template() { fn test_bip49_public_template() {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt", "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt",
"2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX", "2NCTQfJ1sZa3wQ3pPseYRHbaNEpC3AquEfX",
@ -717,6 +883,8 @@ mod test {
Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), Bip49Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ", "2NF2vttKibwyxigxtx95Zw8K7JhDbo5zPVJ",
"2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH", "2Mtmyd8taksxNVWCJ4wVvaiss7QPZGcAJuH",
@ -728,11 +896,13 @@ mod test {
// BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)` // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)`
#[test] #[test]
fn test_bip84_template() { fn test_bip84_template() {
let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
check( check(
Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin), Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s", "bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s",
"bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp", "bcrt1qx0v6zgfwe50m4kqc58cqzcyem7ay2sfl3gvqhp",
@ -743,6 +913,8 @@ mod test {
Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin), Bip84(prvkey, KeychainKind::Internal).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa", "bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa",
"bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45", "bcrt1qqqasfhxpkkf7zrxqnkr2sfhn74dgsrc3e3ky45",
@ -754,12 +926,14 @@ mod test {
// BIP84 public `wpkh(key/{0,1}/*)` // BIP84 public `wpkh(key/{0,1}/*)`
#[test] #[test]
fn test_bip84_public_template() { fn test_bip84_public_template() {
let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap();
let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap();
check( check(
Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h", "bcrt1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2prcdvd0h",
"bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana", "bcrt1q3lncdlwq3lgcaaeyruynjnlccr0ve0kakh6ana",
@ -770,6 +944,8 @@ mod test {
Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin), Bip84Public(pubkey, fingerprint, KeychainKind::Internal).build(Network::Bitcoin),
true, true,
false, false,
false,
Network::Regtest,
&[ &[
"bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2", "bcrt1qm6wqukenh7guu792lj2njgw9n78cmwsy8xy3z2",
"bcrt1q694twxtjn4nnrvnyvra769j0a23rllj5c6cgwp", "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, for example the network of the bitcoin node
found: Network, found: Network,
}, },
/// The address requested comes from an hardened index
HardenedIndex,
#[cfg(feature = "verify")] #[cfg(feature = "verify")]
/// Transaction verification error /// Transaction verification error
Verification(crate::wallet::verify::VerifyError), Verification(crate::wallet::verify::VerifyError),
@ -106,7 +108,7 @@ pub enum Error {
/// Miniscript PSBT error /// Miniscript PSBT error
MiniscriptPsbt(MiniscriptPsbtError), MiniscriptPsbt(MiniscriptPsbtError),
/// BIP32 error /// BIP32 error
Bip32(bitcoin::util::bip32::Error), Bip32(bitcoin::bip32::Error),
/// A secp256k1 error /// A secp256k1 error
Secp256k1(bitcoin::secp256k1::Error), Secp256k1(bitcoin::secp256k1::Error),
/// Error serializing or deserializing JSON data /// Error serializing or deserializing JSON data
@ -114,9 +116,9 @@ pub enum Error {
/// Hex decoding error /// Hex decoding error
Hex(bitcoin::hashes::hex::Error), Hex(bitcoin::hashes::hex::Error),
/// Partially signed bitcoin transaction error /// Partially signed bitcoin transaction error
Psbt(bitcoin::util::psbt::Error), Psbt(bitcoin::psbt::Error),
/// Partially signed bitcoin transaction parse error /// Partially signed bitcoin transaction parse error
PsbtParse(bitcoin::util::psbt::PsbtParseError), PsbtParse(bitcoin::psbt::PsbtParseError),
//KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey), //KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
//MissingInputUTXO(usize), //MissingInputUTXO(usize),
@ -228,6 +230,7 @@ impl fmt::Display for Error {
"Invalid network: requested {} but found {}", "Invalid network: requested {} but found {}",
requested, found requested, found
), ),
Self::HardenedIndex => write!(f, "Requested address from an hardened index"),
#[cfg(feature = "verify")] #[cfg(feature = "verify")]
Self::Verification(err) => write!(f, "Transaction verification error: {}", err), Self::Verification(err) => write!(f, "Transaction verification error: {}", err),
Self::InvalidProgressValue(progress) => { Self::InvalidProgressValue(progress) => {
@ -304,12 +307,12 @@ impl From<crate::keys::KeyError> for Error {
impl_error!(bitcoin::consensus::encode::Error, Encode); impl_error!(bitcoin::consensus::encode::Error, Encode);
impl_error!(miniscript::Error, Miniscript); impl_error!(miniscript::Error, Miniscript);
impl_error!(MiniscriptPsbtError, MiniscriptPsbt); 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!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, Json); impl_error!(serde_json::Error, Json);
impl_error!(bitcoin::hashes::hex::Error, Hex); impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(bitcoin::util::psbt::Error, Psbt); impl_error!(bitcoin::psbt::Error, Psbt);
impl_error!(bitcoin::util::psbt::PsbtParseError, PsbtParse); impl_error!(bitcoin::psbt::PsbtParseError, PsbtParse);
#[cfg(feature = "electrum")] #[cfg(feature = "electrum")]
impl_error!(electrum_client::Error, 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 // 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. // something that should be fairly simple to re-implement.
use bitcoin::util::bip32; use bitcoin::bip32;
use bitcoin::Network; use bitcoin::Network;
use miniscript::ScriptContext; use miniscript::ScriptContext;
@ -141,7 +141,7 @@ impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
(word_count, language): Self::Options, (word_count, language): Self::Options,
entropy: Self::Entropy, entropy: Self::Entropy,
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> { ) -> 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)?; let mnemonic = Mnemonic::from_entropy_in(language, entropy)?;
Ok(GeneratedKey::new(mnemonic, any_network())) Ok(GeneratedKey::new(mnemonic, any_network()))
@ -152,7 +152,7 @@ impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
mod test { mod test {
use std::str::FromStr; use std::str::FromStr;
use bitcoin::util::bip32; use bitcoin::bip32;
use bip39::{Language, Mnemonic}; use bip39::{Language, Mnemonic};

View File

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

View File

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

View File

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

View File

@ -10,13 +10,13 @@
use crate::testutils::TestIncomingTx; use crate::testutils::TestIncomingTx;
use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hashes::hex::{FromHex, ToHex};
use bitcoin::hashes::sha256d; use bitcoin::hashes::sha256d;
use bitcoin::{Address, Amount, PackedLockTime, Script, Sequence, Transaction, Txid, Witness}; use bitcoin::{absolute, Address, Amount, Script, ScriptBuf, Sequence, Transaction, Txid, Witness};
pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType; pub use bitcoincore_rpc::json::AddressType;
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi}; pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
use core::str::FromStr; use core::str::FromStr;
use electrsd::bitcoind::BitcoinD; use electrsd::bitcoind::BitcoinD;
use electrsd::electrum_client::ElectrumApi as _;
use electrsd::{bitcoind, ElectrsD}; use electrsd::{bitcoind, ElectrsD};
pub use electrum_client::{Client as ElectrumClient, ElectrumApi}; pub use electrum_client::{Client as ElectrumClient, ElectrumApi};
#[allow(unused_imports)] #[allow(unused_imports)]
@ -45,7 +45,11 @@ impl TestClient {
let electrsd = ElectrsD::with_conf(electrs_exe, &bitcoind, &conf).unwrap(); 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 bitcoind
.client .client
.generate_to_address(101, &node_address) .generate_to_address(101, &node_address)
@ -107,7 +111,7 @@ impl TestClient {
.collect(); .collect();
if self.get_balance(None, None).unwrap() < Amount::from_sat(required_balance) { 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 // 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) let monitor_script = Address::from_str(&meta_tx.output[0].to_address)
.unwrap() .unwrap()
.assume_checked()
.script_pubkey(); .script_pubkey();
self.wait_for_tx(txid, &monitor_script); 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 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 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); self.wait_for_tx(new_txid, &monitor_script);
debug!("Bumped {}, new txid {}", txid, new_txid); debug!("Bumped {}, new txid {}", txid, new_txid);
@ -170,41 +175,44 @@ impl TestClient {
} }
pub fn generate_manually(&mut self, txs: Vec<Transaction>) -> String { 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::script::Builder;
use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut}; use bitcoin::blockdata::transaction::{OutPoint, TxIn, TxOut};
use bitcoin::hash_types::{BlockHash, TxMerkleNode}; use bitcoin::hash_types::{BlockHash, TxMerkleNode};
use bitcoin::hashes::Hash; use bitcoin::hashes::Hash;
use bitcoin::pow::CompactTarget;
let block_template: serde_json::Value = self let block_template: serde_json::Value = self
.call("getblocktemplate", &[json!({"rules": ["segwit"]})]) .call("getblocktemplate", &[json!({"rules": ["segwit"]})])
.unwrap(); .unwrap();
trace!("getblocktemplate: {:#?}", block_template); trace!("getblocktemplate: {:#?}", block_template);
let header = BlockHeader { let header = Header {
version: block_template["version"].as_i64().unwrap() as i32, version: Version::from_consensus(block_template["version"].as_i64().unwrap() as i32),
prev_blockhash: BlockHash::from_hex( prev_blockhash: BlockHash::from_str(
block_template["previousblockhash"].as_str().unwrap(), block_template["previousblockhash"].as_str().unwrap(),
) )
.unwrap(), .unwrap(),
merkle_root: TxMerkleNode::all_zeros(), merkle_root: TxMerkleNode::all_zeros(),
time: block_template["curtime"].as_u64().unwrap() as u32, 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, nonce: 0,
}; };
debug!("header: {:#?}", header); debug!("header: {:#?}", header);
let height = block_template["height"].as_u64().unwrap() as i64; 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 // burn block subsidy and fees, not a big deal
let mut coinbase_tx = Transaction { let mut coinbase_tx = Transaction {
version: 1, version: 1,
lock_time: PackedLockTime(0), lock_time: absolute::LockTime::ZERO,
input: vec![TxIn { input: vec![TxIn {
previous_output: OutPoint::null(), previous_output: OutPoint::null(),
script_sig: Builder::new().push_int(height).into_script(), script_sig: Builder::new().push_int(height).into_script(),
sequence: Sequence(0xFFFFFFFF), sequence: Sequence(0xFFFFFFFF),
witness: Witness::from_vec(vec![witness_reserved_value]), witness: Witness::from_slice(&vec![witness_reserved_value]),
}], }],
output: vec![], output: vec![],
}; };
@ -225,7 +233,7 @@ impl TestClient {
// now update and replace the coinbase tx // now update and replace the coinbase tx
let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed]; 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 { coinbase_tx.output.push(TxOut {
value: 0, value: 0,
@ -245,11 +253,11 @@ impl TestClient {
// now do PoW :) // now do PoW :)
let target = block.header.target(); 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 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); debug!("generated block hex: {}", block_hex);
self.electrsd.client.block_headers_subscribe().unwrap(); self.electrsd.client.block_headers_subscribe().unwrap();
@ -265,11 +273,12 @@ impl TestClient {
self.wait_for_block(height as usize); 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>) { 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 hashes = self.generate_to_address(num_blocks, &address).unwrap();
let best_hash = hashes.last().unwrap(); let best_hash = hashes.last().unwrap();
let height = self.get_block_info(best_hash).unwrap().height; let height = self.get_block_info(best_hash).unwrap().height;
@ -317,9 +326,11 @@ impl TestClient {
&self &self
.get_new_address(None, address_type) .get_new_address(None, address_type)
.unwrap() .unwrap()
.assume_checked()
.to_string(), .to_string(),
) )
.unwrap() .unwrap()
.assume_checked()
} }
} }
@ -378,7 +389,7 @@ macro_rules! bdk_blockchain_tests {
fn $_fn_name:ident ( $( $test_client:ident : &TestClient )? $(,)? ) -> $blockchain:ty $block:block) => { fn $_fn_name:ident ( $( $test_client:ident : &TestClient )? $(,)? ) -> $blockchain:ty $block:block) => {
#[cfg(test)] #[cfg(test)]
mod bdk_blockchain_tests { 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::testutils::blockchain_tests::TestClient;
use $crate::blockchain::Blockchain; use $crate::blockchain::Blockchain;
use $crate::database::MemoryDatabase; use $crate::database::MemoryDatabase;
@ -386,6 +397,7 @@ macro_rules! bdk_blockchain_tests {
use $crate::wallet::AddressIndex; use $crate::wallet::AddressIndex;
use $crate::{Wallet, FeeRate, SyncOptions}; use $crate::{Wallet, FeeRate, SyncOptions};
use $crate::testutils; use $crate::testutils;
use std::convert::TryFrom;
use super::*; use super::*;
@ -1058,7 +1070,7 @@ macro_rules! bdk_blockchain_tests {
assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance"); assert_eq!(wallet.get_balance().unwrap().untrusted_pending, 50_000, "incorrect balance");
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
let data = [42u8;80]; let data = PushBytesBuf::try_from(vec![42u8;80]).unwrap();
builder.add_data(&data); builder.add_data(&data);
let (mut psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
@ -1066,7 +1078,7 @@ macro_rules! bdk_blockchain_tests {
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
let serialized_tx = bitcoin::consensus::encode::serialize(&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(); blockchain.broadcast(&tx).unwrap();
test_client.generate(1, Some(node_addr)); test_client.generate(1, Some(node_addr));
wallet.sync(&blockchain, SyncOptions::default()).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
@ -1086,18 +1098,18 @@ macro_rules! bdk_blockchain_tests {
wallet.sync(&blockchain, SyncOptions::default()).unwrap(); wallet.sync(&blockchain, SyncOptions::default()).unwrap();
assert_eq!(wallet.get_balance().unwrap().immature, 0, "incorrect balance"); 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(); 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) // make coinbase mature (100 blocks)
let node_addr = test_client.get_node_address(None); let node_addr = test_client.get_node_address(None);
test_client.generate(100, Some(node_addr)); test_client.generate(100, Some(node_addr));
wallet.sync(&blockchain, SyncOptions::default()).unwrap(); 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 // 2. Get a new bech32m address from test bitcoind node taproot wallet
// TODO replace once rust-bitcoincore-rpc with PR 199 released // 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(); let node_addr: bitcoin::Address<bitcoin::address::NetworkUnchecked> = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap();
assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").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 // 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 (wallet, blockchain, _, mut test_client) = init_single_sig();
let bdk_address = wallet.get_address(AddressIndex::New).unwrap().address; 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! { let tx = testutils! {
@tx ( (@addr bdk_address.clone()) => 50_000, (@addr core_address.clone()) => 40_000 ) @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", "label":"taproot key spend",
}]); }]);
let _importdescriptors_result: Value = taproot_wallet_client.call("importdescriptors", &[import_descriptor_args]).expect("import wallet"); 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 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).expect("generated to 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 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 change_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string();
let send_addr_amounts = json!([{ 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 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"); 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 // Test parsing core created PSBT
let mut psbt = PartiallySignedTransaction::from_str(&core_psbt).expect("core taproot 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 secp = Secp256k1::new();
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0; 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 ) => ({ ( @internal $descriptors:expr, $child:expr ) => ({
use $crate::bitcoin::secp256k1::Secp256k1; use $crate::bitcoin::secp256k1::Secp256k1;
@ -146,7 +146,7 @@ macro_rules! testutils {
let mut seed = [0u8; 32]; let mut seed = [0u8; 32];
rand::thread_rng().fill(&mut seed[..]); 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, $crate::bitcoin::Network::Testnet,
&seed, &seed,
); );

View File

@ -13,7 +13,7 @@ use std::convert::AsRef;
use std::ops::Sub; use std::ops::Sub;
use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; 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}; use serde::{Deserialize, Serialize};
@ -97,8 +97,8 @@ impl FeeRate {
} }
/// Calculate fee rate from `fee` and weight units (`wu`). /// Calculate fee rate from `fee` and weight units (`wu`).
pub fn from_wu(fee: u64, wu: usize) -> FeeRate { pub fn from_wu(fee: u64, wu: Weight) -> FeeRate {
Self::from_vb(fee, wu.vbytes()) Self::from_vb(fee, wu.to_vbytes_ceil() as usize)
} }
/// Calculate fee rate from `fee` and `vbytes`. /// Calculate fee rate from `fee` and `vbytes`.
@ -113,8 +113,8 @@ impl FeeRate {
} }
/// Calculate absolute fee in Satoshis using size in weight units. /// Calculate absolute fee in Satoshis using size in weight units.
pub fn fee_wu(&self, wu: usize) -> u64 { pub fn fee_wu(&self, wu: Weight) -> u64 {
self.fee_vb(wu.vbytes()) self.fee_vb(wu.to_vbytes_ceil() as usize)
} }
/// Calculate absolute fee in Satoshis using size in virtual bytes. /// Calculate absolute fee in Satoshis using size in virtual bytes.
@ -405,7 +405,7 @@ mod tests {
let tx_details_a = TransactionDetails { let tx_details_a = TransactionDetails {
transaction: None, transaction: None,
txid: Txid::from_inner([0; 32]), txid: Txid::all_zeros(),
received: 0, received: 0,
sent: 0, sent: 0,
fee: None, fee: None,
@ -414,7 +414,7 @@ mod tests {
let tx_details_b = TransactionDetails { let tx_details_b = TransactionDetails {
transaction: None, transaction: None,
txid: Txid::from_inner([0; 32]), txid: Txid::all_zeros(),
received: 0, received: 0,
sent: 0, sent: 0,
fee: None, fee: None,
@ -423,7 +423,7 @@ mod tests {
let tx_details_c = TransactionDetails { let tx_details_c = TransactionDetails {
transaction: None, transaction: None,
txid: Txid::from_inner([0; 32]), txid: Txid::all_zeros(),
received: 0, received: 0,
sent: 0, sent: 0,
fee: None, fee: None,
@ -432,7 +432,7 @@ mod tests {
let tx_details_d = TransactionDetails { let tx_details_d = TransactionDetails {
transaction: None, transaction: None,
txid: Txid::from_inner([1; 32]), txid: Txid::from_byte_array([1; Txid::LEN]),
received: 0, received: 0,
sent: 0, sent: 0,
fee: None, fee: None,

View File

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

View File

@ -20,7 +20,7 @@
//! # use bdk::wallet::hardwaresigner::HWISigner; //! # use bdk::wallet::hardwaresigner::HWISigner;
//! # use bdk::wallet::AddressIndex::New; //! # use bdk::wallet::AddressIndex::New;
//! # use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet}; //! # use bdk::{FeeRate, KeychainKind, SignOptions, SyncOptions, Wallet};
//! # use hwi::{types::HWIChain, HWIClient}; //! # use hwi::HWIClient;
//! # use std::sync::Arc; //! # use std::sync::Arc;
//! # //! #
//! # fn main() -> Result<(), Box<dyn std::error::Error>> { //! # fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -29,7 +29,7 @@
//! panic!("No devices found!"); //! panic!("No devices found!");
//! } //! }
//! let first_device = devices.remove(0)?; //! 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( //! # let mut wallet = Wallet::new(
//! # "", //! # "",
@ -49,9 +49,9 @@
//! # } //! # }
//! ``` //! ```
use bitcoin::bip32::Fingerprint;
use bitcoin::psbt::PartiallySignedTransaction; use bitcoin::psbt::PartiallySignedTransaction;
use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::util::bip32::Fingerprint;
use hwi::error::Error; use hwi::error::Error;
use hwi::types::{HWIChain, HWIDevice}; use hwi::types::{HWIChain, HWIDevice};

View File

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

View File

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

View File

@ -18,7 +18,7 @@
//! # use bitcoin::*; //! # use bitcoin::*;
//! # use bdk::*; //! # use bdk::*;
//! # use bdk::wallet::tx_builder::CreateTx; //! # 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 wallet = doctest_wallet!();
//! // create a TxBuilder from a wallet //! // create a TxBuilder from a wallet
//! let mut tx_builder = wallet.build_tx(); //! let mut tx_builder = wallet.build_tx();
@ -27,7 +27,7 @@
//! // Create a transaction with one output to `to_address` of 50_000 satoshi //! // Create a transaction with one output to `to_address` of 50_000 satoshi
//! .add_recipient(to_address.script_pubkey(), 50_000) //! .add_recipient(to_address.script_pubkey(), 50_000)
//! // With a custom fee rate of 5.0 satoshi/vbyte //! // 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 //! // Only spend non-change outputs
//! .do_not_spend_change() //! .do_not_spend_change()
//! // Turn on RBF signaling //! // Turn on RBF signaling
@ -41,8 +41,8 @@ use std::collections::HashSet;
use std::default::Default; use std::default::Default;
use std::marker::PhantomData; use std::marker::PhantomData;
use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt}; use bitcoin::psbt::{self, PartiallySignedTransaction as Psbt};
use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction}; use bitcoin::{absolute, script::PushBytes, OutPoint, ScriptBuf, Sequence, Transaction};
use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm};
use crate::{database::BatchDatabase, Error, Utxo, Wallet}; use crate::{database::BatchDatabase, Error, Utxo, Wallet};
@ -79,7 +79,7 @@ impl TxBuilderContext for BumpFee {}
/// # use bitcoin::*; /// # use bitcoin::*;
/// # use core::str::FromStr; /// # use core::str::FromStr;
/// # let wallet = doctest_wallet!(); /// # let wallet = doctest_wallet!();
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); /// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
/// # let addr2 = addr1.clone(); /// # let addr2 = addr1.clone();
/// // chaining /// // chaining
/// let (psbt1, details) = { /// let (psbt1, details) = {
@ -126,9 +126,9 @@ pub struct TxBuilder<'a, D, Cs, Ctx> {
//TODO: TxParams should eventually be exposed publicly. //TODO: TxParams should eventually be exposed publicly.
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub(crate) struct TxParams { pub(crate) struct TxParams {
pub(crate) recipients: Vec<(Script, u64)>, pub(crate) recipients: Vec<(ScriptBuf, u64)>,
pub(crate) drain_wallet: bool, 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) fee_policy: Option<FeePolicy>,
pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>, pub(crate) internal_policy_path: Option<BTreeMap<String, Vec<usize>>>,
pub(crate) external_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) manually_selected_only: bool,
pub(crate) sighash: Option<psbt::PsbtSighashType>, pub(crate) sighash: Option<psbt::PsbtSighashType>,
pub(crate) ordering: TxOrdering, pub(crate) ordering: TxOrdering,
pub(crate) locktime: Option<LockTime>, pub(crate) locktime: Option<absolute::LockTime>,
pub(crate) rbf: Option<RbfValue>, pub(crate) rbf: Option<RbfValue>,
pub(crate) version: Option<Version>, pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy, pub(crate) change_policy: ChangeSpendPolicy,
@ -145,7 +145,7 @@ pub(crate) struct TxParams {
pub(crate) add_global_xpubs: bool, pub(crate) add_global_xpubs: bool,
pub(crate) include_output_redeem_witness_script: bool, pub(crate) include_output_redeem_witness_script: bool,
pub(crate) bumping_fee: Option<PreviousFee>, pub(crate) bumping_fee: Option<PreviousFee>,
pub(crate) current_height: Option<LockTime>, pub(crate) current_height: Option<absolute::LockTime>,
pub(crate) allow_dust: bool, pub(crate) allow_dust: bool,
} }
@ -241,7 +241,10 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
/// # use std::collections::BTreeMap; /// # use std::collections::BTreeMap;
/// # use bitcoin::*; /// # use bitcoin::*;
/// # use bdk::*; /// # 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 wallet = doctest_wallet!();
/// let mut path = BTreeMap::new(); /// let mut path = BTreeMap::new();
/// path.insert("aabbccdd".to_string(), vec![0, 1]); /// 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 { for utxo in utxos {
let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain); let descriptor = self.wallet.get_descriptor_for_keychain(utxo.keychain);
#[allow(deprecated)]
let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap(); let satisfaction_weight = descriptor.max_satisfaction_weight().unwrap();
self.params.utxos.push(WeightedUtxo { self.params.utxos.push(WeightedUtxo {
satisfaction_weight, satisfaction_weight,
@ -424,7 +428,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
/// Use a specific nLockTime while creating the transaction /// Use a specific nLockTime while creating the transaction
/// ///
/// This can cause conflicts if the wallet's descriptors contain an "after" (OP_CLTV) operator. /// 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.params.locktime = Some(locktime);
self self
} }
@ -463,7 +467,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
self 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. /// SegWit descriptors.
/// ///
/// This reduces the size of the PSBT, but some signers might reject them due to the lack of /// 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 self
} }
/// Fill-in the [`psbt::Output::redeem_script`](bitcoin::util::psbt::Output::redeem_script) and /// Fill-in the [`psbt::Output::redeem_script`](bitcoin::psbt::Output::redeem_script) and
/// [`psbt::Output::witness_script`](bitcoin::util::psbt::Output::witness_script) fields. /// [`psbt::Output::witness_script`](bitcoin::psbt::Output::witness_script) fields.
/// ///
/// This is useful for signers which always require it, like ColdCard hardware wallets. /// This is useful for signers which always require it, like ColdCard hardware wallets.
pub fn include_output_redeem_witness_script(&mut self) -> &mut Self { 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. /// 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 { 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 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> { impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, CreateTx> {
/// Replace the recipients already added with a new list /// 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.params.recipients = recipients;
self self
} }
/// Add a recipient to the internal list /// 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.params.recipients.push((script_pubkey, amount));
self self
} }
/// Add data as an output, using OP_RETURN /// Add data as an output, using OP_RETURN
pub fn add_data(&mut self, data: &[u8]) -> &mut Self { pub fn add_data<T: AsRef<PushBytes>>(&mut self, data: &T) -> &mut Self {
let script = Script::new_op_return(data); let script = ScriptBuf::new_op_return(data);
self.add_recipient(script, 0u64); self.add_recipient(script, 0u64);
self self
} }
@ -614,7 +619,10 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, D, Cs, C
/// # use bitcoin::*; /// # use bitcoin::*;
/// # use bdk::*; /// # use bdk::*;
/// # use bdk::wallet::tx_builder::CreateTx; /// # 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 wallet = doctest_wallet!();
/// let mut tx_builder = wallet.build_tx(); /// 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() /// .drain_wallet()
/// // Send the excess (which is all the coins minus the fee) to this address. /// // Send the excess (which is all the coins minus the fee) to this address.
/// .drain_to(to_address.script_pubkey()) /// .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(); /// .enable_rbf();
/// let (psbt, tx_details) = tx_builder.finish()?; /// let (psbt, tx_details) = tx_builder.finish()?;
/// # Ok::<(), bdk::Error>(()) /// # 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_recipient`]: Self::add_recipient
/// [`add_utxos`]: Self::add_utxos /// [`add_utxos`]: Self::add_utxos
/// [`drain_wallet`]: Self::drain_wallet /// [`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.params.drain_to = Some(script_pubkey);
self 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 /// Returns an `Err` if `script_pubkey` can't be found among the recipients of the
/// transaction we are bumping. /// 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 match self
.params .params
.recipients .recipients
@ -790,6 +798,7 @@ mod test {
use bitcoin::consensus::deserialize; use bitcoin::consensus::deserialize;
use bitcoin::hashes::hex::FromHex; use bitcoin::hashes::hex::FromHex;
use std::str::FromStr;
use super::*; use super::*;
@ -821,8 +830,6 @@ mod test {
#[test] #[test]
fn test_output_ordering_bip69() { fn test_output_ordering_bip69() {
use std::str::FromStr;
let original_tx = ordering_test_tx!(); let original_tx = ordering_test_tx!();
let mut tx = original_tx; let mut tx = original_tx;
@ -851,8 +858,11 @@ mod test {
); );
assert_eq!(tx.output[0].value, 800); assert_eq!(tx.output[0].value, 800);
assert_eq!(tx.output[1].script_pubkey, From::from(vec![0xAA])); assert_eq!(tx.output[1].script_pubkey, ScriptBuf::from(vec![0xAA]));
assert_eq!(tx.output[2].script_pubkey, From::from(vec![0xAA, 0xEE])); assert_eq!(
tx.output[2].script_pubkey,
ScriptBuf::from(vec![0xAA, 0xEE])
);
} }
fn get_test_utxos() -> Vec<LocalUtxo> { fn get_test_utxos() -> Vec<LocalUtxo> {
@ -861,7 +871,7 @@ mod test {
vec![ vec![
LocalUtxo { LocalUtxo {
outpoint: OutPoint { outpoint: OutPoint {
txid: bitcoin::Txid::from_inner([0; 32]), txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
vout: 0, vout: 0,
}, },
txout: Default::default(), txout: Default::default(),
@ -870,7 +880,7 @@ mod test {
}, },
LocalUtxo { LocalUtxo {
outpoint: OutPoint { outpoint: OutPoint {
txid: bitcoin::Txid::from_inner([0; 32]), txid: bitcoin::Txid::from_slice(&[0; 32]).unwrap(),
vout: 1, vout: 1,
}, },
txout: Default::default(), txout: Default::default(),

View File

@ -10,7 +10,7 @@
// licenses. // licenses.
use bitcoin::secp256k1::{All, Secp256k1}; use bitcoin::secp256k1::{All, Secp256k1};
use bitcoin::{LockTime, Script, Sequence}; use bitcoin::{absolute, Script, Sequence};
use miniscript::{MiniscriptKey, Satisfier, ToPublicKey}; 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 { 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 { if let Some(current_height) = self.current_height {
current_height >= n.to_consensus_u32() current_height >= n.to_consensus_u32()
} else { } else {
@ -114,17 +114,20 @@ pub(crate) type SecpCtx = Secp256k1<All>;
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use std::str::FromStr;
// When nSequence is lower than this flag the timelock is interpreted as block-height-based, // When nSequence is lower than this flag the timelock is interpreted as block-height-based,
// otherwise it's time-based // otherwise it's time-based
pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22; pub(crate) const SEQUENCE_LOCKTIME_TYPE_FLAG: u32 = 1 << 22;
use super::{check_nsequence_rbf, IsDust}; use super::{check_nsequence_rbf, IsDust};
use crate::bitcoin::{Address, Sequence}; use crate::bitcoin::{Address, Network, Sequence};
use std::str::FromStr;
#[test] #[test]
fn test_is_dust() { fn test_is_dust() {
let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe") let script_p2pkh = Address::from_str("1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe")
.unwrap()
.require_network(Network::Bitcoin)
.unwrap() .unwrap()
.script_pubkey(); .script_pubkey();
assert!(script_p2pkh.is_p2pkh()); assert!(script_p2pkh.is_p2pkh());
@ -132,6 +135,8 @@ mod test {
assert!(!546.is_dust(&script_p2pkh)); assert!(!546.is_dust(&script_p2pkh));
let script_p2wpkh = Address::from_str("bc1qxlh2mnc0yqwas76gqq665qkggee5m98t8yskd8") let script_p2wpkh = Address::from_str("bc1qxlh2mnc0yqwas76gqq665qkggee5m98t8yskd8")
.unwrap()
.require_network(Network::Bitcoin)
.unwrap() .unwrap()
.script_pubkey(); .script_pubkey();
assert!(script_p2wpkh.is_v0_p2wpkh()); assert!(script_p2wpkh.is_v0_p2wpkh());