Compare commits
41 Commits
v0.6.0
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3897e29740 | ||
|
|
8f06e45872 | ||
|
|
766570abfd | ||
|
|
934ec366d9 | ||
|
|
d0733e9496 | ||
|
|
3c7a1f5918 | ||
|
|
85aadaccd2 | ||
|
|
fad0fe9f30 | ||
|
|
47f26447da | ||
|
|
3608ff9f14 | ||
|
|
898dfe6cf1 | ||
|
|
7961ae7f8e | ||
|
|
8bf77c8f07 | ||
|
|
3c7bae9ce9 | ||
|
|
17bcd8ed7d | ||
|
|
b5e9589803 | ||
|
|
1d628d84b5 | ||
|
|
b84fd6ea5c | ||
|
|
8fe4222c33 | ||
|
|
e626f2e255 | ||
|
|
5a0c150ff9 | ||
|
|
00f07818f9 | ||
|
|
136a4bddb2 | ||
|
|
ff7b74ec27 | ||
|
|
8c00326990 | ||
|
|
afcd26032d | ||
|
|
8f422a1bf9 | ||
|
|
45983d2166 | ||
|
|
89cb4de7f6 | ||
|
|
7ca0e0e2bd | ||
|
|
2bddd9baed | ||
|
|
0135ba29c5 | ||
|
|
3ed44ce8cf | ||
|
|
8e7d8312a9 | ||
|
|
4da7488dc4 | ||
|
|
e37680af96 | ||
|
|
5f873ae500 | ||
|
|
2380634496 | ||
|
|
af98b8da06 | ||
|
|
b68ec050e2 | ||
|
|
ac7df09200 |
34
.github/workflows/code_coverage.yml
vendored
34
.github/workflows/code_coverage.yml
vendored
@@ -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/
|
||||||
|
|||||||
4
.github/workflows/cont_integration.yml
vendored
4
.github/workflows/cont_integration.yml
vendored
@@ -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
|
||||||
|
|||||||
2
.github/workflows/nightly_docs.yml
vendored
2
.github/workflows/nightly_docs.yml
vendored
@@ -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
|
||||||
|
|||||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
13
codecov.yaml
Normal 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
|
||||||
@@ -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
|
|
||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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};
|
||||||
|
|||||||
22
src/lib.rs
22
src/lib.rs
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user