Compare commits

...

41 Commits

Author SHA1 Message Date
Alekos Filini
3897e29740 Fix changelog 2021-05-12 15:11:20 +02:00
Alekos Filini
8f06e45872 Bump version to 0.7.1-dev 2021-05-12 15:10:28 +02:00
Alekos Filini
766570abfd Bump version to 0.7.0 2021-05-12 14:20:58 +02:00
Alekos Filini
934ec366d9 Use the released testutils-macros 2021-05-12 14:20:23 +02:00
Alekos Filini
d0733e9496 Bump version in src/lib.rs 2021-05-12 14:19:58 +02:00
Alekos Filini
3c7a1f5918 Bump testutils-macros to v0.6.0 2021-05-12 14:19:00 +02:00
Alekos Filini
85aadaccd2 Update changelog in preparation of v0.7.0 2021-05-12 14:17:46 +02:00
Tobin Harding
fad0fe9f30 Update create transaction example code
The transaction builder changed a while ago, looks like some of the
example code did not get updated.

Update the transaction creation code to use a mutable builder.
2021-05-12 14:13:23 +02:00
Riccardo Casatta
47f26447da continue signing when finding already finalized inputs 2021-05-07 16:32:24 +02:00
Alekos Filini
3608ff9f14 Merge commit 'refs/pull/341/head' of github.com:bitcoindevkit/bdk into release/0.7.0 2021-05-07 11:00:00 +02:00
Riccardo Casatta
898dfe6cf1 get psbt inputs with bounds check 2021-05-06 16:10:11 +02:00
Riccardo Casatta
7961ae7f8e Check index out of bound also for tx inputs not only for psbt inputs 2021-05-06 15:13:25 +02:00
Alekos Filini
8bf77c8f07 Bump version to 0.7.0-rc.1 2021-05-06 13:56:38 +02:00
Alekos Filini
3c7bae9ce9 Rewrite the non_witness_utxo check 2021-05-06 11:39:01 +02:00
Alekos Filini
17bcd8ed7d [signer] Replace force_non_witness_utxo with only_witness_utxo
Instead of providing an opt-in option to force the addition of the
`non_witness_utxo`, we will now add them by default and provide the
option to disable them when they aren't considered necessary.
2021-05-06 08:58:39 +02:00
Alekos Filini
b5e9589803 [signer] Adjust signing behavior with SignOptions 2021-05-06 08:58:38 +02:00
Alekos Filini
1d628d84b5 [signer] Fix error variant 2021-05-05 16:59:59 +02:00
Alekos Filini
b84fd6ea5c Fix import for FromStr 2021-05-05 16:59:57 +02:00
Alekos Filini
8fe4222c33 Merge commit 'refs/pull/336/head' of github.com:bitcoindevkit/bdk 2021-05-05 14:51:36 +02:00
codeShark149
e626f2e255 Added grcov based code coverage reporting in github action 2021-04-30 17:20:20 +05:30
LLFourn
5a0c150ff9 Make wallet methods take &mut psbt
Rather than consuming it because that is unergonomic.
2021-04-28 15:34:25 +10:00
Alekos Filini
00f07818f9 Merge commit 'refs/pull/321/head' of github.com:bitcoindevkit/bdk 2021-04-16 14:08:26 +02:00
Riccardo Casatta
136a4bddb2 Verify PSBT input satisfaction 2021-04-16 12:22:49 +02:00
Riccardo Casatta
ff7b74ec27 destructure tuple to improve clarity 2021-04-16 12:22:47 +02:00
Steve Myers
8c00326990 [ci] Revert fixed nightly-2021-03-23, use actual nightly 2021-04-15 10:15:13 -07:00
Riccardo Casatta
afcd26032d comment out println in tests, fix doc 2021-04-15 16:48:42 +02:00
Riccardo Casatta
8f422a1bf9 Add timelocks to policy satisfaction results
Also for signature the logic has been refactored to handle appropriately nested cases.
2021-04-15 15:57:35 +02:00
Alekos Filini
45983d2166 Merge commit 'refs/pull/322/head' of github.com:bitcoindevkit/bdk 2021-04-15 11:40:34 +02:00
Steve Myers
89cb4de7f6 [ci] Update 'test-readme-examples' job to use nightly-2021-03-23 2021-04-14 20:34:32 -07:00
Steve Myers
7ca0e0e2bd [ci] Update 'Tarpaulin to codecov.io' job to use nightly-2021-03-23 2021-04-14 20:33:42 -07:00
Alekos Filini
2bddd9baed Update CHANGELOG for v0.6.0 2021-04-14 18:49:52 +02:00
Alekos Filini
0135ba29c5 Bump version to 0.6.1-dev 2021-04-14 18:47:31 +02:00
Steve Myers
3ed44ce8cf Remove unneeded script 2021-04-09 09:19:19 -07:00
Steve Myers
8e7d8312a9 [ci] Update 'build-test' job to clippy check all-targets 2021-04-08 14:44:35 -07:00
Steve Myers
4da7488dc4 Update 'cargo-check.sh' to not check +nightly 2021-04-08 14:36:07 -07:00
Steve Myers
e37680af96 Use .flatten() instead of .filter_map(|x| x), clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#filter_map_identity
2021-04-08 14:18:07 -07:00
Steve Myers
5f873ae500 Use .any() instead of .find().is_some(), clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#search_is_some
2021-04-08 14:18:07 -07:00
Steve Myers
2380634496 Use .get(0) instead of .iter().next(), clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#iter_next_slice
2021-04-08 14:18:07 -07:00
Steve Myers
af98b8da06 Compare float equality using error margin EPSILON, clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#float_cmp
2021-04-08 14:17:59 -07:00
Steve Myers
b68ec050e2 Remove redundant clone, clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone
2021-04-08 11:41:58 -07:00
Steve Myers
ac7df09200 Remove needlessly taken reference of both operands, clippy warning
https://rust-lang.github.io/rust-clippy/master/index.html#op_ref
2021-04-08 11:39:38 -07:00
19 changed files with 698 additions and 309 deletions

View File

@@ -3,25 +3,35 @@ on: [push]
name: Code Coverage name: Code Coverage
jobs: jobs:
tarpaulin-codecov:
name: Tarpaulin to codecov.io Codecov:
name: Code Coverage
runs-on: ubuntu-latest runs-on: ubuntu-latest
env:
CARGO_INCREMENTAL: '0'
RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off'
RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Install rustup
run: curl https://sh.rustup.rs -sSf | sh -s -- -y
- name: Set default toolchain - name: Set default toolchain
run: rustup default nightly run: rustup default nightly
- name: Set profile - name: Set profile
run: rustup set profile minimal run: rustup set profile minimal
- name: Update toolchain
run: rustup update
- name: Test
run: cargo test --features all-keys,compiler,esplora,compact_filters --no-default-features
- id: coverage
name: Generate coverage
uses: actions-rs/grcov@v0.1.5
- name: Install tarpaulin - name: Upload coverage to Codecov
run: cargo install cargo-tarpaulin uses: codecov/codecov-action@v1
- name: Tarpaulin
run: cargo tarpaulin --features all-keys,compiler,esplora,compact_filters --run-types Tests,Doctests --exclude-files "testutils/*" --out Xml
- name: Publish to codecov.io
uses: codecov/codecov-action@v1.0.15
with: with:
fail_ci_if_error: true file: ${{ steps.coverage.outputs.report }}
file: ./cobertura.xml directory: ./coverage/reports/

View File

@@ -46,7 +46,7 @@ jobs:
- 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
run: cargo clippy --features ${{ matrix.features }} --no-default-features -- -D warnings run: cargo clippy --all-targets --features ${{ matrix.features }} --no-default-features -- -D warnings
- name: Test - name: Test
run: cargo test --features ${{ matrix.features }} --no-default-features run: cargo test --features ${{ matrix.features }} --no-default-features
@@ -151,7 +151,7 @@ jobs:
run: rustup default 1.51.0 # STABLE run: rustup default 1.51.0 # STABLE
- name: Set profile - name: Set profile
run: rustup set profile minimal run: rustup set profile minimal
- name: Add clippy - name: Add rustfmt
run: rustup component add rustfmt run: rustup component add rustfmt
- name: Update toolchain - name: Update toolchain
run: rustup update run: rustup update

View File

@@ -18,7 +18,7 @@ jobs:
target target
key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} key: nightly-docs-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }}
- name: Set default toolchain - name: Set default toolchain
run: rustup default nightly-2021-03-23 run: rustup default nightly
- name: Set profile - name: Set profile
run: rustup set profile minimal run: rustup set profile minimal
- name: Update toolchain - name: Update toolchain

View File

@@ -6,6 +6,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [v0.7.0] - [v0.6.0]
### Policy
#### Changed
Removed `fill_satisfaction` method in favor of enum parameter in `extract_policy` method
#### Added
Timelocks are considered (optionally) in building the `satisfaction` field
### Wallet
- Changed `Wallet::{sign, finalize_psbt}` now take a `&mut psbt` rather than consuming it.
- Require and validate `non_witness_utxo` for SegWit signatures by default, can be adjusted with `SignOptions`
- Replace the opt-in builder option `force_non_witness_utxo` with the opposite `only_witness_utxo`. From now on we will provide the `non_witness_utxo`, unless explicitly asked not to.
## [v0.6.0] - [v0.5.1]
### Misc ### Misc
#### Changed #### Changed
- New minimum supported rust version is 1.46.0 - New minimum supported rust version is 1.46.0
@@ -322,3 +339,5 @@ final transaction is created by calling `finish` on the builder.
[v0.4.0]: https://github.com/bitcoindevkit/bdk/compare/v0.3.0...v0.4.0 [v0.4.0]: https://github.com/bitcoindevkit/bdk/compare/v0.3.0...v0.4.0
[v0.5.0]: https://github.com/bitcoindevkit/bdk/compare/v0.4.0...v0.5.0 [v0.5.0]: https://github.com/bitcoindevkit/bdk/compare/v0.4.0...v0.5.0
[v0.5.1]: https://github.com/bitcoindevkit/bdk/compare/v0.5.0...v0.5.1 [v0.5.1]: https://github.com/bitcoindevkit/bdk/compare/v0.5.0...v0.5.1
[v0.6.0]: https://github.com/bitcoindevkit/bdk/compare/v0.5.1...v0.6.0
[v0.7.0]: https://github.com/bitcoindevkit/bdk/compare/v0.6.0...v0.7.0

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk" name = "bdk"
version = "0.6.0" version = "0.7.1-dev"
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"
@@ -60,7 +60,7 @@ test-md-docs = ["electrum"]
[dev-dependencies] [dev-dependencies]
bdk-testutils = "0.4" bdk-testutils = "0.4"
bdk-testutils-macros = "0.5" bdk-testutils-macros = "0.6"
serial_test = "0.4" serial_test = "0.4"
lazy_static = "1.4" lazy_static = "1.4"
env_logger = "0.7" env_logger = "0.7"

