Merge bitcoindevkit/bdk#593: Add support for Taproot and tr()
descriptors
20d36c71d470ea5ece98dd218f4dbdb0ba872d11 Update CHANGELOG.md for Taproot (Alekos Filini) ef08fbd3c7f424cb77e231c2daaed2039b28b938 Update to the newest release of rust-bitcoin (Alekos Filini) 5320c8353e05adecf0d18336d48934567257e64d taproot-tests: validate `tap_tree` in psbt outputs (Alekos Filini) c1bfaf9b1ebd92ac8952afd0d5867897672acfb4 Add blockchain tests for parsing, signing, finalizing taproot core psbts (Steve Myers) 0643f76c1fac5ee25f8a9de4c3a2cd57a3974b5e taproot-tests: Add tests for the policy module (Alekos Filini) 89cb425e69303fe62fe7332d8528011b7a98b4df taproot-tests: Add test coverage for tx signing (Alekos Filini) 461397e590471a8d10590b7d754e206310bceef5 taproot-tests: Test taproot key and script spend in the blockchain tests (Alekos Filini) c67116fb55de525a9045527eefcb5cdb6f4d34a9 policy: Consider `tap_key_origins` when looking for sigs in PSBTs (Alekos Filini) 572c3ee70d6d335af0e2e541d2497f8bdcfb63c9 policy: Build `SatisfiableItem::*Signature` based on the context (Alekos Filini) ff1abc63e03e5c24d375eb2c4f20302c9187d24d policy: Refactor `PkOrF` into an enum (Alekos Filini) 308708952b598f8f0f1f6cb5f3743e443485cb1e Fix type inference for the `tr()` descriptor, add basic tests (Alekos Filini) fe1877fb185af7a235ceb1a9696d1edd66f957d8 Support `tr()` descriptors in dsl (Alekos Filini) cdc7057813a4a2acba5224c966da2c3473efa669 Add `tr()` descriptors to the `descriptor!()` macro (Alekos Filini) c121dd0252f392ac4bd5e787c9cb570dedcec478 Use `tap_key_origins` in PSBTs to derive descriptors (Alekos Filini) 855382113380612ca4f4c7da0a4a93c7a4f9aab9 Populate more taproot fields in PSBTs (Alekos Filini) 8a5a87b0752e0bdaa003ff7c3f1d153a447dbe85 Populate `tap_key_origin` in PSBT inputs and outputs (Alekos Filini) 1312184ed7a9c5c7a6f99cf8bb08d43775ba5083 Attach a context to our software signers (Alekos Filini) 906598ad9254643a204df7711693dcc0a73a0332 Refactor signer traits, add support for taproot signatures (Alekos Filini) Pull request description: ### Description This is a work-in-progress PR to update BDK to rust-bitcoin `0.28` which introduces taproot support and a few other improvements. While updating we also introduce taproot support in BDK. High level list of subtasks for this PR: - [x] Update rust-bitcoin and rust-miniscript - [x] Stop using deprecated structs - [x] Add taproot metadata to psbts - [x] Produce schnorr signatures - [x] Finalize taproot txs - [x] Support `tr()` descriptors in the `descriptor!()` macro - [x] Write a lot of tests - [x] Interoperability with other wallets (Core + ?) - [x] Signing/finalizing a psbt made by core - [x] Producing a psbt that core can sign and finalize - [x] Creating psbts - [x] Verify the metadata are correct - [x] Verify sighashes are applied correctly - [x] Create a tx with a foreign taproot and non-taproot utxo - [x] Signing psbts - [x] Signing for a key spend - [x] Signing for a script spend - [x] Signing with a single (wif) key - [x] Signing with an xprv (with and without knowing the utxo being spent in the db) - [x] Signing with weird sighashes - [x] Policy module - [x] Simple key spend - [x] More complex tap tree with a few keys - [x] Verify both `contribution` and `satisfaction` of a PSBT input - [x] Wallet module - [x] Generate addresses Fixes #63 ### Notes to the reviewers #### Milestone I'm adding this to the `0.19` milestone because now that rust-bitcoin and rust-miniscript have been released we should not waiting too long to release a version of BDK that supports the new libraries. #### API Breaks Since this is an API-break because of the new version of rust-bitcoin and rust-miniscript, I'm also taking the chance to update a few things in our lib that I had been thinking about for a while. One example is the signer interface, which had that weird `sign_whole_tx()` method. This has now been removed, and the `Signer` trait replaced with `TransactionSigner` and `InputSigner`. I'm also starting to think that the signer should not only look at the psbt to figure out what to do, but ideally it should also receive some information about the descriptor (for example, the type) to simplify the code. One option is to add an extra parameter, but that would probably only be used by our internal signers and not much else (for example, if you ask an hardware wallet to sign, it will probably already know what kind of wallet you have). Another option is to wrap `PrivateKey` and `DescriptorXKey<ExtendedPrivKey>` which are the two internal signers we support with a struct that contains metadata about the descriptor, and then implement the signer traits on that struct. We could construct this in `Wallet::new()`, after miniscript parses the descriptor. #### MSRV Bump Due to the update of `rust-electrum-client`, which in turn depends on an updated `webpki`, we will have to bump our MSRV beacuse 1.46 is not supported by the new `webpki` version. ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [x] I've added docs for the new feature * [ ] I've updated `CHANGELOG.md` Top commit has no ACKs. Tree-SHA512: 44ec6c4e7fe0bc87862bb76581ddae7905c8796a4a130c90b48b73b4b7d01ea1a20043b8a0ff449fc2db2f7aecc490def8daee2d420809ded1ece7f085a48f55
This commit is contained in:
commit
8fbe40a918
@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Add traits to reuse `Blockchain`s across multiple wallets (`BlockchainFactory` and `StatelessBlockchain`).
|
||||
- Upgrade to rust-bitcoin `0.28`
|
||||
- If using the `sqlite-db` feature all cached wallet data is deleted due to a possible UTXO inconsistency, a wallet.sync will recreate it
|
||||
- Update `PkOrF` in the policy module to become an enum
|
||||
- Add experimental support for Taproot, including:
|
||||
- Support for `tr()` descriptors with complex tapscript trees
|
||||
- Creation of Taproot PSBTs (BIP-371)
|
||||
- Signing Taproot PSBTs (key spend and script spend)
|
||||
- Support for `tr()` descriptors in the `descriptor!()` macro
|
||||
|
||||
## [v0.18.0] - [v0.17.0]
|
||||
|
||||
|
@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0"
|
||||
bdk-macros = "^0.6"
|
||||
log = "^0.4"
|
||||
miniscript = { version = "7.0", features = ["use-serde"] }
|
||||
bitcoin = { version = "0.28", features = ["use-serde", "base64"] }
|
||||
bitcoin = { version = "0.28.1", features = ["use-serde", "base64", "rand"] }
|
||||
serde = { version = "^1.0", features = ["derive"] }
|
||||
serde_json = { version = "^1.0" }
|
||||
rand = "^0.7"
|
||||
|
@ -73,6 +73,48 @@ macro_rules! impl_top_level_pk {
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_top_level_tr {
|
||||
( $internal_key:expr, $tap_tree:expr ) => {{
|
||||
use $crate::miniscript::descriptor::{
|
||||
Descriptor, DescriptorPublicKey, KeyMap, TapTree, Tr,
|
||||
};
|
||||
use $crate::miniscript::Tap;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use $crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks};
|
||||
|
||||
let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
|
||||
|
||||
$internal_key
|
||||
.into_descriptor_key()
|
||||
.and_then(|key: DescriptorKey<Tap>| key.extract(&secp))
|
||||
.map_err($crate::descriptor::DescriptorError::Key)
|
||||
.and_then(|(pk, mut key_map, mut valid_networks)| {
|
||||
let tap_tree = $tap_tree.map(
|
||||
|(tap_tree, tree_keymap, tree_networks): (
|
||||
TapTree<DescriptorPublicKey>,
|
||||
KeyMap,
|
||||
ValidNetworks,
|
||||
)| {
|
||||
key_map.extend(tree_keymap.into_iter());
|
||||
valid_networks =
|
||||
$crate::keys::merge_networks(&valid_networks, &tree_networks);
|
||||
|
||||
tap_tree
|
||||
},
|
||||
);
|
||||
|
||||
Ok((
|
||||
Descriptor::<DescriptorPublicKey>::Tr(Tr::new(pk, tap_tree)?),
|
||||
key_map,
|
||||
valid_networks,
|
||||
))
|
||||
})
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! impl_leaf_opcode {
|
||||
@ -228,6 +270,62 @@ macro_rules! impl_sortedmulti {
|
||||
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! parse_tap_tree {
|
||||
( @merge $tree_a:expr, $tree_b:expr) => {{
|
||||
use std::sync::Arc;
|
||||
use $crate::miniscript::descriptor::TapTree;
|
||||
|
||||
$tree_a
|
||||
.and_then(|tree_a| Ok((tree_a, $tree_b?)))
|
||||
.and_then(|((a_tree, mut a_keymap, a_networks), (b_tree, b_keymap, b_networks))| {
|
||||
a_keymap.extend(b_keymap.into_iter());
|
||||
Ok((TapTree::Tree(Arc::new(a_tree), Arc::new(b_tree)), a_keymap, $crate::keys::merge_networks(&a_networks, &b_networks)))
|
||||
})
|
||||
|
||||
}};
|
||||
|
||||
// Two sub-trees
|
||||
( { { $( $tree_a:tt )* }, { $( $tree_b:tt )* } } ) => {{
|
||||
let tree_a = $crate::parse_tap_tree!( { $( $tree_a )* } );
|
||||
let tree_b = $crate::parse_tap_tree!( { $( $tree_b )* } );
|
||||
|
||||
$crate::parse_tap_tree!(@merge tree_a, tree_b)
|
||||
}};
|
||||
|
||||
// One leaf and a sub-tree
|
||||
( { $op_a:ident ( $( $minisc_a:tt )* ), { $( $tree_b:tt )* } } ) => {{
|
||||
let tree_a = $crate::parse_tap_tree!( $op_a ( $( $minisc_a )* ) );
|
||||
let tree_b = $crate::parse_tap_tree!( { $( $tree_b )* } );
|
||||
|
||||
$crate::parse_tap_tree!(@merge tree_a, tree_b)
|
||||
}};
|
||||
( { { $( $tree_a:tt )* }, $op_b:ident ( $( $minisc_b:tt )* ) } ) => {{
|
||||
let tree_a = $crate::parse_tap_tree!( { $( $tree_a )* } );
|
||||
let tree_b = $crate::parse_tap_tree!( $op_b ( $( $minisc_b )* ) );
|
||||
|
||||
$crate::parse_tap_tree!(@merge tree_a, tree_b)
|
||||
}};
|
||||
|
||||
// Two leaves
|
||||
( { $op_a:ident ( $( $minisc_a:tt )* ), $op_b:ident ( $( $minisc_b:tt )* ) } ) => {{
|
||||
let tree_a = $crate::parse_tap_tree!( $op_a ( $( $minisc_a )* ) );
|
||||
let tree_b = $crate::parse_tap_tree!( $op_b ( $( $minisc_b )* ) );
|
||||
|
||||
$crate::parse_tap_tree!(@merge tree_a, tree_b)
|
||||
}};
|
||||
|
||||
// Single leaf
|
||||
( $op:ident ( $( $minisc:tt )* ) ) => {{
|
||||
use std::sync::Arc;
|
||||
use $crate::miniscript::descriptor::TapTree;
|
||||
|
||||
$crate::fragment!( $op ( $( $minisc )* ) )
|
||||
.map(|(a_minisc, a_keymap, a_networks)| (TapTree::Leaf(Arc::new(a_minisc)), a_keymap, a_networks))
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! apply_modifier {
|
||||
@ -441,6 +539,15 @@ macro_rules! descriptor {
|
||||
( wsh ( $( $minisc:tt )* ) ) => ({
|
||||
$crate::impl_top_level_sh!(Wsh, new, new_sortedmulti, Segwitv0, $( $minisc )*)
|
||||
});
|
||||
|
||||
( tr ( $internal_key:expr ) ) => ({
|
||||
$crate::impl_top_level_tr!($internal_key, None)
|
||||
});
|
||||
( tr ( $internal_key:expr, $( $taptree:tt )* ) ) => ({
|
||||
let tap_tree = $crate::parse_tap_tree!( $( $taptree )* );
|
||||
tap_tree
|
||||
.and_then(|tap_tree| $crate::impl_top_level_tr!($internal_key, Some(tap_tree)))
|
||||
});
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
@ -480,6 +587,23 @@ impl<A, B, C> From<(A, (B, (C, ())))> for TupleThree<A, B, C> {
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! group_multi_keys {
|
||||
( $( $key:expr ),+ ) => {{
|
||||
use $crate::keys::IntoDescriptorKey;
|
||||
|
||||
let keys = vec![
|
||||
$(
|
||||
$key.into_descriptor_key(),
|
||||
)*
|
||||
];
|
||||
|
||||
keys.into_iter().collect::<Result<Vec<_>, _>>()
|
||||
.map_err($crate::descriptor::DescriptorError::Key)
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[macro_export]
|
||||
macro_rules! fragment_internal {
|
||||
@ -640,21 +764,22 @@ macro_rules! fragment {
|
||||
.and_then(|items| $crate::fragment!(thresh_vec($thresh, items)))
|
||||
});
|
||||
( multi_vec ( $thresh:expr, $keys:expr ) ) => ({
|
||||
$crate::keys::make_multi($thresh, $keys)
|
||||
});
|
||||
( multi ( $thresh:expr $(, $key:expr )+ ) ) => ({
|
||||
use $crate::keys::IntoDescriptorKey;
|
||||
let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
|
||||
|
||||
let keys = vec![
|
||||
$(
|
||||
$key.into_descriptor_key(),
|
||||
)*
|
||||
];
|
||||
$crate::keys::make_multi($thresh, $crate::miniscript::Terminal::Multi, $keys, &secp)
|
||||
});
|
||||
( multi ( $thresh:expr $(, $key:expr )+ ) ) => ({
|
||||
$crate::group_multi_keys!( $( $key ),* )
|
||||
.and_then(|keys| $crate::fragment!( multi_vec ( $thresh, keys ) ))
|
||||
});
|
||||
( multi_a_vec ( $thresh:expr, $keys:expr ) ) => ({
|
||||
let secp = $crate::bitcoin::secp256k1::Secp256k1::new();
|
||||
|
||||
keys.into_iter().collect::<Result<Vec<_>, _>>()
|
||||
.map_err($crate::descriptor::DescriptorError::Key)
|
||||
.and_then(|keys| $crate::keys::make_multi($thresh, keys, &secp))
|
||||
$crate::keys::make_multi($thresh, $crate::miniscript::Terminal::MultiA, $keys, &secp)
|
||||
});
|
||||
( multi_a ( $thresh:expr $(, $key:expr )+ ) ) => ({
|
||||
$crate::group_multi_keys!( $( $key ),* )
|
||||
.and_then(|keys| $crate::fragment!( multi_a_vec ( $thresh, keys ) ))
|
||||
});
|
||||
|
||||
// `sortedmulti()` is handled separately
|
||||
@ -1064,4 +1189,35 @@ mod test {
|
||||
|
||||
descriptor!(wsh(v: pk(uncompressed_pk))).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dsl_tr_only_key() {
|
||||
let private_key =
|
||||
PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
|
||||
let (descriptor, _, _) = descriptor!(tr(private_key)).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
descriptor.to_string(),
|
||||
"tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)#heq9m95v"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dsl_tr_simple_tree() {
|
||||
let private_key =
|
||||
PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
|
||||
let (descriptor, _, _) =
|
||||
descriptor!(tr(private_key, { pk(private_key), pk(private_key) })).unwrap();
|
||||
|
||||
assert_eq!(descriptor.to_string(), "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c,{pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c),pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c)})#xy5fjw6d")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dsl_tr_single_leaf() {
|
||||
let private_key =
|
||||
PrivateKey::from_wif("cSQPHDBwXGjVzWRqAHm6zfvQhaTuj1f2bFH58h55ghbjtFwvmeXR").unwrap();
|
||||
let (descriptor, _, _) = descriptor!(tr(private_key, pk(private_key))).unwrap();
|
||||
|
||||
assert_eq!(descriptor.to_string(), "tr(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c,pk(02e96fe52ef0e22d2f131dd425ce1893073a3c6ad20e8cac36726393dfb4856a4c))#lzl2vmc7")
|
||||
}
|
||||
}
|
||||
|
@ -14,15 +14,15 @@
|
||||
//! This module contains generic utilities to work with descriptors, plus some re-exported types
|
||||
//! from [`miniscript`].
|
||||
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::ops::Deref;
|
||||
|
||||
use bitcoin::secp256k1;
|
||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
|
||||
use bitcoin::util::psbt;
|
||||
use bitcoin::util::{psbt, taproot};
|
||||
use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey};
|
||||
use bitcoin::{Network, Script, TxOut};
|
||||
|
||||
use miniscript::descriptor::{DescriptorType, InnerXKey};
|
||||
use miniscript::descriptor::{DescriptorType, InnerXKey, SinglePubKey};
|
||||
pub use miniscript::{
|
||||
descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor,
|
||||
DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0,
|
||||
@ -61,6 +61,13 @@ pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
|
||||
/// [`psbt::Output`]: bitcoin::util::psbt::Output
|
||||
pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>;
|
||||
|
||||
/// Alias for the type of maps that represent taproot key origins in a [`psbt::Input`] or
|
||||
/// [`psbt::Output`]
|
||||
///
|
||||
/// [`psbt::Input`]: bitcoin::util::psbt::Input
|
||||
/// [`psbt::Output`]: bitcoin::util::psbt::Output
|
||||
pub type TapKeyOrigins = BTreeMap<bitcoin::XOnlyPublicKey, (Vec<taproot::TapLeafHash>, KeySource)>;
|
||||
|
||||
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
|
||||
pub trait IntoWalletDescriptor {
|
||||
/// Convert to wallet descriptor
|
||||
@ -302,7 +309,8 @@ where
|
||||
}
|
||||
|
||||
pub(crate) trait DerivedDescriptorMeta {
|
||||
fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HdKeyPaths, DescriptorError>;
|
||||
fn get_hd_keypaths(&self, secp: &SecpCtx) -> HdKeyPaths;
|
||||
fn get_tap_key_origins(&self, secp: &SecpCtx) -> TapKeyOrigins;
|
||||
}
|
||||
|
||||
pub(crate) trait DescriptorMeta {
|
||||
@ -314,6 +322,16 @@ pub(crate) trait DescriptorMeta {
|
||||
hd_keypaths: &HdKeyPaths,
|
||||
secp: &'s SecpCtx,
|
||||
) -> Option<DerivedDescriptor<'s>>;
|
||||
fn derive_from_tap_key_origins<'s>(
|
||||
&self,
|
||||
tap_key_origins: &TapKeyOrigins,
|
||||
secp: &'s SecpCtx,
|
||||
) -> Option<DerivedDescriptor<'s>>;
|
||||
fn derive_from_psbt_key_origins<'s>(
|
||||
&self,
|
||||
key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
|
||||
secp: &'s SecpCtx,
|
||||
) -> Option<DerivedDescriptor<'s>>;
|
||||
fn derive_from_psbt_input<'s>(
|
||||
&self,
|
||||
psbt_input: &psbt::Input,
|
||||
@ -393,61 +411,124 @@ impl DescriptorMeta for ExtendedDescriptor {
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
fn derive_from_hd_keypaths<'s>(
|
||||
fn derive_from_psbt_key_origins<'s>(
|
||||
&self,
|
||||
hd_keypaths: &HdKeyPaths,
|
||||
key_origins: BTreeMap<Fingerprint, (&DerivationPath, SinglePubKey)>,
|
||||
secp: &'s SecpCtx,
|
||||
) -> Option<DerivedDescriptor<'s>> {
|
||||
let index: HashMap<_, _> = hd_keypaths.values().map(|(a, b)| (a, b)).collect();
|
||||
// Ensure that deriving `xpub` with `path` yields `expected`
|
||||
let verify_key = |xpub: &DescriptorXKey<ExtendedPubKey>,
|
||||
path: &DerivationPath,
|
||||
expected: &SinglePubKey| {
|
||||
let derived = xpub
|
||||
.xkey
|
||||
.derive_pub(secp, path)
|
||||
.expect("The path should never contain hardened derivation steps")
|
||||
.public_key;
|
||||
|
||||
match expected {
|
||||
SinglePubKey::FullKey(pk) if &PublicKey::new(derived) == pk => true,
|
||||
SinglePubKey::XOnly(pk) if &XOnlyPublicKey::from(derived) == pk => true,
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
|
||||
let mut path_found = None;
|
||||
self.for_each_key(|key| {
|
||||
if path_found.is_some() {
|
||||
// already found a matching path, we are done
|
||||
return true;
|
||||
}
|
||||
|
||||
// using `for_any_key` should make this stop as soon as we return `true`
|
||||
self.for_any_key(|key| {
|
||||
if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
|
||||
// Check if the key matches one entry in our `index`. If it does, `matches()` will
|
||||
// Check if the key matches one entry in our `key_origins`. If it does, `matches()` will
|
||||
// return the "prefix" that matched, so we remove that prefix from the full path
|
||||
// found in `index` and save it in `derive_path`. We expect this to be a derivation
|
||||
// found in `key_origins` and save it in `derive_path`. We expect this to be a derivation
|
||||
// path of length 1 if the key is `wildcard` and an empty path otherwise.
|
||||
let root_fingerprint = xpub.root_fingerprint(secp);
|
||||
let derivation_path: Option<Vec<ChildNumber>> = index
|
||||
let derive_path = key_origins
|
||||
.get_key_value(&root_fingerprint)
|
||||
.and_then(|(fingerprint, path)| {
|
||||
xpub.matches(&(**fingerprint, (*path).clone()), secp)
|
||||
.and_then(|(fingerprint, (path, expected))| {
|
||||
xpub.matches(&(*fingerprint, (*path).clone()), secp)
|
||||
.zip(Some((path, expected)))
|
||||
})
|
||||
.map(|prefix| {
|
||||
index
|
||||
.get(&xpub.root_fingerprint(secp))
|
||||
.unwrap()
|
||||
.and_then(|(prefix, (full_path, expected))| {
|
||||
let derive_path = full_path
|
||||
.into_iter()
|
||||
.skip(prefix.into_iter().count())
|
||||
.cloned()
|
||||
.collect()
|
||||
.collect::<DerivationPath>();
|
||||
|
||||
// `derive_path` only contains the replacement index for the wildcard, if present, or
|
||||
// an empty path for fixed descriptors. To verify the key we also need the normal steps
|
||||
// that come before the wildcard, so we take them directly from `xpub` and then append
|
||||
// the final index
|
||||
if verify_key(
|
||||
xpub,
|
||||
&xpub.derivation_path.extend(derive_path.clone()),
|
||||
expected,
|
||||
) {
|
||||
Some(derive_path)
|
||||
} else {
|
||||
log::debug!(
|
||||
"Key `{}` derived with {} yields an unexpected key",
|
||||
root_fingerprint,
|
||||
derive_path
|
||||
);
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
match derivation_path {
|
||||
match derive_path {
|
||||
Some(path) if xpub.wildcard != Wildcard::None && path.len() == 1 => {
|
||||
// Ignore hardened wildcards
|
||||
if let ChildNumber::Normal { index } = path[0] {
|
||||
path_found = Some(index)
|
||||
path_found = Some(index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Some(path) if xpub.wildcard == Wildcard::None && path.is_empty() => {
|
||||
path_found = Some(0)
|
||||
path_found = Some(0);
|
||||
return true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
false
|
||||
});
|
||||
|
||||
path_found.map(|path| self.as_derived(path, secp))
|
||||
}
|
||||
|
||||
fn derive_from_hd_keypaths<'s>(
|
||||
&self,
|
||||
hd_keypaths: &HdKeyPaths,
|
||||
secp: &'s SecpCtx,
|
||||
) -> Option<DerivedDescriptor<'s>> {
|
||||
// "Convert" an hd_keypaths map to the format required by `derive_from_psbt_key_origins`
|
||||
let key_origins = hd_keypaths
|
||||
.iter()
|
||||
.map(|(pk, (fingerprint, path))| {
|
||||
(
|
||||
*fingerprint,
|
||||
(path, SinglePubKey::FullKey(PublicKey::new(*pk))),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
self.derive_from_psbt_key_origins(key_origins, secp)
|
||||
}
|
||||
|
||||
fn derive_from_tap_key_origins<'s>(
|
||||
&self,
|
||||
tap_key_origins: &TapKeyOrigins,
|
||||
secp: &'s SecpCtx,
|
||||
) -> Option<DerivedDescriptor<'s>> {
|
||||
// "Convert" a tap_key_origins map to the format required by `derive_from_psbt_key_origins`
|
||||
let key_origins = tap_key_origins
|
||||
.iter()
|
||||
.map(|(pk, (_, (fingerprint, path)))| (*fingerprint, (path, SinglePubKey::XOnly(*pk))))
|
||||
.collect();
|
||||
self.derive_from_psbt_key_origins(key_origins, secp)
|
||||
}
|
||||
|
||||
fn derive_from_psbt_input<'s>(
|
||||
&self,
|
||||
psbt_input: &psbt::Input,
|
||||
@ -457,6 +538,9 @@ impl DescriptorMeta for ExtendedDescriptor {
|
||||
if let Some(derived) = self.derive_from_hd_keypaths(&psbt_input.bip32_derivation, secp) {
|
||||
return Some(derived);
|
||||
}
|
||||
if let Some(derived) = self.derive_from_tap_key_origins(&psbt_input.tap_key_origins, secp) {
|
||||
return Some(derived);
|
||||
}
|
||||
if self.is_deriveable() {
|
||||
// We can't try to bruteforce the derivation index, exit here
|
||||
return None;
|
||||
@ -497,7 +581,7 @@ impl DescriptorMeta for ExtendedDescriptor {
|
||||
}
|
||||
|
||||
impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
|
||||
fn get_hd_keypaths(&self, secp: &SecpCtx) -> Result<HdKeyPaths, DescriptorError> {
|
||||
fn get_hd_keypaths(&self, secp: &SecpCtx) -> HdKeyPaths {
|
||||
let mut answer = BTreeMap::new();
|
||||
self.for_each_key(|key| {
|
||||
if let DescriptorPublicKey::XPub(xpub) = key.as_key().deref() {
|
||||
@ -515,7 +599,64 @@ impl<'s> DerivedDescriptorMeta for DerivedDescriptor<'s> {
|
||||
true
|
||||
});
|
||||
|
||||
Ok(answer)
|
||||
answer
|
||||
}
|
||||
|
||||
fn get_tap_key_origins(&self, secp: &SecpCtx) -> TapKeyOrigins {
|
||||
use miniscript::ToPublicKey;
|
||||
|
||||
let mut answer = BTreeMap::new();
|
||||
let mut insert_path = |pk: &DerivedDescriptorKey<'_>, lh| {
|
||||
let key_origin = match pk.deref() {
|
||||
DescriptorPublicKey::XPub(xpub) => {
|
||||
Some((xpub.root_fingerprint(secp), xpub.full_path(&[])))
|
||||
}
|
||||
DescriptorPublicKey::SinglePub(_) => None,
|
||||
};
|
||||
|
||||
// If this is the internal key, we only insert the key origin if it's not None.
|
||||
// For keys found in the tap tree we always insert a key origin (because the signer
|
||||
// looks for it to know which leaves to sign for), even though it may be None
|
||||
match (lh, key_origin) {
|
||||
(None, Some(ko)) => {
|
||||
answer
|
||||
.entry(pk.to_x_only_pubkey())
|
||||
.or_insert_with(|| (vec![], ko));
|
||||
}
|
||||
(Some(lh), origin) => {
|
||||
answer
|
||||
.entry(pk.to_x_only_pubkey())
|
||||
.or_insert_with(|| (vec![], origin.unwrap_or_default()))
|
||||
.0
|
||||
.push(lh);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
|
||||
if let Descriptor::Tr(tr) = &self {
|
||||
// Internal key first, then iterate the scripts
|
||||
insert_path(tr.internal_key(), None);
|
||||
|
||||
for (_, ms) in tr.iter_scripts() {
|
||||
// Assume always the same leaf version
|
||||
let leaf_hash = taproot::TapLeafHash::from_script(
|
||||
&ms.encode(),
|
||||
taproot::LeafVersion::TapScript,
|
||||
);
|
||||
|
||||
for key in ms.iter_pk_pkh() {
|
||||
let key = match key {
|
||||
miniscript::miniscript::iter::PkPkh::PlainPubkey(pk) => pk,
|
||||
miniscript::miniscript::iter::PkPkh::HashedPubkey(pk) => pk,
|
||||
};
|
||||
|
||||
insert_path(&key, Some(leaf_hash));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@
|
||||
//! ```
|
||||
//! # use std::sync::Arc;
|
||||
//! # use bdk::descriptor::*;
|
||||
//! # use bdk::wallet::signer::*;
|
||||
//! # use bdk::bitcoin::secp256k1::Secp256k1;
|
||||
//! use bdk::descriptor::policy::BuildSatisfaction;
|
||||
//! let secp = Secp256k1::new();
|
||||
@ -29,7 +30,7 @@
|
||||
//! let (extended_desc, key_map) = ExtendedDescriptor::parse_descriptor(&secp, desc)?;
|
||||
//! println!("{:?}", extended_desc);
|
||||
//!
|
||||
//! let signers = Arc::new(key_map.into());
|
||||
//! let signers = Arc::new(SignersContainer::build(key_map, &extended_desc, &secp));
|
||||
//! let policy = extended_desc.extract_policy(&signers, BuildSatisfaction::None, &secp)?;
|
||||
//! println!("policy: {}", serde_json::to_string(&policy)?);
|
||||
//! # Ok::<(), bdk::Error>(())
|
||||
@ -43,7 +44,6 @@ use serde::ser::SerializeMap;
|
||||
use serde::{Serialize, Serializer};
|
||||
|
||||
use bitcoin::hashes::*;
|
||||
use bitcoin::secp256k1;
|
||||
use bitcoin::util::bip32::Fingerprint;
|
||||
use bitcoin::{PublicKey, XOnlyPublicKey};
|
||||
|
||||
@ -66,17 +66,16 @@ use super::XKeyUtils;
|
||||
use bitcoin::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt};
|
||||
use miniscript::psbt::PsbtInputSatisfier;
|
||||
|
||||
/// Raw public key or extended key fingerprint
|
||||
#[derive(Debug, Clone, Default, Serialize)]
|
||||
pub struct PkOrF {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pubkey: Option<PublicKey>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
x_only_pubkey: Option<XOnlyPublicKey>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pubkey_hash: Option<hash160::Hash>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
fingerprint: Option<Fingerprint>,
|
||||
/// A unique identifier for a key
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum PkOrF {
|
||||
/// A legacy public key
|
||||
Pubkey(PublicKey),
|
||||
/// A x-only public key
|
||||
XOnlyPubkey(XOnlyPublicKey),
|
||||
/// An extended key fingerprint
|
||||
Fingerprint(Fingerprint),
|
||||
}
|
||||
|
||||
impl PkOrF {
|
||||
@ -85,27 +84,18 @@ impl PkOrF {
|
||||
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||
key: SinglePubKey::FullKey(pk),
|
||||
..
|
||||
}) => PkOrF {
|
||||
pubkey: Some(*pk),
|
||||
..Default::default()
|
||||
},
|
||||
}) => PkOrF::Pubkey(*pk),
|
||||
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||
key: SinglePubKey::XOnly(pk),
|
||||
..
|
||||
}) => PkOrF {
|
||||
x_only_pubkey: Some(*pk),
|
||||
..Default::default()
|
||||
},
|
||||
DescriptorPublicKey::XPub(xpub) => PkOrF {
|
||||
fingerprint: Some(xpub.root_fingerprint(secp)),
|
||||
..Default::default()
|
||||
},
|
||||
}) => PkOrF::XOnlyPubkey(*pk),
|
||||
DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An item that needs to be satisfied
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "UPPERCASE")]
|
||||
pub enum SatisfiableItem {
|
||||
// Leaves
|
||||
@ -259,7 +249,7 @@ where
|
||||
}
|
||||
|
||||
/// Represent if and how much a policy item is satisfied by the wallet's descriptor
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "UPPERCASE")]
|
||||
pub enum Satisfaction {
|
||||
/// Only a partial satisfaction of some kind of threshold policy
|
||||
@ -433,7 +423,7 @@ impl From<bool> for Satisfaction {
|
||||
}
|
||||
|
||||
/// Descriptor spending policy
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Policy {
|
||||
/// Identifier for this policy node
|
||||
pub id: String,
|
||||
@ -581,13 +571,12 @@ impl Policy {
|
||||
Ok(Some(policy))
|
||||
}
|
||||
|
||||
fn make_multisig(
|
||||
fn make_multisig<Ctx: ScriptContext + 'static>(
|
||||
keys: &[DescriptorPublicKey],
|
||||
signers: &SignersContainer,
|
||||
build_sat: BuildSatisfaction,
|
||||
threshold: usize,
|
||||
sorted: bool,
|
||||
is_ecdsa: bool,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<Option<Policy>, PolicyError> {
|
||||
if threshold == 0 {
|
||||
@ -616,9 +605,7 @@ impl Policy {
|
||||
}
|
||||
|
||||
if let Some(psbt) = build_sat.psbt() {
|
||||
if is_ecdsa && ecdsa_signature_in_psbt(psbt, key, secp)
|
||||
|| !is_ecdsa && schnorr_signature_in_psbt(psbt, key, secp)
|
||||
{
|
||||
if Ctx::find_signature(psbt, key, secp) {
|
||||
satisfaction.add(
|
||||
&Satisfaction::Complete {
|
||||
condition: Default::default(),
|
||||
@ -746,13 +733,15 @@ fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
|
||||
}
|
||||
}
|
||||
|
||||
fn signature(
|
||||
fn make_generic_signature<M: Fn() -> SatisfiableItem, F: Fn(&Psbt) -> bool>(
|
||||
key: &DescriptorPublicKey,
|
||||
signers: &SignersContainer,
|
||||
build_sat: BuildSatisfaction,
|
||||
secp: &SecpCtx,
|
||||
make_policy: M,
|
||||
find_sig: F,
|
||||
) -> Policy {
|
||||
let mut policy: Policy = SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)).into();
|
||||
let mut policy: Policy = make_policy().into();
|
||||
|
||||
policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
|
||||
Satisfaction::Complete {
|
||||
@ -763,7 +752,7 @@ fn signature(
|
||||
};
|
||||
|
||||
if let Some(psbt) = build_sat.psbt() {
|
||||
policy.satisfaction = if ecdsa_signature_in_psbt(psbt, key, secp) {
|
||||
policy.satisfaction = if find_sig(psbt) {
|
||||
Satisfaction::Complete {
|
||||
condition: Default::default(),
|
||||
}
|
||||
@ -776,66 +765,115 @@ fn signature(
|
||||
}
|
||||
|
||||
fn generic_sig_in_psbt<
|
||||
// C is for "check", it's a closure we use to *check* if a psbt input contains the signature
|
||||
// for a specific key
|
||||
C: Fn(&PsbtInput, &SinglePubKey) -> bool,
|
||||
M: Fn(&secp256k1::PublicKey) -> SinglePubKey,
|
||||
// E is for "extract", it extracts a key from the bip32 derivations found in the psbt input
|
||||
E: Fn(&PsbtInput, Fingerprint) -> Option<SinglePubKey>,
|
||||
>(
|
||||
psbt: &Psbt,
|
||||
key: &DescriptorPublicKey,
|
||||
secp: &SecpCtx,
|
||||
map: M,
|
||||
check: C,
|
||||
extract: E,
|
||||
) -> bool {
|
||||
//TODO check signature validity
|
||||
psbt.inputs.iter().all(|input| match key {
|
||||
DescriptorPublicKey::SinglePub(DescriptorSinglePub { key, .. }) => check(input, 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) => check(input, &map(pubkey)),
|
||||
match extract(input, xpub.root_fingerprint(secp)) {
|
||||
Some(pubkey) => check(input, &pubkey),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn ecdsa_signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
|
||||
generic_sig_in_psbt(
|
||||
psbt,
|
||||
key,
|
||||
secp,
|
||||
|pk| SinglePubKey::FullKey(PublicKey::new(*pk)),
|
||||
|input, pk| match pk {
|
||||
SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
|
||||
_ => false,
|
||||
},
|
||||
)
|
||||
trait SigExt: ScriptContext {
|
||||
fn make_signature(
|
||||
key: &DescriptorPublicKey,
|
||||
signers: &SignersContainer,
|
||||
build_sat: BuildSatisfaction,
|
||||
secp: &SecpCtx,
|
||||
) -> Policy;
|
||||
|
||||
fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool;
|
||||
}
|
||||
|
||||
fn schnorr_signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
|
||||
generic_sig_in_psbt(
|
||||
psbt,
|
||||
key,
|
||||
secp,
|
||||
|pk| SinglePubKey::XOnly((*pk).into()),
|
||||
|input, pk| {
|
||||
let pk = match pk {
|
||||
SinglePubKey::XOnly(pk) => pk,
|
||||
_ => return false,
|
||||
};
|
||||
impl<T: ScriptContext + 'static> SigExt for T {
|
||||
fn make_signature(
|
||||
key: &DescriptorPublicKey,
|
||||
signers: &SignersContainer,
|
||||
build_sat: BuildSatisfaction,
|
||||
secp: &SecpCtx,
|
||||
) -> Policy {
|
||||
if T::as_enum().is_taproot() {
|
||||
make_generic_signature(
|
||||
key,
|
||||
signers,
|
||||
build_sat,
|
||||
secp,
|
||||
|| SatisfiableItem::SchnorrSignature(PkOrF::from_key(key, secp)),
|
||||
|psbt| Self::find_signature(psbt, key, secp),
|
||||
)
|
||||
} else {
|
||||
make_generic_signature(
|
||||
key,
|
||||
signers,
|
||||
build_sat,
|
||||
secp,
|
||||
|| SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)),
|
||||
|psbt| Self::find_signature(psbt, key, secp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// This assumes the internal key is never used in the script leaves, which I think is
|
||||
// reasonable
|
||||
match &input.tap_internal_key {
|
||||
Some(ik) if ik == pk => input.tap_key_sig.is_some(),
|
||||
_ => input.tap_script_sigs.keys().any(|(sk, _)| sk == pk),
|
||||
}
|
||||
},
|
||||
)
|
||||
fn find_signature(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
|
||||
if T::as_enum().is_taproot() {
|
||||
generic_sig_in_psbt(
|
||||
psbt,
|
||||
key,
|
||||
secp,
|
||||
|input, pk| {
|
||||
let pk = match pk {
|
||||
SinglePubKey::XOnly(pk) => pk,
|
||||
_ => return false,
|
||||
};
|
||||
|
||||
if input.tap_internal_key == Some(*pk) && input.tap_key_sig.is_some() {
|
||||
true
|
||||
} else {
|
||||
input.tap_script_sigs.keys().any(|(sk, _)| sk == pk)
|
||||
}
|
||||
},
|
||||
|input, fing| {
|
||||
input
|
||||
.tap_key_origins
|
||||
.iter()
|
||||
.find(|(_, (_, (f, _)))| f == &fing)
|
||||
.map(|(pk, _)| SinglePubKey::XOnly(*pk))
|
||||
},
|
||||
)
|
||||
} else {
|
||||
generic_sig_in_psbt(
|
||||
psbt,
|
||||
key,
|
||||
secp,
|
||||
|input, pk| match pk {
|
||||
SinglePubKey::FullKey(pk) => input.partial_sigs.contains_key(pk),
|
||||
_ => false,
|
||||
},
|
||||
|input, fing| {
|
||||
input
|
||||
.bip32_derivation
|
||||
.iter()
|
||||
.find(|(_, (f, _))| f == &fing)
|
||||
.map(|(pk, _)| SinglePubKey::FullKey(PublicKey::new(*pk)))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
|
||||
@ -848,8 +886,10 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
|
||||
Ok(match &self.node {
|
||||
// Leaves
|
||||
Terminal::True | Terminal::False => None,
|
||||
Terminal::PkK(pubkey) => Some(signature(pubkey, signers, build_sat, secp)),
|
||||
Terminal::PkH(pubkey_hash) => Some(signature(pubkey_hash, signers, build_sat, secp)),
|
||||
Terminal::PkK(pubkey) => Some(Ctx::make_signature(pubkey, signers, build_sat, secp)),
|
||||
Terminal::PkH(pubkey_hash) => {
|
||||
Some(Ctx::make_signature(pubkey_hash, signers, build_sat, secp))
|
||||
}
|
||||
Terminal::After(value) => {
|
||||
let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { value: *value }.into();
|
||||
policy.contribution = Satisfaction::Complete {
|
||||
@ -910,15 +950,9 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
|
||||
Terminal::Hash160(hash) => {
|
||||
Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
|
||||
}
|
||||
Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => Policy::make_multisig(
|
||||
pks,
|
||||
signers,
|
||||
build_sat,
|
||||
*k,
|
||||
false,
|
||||
!Ctx::as_enum().is_taproot(),
|
||||
secp,
|
||||
)?,
|
||||
Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => {
|
||||
Policy::make_multisig::<Ctx>(pks, signers, build_sat, *k, false, secp)?
|
||||
}
|
||||
// Identities
|
||||
Terminal::Alt(inner)
|
||||
| Terminal::Swap(inner)
|
||||
@ -1008,28 +1042,42 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
|
||||
build_sat: BuildSatisfaction,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<Option<Policy>, Error> {
|
||||
fn make_sortedmulti<Ctx: ScriptContext>(
|
||||
fn make_sortedmulti<Ctx: ScriptContext + 'static>(
|
||||
keys: &SortedMultiVec<DescriptorPublicKey, Ctx>,
|
||||
signers: &SignersContainer,
|
||||
build_sat: BuildSatisfaction,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<Option<Policy>, Error> {
|
||||
Ok(Policy::make_multisig(
|
||||
Ok(Policy::make_multisig::<Ctx>(
|
||||
keys.pks.as_ref(),
|
||||
signers,
|
||||
build_sat,
|
||||
keys.k,
|
||||
true,
|
||||
true,
|
||||
secp,
|
||||
)?)
|
||||
}
|
||||
|
||||
match self {
|
||||
Descriptor::Pkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))),
|
||||
Descriptor::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))),
|
||||
Descriptor::Pkh(pk) => Ok(Some(miniscript::Legacy::make_signature(
|
||||
pk.as_inner(),
|
||||
signers,
|
||||
build_sat,
|
||||
secp,
|
||||
))),
|
||||
Descriptor::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
|
||||
pk.as_inner(),
|
||||
signers,
|
||||
build_sat,
|
||||
secp,
|
||||
))),
|
||||
Descriptor::Sh(sh) => match sh.as_inner() {
|
||||
ShInner::Wpkh(pk) => Ok(Some(signature(pk.as_inner(), signers, build_sat, secp))),
|
||||
ShInner::Wpkh(pk) => Ok(Some(miniscript::Segwitv0::make_signature(
|
||||
pk.as_inner(),
|
||||
signers,
|
||||
build_sat,
|
||||
secp,
|
||||
))),
|
||||
ShInner::Ms(ms) => Ok(ms.extract_policy(signers, build_sat, secp)?),
|
||||
ShInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
|
||||
ShInner::Wsh(wsh) => match wsh.as_inner() {
|
||||
@ -1045,17 +1093,26 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
|
||||
},
|
||||
Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
|
||||
Descriptor::Tr(tr) => {
|
||||
let mut items = vec![signature(tr.internal_key(), signers, build_sat, secp)];
|
||||
items.append(
|
||||
&mut tr
|
||||
.iter_scripts()
|
||||
.filter_map(|(_, ms)| {
|
||||
ms.extract_policy(signers, build_sat, secp).transpose()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
);
|
||||
// If there's no tap tree, treat this as a single sig, otherwise build a `Thresh`
|
||||
// node with threshold = 1 and the key spend signature plus all the tree leaves
|
||||
let key_spend_sig =
|
||||
miniscript::Tap::make_signature(tr.internal_key(), signers, build_sat, secp);
|
||||
|
||||
Ok(Policy::make_thresh(items, 1)?)
|
||||
if tr.taptree().is_none() {
|
||||
Ok(Some(key_spend_sig))
|
||||
} else {
|
||||
let mut items = vec![key_spend_sig];
|
||||
items.append(
|
||||
&mut tr
|
||||
.iter_scripts()
|
||||
.filter_map(|(_, ms)| {
|
||||
ms.extract_policy(signers, build_sat, secp).transpose()
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
);
|
||||
|
||||
Ok(Policy::make_thresh(items, 1)?)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1109,30 +1166,26 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||
);
|
||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
||||
|
||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||
);
|
||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
||||
);
|
||||
@ -1148,7 +1201,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1156,8 +1209,8 @@ mod test {
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||
&& keys[0].fingerprint.unwrap() == fingerprint0
|
||||
&& keys[1].fingerprint.unwrap() == fingerprint1)
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
);
|
||||
// TODO should this be "Satisfaction::None" since we have no prv keys?
|
||||
// TODO should items and conditions not be empty?
|
||||
@ -1180,15 +1233,15 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2usize
|
||||
&& keys[0].fingerprint.unwrap() == fingerprint0
|
||||
&& keys[1].fingerprint.unwrap() == fingerprint1)
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
);
|
||||
|
||||
assert!(
|
||||
@ -1212,7 +1265,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1220,8 +1273,8 @@ mod test {
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
||||
&& keys[0].fingerprint.unwrap() == fingerprint0
|
||||
&& keys[1].fingerprint.unwrap() == fingerprint1)
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
);
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||
@ -1244,7 +1297,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1252,8 +1305,8 @@ mod test {
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &2
|
||||
&& keys[0].fingerprint.unwrap() == fingerprint0
|
||||
&& keys[1].fingerprint.unwrap() == fingerprint1)
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
);
|
||||
|
||||
assert!(
|
||||
@ -1277,15 +1330,13 @@ mod test {
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let single_key = wallet_desc.derive(0);
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = single_key
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||
);
|
||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
||||
|
||||
let desc = descriptor!(wpkh(prvkey)).unwrap();
|
||||
@ -1293,15 +1344,13 @@ mod test {
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let single_key = wallet_desc.derive(0);
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = single_key
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||
);
|
||||
assert!(matches!(&policy.item, EcdsaSignature(PkOrF::Fingerprint(f)) if f == &fingerprint));
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
||||
);
|
||||
@ -1320,7 +1369,7 @@ mod test {
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let single_key = wallet_desc.derive(0);
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = single_key
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1328,8 +1377,8 @@ mod test {
|
||||
|
||||
assert!(
|
||||
matches!(&policy.item, Multisig { keys, threshold } if threshold == &1
|
||||
&& keys[0].fingerprint.unwrap() == fingerprint0
|
||||
&& keys[1].fingerprint.unwrap() == fingerprint1)
|
||||
&& keys[0] == PkOrF::Fingerprint(fingerprint0)
|
||||
&& keys[1] == PkOrF::Fingerprint(fingerprint1))
|
||||
);
|
||||
assert!(
|
||||
matches!(&policy.contribution, Satisfaction::PartialComplete { n, m, items, conditions, .. } if n == &2
|
||||
@ -1363,7 +1412,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1402,7 +1451,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1427,7 +1476,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1445,7 +1494,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
@ -1467,10 +1516,10 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers = keymap.into();
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers, BuildSatisfaction::None, &secp)
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
@ -1533,7 +1582,7 @@ mod test {
|
||||
addr.to_string()
|
||||
);
|
||||
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
|
||||
let psbt = Psbt::from_str(ALICE_SIGNED_PSBT).unwrap();
|
||||
|
||||
@ -1594,7 +1643,7 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
|
||||
let addr = wallet_desc
|
||||
.as_derived(0, &secp)
|
||||
@ -1682,9 +1731,185 @@ mod test {
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::from(keymap));
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
|
||||
let policy = wallet_desc.extract_policy(&signers_container, BuildSatisfaction::None, &secp);
|
||||
assert!(policy.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_tr_key_spend() {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let (prvkey, _, fingerprint) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||
|
||||
let desc = descriptor!(tr(prvkey)).unwrap();
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
policy,
|
||||
Some(Policy {
|
||||
id: "48u0tz0n".to_string(),
|
||||
item: SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(fingerprint)),
|
||||
satisfaction: Satisfaction::None,
|
||||
contribution: Satisfaction::Complete {
|
||||
condition: Condition::default()
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_tr_script_spend() {
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let (alice_prv, _, alice_fing) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||
let (_, bob_pub, bob_fing) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||
|
||||
let desc = descriptor!(tr(bob_pub, pk(alice_prv))).unwrap();
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
let signers_container = Arc::new(SignersContainer::build(keymap, &wallet_desc, &secp));
|
||||
|
||||
let policy = wallet_desc
|
||||
.extract_policy(&signers_container, BuildSatisfaction::None, &secp)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(policy.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
||||
);
|
||||
assert!(
|
||||
matches!(policy.contribution, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![1])
|
||||
);
|
||||
|
||||
let alice_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(alice_fing));
|
||||
let bob_sig = SatisfiableItem::SchnorrSignature(PkOrF::Fingerprint(bob_fing));
|
||||
|
||||
let thresh_items = match policy.item {
|
||||
SatisfiableItem::Thresh { items, .. } => items,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert_eq!(thresh_items[0].item, bob_sig);
|
||||
assert_eq!(thresh_items[1].item, alice_sig);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_tr_satisfaction_key_spend() {
|
||||
const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSIRYnkGTDxwXMHP32fkDFoGJY28trxbkkVgR2z7jZa2pOJA0AyRF8LgAAAIADAAAAARcgJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQAAA==";
|
||||
const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAUKgMCqtGLSiGYhsTols2UJ/VQQgQi/SXO38uXs2SahdAQAAAAD/////ARyWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRIEiEBFjbZa1xdjLfFjrKzuC1F1LeRyI/gL6IuGKNmUuSARNAIsRvARpRxuyQosVA7guRQT9vXr+S25W2tnP2xOGBsSgq7A4RL8yrbvwDmNlWw9R0Nc/6t+IsyCyy7dD/lbUGgyEWJ5Bkw8cFzBz99n5AxaBiWNvLa8W5JFYEds+42WtqTiQNAMkRfC4AAACAAwAAAAEXICeQZMPHBcwc/fZ+QMWgYljby2vFuSRWBHbPuNlrak4kAAA=";
|
||||
|
||||
let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
|
||||
let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
|
||||
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let (_, pubkey, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||
|
||||
let desc = descriptor!(tr(pubkey)).unwrap();
|
||||
let (wallet_desc, _) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
|
||||
let policy_unsigned = wallet_desc
|
||||
.extract_policy(
|
||||
&SignersContainer::default(),
|
||||
BuildSatisfaction::Psbt(&unsigned_psbt),
|
||||
&secp,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let policy_signed = wallet_desc
|
||||
.extract_policy(
|
||||
&SignersContainer::default(),
|
||||
BuildSatisfaction::Psbt(&signed_psbt),
|
||||
&secp,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(policy_unsigned.satisfaction, Satisfaction::None);
|
||||
assert_eq!(
|
||||
policy_signed.satisfaction,
|
||||
Satisfaction::Complete {
|
||||
condition: Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_tr_satisfaction_script_spend() {
|
||||
const UNSIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2IhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
|
||||
const SIGNED_PSBT: &str = "cHNidP8BAFMBAAAAAWZalxaErOL7P3WPIUc8DsjgE68S+ww+uqiqEI2SAwlPAAAAAAD/////AQiWmAAAAAAAF6kU4R3W8CnGzZcSsaovTYu0X8vHt3WHAAAAAAABASuAlpgAAAAAACJRINa6bLPZwp3/CYWoxyI3mLYcSC5f9LInAMUng94nspa2AQcAAQhCAUALcP9w/+Ddly9DWdhHTnQ9uCDWLPZjR6vKbKePswW2Ee6W5KNfrklus/8z98n7BQ1U4vADHk0FbadeeL8rrbHlARNAC3D/cP/g3ZcvQ1nYR050Pbgg1iz2Y0erymynj7MFthHuluSjX65JbrP/M/fJ+wUNVOLwAx5NBW2nXni/K62x5UEUeEbK57HG1FUp69HHhjBZH9bSvss8e3qhLoMuXPK5hBr2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHUAXNmWieJ80Fs+PMa2C186YOBPZbYG/ieEUkagMwzJ788SoCucNdp5wnxfpuJVygFhglDrXGzujFtC82PrMohwuIhXBgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYjIHhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQarMAhFnhGyuexxtRVKevRx4YwWR/W0r7LPHt6oS6DLlzyuYQaLQH2onWFc3UR6I9ZhuHVeJCi5LNAf4APVd7mHn4BhdViHRwu7j4AAACAAgAAACEWgiPY+kcolS1Hp0niOK/+7VHz6F+nsz8JVxnzWzkgToYNAMkRfC4AAACAAgAAAAEXIIIj2PpHKJUtR6dJ4jiv/u1R8+hfp7M/CVcZ81s5IE6GARgg9qJ1hXN1EeiPWYbh1XiQouSzQH+AD1Xe5h5+AYXVYh0AAA==";
|
||||
|
||||
let unsigned_psbt = Psbt::from_str(UNSIGNED_PSBT).unwrap();
|
||||
let signed_psbt = Psbt::from_str(SIGNED_PSBT).unwrap();
|
||||
|
||||
let secp = Secp256k1::new();
|
||||
|
||||
let (_, alice_pub, _) = setup_keys(ALICE_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||
let (_, bob_pub, _) = setup_keys(BOB_TPRV_STR, ALICE_BOB_PATH, &secp);
|
||||
|
||||
let desc = descriptor!(tr(bob_pub, pk(alice_pub))).unwrap();
|
||||
let (wallet_desc, _) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
|
||||
let policy_unsigned = wallet_desc
|
||||
.extract_policy(
|
||||
&SignersContainer::default(),
|
||||
BuildSatisfaction::Psbt(&unsigned_psbt),
|
||||
&secp,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let policy_signed = wallet_desc
|
||||
.extract_policy(
|
||||
&SignersContainer::default(),
|
||||
BuildSatisfaction::Psbt(&signed_psbt),
|
||||
&secp,
|
||||
)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
matches!(policy_unsigned.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
||||
);
|
||||
assert!(
|
||||
matches!(policy_unsigned.satisfaction, Satisfaction::Partial { n: 2, m: 1, items, .. } if items.is_empty())
|
||||
);
|
||||
|
||||
assert!(
|
||||
matches!(policy_signed.item, SatisfiableItem::Thresh { ref items, threshold: 1 } if items.len() == 2)
|
||||
);
|
||||
assert!(
|
||||
matches!(policy_signed.satisfaction, Satisfaction::PartialComplete { n: 2, m: 1, items, .. } if items == vec![0, 1])
|
||||
);
|
||||
|
||||
let satisfied_items = match policy_signed.item {
|
||||
SatisfiableItem::Thresh { items, .. } => items,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
satisfied_items[0].satisfaction,
|
||||
Satisfaction::Complete {
|
||||
condition: Default::default()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
satisfied_items[1].satisfaction,
|
||||
Satisfaction::Complete {
|
||||
condition: Default::default()
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -792,13 +792,18 @@ pub fn make_pkh<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||
|
||||
// Used internally by `bdk::fragment!` to build `multi()` fragments
|
||||
#[doc(hidden)]
|
||||
pub fn make_multi<Pk: IntoDescriptorKey<Ctx>, Ctx: ScriptContext>(
|
||||
pub fn make_multi<
|
||||
Pk: IntoDescriptorKey<Ctx>,
|
||||
Ctx: ScriptContext,
|
||||
V: Fn(usize, Vec<DescriptorPublicKey>) -> Terminal<DescriptorPublicKey, Ctx>,
|
||||
>(
|
||||
thresh: usize,
|
||||
variant: V,
|
||||
pks: Vec<Pk>,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(Miniscript<DescriptorPublicKey, Ctx>, KeyMap, ValidNetworks), DescriptorError> {
|
||||
let (pks, key_map, valid_networks) = expand_multi_keys(pks, secp)?;
|
||||
let minisc = Miniscript::from_ast(Terminal::Multi(thresh, pks))?;
|
||||
let minisc = Miniscript::from_ast(variant(thresh, pks))?;
|
||||
|
||||
minisc.check_miniscript()?;
|
||||
|
||||
|
@ -387,11 +387,33 @@ macro_rules! bdk_blockchain_tests {
|
||||
Wallet::new(&descriptors.0.to_string(), descriptors.1.as_ref(), Network::Regtest, MemoryDatabase::new()).unwrap()
|
||||
}
|
||||
|
||||
fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
|
||||
enum WalletType {
|
||||
WpkhSingleSig,
|
||||
TaprootKeySpend,
|
||||
TaprootScriptSpend,
|
||||
TaprootScriptSpend2,
|
||||
TaprootScriptSpend3,
|
||||
}
|
||||
|
||||
fn init_wallet(ty: WalletType) -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
let descriptors = testutils! {
|
||||
@descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
|
||||
let descriptors = match ty {
|
||||
WalletType::WpkhSingleSig => testutils! {
|
||||
@descriptors ( "wpkh(Alice)" ) ( "wpkh(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
|
||||
},
|
||||
WalletType::TaprootKeySpend => testutils! {
|
||||
@descriptors ( "tr(Alice)" ) ( "tr(Alice)" ) ( @keys ( "Alice" => (@generate_xprv "/44'/0'/0'/0/*", "/44'/0'/0'/1/*") ) )
|
||||
},
|
||||
WalletType::TaprootScriptSpend => testutils! {
|
||||
@descriptors ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( "tr(Key,and_v(v:pk(Script),older(6)))" ) ( @keys ( "Key" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Script" => (@generate_xprv "/0/*", "/1/*") ) )
|
||||
},
|
||||
WalletType::TaprootScriptSpend2 => testutils! {
|
||||
@descriptors ( "tr(Alice,pk(Bob))" ) ( "tr(Alice,pk(Bob))" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*") ) )
|
||||
},
|
||||
WalletType::TaprootScriptSpend3 => testutils! {
|
||||
@descriptors ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( "tr(Alice,{pk(Bob),pk(Carol)})" ) ( @keys ( "Alice" => (@literal "30e14486f993d5a2d222770e97286c56cec5af115e1fb2e0065f476a0fcf8788"), "Bob" => (@generate_xprv "/0/*", "/1/*"), "Carol" => (@generate_xprv "/0/*", "/1/*") ) )
|
||||
},
|
||||
};
|
||||
|
||||
let test_client = TestClient::default();
|
||||
@ -405,6 +427,10 @@ macro_rules! bdk_blockchain_tests {
|
||||
(wallet, blockchain, descriptors, test_client)
|
||||
}
|
||||
|
||||
fn init_single_sig() -> (Wallet<MemoryDatabase>, $blockchain, (String, Option<String>), TestClient) {
|
||||
init_wallet(WalletType::WpkhSingleSig)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sync_simple() {
|
||||
use std::ops::Deref;
|
||||
@ -1203,6 +1229,130 @@ macro_rules! bdk_blockchain_tests {
|
||||
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_key_spend() {
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_wallet(WalletType::TaprootKeySpend);
|
||||
|
||||
let _ = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 )
|
||||
});
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
||||
|
||||
let tx = {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000);
|
||||
let (mut psbt, _details) = builder.finish().unwrap();
|
||||
wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
psbt.extract_tx()
|
||||
};
|
||||
blockchain.broadcast(&tx).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_script_spend() {
|
||||
let (wallet, blockchain, descriptors, mut test_client) = init_wallet(WalletType::TaprootScriptSpend);
|
||||
|
||||
let _ = test_client.receive(testutils! {
|
||||
@tx ( (@external descriptors, 0) => 50_000 ) ( @confirmations 6 )
|
||||
});
|
||||
wallet.sync(&blockchain, SyncOptions::default()).unwrap();
|
||||
assert_eq!(wallet.get_balance().unwrap(), 50_000);
|
||||
|
||||
let ext_policy = wallet.policies(KeychainKind::External).unwrap().unwrap();
|
||||
let int_policy = wallet.policies(KeychainKind::Internal).unwrap().unwrap();
|
||||
|
||||
let ext_path = vec![(ext_policy.id.clone(), vec![1])].into_iter().collect();
|
||||
let int_path = vec![(int_policy.id.clone(), vec![1])].into_iter().collect();
|
||||
|
||||
let tx = {
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(test_client.get_node_address(None).script_pubkey(), 25_000)
|
||||
.policy_path(ext_path, KeychainKind::External)
|
||||
.policy_path(int_path, KeychainKind::Internal);
|
||||
let (mut psbt, _details) = builder.finish().unwrap();
|
||||
wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
psbt.extract_tx()
|
||||
};
|
||||
blockchain.broadcast(&tx).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_taproot_core_keyspend_psbt() {
|
||||
test_sign_taproot_core_psbt(WalletType::TaprootKeySpend);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_taproot_core_scriptspend2_psbt() {
|
||||
test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sign_taproot_core_scriptspend3_psbt() {
|
||||
test_sign_taproot_core_psbt(WalletType::TaprootScriptSpend3);
|
||||
}
|
||||
|
||||
fn test_sign_taproot_core_psbt(wallet_type: WalletType) {
|
||||
use std::str::FromStr;
|
||||
use serde_json;
|
||||
use bitcoincore_rpc::jsonrpc::serde_json::Value;
|
||||
use bitcoincore_rpc::{Auth, Client, RpcApi};
|
||||
|
||||
let (wallet, _blockchain, _descriptors, test_client) = init_wallet(wallet_type);
|
||||
|
||||
// TODO replace once rust-bitcoincore-rpc with PR 174 released
|
||||
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/174
|
||||
let _createwallet_result: Value = test_client.bitcoind.client.call("createwallet", &["taproot_wallet".into(), true.into(), true.into(), serde_json::to_value("").unwrap(), false.into(), true.into(), true.into(), false.into()]).expect("created wallet");
|
||||
|
||||
let external_descriptor = wallet.get_descriptor_for_keychain(KeychainKind::External);
|
||||
|
||||
// TODO replace once bitcoind released with support for rust-bitcoincore-rpc PR 174
|
||||
let taproot_wallet_client = Client::new(&test_client.bitcoind.rpc_url_with_wallet("taproot_wallet"), Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone())).unwrap();
|
||||
|
||||
let descriptor_info = taproot_wallet_client.get_descriptor_info(external_descriptor.to_string().as_str()).expect("descriptor info");
|
||||
|
||||
let import_descriptor_args = json!([{
|
||||
"desc": descriptor_info.descriptor,
|
||||
"active": true,
|
||||
"timestamp": "now",
|
||||
"label":"taproot key spend",
|
||||
}]);
|
||||
let _importdescriptors_result: Value = taproot_wallet_client.call("importdescriptors", &[import_descriptor_args]).expect("import wallet");
|
||||
let generate_to_address: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).expect("new address");
|
||||
let _generatetoaddress_result = taproot_wallet_client.generate_to_address(101, &generate_to_address).expect("generated to address");
|
||||
let send_to_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string();
|
||||
let change_address = wallet.get_address($crate::wallet::AddressIndex::New).unwrap().address.to_string();
|
||||
let send_addr_amounts = json!([{
|
||||
send_to_address: "0.4321"
|
||||
}]);
|
||||
let send_options = json!({
|
||||
"change_address": change_address,
|
||||
"psbt": true,
|
||||
});
|
||||
let send_result: Value = taproot_wallet_client.call("send", &[send_addr_amounts, Value::Null, "unset".into(), Value::Null, send_options]).expect("send psbt");
|
||||
let core_psbt = send_result["psbt"].as_str().expect("core psbt str");
|
||||
|
||||
use bitcoin::util::psbt::PartiallySignedTransaction;
|
||||
|
||||
// Test parsing core created PSBT
|
||||
let mut psbt = PartiallySignedTransaction::from_str(&core_psbt).expect("core taproot psbt");
|
||||
|
||||
// Test signing core created PSBT
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert_eq!(finalized, true);
|
||||
|
||||
// Test with updated psbt
|
||||
let update_result: Value = taproot_wallet_client.call("utxoupdatepsbt", &[core_psbt.into()]).expect("update psbt utxos");
|
||||
let core_updated_psbt = update_result.as_str().expect("core updated psbt");
|
||||
|
||||
// Test parsing core created and updated PSBT
|
||||
let mut psbt = PartiallySignedTransaction::from_str(&core_updated_psbt).expect("core taproot psbt");
|
||||
|
||||
// Test signing core created and updated PSBT
|
||||
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
|
||||
assert_eq!(finalized, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -24,13 +24,15 @@ use std::sync::Arc;
|
||||
use bitcoin::secp256k1::Secp256k1;
|
||||
|
||||
use bitcoin::consensus::encode::serialize;
|
||||
use bitcoin::util::psbt;
|
||||
use bitcoin::util::{psbt, taproot};
|
||||
use bitcoin::{
|
||||
Address, EcdsaSighashType, Network, OutPoint, Script, Transaction, TxOut, Txid, Witness,
|
||||
Address, EcdsaSighashType, Network, OutPoint, SchnorrSighashType, Script, Transaction, TxOut,
|
||||
Txid, Witness,
|
||||
};
|
||||
|
||||
use miniscript::descriptor::DescriptorTrait;
|
||||
use miniscript::psbt::PsbtInputSatisfier;
|
||||
use miniscript::ToPublicKey;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace};
|
||||
@ -50,7 +52,7 @@ pub use utils::IsDust;
|
||||
|
||||
use address_validator::AddressValidator;
|
||||
use coin_selection::DefaultCoinSelectionAlgorithm;
|
||||
use signer::{SignOptions, Signer, SignerOrdering, SignersContainer};
|
||||
use signer::{SignOptions, SignerOrdering, SignersContainer, TransactionSigner};
|
||||
use tx_builder::{BumpFee, CreateTx, FeePolicy, TxBuilder, TxParams};
|
||||
use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx};
|
||||
|
||||
@ -79,10 +81,10 @@ const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
||||
///
|
||||
/// 1. output *descriptors* from which it can derive addresses.
|
||||
/// 2. A [`Database`] where it tracks transactions and utxos related to the descriptors.
|
||||
/// 3. [`Signer`]s that can contribute signatures to addresses instantiated from the descriptors.
|
||||
/// 3. [`signer`]s that can contribute signatures to addresses instantiated from the descriptors.
|
||||
///
|
||||
/// [`Database`]: crate::database::Database
|
||||
/// [`Signer`]: crate::signer::Signer
|
||||
/// [`signer`]: crate::signer
|
||||
#[derive(Debug)]
|
||||
pub struct Wallet<D> {
|
||||
descriptor: ExtendedDescriptor,
|
||||
@ -197,7 +199,7 @@ where
|
||||
KeychainKind::External,
|
||||
get_checksum(&descriptor.to_string())?.as_bytes(),
|
||||
)?;
|
||||
let signers = Arc::new(SignersContainer::from(keymap));
|
||||
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, &secp));
|
||||
let (change_descriptor, change_signers) = match change_descriptor {
|
||||
Some(desc) => {
|
||||
let (change_descriptor, change_keymap) =
|
||||
@ -207,7 +209,11 @@ where
|
||||
get_checksum(&change_descriptor.to_string())?.as_bytes(),
|
||||
)?;
|
||||
|
||||
let change_signers = Arc::new(SignersContainer::from(change_keymap));
|
||||
let change_signers = Arc::new(SignersContainer::build(
|
||||
change_keymap,
|
||||
&change_descriptor,
|
||||
&secp,
|
||||
));
|
||||
// if !parsed.same_structure(descriptor.as_ref()) {
|
||||
// return Err(Error::DifferentDescriptorStructure);
|
||||
// }
|
||||
@ -457,7 +463,7 @@ where
|
||||
&mut self,
|
||||
keychain: KeychainKind,
|
||||
ordering: SignerOrdering,
|
||||
signer: Arc<dyn Signer>,
|
||||
signer: Arc<dyn TransactionSigner>,
|
||||
) {
|
||||
let signers = match keychain {
|
||||
KeychainKind::External => Arc::make_mut(&mut self.signers),
|
||||
@ -1008,23 +1014,27 @@ where
|
||||
// this helps us doing our job later
|
||||
self.add_input_hd_keypaths(psbt)?;
|
||||
|
||||
// If we aren't allowed to use `witness_utxo`, ensure that every input but finalized one
|
||||
// If we aren't allowed to use `witness_utxo`, ensure that every input (except p2tr and finalized ones)
|
||||
// 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())
|
||||
.filter(|i| i.tap_internal_key.is_none() && i.tap_merkle_root.is_none())
|
||||
.any(|i| i.non_witness_utxo.is_none())
|
||||
{
|
||||
return Err(Error::Signer(signer::SignerError::MissingNonWitnessUtxo));
|
||||
}
|
||||
|
||||
// If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
|
||||
// is using `SIGHASH_ALL`
|
||||
// is using `SIGHASH_ALL` or `SIGHASH_DEFAULT` for taproot
|
||||
if !sign_options.allow_all_sighashes
|
||||
&& !psbt.inputs.iter().all(|i| {
|
||||
i.sighash_type.is_none() || i.sighash_type == Some(EcdsaSighashType::All.into())
|
||||
i.sighash_type.is_none()
|
||||
|| i.sighash_type == Some(EcdsaSighashType::All.into())
|
||||
|| i.sighash_type == Some(SchnorrSighashType::All.into())
|
||||
|| i.sighash_type == Some(SchnorrSighashType::Default.into())
|
||||
})
|
||||
{
|
||||
return Err(Error::Signer(signer::SignerError::NonStandardSighash));
|
||||
@ -1036,13 +1046,7 @@ where
|
||||
.iter()
|
||||
.chain(self.change_signers.signers().iter())
|
||||
{
|
||||
if signer.sign_whole_tx() {
|
||||
signer.sign(psbt, None, &self.secp)?;
|
||||
} else {
|
||||
for index in 0..psbt.inputs.len() {
|
||||
signer.sign(psbt, Some(index), &self.secp)?;
|
||||
}
|
||||
}
|
||||
signer.sign_transaction(psbt, &self.secp)?;
|
||||
}
|
||||
|
||||
// attempt to finalize
|
||||
@ -1227,7 +1231,7 @@ where
|
||||
|
||||
let derived_descriptor = descriptor.as_derived(index, &self.secp);
|
||||
|
||||
let hd_keypaths = derived_descriptor.get_hd_keypaths(&self.secp)?;
|
||||
let hd_keypaths = derived_descriptor.get_hd_keypaths(&self.secp);
|
||||
let script = derived_descriptor.script_pubkey();
|
||||
|
||||
for validator in &self.address_validators {
|
||||
@ -1438,7 +1442,15 @@ where
|
||||
psbt_input: foreign_psbt_input,
|
||||
outpoint,
|
||||
} => {
|
||||
if !params.only_witness_utxo && foreign_psbt_input.non_witness_utxo.is_none() {
|
||||
let is_taproot = foreign_psbt_input
|
||||
.witness_utxo
|
||||
.as_ref()
|
||||
.map(|txout| txout.script_pubkey.is_v1_p2tr())
|
||||
.unwrap_or(false);
|
||||
if !is_taproot
|
||||
&& !params.only_witness_utxo
|
||||
&& foreign_psbt_input.non_witness_utxo.is_none()
|
||||
{
|
||||
return Err(Error::Generic(format!(
|
||||
"Missing non_witness_utxo on foreign utxo {}",
|
||||
outpoint
|
||||
@ -1463,7 +1475,32 @@ where
|
||||
let (desc, _) = self._get_descriptor_for_keychain(keychain);
|
||||
let derived_descriptor = desc.as_derived(child, &self.secp);
|
||||
|
||||
psbt_output.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
|
||||
if let miniscript::Descriptor::Tr(tr) = &derived_descriptor {
|
||||
let tap_tree = if tr.taptree().is_some() {
|
||||
let mut builder = taproot::TaprootBuilder::new();
|
||||
for (depth, ms) in tr.iter_scripts() {
|
||||
let script = ms.encode();
|
||||
builder = builder.add_leaf(depth, script).expect(
|
||||
"Computing spend data on a valid Tree should always succeed",
|
||||
);
|
||||
}
|
||||
Some(
|
||||
psbt::TapTree::from_builder(builder)
|
||||
.expect("The tree should always be valid"),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
psbt_output.tap_tree = tap_tree;
|
||||
psbt_output
|
||||
.tap_key_origins
|
||||
.append(&mut derived_descriptor.get_tap_key_origins(&self.secp));
|
||||
psbt_output.tap_internal_key = Some(tr.internal_key().to_x_only_pubkey());
|
||||
} else {
|
||||
psbt_output
|
||||
.bip32_derivation
|
||||
.append(&mut derived_descriptor.get_hd_keypaths(&self.secp));
|
||||
}
|
||||
if params.include_output_redeem_witness_script {
|
||||
psbt_output.witness_script = derived_descriptor.psbt_witness_script();
|
||||
psbt_output.redeem_script = derived_descriptor.psbt_redeem_script();
|
||||
@ -1496,17 +1533,35 @@ where
|
||||
|
||||
let desc = self.get_descriptor_for_keychain(keychain);
|
||||
let derived_descriptor = desc.as_derived(child, &self.secp);
|
||||
psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp)?;
|
||||
|
||||
if let miniscript::Descriptor::Tr(tr) = &derived_descriptor {
|
||||
psbt_input.tap_key_origins = derived_descriptor.get_tap_key_origins(&self.secp);
|
||||
psbt_input.tap_internal_key = Some(tr.internal_key().to_x_only_pubkey());
|
||||
|
||||
let spend_info = tr.spend_info();
|
||||
psbt_input.tap_merkle_root = spend_info.merkle_root();
|
||||
psbt_input.tap_scripts = spend_info
|
||||
.as_script_map()
|
||||
.keys()
|
||||
.filter_map(|script_ver| {
|
||||
spend_info
|
||||
.control_block(script_ver)
|
||||
.map(|cb| (cb, script_ver.clone()))
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
psbt_input.bip32_derivation = derived_descriptor.get_hd_keypaths(&self.secp);
|
||||
}
|
||||
|
||||
psbt_input.redeem_script = derived_descriptor.psbt_redeem_script();
|
||||
psbt_input.witness_script = derived_descriptor.psbt_witness_script();
|
||||
|
||||
let prev_output = utxo.outpoint;
|
||||
if let Some(prev_tx) = self.database.borrow().get_raw_tx(&prev_output.txid)? {
|
||||
if desc.is_witness() {
|
||||
if desc.is_witness() || desc.is_taproot() {
|
||||
psbt_input.witness_utxo = Some(prev_tx.output[prev_output.vout as usize].clone());
|
||||
}
|
||||
if !desc.is_witness() || !only_witness_utxo {
|
||||
if !desc.is_taproot() && (!desc.is_witness() || !only_witness_utxo) {
|
||||
psbt_input.non_witness_utxo = Some(prev_tx);
|
||||
}
|
||||
}
|
||||
@ -1532,12 +1587,19 @@ where
|
||||
{
|
||||
debug!("Found descriptor {:?}/{}", keychain, child);
|
||||
|
||||
// merge hd_keypaths
|
||||
// merge hd_keypaths or tap_key_origins
|
||||
let desc = self.get_descriptor_for_keychain(keychain);
|
||||
let mut hd_keypaths = desc
|
||||
.as_derived(child, &self.secp)
|
||||
.get_hd_keypaths(&self.secp)?;
|
||||
psbt_input.bip32_derivation.append(&mut hd_keypaths);
|
||||
if desc.is_taproot() {
|
||||
let mut tap_key_origins = desc
|
||||
.as_derived(child, &self.secp)
|
||||
.get_tap_key_origins(&self.secp);
|
||||
psbt_input.tap_key_origins.append(&mut tap_key_origins);
|
||||
} else {
|
||||
let mut hd_keypaths = desc
|
||||
.as_derived(child, &self.secp)
|
||||
.get_hd_keypaths(&self.secp);
|
||||
psbt_input.bip32_derivation.append(&mut hd_keypaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1792,6 +1854,26 @@ pub(crate) mod test {
|
||||
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
|
||||
}
|
||||
|
||||
pub(crate) fn get_test_tr_single_sig() -> &'static str {
|
||||
"tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)"
|
||||
}
|
||||
|
||||
pub(crate) fn get_test_tr_with_taptree() -> &'static str {
|
||||
"tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(cPZzKuNmpuUjD1e8jUU4PVzy2b5LngbSip8mBsxf4e7rSFZVb4Uh),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
|
||||
}
|
||||
|
||||
pub(crate) fn get_test_tr_repeated_key() -> &'static str {
|
||||
"tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100)),and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(200))})"
|
||||
}
|
||||
|
||||
pub(crate) fn get_test_tr_single_sig_xprv() -> &'static str {
|
||||
"tr(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*)"
|
||||
}
|
||||
|
||||
pub(crate) fn get_test_tr_with_taptree_xprv() -> &'static str {
|
||||
"tr(b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55,{pk(tprv8ZgxMBicQKsPdDArR4xSAECuVxeX1jwwSXR4ApKbkYgZiziDc4LdBy2WvJeGDfUSE4UT4hHhbgEwbdq8ajjUHiKDegkwrNU6V55CxcxonVN/*),pk(8aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642)})"
|
||||
}
|
||||
|
||||
macro_rules! assert_fee_rate {
|
||||
($tx:expr, $fees:expr, $fee_rate:expr $( ,@dust_change $( $dust_change:expr )* )* $( ,@add_signature $( $add_signature:expr )* )* ) => ({
|
||||
let mut tx = $tx.clone();
|
||||
@ -1821,6 +1903,17 @@ pub(crate) mod test {
|
||||
});
|
||||
}
|
||||
|
||||
macro_rules! from_str {
|
||||
($e:expr, $t:ty) => {{
|
||||
use std::str::FromStr;
|
||||
<$t>::from_str($e).unwrap()
|
||||
}};
|
||||
|
||||
($e:expr) => {
|
||||
from_str!($e, _)
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "NoRecipients")]
|
||||
fn test_create_tx_empty_recipients() {
|
||||
@ -4097,4 +4190,385 @@ pub(crate) mod test {
|
||||
"when there's no internal descriptor it should just use external"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_psbt_populate_tap_key_origins() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
|
||||
let addr = wallet.get_address(AddressIndex::New).unwrap();
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
psbt.inputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
from_str!("b96d3a3dc76a4fc74e976511b23aecb78e0754c23c0ed7a6513e18cbbc7178e9"),
|
||||
(vec![], (from_str!("f6a5cb8b"), from_str!("m/0")))
|
||||
)],
|
||||
"Wrong input tap_key_origins"
|
||||
);
|
||||
assert_eq!(
|
||||
psbt.outputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
from_str!("e9b03068cf4a2621d4f81e68f6c4216e6bd260fe6edf6acc55c8d8ae5aeff0a8"),
|
||||
(vec![], (from_str!("f6a5cb8b"), from_str!("m/1")))
|
||||
)],
|
||||
"Wrong output tap_key_origins"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_psbt_populate_tap_key_origins_repeated_key() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_repeated_key());
|
||||
let addr = wallet.get_address(AddressIndex::New).unwrap();
|
||||
|
||||
let path = vec![("rn4nre9c".to_string(), vec![0])]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 25_000)
|
||||
.policy_path(path, KeychainKind::External);
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
psbt.inputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
|
||||
(
|
||||
vec![
|
||||
from_str!(
|
||||
"858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
|
||||
),
|
||||
from_str!(
|
||||
"f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
|
||||
)
|
||||
],
|
||||
(Default::default(), Default::default())
|
||||
)
|
||||
)],
|
||||
"Wrong input tap_key_origins"
|
||||
);
|
||||
assert_eq!(
|
||||
psbt.outputs[0]
|
||||
.tap_key_origins
|
||||
.clone()
|
||||
.into_iter()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![(
|
||||
from_str!("2b0558078bec38694a84933d659303e2575dae7e91685911454115bfd64487e3"),
|
||||
(
|
||||
vec![
|
||||
from_str!(
|
||||
"858ad7a7d7f270e2c490c4d6ba00c499e46b18fdd59ea3c2c47d20347110271e"
|
||||
),
|
||||
from_str!(
|
||||
"f6e927ad4492c051fe325894a4f5f14538333b55a35f099876be42009ec8f903"
|
||||
)
|
||||
],
|
||||
(Default::default(), Default::default())
|
||||
)
|
||||
)],
|
||||
"Wrong output tap_key_origins"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_psbt_input_tap_tree() {
|
||||
use crate::bitcoin::psbt::serialize::Deserialize;
|
||||
use crate::bitcoin::psbt::TapTree;
|
||||
use bitcoin::hashes::hex::FromHex;
|
||||
use bitcoin::util::taproot;
|
||||
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree());
|
||||
let addr = wallet.get_address(AddressIndex::Peek(0)).unwrap();
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.drain_to(addr.script_pubkey()).drain_wallet();
|
||||
let (psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
psbt.inputs[0].tap_merkle_root,
|
||||
Some(
|
||||
FromHex::from_hex(
|
||||
"61f81509635053e52d9d1217545916167394490da2287aca4693606e43851986"
|
||||
)
|
||||
.unwrap()
|
||||
),
|
||||
);
|
||||
assert_eq!(
|
||||
psbt.inputs[0].tap_scripts.clone().into_iter().collect::<Vec<_>>(),
|
||||
vec![
|
||||
(taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b7ef769a745e625ed4b9a4982a4dc08274c59187e73e6f07171108f455081cb2").unwrap()).unwrap(), (from_str!("208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac"), taproot::LeafVersion::TapScript)),
|
||||
(taproot::ControlBlock::from_slice(&Vec::<u8>::from_hex("c0b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55b9a515f7be31a70186e3c5937ee4a70cc4b4e1efe876c1d38e408222ffc64834").unwrap()).unwrap(), (from_str!("2051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac"), taproot::LeafVersion::TapScript)),
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
psbt.inputs[0].tap_internal_key,
|
||||
Some(from_str!(
|
||||
"b511bd5771e47ee27558b1765e87b541668304ec567721c7b880edc0a010da55"
|
||||
))
|
||||
);
|
||||
|
||||
// Since we are creating an output to the same address as the input, assert that the
|
||||
// internal_key is the same
|
||||
assert_eq!(
|
||||
psbt.inputs[0].tap_internal_key,
|
||||
psbt.outputs[0].tap_internal_key
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
psbt.outputs[0].tap_tree,
|
||||
Some(TapTree::deserialize(&Vec::<u8>::from_hex("01c022208aee2b8120a5f157f1223f72b5e62b825831a27a9fdf427db7cc697494d4a642ac01c0222051494dc22e24a32fe9dcfbd7e85faf345fa1df296fb49d156e859ef345201295ac",).unwrap()).unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_sign_missing_witness_utxo() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
|
||||
let addr = wallet.get_address(New).unwrap();
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.drain_to(addr.script_pubkey()).drain_wallet();
|
||||
let (mut psbt, _) = builder.finish().unwrap();
|
||||
let witness_utxo = psbt.inputs[0].witness_utxo.take();
|
||||
|
||||
let result = wallet.sign(
|
||||
&mut psbt,
|
||||
SignOptions {
|
||||
allow_all_sighashes: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Signing should have failed because the witness_utxo is missing"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result.unwrap_err(),
|
||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
||||
),
|
||||
"Signing failed with the wrong error type"
|
||||
);
|
||||
|
||||
// restore the witness_utxo
|
||||
psbt.inputs[0].witness_utxo = witness_utxo;
|
||||
|
||||
let result = wallet.sign(
|
||||
&mut psbt,
|
||||
SignOptions {
|
||||
allow_all_sighashes: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "Signing should have worked");
|
||||
assert!(
|
||||
result.unwrap(),
|
||||
"Should finalize the input since we can produce signatures"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_foreign_utxo() {
|
||||
let (wallet1, _, _) = get_funded_wallet(get_test_wpkh());
|
||||
let (wallet2, _, _) = get_funded_wallet(get_test_tr_single_sig());
|
||||
|
||||
let addr = Address::from_str("2N1Ffz3WaNzbeLFBb51xyFMHYSEUXcbiSoX").unwrap();
|
||||
let utxo = wallet2.list_unspent().unwrap().remove(0);
|
||||
let psbt_input = wallet2.get_psbt_input(utxo.clone(), None, false).unwrap();
|
||||
let foreign_utxo_satisfaction = wallet2
|
||||
.get_descriptor_for_keychain(KeychainKind::External)
|
||||
.max_satisfaction_weight()
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
psbt_input.non_witness_utxo.is_none(),
|
||||
"`non_witness_utxo` should never be populated for taproot"
|
||||
);
|
||||
|
||||
let mut builder = wallet1.build_tx();
|
||||
builder
|
||||
.add_recipient(addr.script_pubkey(), 60_000)
|
||||
.add_foreign_utxo(utxo.outpoint, psbt_input, foreign_utxo_satisfaction)
|
||||
.unwrap();
|
||||
let (psbt, details) = builder.finish().unwrap();
|
||||
|
||||
assert_eq!(
|
||||
details.sent - details.received,
|
||||
10_000 + details.fee.unwrap_or(0),
|
||||
"we should have only net spent ~10_000"
|
||||
);
|
||||
|
||||
assert!(
|
||||
psbt.unsigned_tx
|
||||
.input
|
||||
.iter()
|
||||
.any(|input| input.previous_output == utxo.outpoint),
|
||||
"foreign_utxo should be in there"
|
||||
);
|
||||
}
|
||||
|
||||
fn test_spend_from_wallet(wallet: Wallet<AnyDatabase>) {
|
||||
let addr = wallet.get_address(AddressIndex::New).unwrap();
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
let (mut psbt, _) = builder.finish().unwrap();
|
||||
|
||||
assert!(
|
||||
wallet.sign(&mut psbt, Default::default()).unwrap(),
|
||||
"Unable to finalize tx"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_key_spend() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
|
||||
test_spend_from_wallet(wallet);
|
||||
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
|
||||
test_spend_from_wallet(wallet);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_script_spend() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree());
|
||||
test_spend_from_wallet(wallet);
|
||||
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_with_taptree_xprv());
|
||||
test_spend_from_wallet(wallet);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_sign_derive_index_from_psbt() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig_xprv());
|
||||
|
||||
let addr = wallet.get_address(AddressIndex::New).unwrap();
|
||||
|
||||
let mut builder = wallet.build_tx();
|
||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||
let (mut psbt, _) = builder.finish().unwrap();
|
||||
|
||||
// re-create the wallet with an empty db
|
||||
let wallet_empty = Wallet::new(
|
||||
get_test_tr_single_sig_xprv(),
|
||||
None,
|
||||
Network::Regtest,
|
||||
AnyDatabase::Memory(MemoryDatabase::new()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// signing with an empty db means that we will only look at the psbt to infer the
|
||||
// derivation index
|
||||
assert!(
|
||||
wallet_empty.sign(&mut psbt, Default::default()).unwrap(),
|
||||
"Unable to finalize tx"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_sign_explicit_sighash_all() {
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
|
||||
let addr = wallet.get_address(New).unwrap();
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.drain_to(addr.script_pubkey())
|
||||
.sighash(SchnorrSighashType::All.into())
|
||||
.drain_wallet();
|
||||
let (mut psbt, _) = builder.finish().unwrap();
|
||||
|
||||
let result = wallet.sign(&mut psbt, Default::default());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Signing should work because SIGHASH_ALL is safe"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_taproot_sign_non_default_sighash() {
|
||||
let sighash = SchnorrSighashType::NonePlusAnyoneCanPay;
|
||||
|
||||
let (wallet, _, _) = get_funded_wallet(get_test_tr_single_sig());
|
||||
let addr = wallet.get_address(New).unwrap();
|
||||
let mut builder = wallet.build_tx();
|
||||
builder
|
||||
.drain_to(addr.script_pubkey())
|
||||
.sighash(sighash.into())
|
||||
.drain_wallet();
|
||||
let (mut psbt, _) = builder.finish().unwrap();
|
||||
|
||||
let witness_utxo = psbt.inputs[0].witness_utxo.take();
|
||||
|
||||
let result = wallet.sign(&mut psbt, Default::default());
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Signing should have failed because the TX uses non-standard sighashes"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result.unwrap_err(),
|
||||
Error::Signer(SignerError::NonStandardSighash)
|
||||
),
|
||||
"Signing failed with the wrong error type"
|
||||
);
|
||||
|
||||
// try again after opting-in
|
||||
let result = wallet.sign(
|
||||
&mut psbt,
|
||||
SignOptions {
|
||||
allow_all_sighashes: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"Signing should have failed because the witness_utxo is missing"
|
||||
);
|
||||
assert!(
|
||||
matches!(
|
||||
result.unwrap_err(),
|
||||
Error::Signer(SignerError::MissingWitnessUtxo)
|
||||
),
|
||||
"Signing failed with the wrong error type"
|
||||
);
|
||||
|
||||
// restore the witness_utxo
|
||||
psbt.inputs[0].witness_utxo = witness_utxo;
|
||||
|
||||
let result = wallet.sign(
|
||||
&mut psbt,
|
||||
SignOptions {
|
||||
allow_all_sighashes: true,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok(), "Signing should have worked");
|
||||
assert!(
|
||||
result.unwrap(),
|
||||
"Should finalize the input since we can produce signatures"
|
||||
);
|
||||
|
||||
let extracted = psbt.extract_tx();
|
||||
assert_eq!(
|
||||
*extracted.input[0].witness.to_vec()[0].last().unwrap(),
|
||||
sighash as u8,
|
||||
"The signature should have been made with the right sighash"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
//! # #[derive(Debug)]
|
||||
//! # struct CustomHSM;
|
||||
//! # impl CustomHSM {
|
||||
//! # fn sign_input(&self, _psbt: &mut psbt::PartiallySignedTransaction, _input: usize) -> Result<(), SignerError> {
|
||||
//! # fn hsm_sign_input(&self, _psbt: &mut psbt::PartiallySignedTransaction, _input: usize) -> Result<(), SignerError> {
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! # fn connect() -> Self {
|
||||
@ -47,25 +47,22 @@
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! impl Signer for CustomSigner {
|
||||
//! fn sign(
|
||||
//! &self,
|
||||
//! psbt: &mut psbt::PartiallySignedTransaction,
|
||||
//! input_index: Option<usize>,
|
||||
//! _secp: &Secp256k1<All>,
|
||||
//! ) -> Result<(), SignerError> {
|
||||
//! let input_index = input_index.ok_or(SignerError::InputIndexOutOfRange)?;
|
||||
//! self.device.sign_input(psbt, input_index)?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//!
|
||||
//! impl SignerCommon for CustomSigner {
|
||||
//! fn id(&self, _secp: &Secp256k1<All>) -> SignerId {
|
||||
//! self.device.get_id()
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! fn sign_whole_tx(&self) -> bool {
|
||||
//! false
|
||||
//! impl InputSigner for CustomSigner {
|
||||
//! fn sign_input(
|
||||
//! &self,
|
||||
//! psbt: &mut psbt::PartiallySignedTransaction,
|
||||
//! input_index: usize,
|
||||
//! _secp: &Secp256k1<All>,
|
||||
//! ) -> Result<(), SignerError> {
|
||||
//! self.device.hsm_sign_input(psbt, input_index)?;
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
@ -85,23 +82,26 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::ops::Bound::Included;
|
||||
use std::ops::{Bound::Included, Deref};
|
||||
use std::sync::Arc;
|
||||
|
||||
use bitcoin::blockdata::opcodes;
|
||||
use bitcoin::blockdata::script::Builder as ScriptBuilder;
|
||||
use bitcoin::hashes::{hash160, Hash};
|
||||
use bitcoin::secp256k1;
|
||||
use bitcoin::secp256k1::{Message, Secp256k1};
|
||||
use bitcoin::secp256k1::Message;
|
||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
|
||||
use bitcoin::util::{ecdsa, psbt, sighash};
|
||||
use bitcoin::{EcdsaSighashType, PrivateKey, PublicKey, Script, Sighash};
|
||||
use bitcoin::util::{ecdsa, psbt, schnorr, sighash, taproot};
|
||||
use bitcoin::{secp256k1, XOnlyPublicKey};
|
||||
use bitcoin::{EcdsaSighashType, PrivateKey, PublicKey, SchnorrSighashType, Script};
|
||||
|
||||
use miniscript::descriptor::{DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey, KeyMap};
|
||||
use miniscript::{Legacy, MiniscriptKey, Segwitv0};
|
||||
use miniscript::descriptor::{
|
||||
Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey,
|
||||
KeyMap, SinglePubKey,
|
||||
};
|
||||
use miniscript::{Legacy, MiniscriptKey, Segwitv0, Tap};
|
||||
|
||||
use super::utils::SecpCtx;
|
||||
use crate::descriptor::XKeyUtils;
|
||||
use crate::descriptor::{DescriptorMeta, XKeyUtils};
|
||||
|
||||
/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
|
||||
/// multiple of them
|
||||
@ -174,27 +174,46 @@ impl fmt::Display for SignerError {
|
||||
|
||||
impl std::error::Error for SignerError {}
|
||||
|
||||
/// Trait for signers
|
||||
/// Signing context
|
||||
///
|
||||
/// This trait can be implemented to provide customized signers to the wallet. For an example see
|
||||
/// [`this module`](crate::wallet::signer)'s documentation.
|
||||
pub trait Signer: fmt::Debug + Send + Sync {
|
||||
/// Sign a PSBT
|
||||
///
|
||||
/// The `input_index` argument is only provided if the wallet doesn't declare to sign the whole
|
||||
/// transaction in one go (see [`Signer::sign_whole_tx`]). Otherwise its value is `None` and
|
||||
/// can be ignored.
|
||||
fn sign(
|
||||
&self,
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
input_index: Option<usize>,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(), SignerError>;
|
||||
/// Used by our software signers to determine the type of signatures to make
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum SignerContext {
|
||||
/// Legacy context
|
||||
Legacy,
|
||||
/// Segwit v0 context (BIP 143)
|
||||
Segwitv0,
|
||||
/// Taproot context (BIP 340)
|
||||
Tap {
|
||||
/// Whether the signer can sign for the internal key or not
|
||||
is_internal_key: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Return whether or not the signer signs the whole transaction in one go instead of every
|
||||
/// input individually
|
||||
fn sign_whole_tx(&self) -> bool;
|
||||
/// Wrapper structure to pair a signer with its context
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SignerWrapper<S: Sized + fmt::Debug + Clone> {
|
||||
signer: S,
|
||||
ctx: SignerContext,
|
||||
}
|
||||
|
||||
impl<S: Sized + fmt::Debug + Clone> SignerWrapper<S> {
|
||||
/// Create a wrapped signer from a signer and a context
|
||||
pub fn new(signer: S, ctx: SignerContext) -> Self {
|
||||
SignerWrapper { signer, ctx }
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Sized + fmt::Debug + Clone> Deref for SignerWrapper<S> {
|
||||
type Target = S;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.signer
|
||||
}
|
||||
}
|
||||
|
||||
/// Common signer methods
|
||||
pub trait SignerCommon: fmt::Debug + Send + Sync {
|
||||
/// Return the [`SignerId`] for this signer
|
||||
///
|
||||
/// The [`SignerId`] can be used to lookup a signer in the [`Wallet`](crate::Wallet)'s signers map or to
|
||||
@ -211,14 +230,65 @@ pub trait Signer: fmt::Debug + Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
||||
fn sign(
|
||||
/// PSBT Input signer
|
||||
///
|
||||
/// This trait can be implemented to provide custom signers to the wallet. If the signer supports signing
|
||||
/// individual inputs, this trait should be implemented and BDK will provide automatically an implementation
|
||||
/// for [`TransactionSigner`].
|
||||
pub trait InputSigner: SignerCommon {
|
||||
/// Sign a single psbt input
|
||||
fn sign_input(
|
||||
&self,
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
input_index: usize,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(), SignerError>;
|
||||
}
|
||||
|
||||
/// PSBT signer
|
||||
///
|
||||
/// This trait can be implemented when the signer can't sign inputs individually, but signs the whole transaction
|
||||
/// at once.
|
||||
pub trait TransactionSigner: SignerCommon {
|
||||
/// Sign all the inputs of the psbt
|
||||
fn sign_transaction(
|
||||
&self,
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(), SignerError>;
|
||||
}
|
||||
|
||||
impl<T: InputSigner> TransactionSigner for T {
|
||||
fn sign_transaction(
|
||||
&self,
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
input_index: Option<usize>,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(), SignerError> {
|
||||
let input_index = input_index.unwrap();
|
||||
for input_index in 0..psbt.inputs.len() {
|
||||
self.sign_input(psbt, input_index, secp)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SignerCommon for SignerWrapper<DescriptorXKey<ExtendedPrivKey>> {
|
||||
fn id(&self, secp: &SecpCtx) -> SignerId {
|
||||
SignerId::from(self.root_fingerprint(secp))
|
||||
}
|
||||
|
||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||
Some(DescriptorSecretKey::XPrv(self.signer.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSigner for SignerWrapper<DescriptorXKey<ExtendedPrivKey>> {
|
||||
fn sign_input(
|
||||
&self,
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
input_index: usize,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(), SignerError> {
|
||||
if input_index >= psbt.inputs.len() {
|
||||
return Err(SignerError::InputIndexOutOfRange);
|
||||
}
|
||||
@ -229,19 +299,23 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tap_key_origins = psbt.inputs[input_index]
|
||||
.tap_key_origins
|
||||
.iter()
|
||||
.map(|(pk, (_, keysource))| (SinglePubKey::XOnly(*pk), keysource));
|
||||
let (public_key, full_path) = match psbt.inputs[input_index]
|
||||
.bip32_derivation
|
||||
.iter()
|
||||
.filter_map(|(pk, &(fingerprint, ref path))| {
|
||||
if self.matches(&(fingerprint, path.clone()), secp).is_some() {
|
||||
Some((pk, path))
|
||||
.map(|(pk, keysource)| (SinglePubKey::FullKey(PublicKey::new(*pk)), keysource))
|
||||
.chain(tap_key_origins)
|
||||
.find_map(|(pk, keysource)| {
|
||||
if self.matches(keysource, secp).is_some() {
|
||||
Some((pk, keysource.1.clone()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
{
|
||||
Some((pk, full_path)) => (pk, full_path.clone()),
|
||||
}) {
|
||||
Some((pk, full_path)) => (pk, full_path),
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
@ -256,40 +330,47 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
||||
None => self.xkey.derive_priv(secp, &full_path).unwrap(),
|
||||
};
|
||||
|
||||
if &secp256k1::PublicKey::from_secret_key(secp, &derived_key.private_key) != public_key {
|
||||
let computed_pk = secp256k1::PublicKey::from_secret_key(secp, &derived_key.private_key);
|
||||
let valid_key = match public_key {
|
||||
SinglePubKey::FullKey(pk) if pk.inner == computed_pk => true,
|
||||
SinglePubKey::XOnly(x_only) if XOnlyPublicKey::from(computed_pk) == x_only => true,
|
||||
_ => false,
|
||||
};
|
||||
if !valid_key {
|
||||
Err(SignerError::InvalidKey)
|
||||
} else {
|
||||
// HD wallets imply compressed keys
|
||||
PrivateKey {
|
||||
let priv_key = PrivateKey {
|
||||
compressed: true,
|
||||
network: self.xkey.network,
|
||||
inner: derived_key.private_key,
|
||||
}
|
||||
.sign(psbt, Some(input_index), secp)
|
||||
};
|
||||
|
||||
SignerWrapper::new(priv_key, self.ctx).sign_input(psbt, input_index, secp)
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_whole_tx(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn id(&self, secp: &SecpCtx) -> SignerId {
|
||||
SignerId::from(self.root_fingerprint(secp))
|
||||
}
|
||||
|
||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||
Some(DescriptorSecretKey::XPrv(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Signer for PrivateKey {
|
||||
fn sign(
|
||||
impl SignerCommon for SignerWrapper<PrivateKey> {
|
||||
fn id(&self, secp: &SecpCtx) -> SignerId {
|
||||
SignerId::from(self.public_key(secp).to_pubkeyhash())
|
||||
}
|
||||
|
||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||
Some(DescriptorSecretKey::SinglePriv(DescriptorSinglePriv {
|
||||
key: self.signer,
|
||||
origin: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl InputSigner for SignerWrapper<PrivateKey> {
|
||||
fn sign_input(
|
||||
&self,
|
||||
psbt: &mut psbt::PartiallySignedTransaction,
|
||||
input_index: Option<usize>,
|
||||
input_index: usize,
|
||||
secp: &SecpCtx,
|
||||
) -> Result<(), SignerError> {
|
||||
let input_index = input_index.unwrap();
|
||||
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
|
||||
return Err(SignerError::InputIndexOutOfRange);
|
||||
}
|
||||
@ -301,48 +382,123 @@ impl Signer for PrivateKey {
|
||||
}
|
||||
|
||||
let pubkey = PublicKey::from_private_key(secp, self);
|
||||
let x_only_pubkey = XOnlyPublicKey::from(pubkey.inner);
|
||||
|
||||
if let SignerContext::Tap { is_internal_key } = self.ctx {
|
||||
if is_internal_key && psbt.inputs[input_index].tap_key_sig.is_none() {
|
||||
let (hash, hash_ty) = Tap::sighash(psbt, input_index, None)?;
|
||||
sign_psbt_schnorr(
|
||||
&self.inner,
|
||||
x_only_pubkey,
|
||||
None,
|
||||
&mut psbt.inputs[input_index],
|
||||
hash,
|
||||
hash_ty,
|
||||
secp,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((leaf_hashes, _)) =
|
||||
psbt.inputs[input_index].tap_key_origins.get(&x_only_pubkey)
|
||||
{
|
||||
let leaf_hashes = leaf_hashes
|
||||
.iter()
|
||||
.filter(|lh| {
|
||||
!psbt.inputs[input_index]
|
||||
.tap_script_sigs
|
||||
.contains_key(&(x_only_pubkey, **lh))
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for lh in leaf_hashes {
|
||||
let (hash, hash_ty) = Tap::sighash(psbt, input_index, Some(lh))?;
|
||||
sign_psbt_schnorr(
|
||||
&self.inner,
|
||||
x_only_pubkey,
|
||||
Some(lh),
|
||||
&mut psbt.inputs[input_index],
|
||||
hash,
|
||||
hash_ty,
|
||||
secp,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// FIXME: use the presence of `witness_utxo` as an indication that we should make a bip143
|
||||
// sig. Does this make sense? Should we add an extra argument to explicitly switch between
|
||||
// these? The original idea was to declare sign() as sign<Ctx: ScriptContex>() and use Ctx,
|
||||
// but that violates the rules for trait-objects, so we can't do it.
|
||||
let (hash, sighash) = match psbt.inputs[input_index].witness_utxo {
|
||||
Some(_) => Segwitv0::sighash(psbt, input_index)?,
|
||||
None => Legacy::sighash(psbt, input_index)?,
|
||||
let (hash, hash_ty) = match self.ctx {
|
||||
SignerContext::Segwitv0 => Segwitv0::sighash(psbt, input_index, ())?,
|
||||
SignerContext::Legacy => Legacy::sighash(psbt, input_index, ())?,
|
||||
_ => return Ok(()), // handled above
|
||||
};
|
||||
|
||||
let sig = secp.sign_ecdsa(
|
||||
&Message::from_slice(&hash.into_inner()[..]).unwrap(),
|
||||
sign_psbt_ecdsa(
|
||||
&self.inner,
|
||||
pubkey,
|
||||
&mut psbt.inputs[input_index],
|
||||
hash,
|
||||
hash_ty,
|
||||
secp,
|
||||
);
|
||||
|
||||
let final_signature = ecdsa::EcdsaSig {
|
||||
sig,
|
||||
hash_ty: sighash.ecdsa_hash_ty().unwrap(), // FIXME
|
||||
};
|
||||
psbt.inputs[input_index]
|
||||
.partial_sigs
|
||||
.insert(pubkey, final_signature);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_whole_tx(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn sign_psbt_ecdsa(
|
||||
secret_key: &secp256k1::SecretKey,
|
||||
pubkey: PublicKey,
|
||||
psbt_input: &mut psbt::Input,
|
||||
hash: bitcoin::Sighash,
|
||||
hash_ty: EcdsaSighashType,
|
||||
secp: &SecpCtx,
|
||||
) {
|
||||
let sig = secp.sign_ecdsa(
|
||||
&Message::from_slice(&hash.into_inner()[..]).unwrap(),
|
||||
secret_key,
|
||||
);
|
||||
|
||||
fn id(&self, secp: &SecpCtx) -> SignerId {
|
||||
SignerId::from(self.public_key(secp).to_pubkeyhash())
|
||||
}
|
||||
let final_signature = ecdsa::EcdsaSig { sig, hash_ty };
|
||||
psbt_input.partial_sigs.insert(pubkey, final_signature);
|
||||
}
|
||||
|
||||
fn descriptor_secret_key(&self) -> Option<DescriptorSecretKey> {
|
||||
Some(DescriptorSecretKey::SinglePriv(DescriptorSinglePriv {
|
||||
key: *self,
|
||||
origin: None,
|
||||
}))
|
||||
// Calling this with `leaf_hash` = `None` will sign for key-spend
|
||||
fn sign_psbt_schnorr(
|
||||
secret_key: &secp256k1::SecretKey,
|
||||
pubkey: XOnlyPublicKey,
|
||||
leaf_hash: Option<taproot::TapLeafHash>,
|
||||
psbt_input: &mut psbt::Input,
|
||||
hash: taproot::TapSighashHash,
|
||||
hash_ty: SchnorrSighashType,
|
||||
secp: &SecpCtx,
|
||||
) {
|
||||
use schnorr::TapTweak;
|
||||
|
||||
let keypair = secp256k1::KeyPair::from_seckey_slice(secp, secret_key.as_ref()).unwrap();
|
||||
let keypair = match leaf_hash {
|
||||
None => keypair
|
||||
.tap_tweak(secp, psbt_input.tap_merkle_root)
|
||||
.into_inner(),
|
||||
Some(_) => keypair, // no tweak for script spend
|
||||
};
|
||||
|
||||
let sig = secp.sign_schnorr(
|
||||
&Message::from_slice(&hash.into_inner()[..]).unwrap(),
|
||||
&keypair,
|
||||
);
|
||||
|
||||
let final_signature = schnorr::SchnorrSig { sig, hash_ty };
|
||||
|
||||
if let Some(lh) = leaf_hash {
|
||||
psbt_input
|
||||
.tap_script_sigs
|
||||
.insert((pubkey, lh), final_signature);
|
||||
} else {
|
||||
psbt_input.tap_key_sig = Some(final_signature);
|
||||
}
|
||||
}
|
||||
|
||||
@ -377,7 +533,7 @@ impl From<(SignerId, SignerOrdering)> for SignersContainerKey {
|
||||
|
||||
/// Container for multiple signers
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SignersContainer(BTreeMap<SignersContainerKey, Arc<dyn Signer>>);
|
||||
pub struct SignersContainer(BTreeMap<SignersContainerKey, Arc<dyn TransactionSigner>>);
|
||||
|
||||
impl SignersContainer {
|
||||
/// Create a map of public keys to secret keys
|
||||
@ -388,24 +544,37 @@ impl SignersContainer {
|
||||
.filter_map(|secret| secret.as_public(secp).ok().map(|public| (public, secret)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyMap> for SignersContainer {
|
||||
fn from(keymap: KeyMap) -> SignersContainer {
|
||||
let secp = Secp256k1::new();
|
||||
/// Build a new signer container from a [`KeyMap`]
|
||||
///
|
||||
/// Also looks at the corresponding descriptor to determine the [`SignerContext`] to attach to
|
||||
/// the signers
|
||||
pub fn build(
|
||||
keymap: KeyMap,
|
||||
descriptor: &Descriptor<DescriptorPublicKey>,
|
||||
secp: &SecpCtx,
|
||||
) -> SignersContainer {
|
||||
let mut container = SignersContainer::new();
|
||||
|
||||
for (_, secret) in keymap {
|
||||
for (pubkey, secret) in keymap {
|
||||
let ctx = match descriptor {
|
||||
Descriptor::Tr(tr) => SignerContext::Tap {
|
||||
is_internal_key: tr.internal_key() == &pubkey,
|
||||
},
|
||||
_ if descriptor.is_witness() => SignerContext::Segwitv0,
|
||||
_ => SignerContext::Legacy,
|
||||
};
|
||||
|
||||
match secret {
|
||||
DescriptorSecretKey::SinglePriv(private_key) => container.add_external(
|
||||
SignerId::from(private_key.key.public_key(&secp).to_pubkeyhash()),
|
||||
SignerId::from(private_key.key.public_key(secp).to_pubkeyhash()),
|
||||
SignerOrdering::default(),
|
||||
Arc::new(private_key.key),
|
||||
Arc::new(SignerWrapper::new(private_key.key, ctx)),
|
||||
),
|
||||
DescriptorSecretKey::XPrv(xprv) => container.add_external(
|
||||
SignerId::from(xprv.root_fingerprint(&secp)),
|
||||
SignerId::from(xprv.root_fingerprint(secp)),
|
||||
SignerOrdering::default(),
|
||||
Arc::new(xprv),
|
||||
Arc::new(SignerWrapper::new(xprv, ctx)),
|
||||
),
|
||||
};
|
||||
}
|
||||
@ -426,13 +595,17 @@ impl SignersContainer {
|
||||
&mut self,
|
||||
id: SignerId,
|
||||
ordering: SignerOrdering,
|
||||
signer: Arc<dyn Signer>,
|
||||
) -> Option<Arc<dyn Signer>> {
|
||||
signer: Arc<dyn TransactionSigner>,
|
||||
) -> Option<Arc<dyn TransactionSigner>> {
|
||||
self.0.insert((id, ordering).into(), signer)
|
||||
}
|
||||
|
||||
/// Removes a signer from the container and returns it
|
||||
pub fn remove(&mut self, id: SignerId, ordering: SignerOrdering) -> Option<Arc<dyn Signer>> {
|
||||
pub fn remove(
|
||||
&mut self,
|
||||
id: SignerId,
|
||||
ordering: SignerOrdering,
|
||||
) -> Option<Arc<dyn TransactionSigner>> {
|
||||
self.0.remove(&(id, ordering).into())
|
||||
}
|
||||
|
||||
@ -445,12 +618,12 @@ impl SignersContainer {
|
||||
}
|
||||
|
||||
/// Returns the list of signers in the container, sorted by lowest to highest `ordering`
|
||||
pub fn signers(&self) -> Vec<&Arc<dyn Signer>> {
|
||||
pub fn signers(&self) -> Vec<&Arc<dyn TransactionSigner>> {
|
||||
self.0.values().collect()
|
||||
}
|
||||
|
||||
/// Finds the signer with lowest ordering for a given id in the container.
|
||||
pub fn find(&self, id: SignerId) -> Option<&Arc<dyn Signer>> {
|
||||
pub fn find(&self, id: SignerId) -> Option<&Arc<dyn TransactionSigner>> {
|
||||
self.0
|
||||
.range((
|
||||
Included(&(id.clone(), SignerOrdering(0)).into()),
|
||||
@ -508,17 +681,27 @@ impl Default for SignOptions {
|
||||
}
|
||||
|
||||
pub(crate) trait ComputeSighash {
|
||||
type Extra;
|
||||
type Sighash;
|
||||
type SighashType;
|
||||
|
||||
fn sighash(
|
||||
psbt: &psbt::PartiallySignedTransaction,
|
||||
input_index: usize,
|
||||
) -> Result<(Sighash, psbt::PsbtSighashType), SignerError>;
|
||||
extra: Self::Extra,
|
||||
) -> Result<(Self::Sighash, Self::SighashType), SignerError>;
|
||||
}
|
||||
|
||||
impl ComputeSighash for Legacy {
|
||||
type Extra = ();
|
||||
type Sighash = bitcoin::Sighash;
|
||||
type SighashType = EcdsaSighashType;
|
||||
|
||||
fn sighash(
|
||||
psbt: &psbt::PartiallySignedTransaction,
|
||||
input_index: usize,
|
||||
) -> Result<(Sighash, psbt::PsbtSighashType), SignerError> {
|
||||
_extra: (),
|
||||
) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
|
||||
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
|
||||
return Err(SignerError::InputIndexOutOfRange);
|
||||
}
|
||||
@ -528,7 +711,9 @@ impl ComputeSighash for Legacy {
|
||||
|
||||
let sighash = psbt_input
|
||||
.sighash_type
|
||||
.unwrap_or_else(|| EcdsaSighashType::All.into());
|
||||
.unwrap_or_else(|| EcdsaSighashType::All.into())
|
||||
.ecdsa_hash_ty()
|
||||
.map_err(|_| SignerError::InvalidSighash)?;
|
||||
let script = match psbt_input.redeem_script {
|
||||
Some(ref redeem_script) => redeem_script.clone(),
|
||||
None => {
|
||||
@ -567,10 +752,15 @@ fn p2wpkh_script_code(script: &Script) -> Script {
|
||||
}
|
||||
|
||||
impl ComputeSighash for Segwitv0 {
|
||||
type Extra = ();
|
||||
type Sighash = bitcoin::Sighash;
|
||||
type SighashType = EcdsaSighashType;
|
||||
|
||||
fn sighash(
|
||||
psbt: &psbt::PartiallySignedTransaction,
|
||||
input_index: usize,
|
||||
) -> Result<(Sighash, psbt::PsbtSighashType), SignerError> {
|
||||
_extra: (),
|
||||
) -> Result<(Self::Sighash, Self::SighashType), SignerError> {
|
||||
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
|
||||
return Err(SignerError::InputIndexOutOfRange);
|
||||
}
|
||||
@ -631,7 +821,62 @@ impl ComputeSighash for Segwitv0 {
|
||||
value,
|
||||
sighash,
|
||||
)?,
|
||||
sighash.into(),
|
||||
sighash,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ComputeSighash for Tap {
|
||||
type Extra = Option<taproot::TapLeafHash>;
|
||||
type Sighash = taproot::TapSighashHash;
|
||||
type SighashType = SchnorrSighashType;
|
||||
|
||||
fn sighash(
|
||||
psbt: &psbt::PartiallySignedTransaction,
|
||||
input_index: usize,
|
||||
extra: Self::Extra,
|
||||
) -> Result<(Self::Sighash, SchnorrSighashType), SignerError> {
|
||||
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
|
||||
return Err(SignerError::InputIndexOutOfRange);
|
||||
}
|
||||
|
||||
let psbt_input = &psbt.inputs[input_index];
|
||||
|
||||
let sighash_type = psbt_input
|
||||
.sighash_type
|
||||
.unwrap_or_else(|| SchnorrSighashType::Default.into())
|
||||
.schnorr_hash_ty()
|
||||
.map_err(|_| SignerError::InvalidSighash)?;
|
||||
let witness_utxos = psbt
|
||||
.inputs
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|i| i.witness_utxo)
|
||||
.collect::<Vec<_>>();
|
||||
let mut all_witness_utxos = vec![];
|
||||
|
||||
let mut cache = sighash::SighashCache::new(&psbt.unsigned_tx);
|
||||
let is_anyone_can_pay = psbt::PsbtSighashType::from(sighash_type).to_u32() & 0x80 != 0;
|
||||
let prevouts = if is_anyone_can_pay {
|
||||
sighash::Prevouts::One(
|
||||
input_index,
|
||||
witness_utxos[input_index]
|
||||
.as_ref()
|
||||
.ok_or(SignerError::MissingWitnessUtxo)?,
|
||||
)
|
||||
} else if witness_utxos.iter().all(Option::is_some) {
|
||||
all_witness_utxos.extend(witness_utxos.iter().filter_map(|x| x.as_ref()));
|
||||
sighash::Prevouts::All(&all_witness_utxos)
|
||||
} else {
|
||||
return Err(SignerError::MissingWitnessUtxo);
|
||||
};
|
||||
|
||||
// Assume no OP_CODESEPARATOR
|
||||
let extra = extra.map(|leaf_hash| (leaf_hash, 0xFFFFFFFF));
|
||||
|
||||
Ok((
|
||||
cache.taproot_signature_hash(input_index, &prevouts, None, extra, sighash_type)?,
|
||||
sighash_type,
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -666,12 +911,11 @@ mod signers_container_tests {
|
||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||
use bitcoin::secp256k1::{All, Secp256k1};
|
||||
use bitcoin::util::bip32;
|
||||
use bitcoin::util::psbt::PartiallySignedTransaction;
|
||||
use bitcoin::Network;
|
||||
use miniscript::ScriptContext;
|
||||
use std::str::FromStr;
|
||||
|
||||
fn is_equal(this: &Arc<dyn Signer>, that: &Arc<DummySigner>) -> bool {
|
||||
fn is_equal(this: &Arc<dyn TransactionSigner>, that: &Arc<DummySigner>) -> bool {
|
||||
let secp = Secp256k1::new();
|
||||
this.id(&secp) == that.id(&secp)
|
||||
}
|
||||
@ -686,11 +930,11 @@ mod signers_container_tests {
|
||||
let (prvkey1, _, _) = setup_keys(TPRV0_STR);
|
||||
let (prvkey2, _, _) = setup_keys(TPRV1_STR);
|
||||
let desc = descriptor!(sh(multi(2, prvkey1, prvkey2))).unwrap();
|
||||
let (_, keymap) = desc
|
||||
let (wallet_desc, keymap) = desc
|
||||
.into_wallet_descriptor(&secp, Network::Testnet)
|
||||
.unwrap();
|
||||
|
||||
let signers = SignersContainer::from(keymap);
|
||||
let signers = SignersContainer::build(keymap, &wallet_desc, &secp);
|
||||
assert_eq!(signers.ids().len(), 2);
|
||||
|
||||
let signers = signers.signers();
|
||||
@ -752,22 +996,19 @@ mod signers_container_tests {
|
||||
number: u64,
|
||||
}
|
||||
|
||||
impl Signer for DummySigner {
|
||||
fn sign(
|
||||
&self,
|
||||
_psbt: &mut PartiallySignedTransaction,
|
||||
_input_index: Option<usize>,
|
||||
_secp: &SecpCtx,
|
||||
) -> Result<(), SignerError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl SignerCommon for DummySigner {
|
||||
fn id(&self, _secp: &SecpCtx) -> SignerId {
|
||||
SignerId::Dummy(self.number)
|
||||
}
|
||||
}
|
||||
|
||||
fn sign_whole_tx(&self) -> bool {
|
||||
true
|
||||
impl TransactionSigner for DummySigner {
|
||||
fn sign_transaction(
|
||||
&self,
|
||||
_psbt: &mut psbt::PartiallySignedTransaction,
|
||||
_secp: &SecpCtx,
|
||||
) -> Result<(), SignerError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -334,8 +334,9 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
||||
/// 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`.
|
||||
///
|
||||
/// 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.
|
||||
/// Note unless you set [`only_witness_utxo`] any non-taproot `psbt_input` you pass to this
|
||||
/// method must have `non_witness_utxo` set otherwise you will get an error when [`finish`]
|
||||
/// is called.
|
||||
///
|
||||
/// [`only_witness_utxo`]: Self::only_witness_utxo
|
||||
/// [`finish`]: Self::finish
|
||||
|
Loading…
x
Reference in New Issue
Block a user