diff --git a/.github/workflows/cont_integration.yml b/.github/workflows/cont_integration.yml index d03ce943..200f419b 100644 --- a/.github/workflows/cont_integration.yml +++ b/.github/workflows/cont_integration.yml @@ -29,7 +29,7 @@ jobs: uses: Swatinem/rust-cache@v2.2.1 - name: Pin dependencies for MSRV if: matrix.rust.version == '1.57.0' - run: cargo update -p log --precise "0.4.18" && cargo update -p tempfile --precise "3.6.0" + run: cargo update -p log --precise "0.4.18" && cargo update -p tempfile --precise "3.6.0" && cargo update -p rustls:0.21.6 --precise "0.21.1" - name: Build run: cargo build ${{ matrix.features }} - name: Test diff --git a/README.md b/README.md index 56521275..92161130 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ To build with the MSRV you will need to pin dependencies as follows: ``` # log 0.4.19 has MSRV 1.60.0+ cargo update -p log --precise "0.4.18" -# tempfile 3.7.0 has MSRV 1.63.0 +# tempfile 3.7.0 has MSRV 1.63.0+ cargo update -p tempfile --precise "3.6.0" +# rustls 0.21.2 has MSRV 1.60.0+ +cargo update -p rustls:0.21.6 --precise "0.21.1" ``` diff --git a/crates/bdk/Cargo.toml b/crates/bdk/Cargo.toml index 344fb3e5..dc38fdd6 100644 --- a/crates/bdk/Cargo.toml +++ b/crates/bdk/Cargo.toml @@ -15,14 +15,14 @@ rust-version = "1.57" [dependencies] log = "0.4" rand = "^0.8" -miniscript = { version = "9", features = ["serde"], default-features = false } -bitcoin = { version = "0.29", features = ["serde", "base64", "rand"], default-features = false } +miniscript = { version = "10.0.0", features = ["serde"], default-features = false } +bitcoin = { version = "0.30.0", features = ["serde", "base64", "rand-std"], default-features = false } serde = { version = "^1.0", features = ["derive"] } serde_json = { version = "^1.0" } bdk_chain = { path = "../chain", version = "0.5.0", features = ["miniscript", "serde"], default-features = false } # Optional dependencies -hwi = { version = "0.5", optional = true, features = [ "use-miniscript"] } +hwi = { version = "0.7.0", optional = true, features = [ "miniscript"] } bip39 = { version = "1.0.1", optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] @@ -46,8 +46,6 @@ dev-getrandom-wasm = ["getrandom/js"] [dev-dependencies] lazy_static = "1.4" env_logger = "0.7" -# Move back to importing from rust-bitcoin once https://github.com/rust-bitcoin/rust-bitcoin/pull/1342 is released -base64 = "^0.13" assert_matches = "1.5.0" [package.metadata.docs.rs] diff --git a/crates/bdk/README.md b/crates/bdk/README.md index 9a5bd998..ddd67df1 100644 --- a/crates/bdk/README.md +++ b/crates/bdk/README.md @@ -137,7 +137,7 @@ fn main() { - + @@ -174,7 +174,7 @@ fn main() { - + diff --git a/crates/bdk/examples/mnemonic_to_descriptors.rs b/crates/bdk/examples/mnemonic_to_descriptors.rs index 0a560a52..7d2dd601 100644 --- a/crates/bdk/examples/mnemonic_to_descriptors.rs +++ b/crates/bdk/examples/mnemonic_to_descriptors.rs @@ -6,8 +6,8 @@ // You may not use this file except in accordance with one or both of these // licenses. +use bdk::bitcoin::bip32::DerivationPath; use bdk::bitcoin::secp256k1::Secp256k1; -use bdk::bitcoin::util::bip32::DerivationPath; use bdk::bitcoin::Network; use bdk::descriptor; use bdk::descriptor::IntoWalletDescriptor; diff --git a/crates/bdk/src/descriptor/dsl.rs b/crates/bdk/src/descriptor/dsl.rs index 60fac19e..50cd978f 100644 --- a/crates/bdk/src/descriptor/dsl.rs +++ b/crates/bdk/src/descriptor/dsl.rs @@ -516,13 +516,14 @@ macro_rules! descriptor { use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey}; $crate::impl_top_level_pk!(Pkh, $crate::miniscript::Legacy, $key) + .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c))) .map(|(a, b, c)| (Descriptor::::Pkh(a), b, c)) }); ( wpkh ( $key:expr ) ) => ({ use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey}; $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) - .and_then(|(a, b, c)| Ok((a?, b, c))) + .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c))) .map(|(a, b, c)| (Descriptor::::Wpkh(a), b, c)) }); ( sh ( wpkh ( $key:expr ) ) ) => ({ @@ -532,7 +533,7 @@ macro_rules! descriptor { use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, Sh}; $crate::impl_top_level_pk!(Wpkh, $crate::miniscript::Segwitv0, $key) - .and_then(|(a, b, c)| Ok((a?, b, c))) + .and_then(|(a, b, c)| Ok((a.map_err(|e| miniscript::Error::from(e))?, b, c))) .and_then(|(a, b, c)| Ok((Descriptor::::Sh(Sh::new_wpkh(a.into_inner())?), b, c))) }); ( sh ( $( $minisc:tt )* ) ) => ({ @@ -702,7 +703,7 @@ macro_rules! fragment { $crate::keys::make_pkh($key, &secp) }); ( after ( $value:expr ) ) => ({ - $crate::impl_leaf_opcode_value!(After, $crate::bitcoin::PackedLockTime($value)) // TODO!! https://github.com/rust-bitcoin/rust-bitcoin/issues/1302 + $crate::impl_leaf_opcode_value!(After, $crate::miniscript::AbsLockTime::from_consensus($value)) }); ( older ( $value:expr ) ) => ({ $crate::impl_leaf_opcode_value!(Older, $crate::bitcoin::Sequence($value)) // TODO!! @@ -796,7 +797,6 @@ macro_rules! fragment { #[cfg(test)] mod test { use alloc::string::ToString; - use bitcoin::hashes::hex::ToHex; use bitcoin::secp256k1::Secp256k1; use miniscript::descriptor::{DescriptorPublicKey, KeyMap}; use miniscript::{Descriptor, Legacy, Segwitv0}; @@ -805,8 +805,8 @@ mod test { use crate::descriptor::{DescriptorError, DescriptorMeta}; use crate::keys::{DescriptorKey, IntoDescriptorKey, ValidNetworks}; + use bitcoin::bip32; use bitcoin::network::constants::Network::{Bitcoin, Regtest, Signet, Testnet}; - use bitcoin::util::bip32; use bitcoin::PrivateKey; // test the descriptor!() macro @@ -822,18 +822,15 @@ mod test { assert_eq!(desc.is_witness(), is_witness); assert_eq!(!desc.has_wildcard(), is_fixed); for i in 0..expected.len() { - let index = i as u32; - let child_desc = if !desc.has_wildcard() { - desc.at_derivation_index(0) - } else { - desc.at_derivation_index(index) - }; + let child_desc = desc + .at_derivation_index(i as u32) + .expect("i is not hardened"); let address = child_desc.address(Regtest); if let Ok(address) = address { assert_eq!(address.to_string(), *expected.get(i).unwrap()); } else { let script = child_desc.script_pubkey(); - assert_eq!(script.to_hex().as_str(), *expected.get(i).unwrap()); + assert_eq!(script.to_hex_string(), *expected.get(i).unwrap()); } } } @@ -1178,9 +1175,7 @@ mod test { } #[test] - #[should_panic( - expected = "Miniscript(ContextError(CompressedOnly(\"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4\")))" - )] + #[should_panic(expected = "Miniscript(ContextError(UncompressedKeysNotAllowed))")] fn test_dsl_miniscript_checks() { let mut uncompressed_pk = PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap(); diff --git a/crates/bdk/src/descriptor/error.rs b/crates/bdk/src/descriptor/error.rs index 5b0f1930..07a874ef 100644 --- a/crates/bdk/src/descriptor/error.rs +++ b/crates/bdk/src/descriptor/error.rs @@ -22,6 +22,8 @@ pub enum Error { InvalidDescriptorChecksum, /// The descriptor contains hardened derivation steps on public extended keys HardenedDerivationXpub, + /// The descriptor contains multipath keys + MultiPath, /// Error thrown while working with [`keys`](crate::keys) Key(crate::keys::KeyError), @@ -32,11 +34,11 @@ pub enum Error { InvalidDescriptorCharacter(u8), /// BIP32 error - Bip32(bitcoin::util::bip32::Error), + Bip32(bitcoin::bip32::Error), /// Error during base58 decoding - Base58(bitcoin::util::base58::Error), + Base58(bitcoin::base58::Error), /// Key-related error - Pk(bitcoin::util::key::Error), + Pk(bitcoin::key::Error), /// Miniscript error Miniscript(miniscript::Error), /// Hex decoding error @@ -64,6 +66,10 @@ impl fmt::Display for Error { f, "The descriptor contains hardened derivation steps on public extended keys" ), + Self::MultiPath => write!( + f, + "The descriptor contains multipath keys, which are not supported yet" + ), Self::Key(err) => write!(f, "Key error: {}", err), Self::Policy(err) => write!(f, "Policy error: {}", err), Self::InvalidDescriptorCharacter(char) => { @@ -81,9 +87,9 @@ impl fmt::Display for Error { #[cfg(feature = "std")] impl std::error::Error for Error {} -impl_error!(bitcoin::util::bip32::Error, Bip32); -impl_error!(bitcoin::util::base58::Error, Base58); -impl_error!(bitcoin::util::key::Error, Pk); +impl_error!(bitcoin::bip32::Error, Bip32); +impl_error!(bitcoin::base58::Error, Base58); +impl_error!(bitcoin::key::Error, Pk); impl_error!(miniscript::Error, Miniscript); impl_error!(bitcoin::hashes::hex::Error, Hex); impl_error!(crate::descriptor::policy::PolicyError, Policy); diff --git a/crates/bdk/src/descriptor/mod.rs b/crates/bdk/src/descriptor/mod.rs index 9a6dc2b0..d5c3415f 100644 --- a/crates/bdk/src/descriptor/mod.rs +++ b/crates/bdk/src/descriptor/mod.rs @@ -18,17 +18,17 @@ use crate::collections::BTreeMap; use alloc::string::String; use alloc::vec::Vec; -use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource}; -use bitcoin::util::{psbt, taproot}; -use bitcoin::{secp256k1, PublicKey, XOnlyPublicKey}; +use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource}; +use bitcoin::{key::XOnlyPublicKey, secp256k1, PublicKey}; +use bitcoin::{psbt, taproot}; use bitcoin::{Network, TxOut}; use miniscript::descriptor::{ - DefiniteDescriptorKey, DescriptorSecretKey, DescriptorType, InnerXKey, SinglePubKey, + DefiniteDescriptorKey, DescriptorMultiXKey, DescriptorSecretKey, DescriptorType, + DescriptorXKey, InnerXKey, KeyMap, SinglePubKey, Wildcard, }; pub use miniscript::{ - descriptor::DescriptorXKey, descriptor::KeyMap, descriptor::Wildcard, Descriptor, - DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0, + Descriptor, DescriptorPublicKey, Legacy, Miniscript, ScriptContext, Segwitv0, }; use miniscript::{ForEachKey, MiniscriptKey, TranslatePk}; @@ -59,16 +59,16 @@ pub type DerivedDescriptor = Descriptor; /// Alias for the type of maps that represent derivation paths in a [`psbt::Input`] or /// [`psbt::Output`] /// -/// [`psbt::Input`]: bitcoin::util::psbt::Input -/// [`psbt::Output`]: bitcoin::util::psbt::Output +/// [`psbt::Input`]: bitcoin::psbt::Input +/// [`psbt::Output`]: bitcoin::psbt::Output pub type HdKeyPaths = BTreeMap; /// 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, KeySource)>; +/// [`psbt::Input`]: bitcoin::psbt::Input +/// [`psbt::Output`]: bitcoin::psbt::Output +pub type TapKeyOrigins = BTreeMap, 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 { @@ -136,14 +136,10 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) { network: Network, } - impl<'s, 'd> - miniscript::Translator + impl<'s, 'd> miniscript::Translator for Translator<'s, 'd> { - fn pk( - &mut self, - pk: &DescriptorPublicKey, - ) -> Result { + fn pk(&mut self, pk: &DescriptorPublicKey) -> Result { let secp = &self.secp; let (_, _, networks) = if self.descriptor.is_taproot() { @@ -161,7 +157,7 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) { }; if networks.contains(&self.network) { - Ok(miniscript::DummyKey) + Ok(Default::default()) } else { Err(DescriptorError::Key(KeyError::InvalidNetwork)) } @@ -169,35 +165,40 @@ impl IntoWalletDescriptor for (ExtendedDescriptor, KeyMap) { fn sha256( &mut self, _sha256: &::Sha256, - ) -> Result { + ) -> Result { Ok(Default::default()) } fn hash256( &mut self, _hash256: &::Hash256, - ) -> Result { + ) -> Result { Ok(Default::default()) } fn ripemd160( &mut self, _ripemd160: &::Ripemd160, - ) -> Result { + ) -> Result { Ok(Default::default()) } fn hash160( &mut self, _hash160: &::Hash160, - ) -> Result { + ) -> Result { Ok(Default::default()) } } // check the network for the keys - self.0.translate_pk(&mut Translator { + use miniscript::TranslateErr; + match self.0.translate_pk(&mut Translator { secp, network, descriptor: &self.0, - })?; + }) { + Ok(_) => {} + Err(TranslateErr::TranslatorErr(e)) => return Err(e), + Err(TranslateErr::OuterError(e)) => return Err(e.into()), + } Ok(self) } @@ -251,7 +252,12 @@ impl IntoWalletDescriptor for DescriptorTemplateOut { } // fixup the network for keys that need it in the descriptor - let translated = desc.translate_pk(&mut Translator { network })?; + use miniscript::TranslateErr; + let translated = match desc.translate_pk(&mut Translator { network }) { + Ok(descriptor) => descriptor, + Err(TranslateErr::TranslatorErr(e)) => return Err(e), + Err(TranslateErr::OuterError(e)) => return Err(e.into()), + }; // ...and in the key map let fixed_keymap = keymap .into_iter() @@ -302,6 +308,10 @@ pub(crate) fn into_wallet_descriptor_checked( return Err(DescriptorError::HardenedDerivationXpub); } + if descriptor.is_multipath() { + return Err(DescriptorError::MultiPath); + } + // Run miniscript's sanity check, which will look for duplicated keys and other potential // issues descriptor.sanity_check()?; @@ -340,6 +350,18 @@ pub(crate) trait XKeyUtils { fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint; } +impl XKeyUtils for DescriptorMultiXKey +where + T: InnerXKey, +{ + fn root_fingerprint(&self, secp: &SecpCtx) -> Fingerprint { + match self.origin { + Some((fingerprint, _)) => fingerprint, + None => self.xkey.xkey_fingerprint(secp), + } + } +} + impl XKeyUtils for DescriptorXKey where T: InnerXKey, @@ -494,7 +516,10 @@ impl DescriptorMeta for ExtendedDescriptor { false }); - path_found.map(|path| self.at_derivation_index(path)) + path_found.map(|path| { + self.at_derivation_index(path) + .expect("We ignore hardened wildcards") + }) } fn derive_from_hd_keypaths( @@ -545,7 +570,7 @@ impl DescriptorMeta for ExtendedDescriptor { return None; } - let descriptor = self.at_derivation_index(0); + let descriptor = self.at_derivation_index(0).expect("0 is not hardened"); match descriptor.desc_type() { // TODO: add pk() here DescriptorType::Pkh @@ -585,11 +610,10 @@ mod test { use core::str::FromStr; use assert_matches::assert_matches; - use bitcoin::consensus::encode::deserialize; use bitcoin::hashes::hex::FromHex; use bitcoin::secp256k1::Secp256k1; - use bitcoin::util::{bip32, psbt}; - use bitcoin::Script; + use bitcoin::ScriptBuf; + use bitcoin::{bip32, psbt::Psbt}; use super::*; use crate::psbt::PsbtUtils; @@ -600,7 +624,7 @@ mod test { "wpkh(02b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a8737)", ) .unwrap(); - let psbt: psbt::PartiallySignedTransaction = deserialize( + let psbt = Psbt::deserialize( &Vec::::from_hex( "70736274ff010052010000000162307be8e431fbaff807cdf9cdc3fde44d7402\ 11bc8342c31ffd6ec11fe35bcc0100000000ffffffff01328601000000000016\ @@ -623,7 +647,7 @@ mod test { "pkh([0f056943/44h/0h/0h]tpubDDpWvmUrPZrhSPmUzCMBHffvC3HyMAPnWDSAQNBTnj1iZeJa7BZQEttFiP4DS4GCcXQHezdXhn86Hj6LHX5EDstXPWrMaSneRWM8yUf6NFd/10/*)", ) .unwrap(); - let psbt: psbt::PartiallySignedTransaction = deserialize( + let psbt = Psbt::deserialize( &Vec::::from_hex( "70736274ff010053010000000145843b86be54a3cd8c9e38444e1162676c00df\ e7964122a70df491ea12fd67090100000000ffffffff01c19598000000000017\ @@ -654,7 +678,7 @@ mod test { "wsh(and_v(v:pk(03b6633fef2397a0a9de9d7b6f23aef8368a6e362b0581f0f0af70d5ecfd254b14),older(6)))", ) .unwrap(); - let psbt: psbt::PartiallySignedTransaction = deserialize( + let psbt = Psbt::deserialize( &Vec::::from_hex( "70736274ff01005302000000011c8116eea34408ab6529223c9a176606742207\ 67a1ff1d46a6e3c4a88243ea6e01000000000600000001109698000000000017\ @@ -678,7 +702,7 @@ mod test { "sh(and_v(v:pk(021403881a5587297818fcaf17d239cefca22fce84a45b3b1d23e836c4af671dbb),after(630000)))", ) .unwrap(); - let psbt: psbt::PartiallySignedTransaction = deserialize( + let psbt = Psbt::deserialize( &Vec::::from_hex( "70736274ff0100530100000001bc8c13df445dfadcc42afa6dc841f85d22b01d\ a6270ebf981740f4b7b1d800390000000000feffffff01ba9598000000000017\ @@ -845,6 +869,12 @@ mod test { assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub)); + let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)"; + let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet); + + assert_matches!(result, Err(DescriptorError::MultiPath)); + + // repeated pubkeys let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))"; let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet); @@ -861,9 +891,9 @@ mod test { let (descriptor, _) = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap(); - let descriptor = descriptor.at_derivation_index(0); + let descriptor = descriptor.at_derivation_index(0).unwrap(); - let script = Script::from_str("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap(); + let script = ScriptBuf::from_hex("5321022f533b667e2ea3b36e21961c9fe9dca340fbe0af5210173a83ae0337ab20a57621026bb53a98e810bd0ee61a0ed1164ba6c024786d76554e793e202dc6ce9c78c4ea2102d5b8a7d66a41ffdb6f4c53d61994022e886b4f45001fb158b95c9164d45f8ca3210324b75eead2c1f9c60e8adeb5e7009fec7a29afcdb30d829d82d09562fe8bae8521032d34f8932200833487bd294aa219dcbe000b9f9b3d824799541430009f0fa55121037468f8ea99b6c64788398b5ad25480cad08f4b0d65be54ce3a55fd206b5ae4722103f72d3d96663b0ea99b0aeb0d7f273cab11a8de37885f1dddc8d9112adb87169357ae").unwrap(); let mut psbt_input = psbt::Input::default(); psbt_input diff --git a/crates/bdk/src/descriptor/policy.rs b/crates/bdk/src/descriptor/policy.rs index 5de20ae7..14b2459e 100644 --- a/crates/bdk/src/descriptor/policy.rs +++ b/crates/bdk/src/descriptor/policy.rs @@ -45,9 +45,9 @@ use core::fmt; use serde::ser::SerializeMap; use serde::{Serialize, Serializer}; +use bitcoin::bip32::Fingerprint; use bitcoin::hashes::{hash160, ripemd160, sha256}; -use bitcoin::util::bip32::Fingerprint; -use bitcoin::{LockTime, PublicKey, Sequence, XOnlyPublicKey}; +use bitcoin::{absolute, key::XOnlyPublicKey, PublicKey, Sequence}; use miniscript::descriptor::{ DescriptorPublicKey, ShInner, SinglePub, SinglePubKey, SortedMultiVec, WshInner, @@ -68,7 +68,7 @@ use crate::wallet::utils::{After, Older, SecpCtx}; use super::checksum::calc_checksum; use super::error::Error; use super::XKeyUtils; -use bitcoin::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt}; +use bitcoin::psbt::{self, Psbt}; use miniscript::psbt::PsbtInputSatisfier; /// A unique identifier for a key @@ -95,6 +95,9 @@ impl PkOrF { .. }) => PkOrF::XOnlyPubkey(*pk), DescriptorPublicKey::XPub(xpub) => PkOrF::Fingerprint(xpub.root_fingerprint(secp)), + DescriptorPublicKey::MultiXPub(multi) => { + PkOrF::Fingerprint(multi.root_fingerprint(secp)) + } } } } @@ -131,7 +134,7 @@ pub enum SatisfiableItem { /// Absolute timeclock timestamp AbsoluteTimelock { /// The timelock value - value: LockTime, + value: absolute::LockTime, }, /// Relative timelock locktime RelativeTimelock { @@ -451,11 +454,14 @@ pub struct Condition { pub csv: Option, /// Optional timelock condition #[serde(skip_serializing_if = "Option::is_none")] - pub timelock: Option, + pub timelock: Option, } impl Condition { - fn merge_nlocktime(a: LockTime, b: LockTime) -> Result { + fn merge_nlocktime( + a: absolute::LockTime, + b: absolute::LockTime, + ) -> Result { if !a.is_same_unit(b) { Err(PolicyError::MixedTimelockUnits) } else if a > b { @@ -749,6 +755,7 @@ fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId { .. }) => pk.to_pubkeyhash(SigType::Ecdsa).into(), DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(), + DescriptorPublicKey::MultiXPub(xpub) => xpub.root_fingerprint(secp).into(), } } @@ -786,9 +793,9 @@ fn make_generic_signature SatisfiableItem, F: Fn(&Psbt) -> bool>( fn generic_sig_in_psbt< // C is for "check", it's a closure we use to *check* if a psbt input contains the signature // for a specific key - C: Fn(&PsbtInput, &SinglePubKey) -> bool, + C: Fn(&psbt::Input, &SinglePubKey) -> bool, // E is for "extract", it extracts a key from the bip32 derivations found in the psbt input - E: Fn(&PsbtInput, Fingerprint) -> Option, + E: Fn(&psbt::Input, Fingerprint) -> Option, >( psbt: &Psbt, key: &DescriptorPublicKey, @@ -806,6 +813,13 @@ fn generic_sig_in_psbt< None => false, } } + DescriptorPublicKey::MultiXPub(xpub) => { + //TODO check actual derivation matches + match extract(input, xpub.root_fingerprint(secp)) { + Some(pubkey) => check(input, &pubkey), + None => false, + } + } }) } @@ -911,12 +925,12 @@ impl ExtractPolicy for Miniscript { let mut policy: Policy = SatisfiableItem::AbsoluteTimelock { - value: value.into(), + value: (*value).into(), } .into(); policy.contribution = Satisfaction::Complete { condition: Condition { - timelock: Some(value.into()), + timelock: Some((*value).into()), csv: None, }, }; @@ -928,9 +942,9 @@ impl ExtractPolicy for Miniscript::check_after(&after, value.into()); + Satisfier::::check_after(&after, (*value).into()); let inputs_sat = psbt_inputs_sat(psbt).all(|sat| { - Satisfier::::check_after(&sat, value.into()) + Satisfier::::check_after(&sat, (*value).into()) }); if after_sat && inputs_sat { policy.satisfaction = policy.contribution.clone(); @@ -1156,8 +1170,8 @@ mod test { use crate::wallet::signer::SignersContainer; use alloc::{string::ToString, sync::Arc}; use assert_matches::assert_matches; + use bitcoin::bip32; use bitcoin::secp256k1::Secp256k1; - use bitcoin::util::bip32; use bitcoin::Network; use core::str::FromStr; @@ -1575,6 +1589,7 @@ mod test { let addr = wallet_desc .at_derivation_index(0) + .unwrap() .address(Network::Testnet) .unwrap(); assert_eq!( @@ -1641,6 +1656,7 @@ mod test { let addr = wallet_desc .at_derivation_index(0) + .unwrap() .address(Network::Testnet) .unwrap(); assert_eq!( diff --git a/crates/bdk/src/descriptor/template.rs b/crates/bdk/src/descriptor/template.rs index 599cf69e..c5e8b31c 100644 --- a/crates/bdk/src/descriptor/template.rs +++ b/crates/bdk/src/descriptor/template.rs @@ -14,7 +14,7 @@ //! This module contains the definition of various common script templates that are ready to be //! used. See the documentation of each template for an example. -use bitcoin::util::bip32; +use bitcoin::bip32; use bitcoin::Network; use miniscript::{Legacy, Segwitv0, Tap}; @@ -195,7 +195,7 @@ impl> DescriptorTemplate for P2TR { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip44; /// -/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; +/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip44(key.clone(), KeychainKind::External), /// Some(Bip44(key, KeychainKind::Internal)), @@ -232,8 +232,8 @@ impl> DescriptorTemplate for Bip44 { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip44Public; /// -/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; -/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; +/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?; +/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip44Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)), @@ -270,7 +270,7 @@ impl> DescriptorTemplate for Bip44Public { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip49; /// -/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; +/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip49(key.clone(), KeychainKind::External), /// Some(Bip49(key, KeychainKind::Internal)), @@ -307,8 +307,8 @@ impl> DescriptorTemplate for Bip49 { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip49Public; /// -/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; -/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; +/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?; +/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip49Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)), @@ -345,7 +345,7 @@ impl> DescriptorTemplate for Bip49Public { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip84; /// -/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; +/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip84(key.clone(), KeychainKind::External), /// Some(Bip84(key, KeychainKind::Internal)), @@ -382,8 +382,8 @@ impl> DescriptorTemplate for Bip84 { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip84Public; /// -/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; -/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; +/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; +/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip84Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)), @@ -420,7 +420,7 @@ impl> DescriptorTemplate for Bip84Public { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip86; /// -/// let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; +/// let key = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?; /// let mut wallet = Wallet::new_no_persist( /// Bip86(key.clone(), KeychainKind::External), /// Some(Bip86(key, KeychainKind::Internal)), @@ -457,8 +457,8 @@ impl> DescriptorTemplate for Bip86 { /// # use bdk::wallet::AddressIndex::New; /// use bdk::template::Bip86Public; /// -/// let key = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; -/// let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f")?; +/// let key = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?; +/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?; /// let mut wallet = Wallet::new_no_persist( /// Bip86Public(key.clone(), fingerprint, KeychainKind::External), /// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)), @@ -565,30 +565,30 @@ mod test { // BIP44 `pkh(key/44'/{0,1}'/0'/{0,1}/*)` #[test] fn test_bip44_template_cointype() { - use bitcoin::util::bip32::ChildNumber::{self, Hardened}; + use bitcoin::bip32::ChildNumber::{self, Hardened}; - let xprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap(); + let xprvkey = bitcoin::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K2fpbqApQL69a4oKdGVnVN52R82Ft7d1pSqgKmajF62acJo3aMszZb6qQ22QsVECSFxvf9uyxFUvFYQMq3QbtwtRSMjLAhMf").unwrap(); assert_eq!(Network::Bitcoin, xprvkey.network); let xdesc = Bip44(xprvkey, KeychainKind::Internal) .build(Network::Bitcoin) .unwrap(); if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 { - let path: Vec = pkh.into_inner().full_derivation_path().into(); + let path: Vec = pkh.into_inner().full_derivation_path().unwrap().into(); let purpose = path.get(0).unwrap(); assert_matches!(purpose, Hardened { index: 44 }); let coin_type = path.get(1).unwrap(); assert_matches!(coin_type, Hardened { index: 0 }); } - let tprvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let tprvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); assert_eq!(Network::Testnet, tprvkey.network); let tdesc = Bip44(tprvkey, KeychainKind::Internal) .build(Network::Testnet) .unwrap(); if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 { - let path: Vec = pkh.into_inner().full_derivation_path().into(); + let path: Vec = pkh.into_inner().full_derivation_path().unwrap().into(); let purpose = path.get(0).unwrap(); assert_matches!(purpose, Hardened { index: 44 }); let coin_type = path.get(1).unwrap(); @@ -612,9 +612,9 @@ mod test { for i in 0..expected.len() { let index = i as u32; let child_desc = if !desc.has_wildcard() { - desc.at_derivation_index(0) + desc.at_derivation_index(0).unwrap() } else { - desc.at_derivation_index(index) + desc.at_derivation_index(index).unwrap() }; let address = child_desc.address(network).unwrap(); assert_eq!(address.to_string(), *expected.get(i).unwrap()); @@ -740,7 +740,7 @@ mod test { // BIP44 `pkh(key/44'/0'/0'/{0,1}/*)` #[test] fn test_bip44_template() { - let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( Bip44(prvkey, KeychainKind::External).build(Network::Bitcoin), false, @@ -770,8 +770,8 @@ mod test { // BIP44 public `pkh(key/{0,1}/*)` #[test] fn test_bip44_public_template() { - let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); - let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); + let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU").unwrap(); + let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( Bip44Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), false, @@ -801,7 +801,7 @@ mod test { // BIP49 `sh(wpkh(key/49'/0'/0'/{0,1}/*))` #[test] fn test_bip49_template() { - let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( Bip49(prvkey, KeychainKind::External).build(Network::Bitcoin), true, @@ -831,8 +831,8 @@ mod test { // BIP49 public `sh(wpkh(key/{0,1}/*))` #[test] fn test_bip49_public_template() { - let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); - let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); + let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L").unwrap(); + let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( Bip49Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), true, @@ -862,7 +862,7 @@ mod test { // BIP84 `wpkh(key/84'/0'/0'/{0,1}/*)` #[test] fn test_bip84_template() { - let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); + let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap(); check( Bip84(prvkey, KeychainKind::External).build(Network::Bitcoin), true, @@ -892,8 +892,8 @@ mod test { // BIP84 public `wpkh(key/{0,1}/*)` #[test] fn test_bip84_public_template() { - let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); - let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("c55b303f").unwrap(); + let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q").unwrap(); + let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f").unwrap(); check( Bip84Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), true, @@ -924,7 +924,7 @@ mod test { // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki #[test] fn test_bip86_template() { - let prvkey = bitcoin::util::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap(); + let prvkey = bitcoin::bip32::ExtendedPrivKey::from_str("xprv9s21ZrQH143K3GJpoapnV8SFfukcVBSfeCficPSGfubmSFDxo1kuHnLisriDvSnRRuL2Qrg5ggqHKNVpxR86QEC8w35uxmGoggxtQTPvfUu").unwrap(); check( Bip86(prvkey, KeychainKind::External).build(Network::Bitcoin), false, @@ -955,8 +955,8 @@ mod test { // Used addresses in test vector in https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki #[test] fn test_bip86_public_template() { - let pubkey = bitcoin::util::bip32::ExtendedPubKey::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap(); - let fingerprint = bitcoin::util::bip32::Fingerprint::from_str("73c5da0a").unwrap(); + let pubkey = bitcoin::bip32::ExtendedPubKey::from_str("xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ").unwrap(); + let fingerprint = bitcoin::bip32::Fingerprint::from_str("73c5da0a").unwrap(); check( Bip86Public(pubkey, fingerprint, KeychainKind::External).build(Network::Bitcoin), false, diff --git a/crates/bdk/src/error.rs b/crates/bdk/src/error.rs index 22817a3e..fcb5a6f7 100644 --- a/crates/bdk/src/error.rs +++ b/crates/bdk/src/error.rs @@ -84,9 +84,9 @@ pub enum Error { /// Miniscript PSBT error MiniscriptPsbt(MiniscriptPsbtError), /// BIP32 error - Bip32(bitcoin::util::bip32::Error), + Bip32(bitcoin::bip32::Error), /// Partially signed bitcoin transaction error - Psbt(bitcoin::util::psbt::Error), + Psbt(bitcoin::psbt::Error), } /// Errors returned by miniscript when updating inconsistent PSBTs @@ -197,5 +197,5 @@ impl From for Error { impl_error!(miniscript::Error, Miniscript); impl_error!(MiniscriptPsbtError, MiniscriptPsbt); -impl_error!(bitcoin::util::bip32::Error, Bip32); -impl_error!(bitcoin::util::psbt::Error, Psbt); +impl_error!(bitcoin::bip32::Error, Bip32); +impl_error!(bitcoin::psbt::Error, Psbt); diff --git a/crates/bdk/src/keys/bip39.rs b/crates/bdk/src/keys/bip39.rs index 78f54493..8b09ac28 100644 --- a/crates/bdk/src/keys/bip39.rs +++ b/crates/bdk/src/keys/bip39.rs @@ -15,7 +15,7 @@ // something that should be fairly simple to re-implement. use alloc::string::String; -use bitcoin::util::bip32; +use bitcoin::bip32; use bitcoin::Network; use miniscript::ScriptContext; @@ -142,7 +142,7 @@ impl GeneratableKey for Mnemonic { (word_count, language): Self::Options, entropy: Self::Entropy, ) -> Result, Self::Error> { - let entropy = &entropy.as_ref()[..(word_count as usize / 8)]; + let entropy = &entropy[..(word_count as usize / 8)]; let mnemonic = Mnemonic::from_entropy_in(language, entropy)?; Ok(GeneratedKey::new(mnemonic, any_network())) @@ -154,7 +154,7 @@ mod test { use alloc::string::ToString; use core::str::FromStr; - use bitcoin::util::bip32; + use bitcoin::bip32; use bip39::{Language, Mnemonic}; diff --git a/crates/bdk/src/keys/mod.rs b/crates/bdk/src/keys/mod.rs index b4cfb6de..c9162593 100644 --- a/crates/bdk/src/keys/mod.rs +++ b/crates/bdk/src/keys/mod.rs @@ -22,8 +22,8 @@ use core::str::FromStr; use bitcoin::secp256k1::{self, Secp256k1, Signing}; -use bitcoin::util::bip32; -use bitcoin::{Network, PrivateKey, PublicKey, XOnlyPublicKey}; +use bitcoin::bip32; +use bitcoin::{key::XOnlyPublicKey, Network, PrivateKey, PublicKey}; use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard}; pub use miniscript::descriptor::{ @@ -388,12 +388,12 @@ impl From for ExtendedKey { /// /// ``` /// use bdk::bitcoin; -/// use bdk::bitcoin::util::bip32; +/// use bdk::bitcoin::bip32; /// use bdk::keys::{DerivableKey, ExtendedKey, KeyError, ScriptContext}; /// /// struct MyCustomKeyType { /// key_data: bitcoin::PrivateKey, -/// chain_code: Vec, +/// chain_code: [u8; 32], /// network: bitcoin::Network, /// } /// @@ -404,7 +404,7 @@ impl From for ExtendedKey { /// depth: 0, /// parent_fingerprint: bip32::Fingerprint::default(), /// private_key: self.key_data.inner, -/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()), +/// chain_code: bip32::ChainCode::from(&self.chain_code), /// child_number: bip32::ChildNumber::Normal { index: 0 }, /// }; /// @@ -419,14 +419,14 @@ impl From for ExtendedKey { /// /// ``` /// use bdk::bitcoin; -/// use bdk::bitcoin::util::bip32; +/// use bdk::bitcoin::bip32; /// use bdk::keys::{ /// any_network, DerivableKey, DescriptorKey, ExtendedKey, KeyError, ScriptContext, /// }; /// /// struct MyCustomKeyType { /// key_data: bitcoin::PrivateKey, -/// chain_code: Vec, +/// chain_code: [u8; 32], /// } /// /// impl DerivableKey for MyCustomKeyType { @@ -436,7 +436,7 @@ impl From for ExtendedKey { /// depth: 0, /// parent_fingerprint: bip32::Fingerprint::default(), /// private_key: self.key_data.inner, -/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()), +/// chain_code: bip32::ChainCode::from(&self.chain_code), /// child_number: bip32::ChildNumber::Normal { index: 0 }, /// }; /// @@ -927,13 +927,13 @@ pub enum KeyError { Message(String), /// BIP32 error - Bip32(bitcoin::util::bip32::Error), + Bip32(bitcoin::bip32::Error), /// Miniscript error Miniscript(miniscript::Error), } impl_error!(miniscript::Error, Miniscript, KeyError); -impl_error!(bitcoin::util::bip32::Error, Bip32, KeyError); +impl_error!(bitcoin::bip32::Error, Bip32, KeyError); impl fmt::Display for KeyError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -953,7 +953,7 @@ impl std::error::Error for KeyError {} #[cfg(test)] pub mod test { - use bitcoin::util::bip32; + use bitcoin::bip32; use super::*; diff --git a/crates/bdk/src/psbt/mod.rs b/crates/bdk/src/psbt/mod.rs index 87243236..bc6ce858 100644 --- a/crates/bdk/src/psbt/mod.rs +++ b/crates/bdk/src/psbt/mod.rs @@ -13,7 +13,7 @@ use crate::FeeRate; use alloc::vec::Vec; -use bitcoin::util::psbt::PartiallySignedTransaction as Psbt; +use bitcoin::psbt::PartiallySignedTransaction as Psbt; use bitcoin::TxOut; // TODO upstream the functions here to `rust-bitcoin`? diff --git a/crates/bdk/src/types.rs b/crates/bdk/src/types.rs index e21bef90..1b96ef0f 100644 --- a/crates/bdk/src/types.rs +++ b/crates/bdk/src/types.rs @@ -15,7 +15,7 @@ use core::ops::Sub; use bdk_chain::ConfirmationTime; use bitcoin::blockdata::transaction::{OutPoint, Transaction, TxOut}; -use bitcoin::{hash_types::Txid, util::psbt}; +use bitcoin::{hash_types::Txid, psbt, Weight}; use serde::{Deserialize, Serialize}; @@ -99,8 +99,8 @@ impl FeeRate { } /// Calculate fee rate from `fee` and weight units (`wu`). - pub fn from_wu(fee: u64, wu: usize) -> FeeRate { - Self::from_vb(fee, wu.vbytes()) + pub fn from_wu(fee: u64, wu: Weight) -> FeeRate { + Self::from_vb(fee, wu.to_vbytes_ceil() as usize) } /// Calculate fee rate from `fee` and `vbytes`. @@ -120,8 +120,8 @@ impl FeeRate { } /// Calculate absolute fee in Satoshis using size in weight units. - pub fn fee_wu(&self, wu: usize) -> u64 { - self.fee_vb(wu.vbytes()) + pub fn fee_wu(&self, wu: Weight) -> u64 { + self.fee_vb(wu.to_vbytes_ceil() as usize) } /// Calculate absolute fee in Satoshis using size in virtual bytes. diff --git a/crates/bdk/src/wallet/coin_selection.rs b/crates/bdk/src/wallet/coin_selection.rs index e7927cab..6f30fd14 100644 --- a/crates/bdk/src/wallet/coin_selection.rs +++ b/crates/bdk/src/wallet/coin_selection.rs @@ -38,12 +38,12 @@ //! &self, //! required_utxos: Vec, //! optional_utxos: Vec, -//! fee_rate: FeeRate, +//! fee_rate: bdk::FeeRate, //! target_amount: u64, //! drain_script: &Script, //! ) -> Result { //! let mut selected_amount = 0; -//! let mut additional_weight = 0; +//! let mut additional_weight = Weight::ZERO; //! let all_utxos_selected = required_utxos //! .into_iter() //! .chain(optional_utxos) @@ -51,7 +51,9 @@ //! (&mut selected_amount, &mut additional_weight), //! |(selected_amount, additional_weight), weighted_utxo| { //! **selected_amount += weighted_utxo.utxo.txout().value; -//! **additional_weight += TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight; +//! **additional_weight += Weight::from_wu( +//! (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, +//! ); //! Some(weighted_utxo.utxo) //! }, //! ) @@ -80,7 +82,10 @@ //! # let mut wallet = doctest_wallet!(); //! // create wallet, sync, ... //! -//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); +//! let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt") +//! .unwrap() +//! .require_network(Network::Testnet) +//! .unwrap(); //! let (psbt, details) = { //! let mut builder = wallet.build_tx().coin_selection(AlwaysSpendEverything); //! builder.add_recipient(to_address.script_pubkey(), 50_000); @@ -99,7 +104,7 @@ use crate::{error::Error, Utxo}; use alloc::vec::Vec; use bitcoin::consensus::encode::serialize; -use bitcoin::Script; +use bitcoin::{Script, Weight}; use core::convert::TryInto; use rand::seq::SliceRandom; @@ -302,8 +307,9 @@ fn select_sorted_utxos( (&mut selected_amount, &mut fee_amount), |(selected_amount, fee_amount), (must_use, weighted_utxo)| { if must_use || **selected_amount < target_amount + **fee_amount { - **fee_amount += - fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight); + **fee_amount += fee_rate.fee_wu(Weight::from_wu( + (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, + )); **selected_amount += weighted_utxo.utxo.txout().value; log::debug!( @@ -351,7 +357,9 @@ struct OutputGroup { impl OutputGroup { fn new(weighted_utxo: WeightedUtxo, fee_rate: FeeRate) -> Self { - let fee = fee_rate.fee_wu(TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight); + let fee = fee_rate.fee_wu(Weight::from_wu( + (TXIN_BASE_WEIGHT + weighted_utxo.satisfaction_weight) as u64, + )); let effective_value = weighted_utxo.utxo.txout().value as i64 - fee as i64; OutputGroup { weighted_utxo, @@ -681,7 +689,7 @@ mod test { use core::str::FromStr; use bdk_chain::ConfirmationTime; - use bitcoin::{OutPoint, Script, TxOut}; + use bitcoin::{OutPoint, ScriptBuf, TxOut}; use super::*; use crate::types::*; @@ -710,7 +718,7 @@ mod test { outpoint, txout: TxOut { value, - script_pubkey: Script::new(), + script_pubkey: ScriptBuf::new(), }, keychain: KeychainKind::External, is_spent: false, @@ -773,7 +781,7 @@ mod test { .unwrap(), txout: TxOut { value: rng.gen_range(0..200000000), - script_pubkey: Script::new(), + script_pubkey: ScriptBuf::new(), }, keychain: KeychainKind::External, is_spent: false, @@ -802,7 +810,7 @@ mod test { .unwrap(), txout: TxOut { value: utxos_value, - script_pubkey: Script::new(), + script_pubkey: ScriptBuf::new(), }, keychain: KeychainKind::External, is_spent: false, @@ -825,7 +833,7 @@ mod test { #[test] fn test_largest_first_coin_selection_success() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 250_000 + FEE_AMOUNT; let result = LargestFirstCoinSelection::default() @@ -846,7 +854,7 @@ mod test { #[test] fn test_largest_first_coin_selection_use_all() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; let result = LargestFirstCoinSelection::default() @@ -867,7 +875,7 @@ mod test { #[test] fn test_largest_first_coin_selection_use_only_necessary() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; let result = LargestFirstCoinSelection::default() @@ -889,7 +897,7 @@ mod test { #[should_panic(expected = "InsufficientFunds")] fn test_largest_first_coin_selection_insufficient_funds() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 500_000 + FEE_AMOUNT; LargestFirstCoinSelection::default() @@ -907,7 +915,7 @@ mod test { #[should_panic(expected = "InsufficientFunds")] fn test_largest_first_coin_selection_insufficient_funds_high_fees() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 250_000 + FEE_AMOUNT; LargestFirstCoinSelection::default() @@ -924,7 +932,7 @@ mod test { #[test] fn test_oldest_first_coin_selection_success() { let utxos = get_oldest_first_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 180_000 + FEE_AMOUNT; let result = OldestFirstCoinSelection::default() @@ -945,7 +953,7 @@ mod test { #[test] fn test_oldest_first_coin_selection_use_all() { let utxos = get_oldest_first_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; let result = OldestFirstCoinSelection::default() @@ -966,7 +974,7 @@ mod test { #[test] fn test_oldest_first_coin_selection_use_only_necessary() { let utxos = get_oldest_first_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; let result = OldestFirstCoinSelection::default() @@ -988,7 +996,7 @@ mod test { #[should_panic(expected = "InsufficientFunds")] fn test_oldest_first_coin_selection_insufficient_funds() { let utxos = get_oldest_first_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 600_000 + FEE_AMOUNT; OldestFirstCoinSelection::default() @@ -1008,7 +1016,7 @@ mod test { let utxos = get_oldest_first_test_utxos(); let target_amount: u64 = utxos.iter().map(|wu| wu.utxo.txout().value).sum::() - 50; - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); OldestFirstCoinSelection::default() .coin_select( @@ -1027,7 +1035,7 @@ mod test { // select three outputs let utxos = generate_same_value_utxos(100_000, 20); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 250_000 + FEE_AMOUNT; @@ -1049,7 +1057,7 @@ mod test { #[test] fn test_bnb_coin_selection_required_are_enough() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; let result = BranchAndBoundCoinSelection::default() @@ -1070,7 +1078,7 @@ mod test { #[test] fn test_bnb_coin_selection_optional_are_enough() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 299756 + FEE_AMOUNT; let result = BranchAndBoundCoinSelection::default() @@ -1106,7 +1114,7 @@ mod test { assert_eq!(amount, 100_000); let amount: u64 = optional.iter().map(|u| u.utxo.txout().value).sum(); assert!(amount > 150_000); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 150_000 + FEE_AMOUNT; @@ -1129,7 +1137,7 @@ mod test { #[should_panic(expected = "InsufficientFunds")] fn test_bnb_coin_selection_insufficient_funds() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 500_000 + FEE_AMOUNT; BranchAndBoundCoinSelection::default() @@ -1147,7 +1155,7 @@ mod test { #[should_panic(expected = "InsufficientFunds")] fn test_bnb_coin_selection_insufficient_funds_high_fees() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 250_000 + FEE_AMOUNT; BranchAndBoundCoinSelection::default() @@ -1164,7 +1172,7 @@ mod test { #[test] fn test_bnb_coin_selection_check_fee_rate() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 99932; // first utxo's effective value let result = BranchAndBoundCoinSelection::new(0) @@ -1192,7 +1200,7 @@ mod test { for _i in 0..200 { let mut optional_utxos = generate_random_utxos(&mut rng, 16); let target_amount = sum_random_utxos(&mut rng, &mut optional_utxos); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let result = BranchAndBoundCoinSelection::new(0) .coin_select( vec![], @@ -1220,7 +1228,7 @@ mod test { let size_of_change = 31; let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let target_amount = 20_000 + FEE_AMOUNT; BranchAndBoundCoinSelection::new(size_of_change) .bnb( @@ -1251,7 +1259,7 @@ mod test { let cost_of_change = size_of_change as f32 * fee_rate.as_sat_per_vb(); let target_amount = 20_000 + FEE_AMOUNT; - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); BranchAndBoundCoinSelection::new(size_of_change) .bnb( @@ -1287,7 +1295,7 @@ mod test { // cost_of_change + 5. let target_amount = 2 * 50_000 - 2 * 67 - cost_of_change.ceil() as i64 + 5; - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let result = BranchAndBoundCoinSelection::new(size_of_change) .bnb( @@ -1327,7 +1335,7 @@ mod test { let target_amount = optional_utxos[3].effective_value + optional_utxos[23].effective_value; - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let result = BranchAndBoundCoinSelection::new(0) .bnb( @@ -1358,7 +1366,7 @@ mod test { .map(|u| OutputGroup::new(u, fee_rate)) .collect(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let result = BranchAndBoundCoinSelection::default().single_random_draw( vec![], @@ -1376,7 +1384,7 @@ mod test { #[test] fn test_bnb_exclude_negative_effective_value() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let selection = BranchAndBoundCoinSelection::default().coin_select( vec![], @@ -1398,7 +1406,7 @@ mod test { #[test] fn test_bnb_include_negative_effective_value_when_required() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let (required, optional) = utxos .into_iter() @@ -1424,7 +1432,7 @@ mod test { #[test] fn test_bnb_sum_of_effective_value_negative() { let utxos = get_test_utxos(); - let drain_script = Script::default(); + let drain_script = ScriptBuf::default(); let selection = BranchAndBoundCoinSelection::default().coin_select( utxos, diff --git a/crates/bdk/src/wallet/export.rs b/crates/bdk/src/wallet/export.rs index a4d93976..270d7340 100644 --- a/crates/bdk/src/wallet/export.rs +++ b/crates/bdk/src/wallet/export.rs @@ -232,7 +232,7 @@ mod test { input: vec![], output: vec![], version: 0, - lock_time: bitcoin::PackedLockTime::ZERO, + lock_time: bitcoin::absolute::LockTime::ZERO, }; wallet .insert_checkpoint(BlockId { diff --git a/crates/bdk/src/wallet/hardwaresigner.rs b/crates/bdk/src/wallet/hardwaresigner.rs index dce1da8b..aec49297 100644 --- a/crates/bdk/src/wallet/hardwaresigner.rs +++ b/crates/bdk/src/wallet/hardwaresigner.rs @@ -19,7 +19,7 @@ //! # use bdk::wallet::hardwaresigner::HWISigner; //! # use bdk::wallet::AddressIndex::New; //! # use bdk::{FeeRate, KeychainKind, SignOptions, Wallet}; -//! # use hwi::{types::HWIChain, HWIClient}; +//! # use hwi::HWIClient; //! # use std::sync::Arc; //! # //! # fn main() -> Result<(), Box> { @@ -28,7 +28,7 @@ //! panic!("No devices found!"); //! } //! let first_device = devices.remove(0)?; -//! let custom_signer = HWISigner::from_device(&first_device, HWIChain::Test)?; +//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?; //! //! # let mut wallet = Wallet::new_no_persist( //! # "", @@ -47,9 +47,9 @@ //! # } //! ``` +use bitcoin::bip32::Fingerprint; use bitcoin::psbt::PartiallySignedTransaction; use bitcoin::secp256k1::{All, Secp256k1}; -use bitcoin::util::bip32::Fingerprint; use hwi::error::Error; use hwi::types::{HWIChain, HWIDevice}; diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 5eeea45a..2ef69f31 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -29,11 +29,12 @@ use bdk_chain::{ IndexedTxGraph, Persist, PersistBackend, }; use bitcoin::consensus::encode::serialize; +use bitcoin::psbt; use bitcoin::secp256k1::Secp256k1; -use bitcoin::util::psbt; +use bitcoin::sighash::{EcdsaSighashType, TapSighashType}; use bitcoin::{ - Address, EcdsaSighashType, LockTime, Network, OutPoint, SchnorrSighashType, Script, Sequence, - Transaction, TxOut, Txid, Witness, + absolute, Address, Network, OutPoint, Script, ScriptBuf, Sequence, Transaction, TxOut, Txid, + Weight, Witness, }; use core::fmt; use core::ops::Deref; @@ -322,11 +323,11 @@ impl Wallet { let (index, spk, additions) = match address_index { AddressIndex::New => { let ((index, spk), index_additions) = txout_index.reveal_next_spk(&keychain); - (index, spk.clone(), Some(index_additions)) + (index, spk.into(), Some(index_additions)) } AddressIndex::LastUnused => { let ((index, spk), index_additions) = txout_index.next_unused_spk(&keychain); - (index, spk.clone(), Some(index_additions)) + (index, spk.into(), Some(index_additions)) } AddressIndex::Peek(index) => { let (index, spk) = txout_index @@ -396,7 +397,7 @@ impl Wallet { /// script pubkeys the wallet is storing internally). pub fn spks_of_all_keychains( &self, - ) -> BTreeMap + Clone> { + ) -> BTreeMap + Clone> { self.indexed_graph.index.spks_of_all_keychains() } @@ -408,7 +409,7 @@ impl Wallet { pub fn spks_of_keychain( &self, keychain: KeychainKind, - ) -> impl Iterator + Clone { + ) -> impl Iterator + Clone { self.indexed_graph.index.spks_of_keychain(&keychain) } @@ -599,7 +600,7 @@ impl Wallet { /// # use bdk::*; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); - /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); + /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let (psbt, details) = { /// let mut builder = wallet.build_tx(); /// builder @@ -716,7 +717,7 @@ impl Wallet { None => self .chain .tip() - .map(|cp| LockTime::from_height(cp.height()).expect("Invalid height")), + .map(|cp| absolute::LockTime::from_height(cp.height()).expect("Invalid height")), h => h, }; @@ -726,7 +727,7 @@ impl Wallet { // Fee sniping can be partially prevented by setting the timelock // to current_height. If we don't know the current_height, // we default to 0. - let fee_sniping_height = current_height.unwrap_or(LockTime::ZERO); + let fee_sniping_height = current_height.unwrap_or(absolute::LockTime::ZERO); // We choose the biggest between the required nlocktime and the fee sniping // height @@ -734,7 +735,7 @@ impl Wallet { // No requirement, just use the fee_sniping_height None => fee_sniping_height, // There's a block-based requirement, but the value is lower than the fee_sniping_height - Some(value @ LockTime::Blocks(_)) if value < fee_sniping_height => fee_sniping_height, + Some(value @ absolute::LockTime::Blocks(_)) if value < fee_sniping_height => fee_sniping_height, // There's a time-based requirement or a block-based requirement greater // than the fee_sniping_height use that value Some(value) => value, @@ -750,7 +751,9 @@ impl Wallet { let n_sequence = match (params.rbf, requirements.csv) { // No RBF or CSV but there's an nLockTime, so the nSequence cannot be final - (None, None) if lock_time != LockTime::ZERO => Sequence::ENABLE_LOCKTIME_NO_RBF, + (None, None) if lock_time != absolute::LockTime::ZERO => { + Sequence::ENABLE_LOCKTIME_NO_RBF + } // No RBF, CSV or nLockTime, make the transaction final (None, None) => Sequence::MAX, @@ -813,7 +816,7 @@ impl Wallet { let mut tx = Transaction { version, - lock_time: lock_time.into(), + lock_time, input: vec![], output: vec![], }; @@ -861,7 +864,7 @@ impl Wallet { // end up with a transaction with a slightly higher fee rate than the requested one. // If, instead, we undershoot, we may end up with a feerate lower than the requested one // - we might come up with non broadcastable txs! - fee_amount += fee_rate.fee_wu(2); + fee_amount += fee_rate.fee_wu(Weight::from_wu(2)); if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed && internal_descriptor.is_none() @@ -878,7 +881,7 @@ impl Wallet { params.drain_wallet, params.manually_selected_only, params.bumping_fee.is_some(), // we mandate confirmed transactions if we're bumping the fee - current_height.map(LockTime::to_consensus_u32), + current_height.map(absolute::LockTime::to_consensus_u32), ); // get drain script @@ -888,7 +891,7 @@ impl Wallet { let change_keychain = self.map_keychain(KeychainKind::Internal); let ((index, spk), index_additions) = self.indexed_graph.index.next_unused_spk(&change_keychain); - let spk = spk.clone(); + let spk = spk.into(); self.indexed_graph.index.mark_used(&change_keychain, index); self.persist .stage(ChangeSet::from(IndexedAdditions::from(index_additions))); @@ -912,7 +915,7 @@ impl Wallet { .iter() .map(|u| bitcoin::TxIn { previous_output: u.outpoint(), - script_sig: Script::default(), + script_sig: ScriptBuf::default(), sequence: n_sequence, witness: Witness::new(), }) @@ -1000,7 +1003,7 @@ impl Wallet { /// # use bdk::*; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); - /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); + /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let (mut psbt, _) = { /// let mut builder = wallet.build_tx(); /// builder @@ -1014,7 +1017,7 @@ impl Wallet { /// let (mut psbt, _) = { /// let mut builder = wallet.build_fee_bump(tx.txid())?; /// builder - /// .fee_rate(FeeRate::from_sat_per_vb(5.0)); + /// .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)); /// builder.finish()? /// }; /// @@ -1078,6 +1081,7 @@ impl Wallet { let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) { Some(&(keychain, derivation_index)) => { + #[allow(deprecated)] let satisfaction_weight = self .get_descriptor_for_keychain(keychain) .max_satisfaction_weight() @@ -1170,7 +1174,7 @@ impl Wallet { /// # use bdk::*; /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)"; /// # let mut wallet = doctest_wallet!(); - /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); + /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// let (mut psbt, _) = { /// let mut builder = wallet.build_tx(); /// builder.add_recipient(to_address.script_pubkey(), 50_000); @@ -1207,8 +1211,8 @@ impl Wallet { && !psbt.inputs.iter().all(|i| { i.sighash_type.is_none() || i.sighash_type == Some(EcdsaSighashType::All.into()) - || i.sighash_type == Some(SchnorrSighashType::All.into()) - || i.sighash_type == Some(SchnorrSighashType::Default.into()) + || i.sighash_type == Some(TapSighashType::All.into()) + || i.sighash_type == Some(TapSighashType::Default.into()) }) { return Err(Error::Signer(signer::SignerError::NonStandardSighash)); @@ -1403,13 +1407,14 @@ impl Wallet { .index .index_of_spk(&txout.script_pubkey)?; let descriptor = self.get_descriptor_for_keychain(keychain); - Some(descriptor.at_derivation_index(child)) + descriptor.at_derivation_index(child).ok() } fn get_available_utxos(&self) -> Vec<(LocalUtxo, usize)> { self.list_unspent() .map(|utxo| { let keychain = utxo.keychain; + #[allow(deprecated)] ( utxo, self.get_descriptor_for_keychain(keychain) @@ -1620,7 +1625,9 @@ impl Wallet { }; let desc = self.get_descriptor_for_keychain(keychain); - let derived_descriptor = desc.at_derivation_index(child); + let derived_descriptor = desc + .at_derivation_index(child) + .expect("child can't be hardened"); psbt_input .update_with_descriptor_unchecked(&derived_descriptor) @@ -1669,7 +1676,9 @@ impl Wallet { ); let desc = self.get_descriptor_for_keychain(keychain); - let desc = desc.at_derivation_index(child); + let desc = desc + .at_derivation_index(child) + .expect("child can't be hardened"); if is_input { psbt.update_input_with_descriptor(index, &desc) @@ -1871,7 +1880,7 @@ fn new_tx_details( /// Macro for getting a wallet for use in a doctest macro_rules! doctest_wallet { () => {{ - use $crate::bitcoin::{BlockHash, Transaction, PackedLockTime, TxOut, Network, hashes::Hash}; + use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash}; use $crate::chain::{ConfirmationTime, BlockId}; use $crate::wallet::{AddressIndex, Wallet}; let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)"; @@ -1886,7 +1895,7 @@ macro_rules! doctest_wallet { let address = wallet.get_address(AddressIndex::New).address; let tx = Transaction { version: 1, - lock_time: PackedLockTime(0), + lock_time: absolute::LockTime::ZERO, input: vec![], output: vec![TxOut { value: 500_000, diff --git a/crates/bdk/src/wallet/signer.rs b/crates/bdk/src/wallet/signer.rs index 68dc4645..68b2ecb1 100644 --- a/crates/bdk/src/wallet/signer.rs +++ b/crates/bdk/src/wallet/signer.rs @@ -19,7 +19,7 @@ //! # use core::str::FromStr; //! # use bitcoin::secp256k1::{Secp256k1, All}; //! # use bitcoin::*; -//! # use bitcoin::util::psbt; +//! # use bitcoin::psbt; //! # use bdk::signer::*; //! # use bdk::*; //! # #[derive(Debug)] @@ -86,18 +86,17 @@ use core::cmp::Ordering; use core::fmt; use core::ops::{Bound::Included, Deref}; -use bitcoin::blockdata::opcodes; -use bitcoin::blockdata::script::Builder as ScriptBuilder; -use bitcoin::hashes::{hash160, Hash}; +use bitcoin::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint}; +use bitcoin::hashes::hash160; use bitcoin::secp256k1::Message; -use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint}; -use bitcoin::util::{ecdsa, psbt, schnorr, sighash, taproot}; -use bitcoin::{secp256k1, XOnlyPublicKey}; -use bitcoin::{EcdsaSighashType, PrivateKey, PublicKey, SchnorrSighashType, Script}; +use bitcoin::sighash::{EcdsaSighashType, TapSighash, TapSighashType}; +use bitcoin::{ecdsa, psbt, sighash, taproot}; +use bitcoin::{key::TapTweak, key::XOnlyPublicKey, secp256k1}; +use bitcoin::{PrivateKey, PublicKey}; use miniscript::descriptor::{ - Descriptor, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, KeyMap, SinglePriv, - SinglePubKey, + Descriptor, DescriptorMultiXKey, DescriptorPublicKey, DescriptorSecretKey, DescriptorXKey, + InnerXKey, KeyMap, SinglePriv, SinglePubKey, }; use miniscript::{Legacy, Segwitv0, SigType, Tap, ToPublicKey}; @@ -130,7 +129,7 @@ impl From for SignerId { } /// Signing error -#[derive(Debug, PartialEq, Eq, Clone)] +#[derive(Debug)] pub enum SignerError { /// The private key is missing for the required public key MissingKey, @@ -383,6 +382,48 @@ impl InputSigner for SignerWrapper> { } } +fn multikey_to_xkeys( + multikey: DescriptorMultiXKey, +) -> Vec> { + multikey + .derivation_paths + .into_paths() + .into_iter() + .map(|derivation_path| DescriptorXKey { + origin: multikey.origin.clone(), + xkey: multikey.xkey.clone(), + derivation_path, + wildcard: multikey.wildcard, + }) + .collect() +} + +impl SignerCommon for SignerWrapper> { + fn id(&self, secp: &SecpCtx) -> SignerId { + SignerId::from(self.root_fingerprint(secp)) + } + + fn descriptor_secret_key(&self) -> Option { + Some(DescriptorSecretKey::MultiXPrv(self.signer.clone())) + } +} + +impl InputSigner for SignerWrapper> { + fn sign_input( + &self, + psbt: &mut psbt::PartiallySignedTransaction, + input_index: usize, + sign_options: &SignOptions, + secp: &SecpCtx, + ) -> Result<(), SignerError> { + let xkeys = multikey_to_xkeys(self.signer.clone()); + for xkey in xkeys { + SignerWrapper::new(xkey, self.ctx).sign_input(psbt, input_index, sign_options, secp)? + } + Ok(()) + } +} + impl SignerCommon for SignerWrapper { fn id(&self, secp: &SecpCtx) -> SignerId { SignerId::from(self.public_key(secp).to_pubkeyhash(SigType::Ecdsa)) @@ -477,8 +518,16 @@ impl InputSigner for SignerWrapper { } let (hash, hash_ty) = match self.ctx { - SignerContext::Segwitv0 => Segwitv0::sighash(psbt, input_index, ())?, - SignerContext::Legacy => Legacy::sighash(psbt, input_index, ())?, + SignerContext::Segwitv0 => { + let (h, t) = Segwitv0::sighash(psbt, input_index, ())?; + let h = h.to_raw_hash(); + (h, t) + } + SignerContext::Legacy => { + let (h, t) = Legacy::sighash(psbt, input_index, ())?; + let h = h.to_raw_hash(); + (h, t) + } _ => return Ok(()), // handled above }; sign_psbt_ecdsa( @@ -499,12 +548,12 @@ fn sign_psbt_ecdsa( secret_key: &secp256k1::SecretKey, pubkey: PublicKey, psbt_input: &mut psbt::Input, - hash: bitcoin::Sighash, + hash: impl bitcoin::hashes::Hash + bitcoin::secp256k1::ThirtyTwoByteHash, hash_ty: EcdsaSighashType, secp: &SecpCtx, allow_grinding: bool, ) { - let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap(); + let msg = &Message::from(hash); let sig = if allow_grinding { secp.sign_ecdsa_low_r(msg, secret_key) } else { @@ -513,7 +562,7 @@ fn sign_psbt_ecdsa( secp.verify_ecdsa(msg, &sig, &pubkey.inner) .expect("invalid or corrupted ecdsa signature"); - let final_signature = ecdsa::EcdsaSig { sig, hash_ty }; + let final_signature = ecdsa::Signature { sig, hash_ty }; psbt_input.partial_sigs.insert(pubkey, final_signature); } @@ -523,12 +572,10 @@ fn sign_psbt_schnorr( pubkey: XOnlyPublicKey, leaf_hash: Option, psbt_input: &mut psbt::Input, - hash: taproot::TapSighashHash, - hash_ty: SchnorrSighashType, + hash: TapSighash, + hash_ty: TapSighashType, secp: &SecpCtx, ) { - use schnorr::TapTweak; - let keypair = secp256k1::KeyPair::from_seckey_slice(secp, secret_key.as_ref()).unwrap(); let keypair = match leaf_hash { None => keypair @@ -537,12 +584,12 @@ fn sign_psbt_schnorr( Some(_) => keypair, // no tweak for script spend }; - let msg = &Message::from_slice(&hash.into_inner()[..]).unwrap(); + let msg = &Message::from(hash); let sig = secp.sign_schnorr(msg, &keypair); secp.verify_schnorr(&sig, msg, &XOnlyPublicKey::from_keypair(&keypair).0) .expect("invalid or corrupted schnorr signature"); - let final_signature = schnorr::SchnorrSig { sig, hash_ty }; + let final_signature = taproot::Signature { sig, hash_ty }; if let Some(lh) = leaf_hash { psbt_input @@ -632,6 +679,11 @@ impl SignersContainer { SignerOrdering::default(), Arc::new(SignerWrapper::new(xprv, ctx)), ), + DescriptorSecretKey::MultiXPrv(xprv) => container.add_external( + SignerId::from(xprv.root_fingerprint(secp)), + SignerOrdering::default(), + Arc::new(SignerWrapper::new(xprv, ctx)), + ), }; } @@ -802,7 +854,7 @@ pub(crate) trait ComputeSighash { impl ComputeSighash for Legacy { type Extra = (); - type Sighash = bitcoin::Sighash; + type Sighash = sighash::LegacySighash; type SighashType = EcdsaSighashType; fn sighash( @@ -849,19 +901,9 @@ impl ComputeSighash for Legacy { } } -fn p2wpkh_script_code(script: &Script) -> Script { - ScriptBuilder::new() - .push_opcode(opcodes::all::OP_DUP) - .push_opcode(opcodes::all::OP_HASH160) - .push_slice(&script[2..]) - .push_opcode(opcodes::all::OP_EQUALVERIFY) - .push_opcode(opcodes::all::OP_CHECKSIG) - .into_script() -} - impl ComputeSighash for Segwitv0 { type Extra = (); - type Sighash = bitcoin::Sighash; + type Sighash = sighash::SegwitV0Sighash; type SighashType = EcdsaSighashType; fn sighash( @@ -908,14 +950,21 @@ impl ComputeSighash for Segwitv0 { Some(ref witness_script) => witness_script.clone(), None => { if utxo.script_pubkey.is_v0_p2wpkh() { - p2wpkh_script_code(&utxo.script_pubkey) + utxo.script_pubkey + .p2wpkh_script_code() + .expect("We check above that the spk is a p2wpkh") } else if psbt_input .redeem_script .as_ref() - .map(Script::is_v0_p2wpkh) + .map(|s| s.is_v0_p2wpkh()) .unwrap_or(false) { - p2wpkh_script_code(psbt_input.redeem_script.as_ref().unwrap()) + psbt_input + .redeem_script + .as_ref() + .unwrap() + .p2wpkh_script_code() + .expect("We check above that the spk is a p2wpkh") } else { return Err(SignerError::MissingWitnessScript); } @@ -936,14 +985,14 @@ impl ComputeSighash for Segwitv0 { impl ComputeSighash for Tap { type Extra = Option; - type Sighash = taproot::TapSighashHash; - type SighashType = SchnorrSighashType; + type Sighash = TapSighash; + type SighashType = TapSighashType; fn sighash( psbt: &psbt::PartiallySignedTransaction, input_index: usize, extra: Self::Extra, - ) -> Result<(Self::Sighash, SchnorrSighashType), SignerError> { + ) -> Result<(Self::Sighash, TapSighashType), SignerError> { if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() { return Err(SignerError::InputIndexOutOfRange); } @@ -952,8 +1001,8 @@ impl ComputeSighash for Tap { let sighash_type = psbt_input .sighash_type - .unwrap_or_else(|| SchnorrSighashType::Default.into()) - .schnorr_hash_ty() + .unwrap_or_else(|| TapSighashType::Default.into()) + .taproot_hash_ty() .map_err(|_| SignerError::InvalidSighash)?; let witness_utxos = (0..psbt.inputs.len()) .map(|i| psbt.get_utxo_for(i)) @@ -1015,8 +1064,8 @@ mod signers_container_tests { use crate::descriptor::IntoWalletDescriptor; use crate::keys::{DescriptorKey, IntoDescriptorKey}; use assert_matches::assert_matches; + use bitcoin::bip32; use bitcoin::secp256k1::{All, Secp256k1}; - use bitcoin::util::bip32; use bitcoin::Network; use core::str::FromStr; use miniscript::ScriptContext; diff --git a/crates/bdk/src/wallet/tx_builder.rs b/crates/bdk/src/wallet/tx_builder.rs index 165f01f2..d7bcd711 100644 --- a/crates/bdk/src/wallet/tx_builder.rs +++ b/crates/bdk/src/wallet/tx_builder.rs @@ -18,7 +18,7 @@ //! # use bitcoin::*; //! # use bdk::*; //! # use bdk::wallet::tx_builder::CreateTx; -//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); +//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); //! # let mut wallet = doctest_wallet!(); //! // create a TxBuilder from a wallet //! let mut tx_builder = wallet.build_tx(); @@ -27,7 +27,7 @@ //! // Create a transaction with one output to `to_address` of 50_000 satoshi //! .add_recipient(to_address.script_pubkey(), 50_000) //! // With a custom fee rate of 5.0 satoshi/vbyte -//! .fee_rate(FeeRate::from_sat_per_vb(5.0)) +//! .fee_rate(bdk::FeeRate::from_sat_per_vb(5.0)) //! // Only spend non-change outputs //! .do_not_spend_change() //! // Turn on RBF signaling @@ -43,8 +43,8 @@ use bdk_chain::PersistBackend; use core::cell::RefCell; use core::marker::PhantomData; -use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt}; -use bitcoin::{LockTime, OutPoint, Script, Sequence, Transaction}; +use bitcoin::psbt::{self, PartiallySignedTransaction as Psbt}; +use bitcoin::{absolute, script::PushBytes, OutPoint, ScriptBuf, Sequence, Transaction}; use super::coin_selection::{CoinSelectionAlgorithm, DefaultCoinSelectionAlgorithm}; use super::ChangeSet; @@ -82,7 +82,7 @@ impl TxBuilderContext for BumpFee {} /// # use bitcoin::*; /// # use core::str::FromStr; /// # let mut wallet = doctest_wallet!(); -/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap(); +/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked(); /// # let addr2 = addr1.clone(); /// // chaining /// let (psbt1, details) = { @@ -129,9 +129,9 @@ pub struct TxBuilder<'a, D, Cs, Ctx> { //TODO: TxParams should eventually be exposed publicly. #[derive(Default, Debug, Clone)] pub(crate) struct TxParams { - pub(crate) recipients: Vec<(Script, u64)>, + pub(crate) recipients: Vec<(ScriptBuf, u64)>, pub(crate) drain_wallet: bool, - pub(crate) drain_to: Option