View File

@@ -130,7 +130,7 @@ fn main() -> Result<(), bdk::Error> {
### Sign a transaction ### Sign a transaction
```rust,no_run ```rust,no_run
use bdk::{Wallet, database::MemoryDatabase}; use bdk::{Wallet, SignOptions, database::MemoryDatabase};
use bitcoin::consensus::deserialize; use bitcoin::consensus::deserialize;
@@ -143,9 +143,9 @@ fn main() -> Result<(), bdk::Error> {
)?; )?;
let psbt = "..."; let psbt = "...";
let psbt = deserialize(&base64::decode(psbt).unwrap())?; let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
let (signed_psbt, finalized) = wallet.sign(psbt, None)?; let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
Ok(()) Ok(())
} }
@@ -166,4 +166,4 @@ 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.

13
codecov.yaml Normal file
View File

@@ -0,0 +1,13 @@
coverage:
status:
project:
default:
target: auto
threshold: 1%
base: auto
informational: false
patch:
default:
target: auto
threshold: 100%
base: auto

View File

@@ -1,31 +0,0 @@
#!/bin/bash
#
# Run various invocations of cargo check
features=( "default" "compiler" "electrum" "esplora" "compact_filters" "key-value-db" "async-interface" "all-keys" "keys-bip39" )
toolchains=( "+stable" "+1.46" "+nightly" )
main() {
check_src
check_all_targets
}
# Check with all features, with various toolchains.
check_src() {
for toolchain in "${toolchains[@]}"; do
cmd="cargo $toolchain clippy --all-targets --no-default-features"
for feature in "${features[@]}"; do
touch_files
$cmd --features "$feature"
done
done
}
# Touch files to prevent cached warnings from not showing up.
touch_files() {
touch $(find . -name *.rs)
}
main
exit 0

View File

@@ -27,6 +27,8 @@ use miniscript::descriptor::{DescriptorPublicKey, DescriptorType, DescriptorXKey
pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0}; pub use miniscript::{descriptor::KeyMap, Descriptor, Legacy, Miniscript, ScriptContext, Segwitv0};
use miniscript::{DescriptorTrait, ForEachKey, TranslatePk}; use miniscript::{DescriptorTrait, ForEachKey, TranslatePk};
use crate::descriptor::policy::BuildSatisfaction;
pub mod checksum; pub mod checksum;
pub(crate) mod derived; pub(crate) mod derived;
#[doc(hidden)] #[doc(hidden)]
@@ -255,6 +257,7 @@ pub trait ExtractPolicy {
fn extract_policy( fn extract_policy(
&self, &self,
signers: &SignersContainer, signers: &SignersContainer,
psbt: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, DescriptorError>; ) -> Result<Option<Policy>, DescriptorError>;
} }

View File

@@ -22,6 +22,7 @@
//! # use std::sync::Arc; //! # use std::sync::Arc;
//! # use bdk::descriptor::*; //! # use bdk::descriptor::*;
//! # use bdk::bitcoin::secp256k1::Secp256k1; //! # use bdk::bitcoin::secp256k1::Secp256k1;
//! use bdk::descriptor::policy::BuildSatisfaction;
//! let secp = Secp256k1::new(); //! let secp = Secp256k1::new();
//! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))"; //! let desc = "wsh(and_v(v:pk(cV3oCth6zxZ1UVsHLnGothsWNsaoxRhC6aeNi5VbSdFpwUkgkEci),or_d(pk(cVMTy7uebJgvFaSBwcgvwk8qn8xSLc97dKow4MBetjrrahZoimm2),older(12960))))";
//! //!
@@ -29,7 +30,7 @@
//! println!("{:?}", extended_desc); //! println!("{:?}", extended_desc);
//! //!
//! let signers = Arc::new(key_map.into()); //! let signers = Arc::new(key_map.into());
//! let policy = extended_desc.extract_policy(&signers, &secp)?; //! let policy = extended_desc.extract_policy(&signers, BuildSatisfaction::None, &secp)?;
//! println!("policy: {}", serde_json::to_string(&policy)?); //! println!("policy: {}", serde_json::to_string(&policy)?);
//! # Ok::<(), bdk::Error>(()) //! # Ok::<(), bdk::Error>(())
//! ``` //! ```
@@ -47,19 +48,15 @@ use bitcoin::PublicKey;
use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner}; use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
use miniscript::{ use miniscript::{
Descriptor, ForEachKey, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal, Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal, ToPublicKey,
ToPublicKey,
}; };
#[allow(unused_imports)] #[allow(unused_imports)]
use log::{debug, error, info, trace}; use log::{debug, error, info, trace};
use crate::descriptor::{ use crate::descriptor::{DerivedDescriptorKey, ExtractPolicy};
DerivedDescriptor, DerivedDescriptorKey, DescriptorMeta, ExtendedDescriptor, ExtractPolicy,
};
use crate::psbt::PsbtUtils;
use crate::wallet::signer::{SignerId, SignersContainer}; use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{self, SecpCtx}; use crate::wallet::utils::{self, After, Older, SecpCtx};
use super::checksum::get_checksum; use super::checksum::get_checksum;
use super::error::Error; use super::error::Error;
@@ -563,13 +560,18 @@ impl Policy {
conditions: Default::default(), conditions: Default::default(),
sorted: None, sorted: None,
}; };
let mut satisfaction = contribution.clone();
for (index, item) in items.iter().enumerate() { for (index, item) in items.iter().enumerate() {
contribution.add(&item.contribution, index)?; contribution.add(&item.contribution, index)?;
satisfaction.add(&item.satisfaction, index)?;
} }
contribution.finalize(); contribution.finalize();
satisfaction.finalize();
let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into(); let mut policy: Policy = SatisfiableItem::Thresh { items, threshold }.into();
policy.contribution = contribution; policy.contribution = contribution;
policy.satisfaction = satisfaction;
Ok(Some(policy)) Ok(Some(policy))
} }
@@ -577,6 +579,7 @@ impl Policy {
fn make_multisig( fn make_multisig(
keys: &[DescriptorPublicKey], keys: &[DescriptorPublicKey],
signers: &SignersContainer, signers: &SignersContainer,
build_sat: BuildSatisfaction,
threshold: usize, threshold: usize,
sorted: bool, sorted: bool,
secp: &SecpCtx, secp: &SecpCtx,
@@ -594,6 +597,8 @@ impl Policy {
conditions: Default::default(), conditions: Default::default(),
sorted: Some(sorted), sorted: Some(sorted),
}; };
let mut satisfaction = contribution.clone();
for (index, key) in keys.iter().enumerate() { for (index, key) in keys.iter().enumerate() {
if signers.find(signer_id(key, secp)).is_some() { if signers.find(signer_id(key, secp)).is_some() {
contribution.add( contribution.add(
@@ -603,7 +608,19 @@ impl Policy {
index, index,
)?; )?;
} }
if let Some(psbt) = build_sat.psbt() {
if signature_in_psbt(psbt, key, secp) {
satisfaction.add(
&Satisfaction::Complete {
condition: Default::default(),
},
index,
)?;
}
}
} }
satisfaction.finalize();
contribution.finalize(); contribution.finalize();
let mut policy: Policy = SatisfiableItem::Multisig { let mut policy: Policy = SatisfiableItem::Multisig {
@@ -612,6 +629,7 @@ impl Policy {
} }
.into(); .into();
policy.contribution = contribution; policy.contribution = contribution;
policy.satisfaction = satisfaction;
Ok(Some(policy)) Ok(Some(policy))
} }
@@ -698,52 +716,6 @@ impl Policy {
_ => Ok(Condition::default()), _ => Ok(Condition::default()),
} }
} }
/// fill `self.satisfaction` with the signatures we already have in the PSBT
pub fn fill_satisfactions(
&mut self,
psbt: &PSBT,
desc: &ExtendedDescriptor, // can't put in self because non Serialize
secp: &SecpCtx,
) -> Result<(), Error> {
// Start from an empty Satisfaction (like a contribution without signers)
let policy = desc.extract_policy(&SignersContainer::default(), &secp)?;
if let Some(policy) = policy {
self.satisfaction = policy.contribution;
for (i, input) in psbt.inputs.iter().enumerate() {
let s = PsbtInputSatisfier::new(psbt, i);
let derived_desc = desc.derive_from_psbt_input(input, psbt.get_utxo_for(i), secp);
self.add_satisfaction(s, derived_desc);
}
self.satisfaction.finalize();
}
Ok(())
}
fn add_satisfaction<S: Satisfier<bitcoin::PublicKey>>(
&mut self,
satisfier: S,
derived_desc: Option<DerivedDescriptor>,
) {
if let Some(derived_desc) = derived_desc {
let mut index = 0;
derived_desc.for_each_key(|k| {
if satisfier.lookup_sig(&k.as_key().to_public_key()).is_some() {
//TODO check signature verifies
let _ = self.satisfaction.add(
&Satisfaction::Complete {
condition: Default::default(),
},
index,
);
}
index += 1;
true
});
}
}
} }
impl From<SatisfiableItem> for Policy { impl From<SatisfiableItem> for Policy {
@@ -759,7 +731,12 @@ fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
} }
} }
fn signature(key: &DescriptorPublicKey, signers: &SignersContainer, secp: &SecpCtx) -> Policy { fn signature(
key: &DescriptorPublicKey,
signers: &SignersContainer,
build_sat: BuildSatisfaction,
secp: &SecpCtx,
) -> Policy {
let mut policy: Policy = SatisfiableItem::Signature(PkOrF::from_key(key, secp)).into(); let mut policy: Policy = SatisfiableItem::Signature(PkOrF::from_key(key, secp)).into();
policy.contribution = if signers.find(signer_id(key, secp)).is_some() { policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
@@ -770,9 +747,38 @@ fn signature(key: &DescriptorPublicKey, signers: &SignersContainer, secp: &SecpC
Satisfaction::None Satisfaction::None
}; };
if let Some(psbt) = build_sat.psbt() {
policy.satisfaction = if signature_in_psbt(psbt, key, secp) {
Satisfaction::Complete {
condition: Default::default(),
}
} else {
Satisfaction::None
};
}
policy policy
} }
fn signature_in_psbt(psbt: &PSBT, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
//TODO check signature validity
psbt.inputs.iter().all(|input| match key {
DescriptorPublicKey::SinglePub(key) => input.partial_sigs.contains_key(&key.key),
DescriptorPublicKey::XPub(xpub) => {
let pubkey = input
.bip32_derivation
.iter()
.find(|(_, (f, _))| *f == xpub.root_fingerprint(secp))
.map(|(p, _)| p);
//TODO check actual derivation matches
match pubkey {
Some(pubkey) => input.partial_sigs.contains_key(pubkey),
None => false,
}
}
})
}
fn signature_key( fn signature_key(
key: &<DescriptorPublicKey as MiniscriptKey>::Hash, key: &<DescriptorPublicKey as MiniscriptKey>::Hash,
signers: &SignersContainer, signers: &SignersContainer,
@@ -796,12 +802,13 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
fn extract_policy( fn extract_policy(
&self, &self,
signers: &SignersContainer, signers: &SignersContainer,
build_sat: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, Error> { ) -> Result<Option<Policy>, Error> {
Ok(match &self.node { Ok(match &self.node {
// Leaves // Leaves
Terminal::True | Terminal::False => None, Terminal::True | Terminal::False => None,
Terminal::PkK(pubkey) => Some(signature(pubkey, signers, secp)), Terminal::PkK(pubkey) => Some(signature(pubkey, signers, build_sat, secp)),
Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, signers, secp)), Terminal::PkH(pubkey_hash) => Some(signature_key(pubkey_hash, signers, secp)),
Terminal::After(value) => { Terminal::After(value) => {
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into(); let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
@@ -811,6 +818,20 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
csv: None, csv: None,
}, },
}; };
if let BuildSatisfaction::PsbtTimelocks {
current_height,
psbt,
..
} = build_sat
{
let after = After::new(Some(current_height), false);
let after_sat = Satisfier::<bitcoin::PublicKey>::check_after(&after, *value);
let inputs_sat = psbt_inputs_sat(psbt)
.all(|sat| Satisfier::<bitcoin::PublicKey>::check_after(&sat, *value));
if after_sat && inputs_sat {
policy.satisfaction = policy.contribution.clone();
}
}
Some(policy) Some(policy)
} }
@@ -822,6 +843,20 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
csv: Some(*value), csv: Some(*value),
}, },
}; };
if let BuildSatisfaction::PsbtTimelocks {
current_height,
input_max_height,
psbt,
} = build_sat
{
let older = Older::new(Some(current_height), Some(input_max_height), false);
let older_sat = Satisfier::<bitcoin::PublicKey>::check_older(&older, *value);
let inputs_sat = psbt_inputs_sat(psbt)
.all(|sat| Satisfier::<bitcoin::PublicKey>::check_older(&sat, *value));
if older_sat && inputs_sat {
policy.satisfaction = policy.contribution.clone();
}
}
Some(policy) Some(policy)
} }
@@ -835,7 +870,9 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
Terminal::Hash160(hash) => { Terminal::Hash160(hash) => {
Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into()) Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
} }
Terminal::Multi(k, pks) => Policy::make_multisig(pks, signers, *k, false, secp)?, Terminal::Multi(k, pks) => {
Policy::make_multisig(pks, signers, build_sat, *k, false, secp)?
}
// Identities // Identities
Terminal::Alt(inner) Terminal::Alt(inner)
| Terminal::Swap(inner) | Terminal::Swap(inner)
@@ -843,34 +880,34 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
| Terminal::DupIf(inner) | Terminal::DupIf(inner)
| Terminal::Verify(inner) | Terminal::Verify(inner)
| Terminal::NonZero(inner) | Terminal::NonZero(inner)
| Terminal::ZeroNotEqual(inner) => inner.extract_policy(signers, secp)?, | Terminal::ZeroNotEqual(inner) => inner.extract_policy(signers, build_sat, secp)?,
// Complex policies // Complex policies
Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and( Terminal::AndV(a, b) | Terminal::AndB(a, b) => Policy::make_and(
a.extract_policy(signers, secp)?, a.extract_policy(signers, build_sat, secp)?,
b.extract_policy(signers, secp)?, b.extract_policy(signers, build_sat, secp)?,
)?, )?,
Terminal::AndOr(x, y, z) => Policy::make_or( Terminal::AndOr(x, y, z) => Policy::make_or(
Policy::make_and( Policy::make_and(
x.extract_policy(signers, secp)?, x.extract_policy(signers, build_sat, secp)?,
y.extract_policy(signers, secp)?, y.extract_policy(signers, build_sat, secp)?,
)?, )?,
z.extract_policy(signers, secp)?, z.extract_policy(signers, build_sat, secp)?,
)?, )?,
Terminal::OrB(a, b) Terminal::OrB(a, b)
| Terminal::OrD(a, b) | Terminal::OrD(a, b)
| Terminal::OrC(a, b) | Terminal::OrC(a, b)
| Terminal::OrI(a, b) => Policy::make_or( | Terminal::OrI(a, b) => Policy::make_or(
a.extract_policy(signers, secp)?, a.extract_policy(signers, build_sat, secp)?,
b.extract_policy(signers, secp)?, b.extract_policy(signers, build_sat, secp)?,
)?, )?,
Terminal::Thresh(k, nodes) => { Terminal::Thresh(k, nodes) => {
let mut threshold = *k; let mut threshold = *k;
let mapped: Vec<_> = nodes let mapped: Vec<_> = nodes
.iter() .iter()
.map(|n| n.extract_policy(signers, secp)) .map(|n| n.extract_policy(signers, build_sat, secp))
.collect::<Result<Vec<_>, _>>()? .collect::<Result<Vec<_>, _>>()?
.into_iter() .into_iter()
.filter_map(|x| x) .flatten()
.collect(); .collect();
if mapped.len() < nodes.len() { if mapped.len() < nodes.len() {
@@ -886,20 +923,55 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
} }
} }
fn psbt_inputs_sat(psbt: &PSBT) -> impl Iterator<Item = PsbtInputSatisfier> {
(0..psbt.inputs.len()).map(move |i| PsbtInputSatisfier::new(psbt, i))
}
/// Options to build the satisfaction field in the policy
#[derive(Debug, Clone, Copy)]
pub enum BuildSatisfaction<'a> {
/// Don't generate `satisfaction` field
None,
/// Analyze the given PSBT to check for existing signatures
Psbt(&'a PSBT),
/// Like `Psbt` variant and also check for expired timelocks
PsbtTimelocks {
/// Given PSBT
psbt: &'a PSBT,
/// Current blockchain height
current_height: u32,
/// The highest confirmation height between the inputs
/// CSV should consider different inputs, but we consider the worst condition for the tx as whole
input_max_height: u32,
},
}
impl<'a> BuildSatisfaction<'a> {
fn psbt(&self) -> Option<&'a PSBT> {
match self {
BuildSatisfaction::None => None,
BuildSatisfaction::Psbt(psbt) => Some(psbt),
BuildSatisfaction::PsbtTimelocks { psbt, .. } => Some(psbt),
}
}
}
impl ExtractPolicy for Descriptor<DescriptorPublicKey> { impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
fn extract_policy( fn extract_policy(
&self, &self,
signers: &SignersContainer, signers: &SignersContainer,
build_sat: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, Error> { ) -> Result<Option<Policy>, Error> {
fn make_sortedmulti<Ctx: ScriptContext>( fn make_sortedmulti<Ctx: ScriptContext>(
keys: &SortedMultiVec<DescriptorPublicKey, Ctx>, keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
signers: &SignersContainer, signers: &SignersContainer,
build_sat: BuildSatisfaction,
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<Option<Policy>, Error> { ) -> Result<Option<Policy>, Error> {
Ok(Policy::make_multisig( Ok(Policy::make_multisig(
keys.pks.as_ref(), keys.pks.as_ref(),
signers, signers,
build_sat,
keys.k, keys.k,
true, true,
secp, secp,
@@ -907,22 +979,24 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
} }
match self { match self {
Descriptor::Pkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))), Descriptor::Pkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))),
Descriptor::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))), Descriptor::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))),
Descriptor::Sh(sh) => match sh.as_inner() { Descriptor::Sh(sh) => match sh.as_inner() {
ShInner::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, secp))), ShInner::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))),
ShInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?), ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp), ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
ShInner::Wsh(wsh) => match wsh.as_inner() { ShInner::Wsh(wsh) => match wsh.as_inner() {
WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?), WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp), WshInner::SortedMulti(ref keys) => {
make_sortedmulti(keys, signers, build_sat, secp)
}
}, },
}, },
Descriptor::Wsh(wsh) => match wsh.as_inner() { Descriptor::Wsh(wsh) => match wsh.as_inner() {
WshInner::Ms(ms) => Ok(ms.extract_policy(signers, secp)?), WshInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, secp), WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
}, },
Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, secp)?), Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
} }
} }
} }
@@ -978,7 +1052,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -993,7 +1067,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1017,14 +1091,14 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert!( assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& &keys[0].fingerprint.unwrap() == &fingerprint0 && keys[0].fingerprint.unwrap() == fingerprint0
&& &keys[1].fingerprint.unwrap() == &fingerprint1) && keys[1].fingerprint.unwrap() == fingerprint1)
); );
// TODO should this be "Satisfaction::None" since we have no prv keys? // TODO should this be "Satisfaction::None" since we have no prv keys?
// TODO should items and conditions not be empty? // TODO should items and conditions not be empty?
@@ -1049,13 +1123,13 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
assert!( assert!(
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
&& &keys[0].fingerprint.unwrap() == &fingerprint0 && keys[0].fingerprint.unwrap() == fingerprint0
&& &keys[1].fingerprint.unwrap() == &fingerprint1) && keys[1].fingerprint.unwrap() == fingerprint1)
); );
assert!( assert!(
@@ -1081,7 +1155,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1113,7 +1187,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1146,7 +1220,7 @@ mod test {
let single_key = wallet_desc.derive(0); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1162,7 +1236,7 @@ mod test {
let single_key = wallet_desc.derive(0); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1189,7 +1263,7 @@ mod test {
let single_key = wallet_desc.derive(0); let single_key = wallet_desc.derive(0);
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = single_key let policy = single_key
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1232,7 +1306,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1271,7 +1345,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
println!("desc policy = {:?}", policy); // TODO remove println!("desc policy = {:?}", policy); // TODO remove
@@ -1296,7 +1370,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
println!("desc policy = {:?}", policy); // TODO remove println!("desc policy = {:?}", policy); // TODO remove
@@ -1314,7 +1388,7 @@ mod test {
.unwrap(); .unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers_container, &secp) .extract_policy(&signers_container, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1337,7 +1411,7 @@ mod test {
let signers = keymap.into(); let signers = keymap.into();
let policy = wallet_desc let policy = wallet_desc
.extract_policy(&signers, &secp) .extract_policy(&signers, BuildSatisfaction::None, &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
@@ -1369,11 +1443,12 @@ mod test {
assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5))); assert_eq!(out_of_range, Err(PolicyError::IndexOutOfRange(5)));
} }
const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
const ALICE_BOB_PATH: &str = "m/0'";
#[test] #[test]
fn test_extract_satisfaction() { fn test_extract_satisfaction() {
const ALICE_TPRV_STR:&str = "tprv8ZgxMBicQKsPf6T5X327efHnvJDr45Xnb8W4JifNWtEoqXu9MRYS4v1oYe6DFcMVETxy5w3bqpubYRqvcVTqovG1LifFcVUuJcbwJwrhYzP";
const BOB_TPRV_STR:&str = "tprv8ZgxMBicQKsPeinZ155cJAn117KYhbaN6MV3WeG6sWhxWzcvX1eg1awd4C9GpUN1ncLEM2rzEvunAg3GizdZD4QPPCkisTz99tXXB4wZArp";
const ALICE_BOB_PATH: &str = "m/0'";
const ALICE_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA"; const ALICE_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
const BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBAQVHUiEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZsshAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIUq4iBgL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiAwcLu4+AAAAgAAAAAAiBgN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmywzJEXwuAAAAgAAAAAAAAA=="; const BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBAQVHUiEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZsshAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIUq4iBgL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiAwcLu4+AAAAgAAAAAAiBgN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmywzJEXwuAAAAgAAAAAAAAA==";
const ALICE_BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEI2wQARzBEAiAY9Iy41HlWFzUOnKgfoG7b7ijI1eeMEoFpZtXH3IKR1QIgWtw7QvZf9TLeCAwr0e5psEHd3gD/5ufvvNXroSTUq4EBSDBFAiEA+cw7TOTMJJbq8CeWlu+kbDt+iKsrvurjHVZYS+sLNhkCIHrAIs+HWyku1JoQ7Av3NXs7tKOoadNFFLbAjH1GeGp2AUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSrgAA"; const ALICE_BOB_SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAZb0njwT2wRS3AumaaP3yb7T4MxOePpSWih4Nq+jWChMAQAAAAD/////Af4lAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASuJJgAAAAAAACIAIERw5kTLo9DUH9QDJSClHQwPpC7VGJ+ZMDpa8U+2fzcYIgIC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhIMEUCIQD5zDtM5MwklurwJ5aW76RsO36Iqyu+6uMdVlhL6ws2GQIgesAiz4dbKS7UmhDsC/c1ezu0o6hp00UUtsCMfUZ4anYBIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstHMEQCIBj0jLjUeVYXNQ6cqB+gbtvuKMjV54wSgWlm1cfcgpHVAiBa3DtC9l/1Mt4IDCvR7mmwQd3eAP/m5++81euhJNSrgQEBBUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSriIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEI2wQARzBEAiAY9Iy41HlWFzUOnKgfoG7b7ijI1eeMEoFpZtXH3IKR1QIgWtw7QvZf9TLeCAwr0e5psEHd3gD/5ufvvNXroSTUq4EBSDBFAiEA+cw7TOTMJJbq8CeWlu+kbDt+iKsrvurjHVZYS+sLNhkCIHrAIs+HWyku1JoQ7Av3NXs7tKOoadNFFLbAjH1GeGp2AUdSIQN4C2NhCT9V+7h1vb7ryHIwqNzfz6RaXmw/lAfwvjZmyyEC+GE/y+LptI8xmiR6sOe998IGzybox0Qfz4+BQl1nmYhSrgAA";
@@ -1400,45 +1475,135 @@ mod test {
let signers_container = Arc::new(SignersContainer::from(keymap)); let signers_container = Arc::new(SignersContainer::from(keymap));
let original_policy = wallet_desc let psbt: PSBT = deserialize(&base64::decode(ALICE_SIGNED_PSBT).unwrap()).unwrap();
.extract_policy(&signers_container, &secp)
let policy_alice_psbt = wallet_desc
.extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
.unwrap() .unwrap()
.unwrap(); .unwrap();
//println!("{}", serde_json::to_string(&policy_alice_psbt).unwrap());
let psbt: PSBT = deserialize(&base64::decode(ALICE_SIGNED_PSBT).unwrap()).unwrap();
let mut policy_clone = original_policy.clone();
policy_clone
.fill_satisfactions(&psbt, &wallet_desc, &secp)
.unwrap();
assert!( assert!(
matches!(&policy_clone.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2 matches!(&policy_alice_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2 && m == &2
&& items == &vec![0] && items == &vec![0]
) )
); );
let mut policy_clone = original_policy.clone();
let psbt: PSBT = deserialize(&base64::decode(BOB_SIGNED_PSBT).unwrap()).unwrap(); let psbt: PSBT = deserialize(&base64::decode(BOB_SIGNED_PSBT).unwrap()).unwrap();
policy_clone let policy_bob_psbt = wallet_desc
.fill_satisfactions(&psbt, &wallet_desc, &secp) .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
.unwrap()
.unwrap(); .unwrap();
//println!("{}", serde_json::to_string(&policy_bob_psbt).unwrap());
assert!( assert!(
matches!(&policy_clone.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2 matches!(&policy_bob_psbt.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &2
&& m == &2 && m == &2
&& items == &vec![1] && items == &vec![1]
) )
); );
let mut policy_clone = original_policy.clone();
let psbt: PSBT = deserialize(&base64::decode(ALICE_BOB_SIGNED_PSBT).unwrap()).unwrap(); let psbt: PSBT = deserialize(&base64::decode(ALICE_BOB_SIGNED_PSBT).unwrap()).unwrap();
policy_clone let policy_alice_bob_psbt = wallet_desc
.fill_satisfactions(&psbt, &wallet_desc, &secp) .extract_policy(&signers_container, BuildSatisfaction::Psbt(&psbt), &secp)
.unwrap()
.unwrap(); .unwrap();
assert!( assert!(
matches!(&policy_clone.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2 matches!(&policy_alice_bob_psbt.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &2
&& m == &2 && m == &2
&& items == &vec![0, 1] && items == &vec![0, 1]
) )
); );
} }
#[test]
fn test_extract_satisfaction_timelock() {
//const PSBT_POLICY_CONSIDER_TIMELOCK_NOT_EXPIRED: &str = "cHNidP8BAFMBAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAD/////ATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED: &str = "cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAAA";
const PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED: &str ="cHNidP8BAFMCAAAAAdld52uJFGT7Yde0YZdSVh2vL020Zm2exadH5R4GSNScAAAAAAACAAAAATrcAAAAAAAAF6kUXv2Fn+YemPP4PUpNR1ZbU16/eRCHAAAAAAABASvI3AAAAAAAACIAILhzvvcBzw/Zfnc9ispRK0PCahxn1F6RHXTZAmw5tqNPIgIDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42ZstIMEUCIQCtZxNm6H3Ux3pnc64DSpgohMdBj+57xhFHcURYt2BpPAIgG3OnI7bcj/3GtWX1HHyYGSI7QGa/zq5YnsmK1Cw29NABAQVSdmNSsmlofCEDeAtjYQk/Vfu4db2+68hyMKjc38+kWl5sP5QH8L42Zsusk3whAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIrJNShyIGAvhhP8vi6bSPMZokerDnvffCBs8m6MdEH8+PgUJdZ5mIDBwu7j4AAACAAAAAACIGA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLDMkRfC4AAACAAAAAAAEHAAEIoAQASDBFAiEArWcTZuh91Md6Z3OuA0qYKITHQY/ue8YRR3FEWLdgaTwCIBtzpyO23I/9xrVl9Rx8mBkiO0Bmv86uWJ7JitQsNvTQAQEBUnZjUrJpaHwhA3gLY2EJP1X7uHW9vuvIcjCo3N/PpFpebD+UB/C+NmbLrJN8IQL4YT/L4um0jzGaJHqw5733wgbPJujHRB/Pj4FCXWeZiKyTUocAAA==";
let secp = Secp256k1::new();
let (prvkey_alice, _, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
let (prvkey_bob, _, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
let desc =
descriptor!(wsh(thresh(2,d:v:older(2),s:pk(prvkey_alice),s:pk(prvkey_bob)))).unwrap();
let (wallet_desc, keymap) = desc
.into_wallet_descriptor(&secp, Network::Testnet)
.unwrap();
let signers_container = Arc::new(SignersContainer::from(keymap));
let addr = wallet_desc
.as_derived(0, &secp)
.address(Network::Testnet)
.unwrap();
assert_eq!(
"tb1qhpemaacpeu8ajlnh8k9v55ftg0px58r8630fz8t5mypxcwdk5d8sum522g",
addr.to_string()
);
let psbt: PSBT =
deserialize(&base64::decode(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED).unwrap()).unwrap();
let build_sat = BuildSatisfaction::PsbtTimelocks {
psbt: &psbt,
current_height: 10,
input_max_height: 9,
};
let policy = wallet_desc
.extract_policy(&signers_container, build_sat, &secp)
.unwrap()
.unwrap();
assert!(
matches!(&policy.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
&& m == &2
&& items.is_empty()
)
);
//println!("{}", serde_json::to_string(&policy).unwrap());
let build_sat_expired = BuildSatisfaction::PsbtTimelocks {
psbt: &psbt,
current_height: 12,
input_max_height: 9,
};
let policy_expired = wallet_desc
.extract_policy(&signers_container, build_sat_expired, &secp)
.unwrap()
.unwrap();
assert!(
matches!(&policy_expired.satisfaction, Satisfaction::Partial { n, m, items, .. } if n == &3
&& m == &2
&& items == &vec![0]
)
);
//println!("{}", serde_json::to_string(&policy_expired).unwrap());
let psbt_signed: PSBT =
deserialize(&base64::decode(PSBT_POLICY_CONSIDER_TIMELOCK_EXPIRED_SIGNED).unwrap())
.unwrap();
let build_sat_expired_signed = BuildSatisfaction::PsbtTimelocks {
psbt: &psbt_signed,
current_height: 12,
input_max_height: 9,
};
let policy_expired_signed = wallet_desc
.extract_policy(&signers_container, build_sat_expired_signed, &secp)
.unwrap()
.unwrap();
assert!(
matches!(&policy_expired_signed.satisfaction, Satisfaction::PartialComplete { n, m, items, .. } if n == &3
&& m == &2
&& items == &vec![0, 1]
)
);
//println!("{}", serde_json::to_string(&policy_expired_signed).unwrap());
}
} }

View File

@@ -455,11 +455,12 @@ expand_make_bipxx!(segwit_v0, Segwitv0);
mod test { mod test {
// test existing descriptor templates, make sure they are expanded to the right descriptors // test existing descriptor templates, make sure they are expanded to the right descriptors
use std::str::FromStr;
use super::*; use super::*;
use crate::descriptor::derived::AsDerived; use crate::descriptor::derived::AsDerived;
use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::descriptor::{DescriptorError, DescriptorMeta};
use crate::keys::ValidNetworks; use crate::keys::ValidNetworks;
use bitcoin::hashes::core::str::FromStr;
use bitcoin::network::constants::Network::Regtest; use bitcoin::network::constants::Network::Regtest;
use bitcoin::secp256k1::Secp256k1; use bitcoin::secp256k1::Secp256k1;
use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap}; use miniscript::descriptor::{DescriptorPublicKey, DescriptorTrait, KeyMap};

View File

@@ -43,7 +43,7 @@
//! interact with the bitcoin P2P network. //! interact with the bitcoin P2P network.
//! //!
//! ```toml //! ```toml
//! bdk = "0.6.0" //! bdk = "0.7.0"
//! ``` //! ```
//! //!
//! ## Sync the balance of a descriptor //! ## Sync the balance of a descriptor
@@ -125,12 +125,15 @@
//! wallet.sync(noop_progress(), None)?; //! wallet.sync(noop_progress(), None)?;
//! //!
//! let send_to = wallet.get_address(New)?; //! let send_to = wallet.get_address(New)?;
//! let (psbt, details) = wallet.build_tx() //! let (psbt, details) = {
//! .add_recipient(send_to.script_pubkey(), 50_000) //! let mut builder = wallet.build_tx();
//! .enable_rbf() //! builder
//! .do_not_spend_change() //! .add_recipient(send_to.script_pubkey(), 50_000)
//! .fee_rate(FeeRate::from_sat_per_vb(5.0)) //! .enable_rbf()
//! .finish()?; //! .do_not_spend_change()
//! .fee_rate(FeeRate::from_sat_per_vb(5.0))
//! builder.finish()?
//! };
//! //!
//! println!("Transaction details: {:#?}", details); //! println!("Transaction details: {:#?}", details);
//! println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt))); //! println!("Unsigned PSBT: {}", base64::encode(&serialize(&psbt)));
@@ -158,9 +161,9 @@
//! )?; //! )?;
//! //!
//! let psbt = "..."; //! let psbt = "...";
//! let psbt = deserialize(&base64::decode(psbt).unwrap())?; //! let mut psbt = deserialize(&base64::decode(psbt).unwrap())?;
//! //!
//! let (signed_psbt, finalized) = wallet.sign(psbt, None)?; //! let finalized = wallet.sign(&mut psbt, None)?;
//! //!
//! Ok(()) //! Ok(())
//! } //! }
@@ -256,6 +259,7 @@ pub use error::Error;
pub use types::*; pub use types::*;
pub use wallet::address_validator; pub use wallet::address_validator;
pub use wallet::signer; pub use wallet::signer;
pub use wallet::signer::SignOptions;
pub use wallet::tx_builder::TxBuilder; pub use wallet::tx_builder::TxBuilder;
pub use wallet::Wallet; pub use wallet::Wallet;

View File

@@ -37,3 +37,85 @@ impl PsbtUtils for PSBT {
} }
} }
} }
#[cfg(test)]
mod test {
use crate::bitcoin::consensus::deserialize;
use crate::bitcoin::TxIn;
use crate::psbt::PSBT;
use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
use crate::wallet::AddressIndex;
use crate::SignOptions;
// from bip 174
const PSBT_STR: &str = "cHNidP8BAKACAAAAAqsJSaCMWvfEm4IS9Bfi8Vqz9cM9zxU4IagTn4d6W3vkAAAAAAD+////qwlJoIxa98SbghL0F+LxWrP1wz3PFTghqBOfh3pbe+QBAAAAAP7///8CYDvqCwAAAAAZdqkUdopAu9dAy+gdmI5x3ipNXHE5ax2IrI4kAAAAAAAAGXapFG9GILVT+glechue4O/p+gOcykWXiKwAAAAAAAEHakcwRAIgR1lmF5fAGwNrJZKJSGhiGDR9iYZLcZ4ff89X0eURZYcCIFMJ6r9Wqk2Ikf/REf3xM286KdqGbX+EhtdVRs7tr5MZASEDXNxh/HupccC1AaZGoqg7ECy0OIEhfKaC3Ibi1z+ogpIAAQEgAOH1BQAAAAAXqRQ1RebjO4MsRwUPJNPuuTycA5SLx4cBBBYAFIXRNTfy4mVAWjTbr6nj3aAfuCMIAAAA";
#[test]
#[should_panic(expected = "InputIndexOutOfRange")]
fn test_psbt_malformed_psbt_input_legacy() {
let psbt_bip: PSBT = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
psbt.inputs.push(psbt_bip.inputs[0].clone());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
#[test]
#[should_panic(expected = "InputIndexOutOfRange")]
fn test_psbt_malformed_psbt_input_segwit() {
let psbt_bip: PSBT = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
psbt.inputs.push(psbt_bip.inputs[1].clone());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
#[test]
#[should_panic(expected = "InputIndexOutOfRange")]
fn test_psbt_malformed_tx_input() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
psbt.global.unsigned_tx.input.push(TxIn::default());
let options = SignOptions {
trust_witness_utxo: true,
assume_height: None,
};
let _ = wallet.sign(&mut psbt, options).unwrap();
}
#[test]
fn test_psbt_sign_with_finalized() {
let psbt_bip: PSBT = deserialize(&base64::decode(PSBT_STR).unwrap()).unwrap();
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let send_to = wallet.get_address(AddressIndex::New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
// add a finalized input
psbt.inputs.push(psbt_bip.inputs[0].clone());
psbt.global
.unsigned_tx
.input
.push(psbt_bip.global.unsigned_tx.input[0].clone());
let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
}
}

View File

@@ -654,7 +654,7 @@ mod test {
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 254.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -675,7 +675,7 @@ mod test {
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 254.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -696,7 +696,7 @@ mod test {
assert_eq!(result.selected.len(), 1); assert_eq!(result.selected.len(), 1);
assert_eq!(result.selected_amount(), 200_000); assert_eq!(result.selected_amount(), 200_000);
assert_eq!(result.fee_amount, 118.0); assert!((result.fee_amount - 118.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -756,7 +756,7 @@ mod test {
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_000); assert_eq!(result.selected_amount(), 300_000);
assert_eq!(result.fee_amount, 254.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -777,7 +777,7 @@ mod test {
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300_010); assert_eq!(result.selected_amount(), 300_010);
assert_eq!(result.fee_amount, 254.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -798,7 +798,7 @@ mod test {
assert_eq!(result.selected.len(), 3); assert_eq!(result.selected.len(), 3);
assert_eq!(result.selected_amount(), 300010); assert_eq!(result.selected_amount(), 300010);
assert_eq!(result.fee_amount, 254.0); assert!((result.fee_amount - 254.0).abs() < f32::EPSILON);
} }
#[test] #[test]
@@ -968,7 +968,7 @@ mod test {
cost_of_change, cost_of_change,
) )
.unwrap(); .unwrap();
assert_eq!(result.fee_amount, 186.0); assert!((result.fee_amount - 186.0).abs() < f32::EPSILON);
assert_eq!(result.selected_amount(), 100_000); assert_eq!(result.selected_amount(), 100_000);
} }
@@ -1031,9 +1031,8 @@ mod test {
); );
assert!(result.selected_amount() > target_amount); assert!(result.selected_amount() > target_amount);
assert_eq!( assert!(
result.fee_amount, (result.fee_amount - (50.0 + result.selected.len() as f32 * 68.0)).abs() < f32::EPSILON
50.0 + result.selected.len() as f32 * 68.0
); );
} }
} }

View File

@@ -46,13 +46,14 @@ pub use utils::IsDust;
use address_validator::AddressValidator; use address_validator::AddressValidator;
use coin_selection::DefaultCoinSelectionAlgorithm; use coin_selection::DefaultCoinSelectionAlgorithm;
use signer::{Signer, SignerOrdering, SignersContainer}; use signer::{SignOptions, Signer, SignerOrdering, SignersContainer};
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams}; use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI}; use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LIMIT_SATOSHI};
use crate::blockchain::{Blockchain, Progress}; use crate::blockchain::{Blockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils}; use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::descriptor::derived::AsDerived; use crate::descriptor::derived::AsDerived;
use crate::descriptor::policy::BuildSatisfaction;
use crate::descriptor::{ use crate::descriptor::{
get_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DerivedDescriptorMeta, get_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DerivedDescriptorMeta,
DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor,
@@ -60,6 +61,7 @@ use crate::descriptor::{
}; };
use crate::error::Error; use crate::error::Error;
use crate::psbt::PsbtUtils; use crate::psbt::PsbtUtils;
use crate::signer::SignerError;
use crate::types::*; use crate::types::*;
const CACHE_ADDR_BATCH_SIZE: u32 = 100; const CACHE_ADDR_BATCH_SIZE: u32 = 100;
@@ -190,7 +192,7 @@ pub enum AddressIndex {
/// 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`
/// and `AddressIndex::LastUsed` may have already been used. Also if the index is reset to a /// and `AddressIndex::LastUsed` may have already been used. Also if the index is reset to a
/// value earlier than the [`crate::blockchain::Blockchain`] stop_gap (default is 20) then a /// value earlier than the [`crate::blockchain::Blockchain`] stop_gap (default is 20) then a
/// larger stop_gap should be used to monitor for all possibly used addresses. /// larger stop_gap should be used to monitor for all possibly used addresses.
Reset(u32), Reset(u32),
} }
@@ -372,14 +374,14 @@ where
) -> Result<(PSBT, TransactionDetails), Error> { ) -> Result<(PSBT, TransactionDetails), Error> {
let external_policy = self let external_policy = self
.descriptor .descriptor
.extract_policy(&self.signers, &self.secp)? .extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
.unwrap(); .unwrap();
let internal_policy = self let internal_policy = self
.change_descriptor .change_descriptor
.as_ref() .as_ref()
.map(|desc| { .map(|desc| {
Ok::<_, Error>( Ok::<_, Error>(
desc.extract_policy(&self.change_signers, &self.secp)? desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
.unwrap(), .unwrap(),
) )
}) })
@@ -698,24 +700,24 @@ where
/// # 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();
/// let (psbt, _) = { /// let (mut psbt, _) = {
/// let mut builder = wallet.build_tx(); /// let mut builder = wallet.build_tx();
/// builder /// builder
/// .add_recipient(to_address.script_pubkey(), 50_000) /// .add_recipient(to_address.script_pubkey(), 50_000)
/// .enable_rbf(); /// .enable_rbf();
/// builder.finish()? /// builder.finish()?
/// }; /// };
/// let (psbt, _) = wallet.sign(psbt, None)?; /// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
/// let tx = psbt.extract_tx(); /// let tx = psbt.extract_tx();
/// // broadcast tx but it's taking too long to confirm so we want to bump the fee /// // broadcast tx but it's taking too long to confirm so we want to bump the fee
/// let (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(FeeRate::from_sat_per_vb(5.0));
/// builder.finish()? /// builder.finish()?
/// }; /// };
/// ///
/// let (psbt, _) = wallet.sign(psbt, None)?; /// let _ = wallet.sign(&mut psbt, SignOptions::default())?;
/// let fee_bumped_tx = psbt.extract_tx(); /// let fee_bumped_tx = psbt.extract_tx();
/// // broadcast fee_bumped_tx to replace original /// // broadcast fee_bumped_tx to replace original
/// # Ok::<(), bdk::Error>(()) /// # Ok::<(), bdk::Error>(())
@@ -832,6 +834,11 @@ where
/// Sign a transaction with all the wallet's signers, in the order specified by every signer's /// Sign a transaction with all the wallet's signers, in the order specified by every signer's
/// [`SignerOrdering`] /// [`SignerOrdering`]
/// ///
/// The [`SignOptions`] can be used to tweak the behavior of the software signers, and the way
/// the transaction is finalized at the end. Note that it can't be guaranteed that *every*
/// signers will follow the options, but the "software signers" (WIF keys and `xprv`) defined
/// in this library will.
///
/// ## Example /// ## Example
/// ///
/// ``` /// ```
@@ -842,17 +849,29 @@ where
/// # 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();
/// let (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);
/// builder.finish()? /// builder.finish()?
/// }; /// };
/// let (signed_psbt, finalized) = wallet.sign(psbt, None)?; /// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
/// assert!(finalized, "we should have signed all the inputs"); /// assert!(finalized, "we should have signed all the inputs");
/// # Ok::<(), bdk::Error>(()) /// # Ok::<(), bdk::Error>(())
pub fn sign(&self, mut psbt: PSBT, assume_height: Option<u32>) -> Result<(PSBT, bool), Error> { pub fn sign(&self, psbt: &mut PSBT, sign_options: SignOptions) -> Result<bool, Error> {
// this helps us doing our job later // this helps us doing our job later
self.add_input_hd_keypaths(&mut psbt)?; self.add_input_hd_keypaths(psbt)?;
// If we aren't allowed to use `witness_utxo`, ensure that every input but finalized one
// has the `non_witness_utxo`
if !sign_options.trust_witness_utxo
&& psbt
.inputs
.iter()
.filter(|i| i.final_script_witness.is_none() && i.final_script_sig.is_none())
.any(|i| i.non_witness_utxo.is_none())
{
return Err(Error::Signer(signer::SignerError::MissingNonWitnessUtxo));
}
for signer in self for signer in self
.signers .signers
@@ -861,28 +880,32 @@ where
.chain(self.change_signers.signers().iter()) .chain(self.change_signers.signers().iter())
{ {
if signer.sign_whole_tx() { if signer.sign_whole_tx() {
signer.sign(&mut psbt, None, &self.secp)?; signer.sign(psbt, None, &self.secp)?;
} else { } else {
for index in 0..psbt.inputs.len() { for index in 0..psbt.inputs.len() {
signer.sign(&mut psbt, Some(index), &self.secp)?; signer.sign(psbt, Some(index), &self.secp)?;
} }
} }
} }
// attempt to finalize // attempt to finalize
self.finalize_psbt(psbt, assume_height) self.finalize_psbt(psbt, sign_options)
} }
/// Return the spending policies for the wallet's descriptor /// Return the spending policies for the wallet's descriptor
pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, Error> { pub fn policies(&self, keychain: KeychainKind) -> Result<Option<Policy>, Error> {
match (keychain, self.change_descriptor.as_ref()) { match (keychain, self.change_descriptor.as_ref()) {
(KeychainKind::External, _) => { (KeychainKind::External, _) => Ok(self.descriptor.extract_policy(
Ok(self.descriptor.extract_policy(&self.signers, &self.secp)?) &self.signers,
} BuildSatisfaction::None,
&self.secp,
)?),
(KeychainKind::Internal, None) => Ok(None), (KeychainKind::Internal, None) => Ok(None),
(KeychainKind::Internal, Some(desc)) => { (KeychainKind::Internal, Some(desc)) => Ok(desc.extract_policy(
Ok(desc.extract_policy(&self.change_signers, &self.secp)?) &self.change_signers,
} BuildSatisfaction::None,
&self.secp,
)?),
} }
} }
@@ -902,16 +925,17 @@ where
} }
/// Try to finalize a PSBT /// Try to finalize a PSBT
pub fn finalize_psbt( ///
&self, /// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
mut psbt: PSBT, pub fn finalize_psbt(&self, psbt: &mut PSBT, sign_options: SignOptions) -> Result<bool, Error> {
assume_height: Option<u32>,
) -> Result<(PSBT, bool), Error> {
let tx = &psbt.global.unsigned_tx; let tx = &psbt.global.unsigned_tx;
let mut finished = true; let mut finished = true;
for (n, input) in tx.input.iter().enumerate() { for (n, input) in tx.input.iter().enumerate() {
let psbt_input = &psbt.inputs[n]; let psbt_input = &psbt
.inputs
.get(n)
.ok_or(Error::Signer(SignerError::InputIndexOutOfRange))?;
if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() { if psbt_input.final_script_sig.is_some() || psbt_input.final_script_witness.is_some() {
continue; continue;
} }
@@ -922,7 +946,7 @@ where
.borrow() .borrow()
.get_tx(&input.previous_output.txid, false)? .get_tx(&input.previous_output.txid, false)?
.map(|tx| tx.height.unwrap_or(std::u32::MAX)); .map(|tx| tx.height.unwrap_or(std::u32::MAX));
let current_height = assume_height.or(self.current_height); let current_height = sign_options.assume_height.or(self.current_height);
debug!( debug!(
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}", "Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
@@ -979,7 +1003,7 @@ where
} }
} }
Ok((psbt, finished)) Ok(finished)
} }
/// Return the secp256k1 context used for all signing operations /// Return the secp256k1 context used for all signing operations
@@ -1253,28 +1277,23 @@ where
match utxo { match utxo {
Utxo::Local(utxo) => { Utxo::Local(utxo) => {
*psbt_input = match self.get_psbt_input( *psbt_input =
utxo, match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) {
params.sighash, Ok(psbt_input) => psbt_input,
params.force_non_witness_utxo, Err(e) => match e {
) { Error::UnknownUtxo => Input {
Ok(psbt_input) => psbt_input, sighash_type: params.sighash,
Err(e) => match e { ..Input::default()
Error::UnknownUtxo => Input { },
sighash_type: params.sighash, _ => return Err(e),
..Input::default()
}, },
_ => return Err(e), }
},
}
} }
Utxo::Foreign { Utxo::Foreign {
psbt_input: foreign_psbt_input, psbt_input: foreign_psbt_input,
outpoint, outpoint,
} => { } => {
if params.force_non_witness_utxo if !params.only_witness_utxo && foreign_psbt_input.non_witness_utxo.is_none() {
&& foreign_psbt_input.non_witness_utxo.is_none()
{
return Err(Error::Generic(format!( return Err(Error::Generic(format!(
"Missing non_witness_utxo on foreign utxo {}", "Missing non_witness_utxo on foreign utxo {}",
outpoint outpoint
@@ -1318,7 +1337,7 @@ where
&self, &self,
utxo: LocalUtxo, utxo: LocalUtxo,
sighash_type: Option<SigHashType>, sighash_type: Option<SigHashType>,
force_non_witness_utxo: bool, only_witness_utxo: bool,
) -> Result<Input, Error> { ) -> Result<Input, Error> {
// Try to find the prev_script in our db to figure out if this is internal or external, // Try to find the prev_script in our db to figure out if this is internal or external,
// and the derivation index // and the derivation index
@@ -1345,7 +1364,7 @@ where
if desc.is_witness() { if desc.is_witness() {
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone()); psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
} }
if !desc.is_witness() || force_non_witness_utxo { if !desc.is_witness() || !only_witness_utxo {
psbt_input.non_witness_utxo = Some(prev_tx); psbt_input.non_witness_utxo = Some(prev_tx);
} }
} }
@@ -1486,7 +1505,7 @@ where
} }
#[cfg(test)] #[cfg(test)]
mod test { pub(crate) mod test {
use std::str::FromStr; use std::str::FromStr;
use bitcoin::{util::psbt, Network}; use bitcoin::{util::psbt, Network};
@@ -1633,7 +1652,7 @@ mod test {
.database .database
.borrow_mut() .borrow_mut()
.set_script_pubkey( .set_script_pubkey(
&bitcoin::Address::from_str(&tx_meta.output.iter().next().unwrap().to_address) &bitcoin::Address::from_str(&tx_meta.output.get(0).unwrap().to_address)
.unwrap() .unwrap()
.script_pubkey(), .script_pubkey(),
KeychainKind::External, KeychainKind::External,
@@ -2232,6 +2251,7 @@ mod test {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
.only_witness_utxo()
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
@@ -2250,20 +2270,18 @@ mod test {
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_none());
assert!(psbt.inputs[0].witness_utxo.is_some()); assert!(psbt.inputs[0].witness_utxo.is_some());
} }
#[test] #[test]
fn test_create_tx_both_non_witness_utxo_and_witness_utxo() { fn test_create_tx_both_non_witness_utxo_and_witness_utxo_default() {
let (wallet, _, _) = let (wallet, _, _) =
get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))"); get_funded_wallet("wsh(pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW))");
let addr = wallet.get_address(New).unwrap(); let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
.drain_wallet() .drain_wallet();
.force_non_witness_utxo();
let (psbt, _) = builder.finish().unwrap(); let (psbt, _) = builder.finish().unwrap();
assert!(psbt.inputs[0].non_witness_utxo.is_some()); assert!(psbt.inputs[0].non_witness_utxo.is_some());
@@ -2401,6 +2419,7 @@ mod test {
let (wallet1, _, _) = get_funded_wallet(get_test_wpkh()); let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
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();
let utxo = wallet2.list_unspent().unwrap().remove(0); let utxo = wallet2.list_unspent().unwrap().remove(0);
let foreign_utxo_satisfaction = wallet2 let foreign_utxo_satisfaction = wallet2
@@ -2416,9 +2435,10 @@ mod test {
let mut builder = wallet1.build_tx(); let mut builder = wallet1.build_tx();
builder builder
.add_recipient(addr.script_pubkey(), 60_000) .add_recipient(addr.script_pubkey(), 60_000)
.only_witness_utxo()
.add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction) .add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
.unwrap(); .unwrap();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
assert_eq!( assert_eq!(
details.sent - details.received, details.sent - details.received,
@@ -2431,19 +2451,34 @@ mod test {
.unsigned_tx .unsigned_tx
.input .input
.iter() .iter()
.find(|input| input.previous_output == utxo.outpoint) .any(|input| input.previous_output == utxo.outpoint),
.is_some(),
"foreign_utxo should be in there" "foreign_utxo should be in there"
); );
let (psbt, finished) = wallet1.sign(psbt, None).unwrap(); let finished = wallet1
.sign(
&mut psbt,
SignOptions {
trust_witness_utxo: true,
..Default::default()
},
)
.unwrap();
assert!( assert!(
!finished, !finished,
"only one of the inputs should have been signed so far" "only one of the inputs should have been signed so far"
); );
let (_, finished) = wallet2.sign(psbt, None).unwrap(); let finished = wallet2
.sign(
&mut psbt,
SignOptions {
trust_witness_utxo: true,
..Default::default()
},
)
.unwrap();
assert!(finished, "all the inputs should have been signed now"); assert!(finished, "all the inputs should have been signed now");
} }
@@ -2521,7 +2556,7 @@ mod test {
} }
#[test] #[test]
fn test_add_foreign_utxo_force_non_witness_utxo() { fn test_add_foreign_utxo_only_witness_utxo() {
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)");
@@ -2534,9 +2569,7 @@ mod test {
.unwrap(); .unwrap();
let mut builder = wallet1.build_tx(); let mut builder = wallet1.build_tx();
builder builder.add_recipient(addr.script_pubkey(), 60_000);
.add_recipient(addr.script_pubkey(), 60_000)
.force_non_witness_utxo();
{ {
let mut builder = builder.clone(); let mut builder = builder.clone();
@@ -2553,6 +2586,22 @@ mod test {
); );
} }
{
let mut builder = builder.clone();
let psbt_input = psbt::Input {
witness_utxo: Some(utxo2.txout.clone()),
..Default::default()
};
builder
.only_witness_utxo()
.add_foreign_utxo(utxo2.outpoint, psbt_input, satisfaction_weight)
.unwrap();
assert!(
builder.finish().is_ok(),
"psbt_input with just witness_utxo should succeed when `only_witness_utxo` is enabled"
);
}
{ {
let mut builder = builder.clone(); let mut builder = builder.clone();
let tx2 = wallet2 let tx2 = wallet2
@@ -2572,7 +2621,7 @@ mod test {
.unwrap(); .unwrap();
assert!( assert!(
builder.finish().is_ok(), builder.finish().is_ok(),
"psbt_input with non_witness_utxo should succeed with force_non_witness_utxo" "psbt_input with non_witness_utxo should succeed by default"
); );
} }
} }
@@ -3462,12 +3511,12 @@ mod test {
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (mut psbt, _) = builder.finish().unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true); assert_eq!(finalized, true);
let extracted = signed_psbt.extract_tx(); let extracted = psbt.extract_tx();
assert_eq!(extracted.input[0].witness.len(), 2); assert_eq!(extracted.input[0].witness.len(), 2);
} }
@@ -3479,12 +3528,12 @@ mod test {
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (mut psbt, _) = builder.finish().unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true); assert_eq!(finalized, true);
let extracted = signed_psbt.extract_tx(); let extracted = psbt.extract_tx();
assert_eq!(extracted.input[0].witness.len(), 2); assert_eq!(extracted.input[0].witness.len(), 2);
} }
@@ -3496,12 +3545,12 @@ mod test {
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (mut psbt, _) = builder.finish().unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true); assert_eq!(finalized, true);
let extracted = signed_psbt.extract_tx(); let extracted = psbt.extract_tx();
assert_eq!(extracted.input[0].witness.len(), 2); assert_eq!(extracted.input[0].witness.len(), 2);
} }
@@ -3513,12 +3562,12 @@ mod test {
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (mut psbt, _) = builder.finish().unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true); assert_eq!(finalized, true);
let extracted = signed_psbt.extract_tx(); let extracted = psbt.extract_tx();
assert_eq!(extracted.input[0].witness.len(), 2); assert_eq!(extracted.input[0].witness.len(), 2);
} }
@@ -3531,12 +3580,12 @@ mod test {
builder builder
.set_single_recipient(addr.script_pubkey()) .set_single_recipient(addr.script_pubkey())
.drain_wallet(); .drain_wallet();
let (psbt, _) = builder.finish().unwrap(); let (mut psbt, _) = builder.finish().unwrap();
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true); assert_eq!(finalized, true);
let extracted = signed_psbt.extract_tx(); let extracted = psbt.extract_tx();
assert_eq!(extracted.input[0].witness.len(), 2); assert_eq!(extracted.input[0].witness.len(), 2);
} }
@@ -3553,10 +3602,10 @@ mod test {
psbt.inputs[0].bip32_derivation.clear(); psbt.inputs[0].bip32_derivation.clear();
assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0); assert_eq!(psbt.inputs[0].bip32_derivation.len(), 0);
let (signed_psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert_eq!(finalized, true); assert_eq!(finalized, true);
let extracted = signed_psbt.extract_tx(); let extracted = psbt.extract_tx();
assert_eq!(extracted.input[0].witness.len(), 2); assert_eq!(extracted.input[0].witness.len(), 2);
} }
@@ -3602,7 +3651,15 @@ mod test {
psbt.inputs.push(dud_input); psbt.inputs.push(dud_input);
psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default()); psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default());
let (psbt, is_final) = wallet.sign(psbt, None).unwrap(); let is_final = wallet
.sign(
&mut psbt,
SignOptions {
trust_witness_utxo: true,
..Default::default()
},
)
.unwrap();
assert!( assert!(
!is_final, !is_final,
"shouldn't be final since we can't sign one of the inputs" "shouldn't be final since we can't sign one of the inputs"
@@ -3616,7 +3673,7 @@ mod test {
#[test] #[test]
fn test_unused_address() { fn test_unused_address() {
let db = MemoryDatabase::new(); let db = MemoryDatabase::new();
let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)", let wallet = Wallet::new_offline("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
None, Network::Testnet, db).unwrap(); None, Network::Testnet, db).unwrap();
assert_eq!( assert_eq!(

View File

@@ -206,6 +206,12 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
if psbt.inputs[input_index].final_script_sig.is_some()
|| psbt.inputs[input_index].final_script_witness.is_some()
{
return Ok(());
}
let (public_key, full_path) = match psbt.inputs[input_index] let (public_key, full_path) = match psbt.inputs[input_index]
.bip32_derivation .bip32_derivation
.iter() .iter()
@@ -261,10 +267,16 @@ impl Signer for PrivateKey {
secp: &SecpCtx, secp: &SecpCtx,
) -> Result<(), SignerError> { ) -> Result<(), SignerError> {
let input_index = input_index.unwrap(); let input_index = input_index.unwrap();
if input_index >= psbt.inputs.len() { if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
if psbt.inputs[input_index].final_script_sig.is_some()
|| psbt.inputs[input_index].final_script_witness.is_some()
{
return Ok(());
}
let pubkey = self.public_key(&secp); let pubkey = self.public_key(&secp);
if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) { if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
return Ok(()); return Ok(());
@@ -427,6 +439,43 @@ impl SignersContainer {
} }
} }
/// Options for a software signer
///
/// Adjust the behavior of our software signers and the way a transaction is finalized
#[derive(Debug, Clone)]
pub struct SignOptions {
/// Whether the signer should trust the `witness_utxo`, if the `non_witness_utxo` hasn't been
/// provided
///
/// Defaults to `false` to mitigate the "SegWit bug" which chould trick the wallet into
/// paying a fee larger than expected.
///
/// Some wallets, especially if relatively old, might not provide the `non_witness_utxo` for
/// SegWit transactions in the PSBT they generate: in those cases setting this to `true`
/// should correctly produce a signature, at the expense of an increased trust in the creator
/// of the PSBT.
///
/// For more details see: <https://blog.trezor.io/details-of-firmware-updates-for-trezor-one-version-1-9-1-and-trezor-model-t-version-2-3-1-1eba8f60f2dd>
pub trust_witness_utxo: bool,
/// Whether the wallet should assume a specific height has been reached when trying to finalize
/// a transaction
///
/// The wallet will only "use" a timelock to satisfy the spending policy of an input if the
/// timelock height has already been reached. This option allows overriding the "current height" to let the
/// wallet use timelocks in the future to spend a coin.
pub assume_height: Option<u32>,
}
impl Default for SignOptions {
fn default() -> Self {
SignOptions {
trust_witness_utxo: false,
assume_height: None,
}
}
}
pub(crate) trait ComputeSighash { pub(crate) trait ComputeSighash {
fn sighash( fn sighash(
psbt: &psbt::PartiallySignedTransaction, psbt: &psbt::PartiallySignedTransaction,
@@ -439,7 +488,7 @@ impl ComputeSighash for Legacy {
psbt: &psbt::PartiallySignedTransaction, psbt: &psbt::PartiallySignedTransaction,
input_index: usize, input_index: usize,
) -> Result<(SigHash, SigHashType), SignerError> { ) -> Result<(SigHash, SigHashType), SignerError> {
if input_index >= psbt.inputs.len() { if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
@@ -487,25 +536,42 @@ impl ComputeSighash for Segwitv0 {
psbt: &psbt::PartiallySignedTransaction, psbt: &psbt::PartiallySignedTransaction,
input_index: usize, input_index: usize,
) -> Result<(SigHash, SigHashType), SignerError> { ) -> Result<(SigHash, SigHashType), SignerError> {
if input_index >= psbt.inputs.len() { if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange); return Err(SignerError::InputIndexOutOfRange);
} }
let psbt_input = &psbt.inputs[input_index]; let psbt_input = &psbt.inputs[input_index];
let tx_input = &psbt.global.unsigned_tx.input[input_index];
let sighash = psbt_input.sighash_type.unwrap_or(SigHashType::All); let sighash = psbt_input.sighash_type.unwrap_or(SigHashType::All);
let witness_utxo = psbt_input // Always try first with the non-witness utxo
.witness_utxo let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
.as_ref() // Check the provided prev-tx
.ok_or(SignerError::MissingNonWitnessUtxo)?; if prev_tx.txid() != tx_input.previous_output.txid {
let value = witness_utxo.value; return Err(SignerError::InvalidNonWitnessUtxo);
}
// The output should be present, if it's missing the `non_witness_utxo` is invalid
prev_tx
.output
.get(tx_input.previous_output.vout as usize)
.ok_or(SignerError::InvalidNonWitnessUtxo)?
} else if let Some(witness_utxo) = &psbt_input.witness_utxo {
// Fallback to the witness_utxo. If we aren't allowed to use it, signing should fail
// before we get to this point
witness_utxo
} else {
// Nothing has been provided
return Err(SignerError::MissingNonWitnessUtxo);
};
let value = utxo.value;
let script = match psbt_input.witness_script { let script = match psbt_input.witness_script {
Some(ref witness_script) => witness_script.clone(), Some(ref witness_script) => witness_script.clone(),
None => { None => {
if witness_utxo.script_pubkey.is_v0_p2wpkh() { if utxo.script_pubkey.is_v0_p2wpkh() {
p2wpkh_script_code(&witness_utxo.script_pubkey) p2wpkh_script_code(&utxo.script_pubkey)
} else if psbt_input } else if psbt_input
.redeem_script .redeem_script
.as_ref() .as_ref()

View File

@@ -146,7 +146,7 @@ pub(crate) struct TxParams {
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,
pub(crate) force_non_witness_utxo: bool, pub(crate) only_witness_utxo: bool,
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>,
@@ -336,10 +336,10 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
/// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`. /// 1. The `psbt_input` does not contain a `witness_utxo` or `non_witness_utxo`.
/// 2. The data in `non_witness_utxo` does not match what is in `outpoint`. /// 2. The data in `non_witness_utxo` does not match what is in `outpoint`.
/// ///
/// Note if you set [`force_non_witness_utxo`] any `psbt_input` you pass to this method must /// Note unless you set [`only_witness_utxo`] any `psbt_input` you pass to this method must
/// have `non_witness_utxo` set otherwise you will get an error when [`finish`] is called. /// have `non_witness_utxo` set otherwise you will get an error when [`finish`] is called.
/// ///
/// [`force_non_witness_utxo`]: Self::force_non_witness_utxo /// [`only_witness_utxo`]: Self::only_witness_utxo
/// [`finish`]: Self::finish /// [`finish`]: Self::finish
/// [`max_satisfaction_weight`]: miniscript::Descriptor::max_satisfaction_weight /// [`max_satisfaction_weight`]: miniscript::Descriptor::max_satisfaction_weight
pub fn add_foreign_utxo( pub fn add_foreign_utxo(
@@ -464,12 +464,13 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderConte
self self
} }
/// Fill-in the [`psbt::Input::non_witness_utxo`](bitcoin::util::psbt::Input::non_witness_utxo) field even if the wallet only has SegWit /// Only Fill-in the [`psbt::Input::witness_utxo`](bitcoin::util::psbt::Input::witness_utxo) field when spending from
/// descriptors. /// SegWit descriptors.
/// ///
/// This is useful for signers which always require it, like Trezor hardware wallets. /// This reduces the size of the PSBT, but some signers might reject them due to the lack of
pub fn force_non_witness_utxo(&mut self) -> &mut Self { /// the `non_witness_utxo`.
self.params.force_non_witness_utxo = true; pub fn only_witness_utxo(&mut self) -> &mut Self {
self.params.only_witness_utxo = true;
self self
} }

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "bdk-testutils-macros" name = "bdk-testutils-macros"
version = "0.5.0" version = "0.6.0"
authors = ["Alekos Filini <alekos.filini@gmail.com>"] authors = ["Alekos Filini <alekos.filini@gmail.com>"]
edition = "2018" edition = "2018"
homepage = "https://bitcoindevkit.org" homepage = "https://bitcoindevkit.org"

View File

@@ -297,8 +297,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 25_000); builder.add_recipient(node_addr.script_pubkey(), 25_000);
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx(); let tx = psbt.extract_tx();
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
@@ -326,8 +326,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 25_000); builder.add_recipient(node_addr.script_pubkey(), 25_000);
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap(); let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -367,8 +367,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
for _ in 0..5 { for _ in 0..5 {
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 5_000); builder.add_recipient(node_addr.script_pubkey(), 5_000);
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
@@ -401,8 +401,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 5_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -411,8 +411,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(2.1)); builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -437,8 +437,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -447,8 +447,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(5.0)); builder.fee_rate(FeeRate::from_sat_per_vb(5.0));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -473,8 +473,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -483,8 +483,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(10.0)); builder.fee_rate(FeeRate::from_sat_per_vb(10.0));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -507,8 +507,8 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_tx(); let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf(); builder.add_recipient(node_addr.script_pubkey().clone(), 49_000).enable_rbf();
let (psbt, details) = builder.finish().unwrap(); let (mut psbt, details) = builder.finish().unwrap();
let (psbt, finalized) = wallet.sign(psbt, None).unwrap(); let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap(); wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();
@@ -517,10 +517,10 @@ pub fn bdk_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenStream
let mut builder = wallet.build_fee_bump(details.txid).unwrap(); let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(123.0)); builder.fee_rate(FeeRate::from_sat_per_vb(123.0));
let (new_psbt, new_details) = builder.finish().unwrap(); let (mut new_psbt, new_details) = builder.finish().unwrap();
println!("{:#?}", new_details); println!("{:#?}", new_details);
let (new_psbt, finalized) = wallet.sign(new_psbt, None).unwrap(); let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction"); assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap(); wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap(); wallet.sync(noop_progress(), None).unwrap();