Merge bitcoindevkit/bdk#606: Upgrade to rust-bitcoin 0.28
00164588f2ddab91bb4468ec010dc1bbca277eb5 Stop using deprecated structs (Alekos Filini) a16c18255ceb3e13ff92ee41b2b6f6510491eeb8 Upgrade to rust-bitcoin 0.28 and miniscript 7.0 (Alekos Filini) Pull request description: ### Description Upgrade all our dependencies to work with the new release of rust-bitcoin ### Notes to the reviewers The commits in this pr were originally part of #593 ### 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 updated `CHANGELOG.md` ACKs for top commit: rajarshimaitra: ACK 00164588f2ddab91bb4468ec010dc1bbca277eb5 Tree-SHA512: eef7e94246e619686b4dfffd6e4cb685630fe2eaf9447f2f0b49ed2643d67f81c50e0d89b66267db4552a05e58f638d885eb7056270648403f716803fce9e275
This commit is contained in:
commit
e5486536ae
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- added `OldestFirstCoinSelection` impl to `CoinSelectionAlgorithm`
|
- added `OldestFirstCoinSelection` impl to `CoinSelectionAlgorithm`
|
||||||
- New MSRV set to `1.56`
|
- New MSRV set to `1.56`
|
||||||
- Add traits to reuse `Blockchain`s across multiple wallets (`BlockchainFactory` and `StatelessBlockchain`).
|
- Add traits to reuse `Blockchain`s across multiple wallets (`BlockchainFactory` and `StatelessBlockchain`).
|
||||||
|
- Upgrade to rust-bitcoin `0.28`
|
||||||
|
|
||||||
|
|
||||||
## [v0.18.0] - [v0.17.0]
|
## [v0.18.0] - [v0.17.0]
|
||||||
|
12
Cargo.toml
12
Cargo.toml
@ -14,15 +14,15 @@ license = "MIT OR Apache-2.0"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
bdk-macros = "^0.6"
|
bdk-macros = "^0.6"
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
miniscript = { version = "^6.1", features = ["use-serde"] }
|
miniscript = { version = "7.0", features = ["use-serde"] }
|
||||||
bitcoin = { version = "^0.27", features = ["use-serde", "base64"] }
|
bitcoin = { version = "0.28", features = ["use-serde", "base64"] }
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
serde = { version = "^1.0", features = ["derive"] }
|
||||||
serde_json = { version = "^1.0" }
|
serde_json = { version = "^1.0" }
|
||||||
rand = "^0.7"
|
rand = "^0.7"
|
||||||
|
|
||||||
# Optional dependencies
|
# Optional dependencies
|
||||||
sled = { version = "0.34", optional = true }
|
sled = { version = "0.34", optional = true }
|
||||||
electrum-client = { version = "0.8", optional = true }
|
electrum-client = { version = "0.10", optional = true }
|
||||||
rusqlite = { version = "0.25.3", optional = true }
|
rusqlite = { version = "0.25.3", optional = true }
|
||||||
ahash = { version = "=0.7.4", optional = true }
|
ahash = { version = "=0.7.4", optional = true }
|
||||||
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
|
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
|
||||||
@ -37,8 +37,8 @@ lazy_static = { version = "1.4", optional = true }
|
|||||||
bip39 = { version = "1.0.1", optional = true }
|
bip39 = { version = "1.0.1", optional = true }
|
||||||
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
bitcoinconsensus = { version = "0.19.0-3", optional = true }
|
||||||
|
|
||||||
# Needed by bdk_blockchain_tests macro
|
# Needed by bdk_blockchain_tests macro and the `rpc` feature
|
||||||
bitcoincore-rpc = { version = "0.14", optional = true }
|
bitcoincore-rpc = { version = "0.15", optional = true }
|
||||||
|
|
||||||
# Platform-specific dependencies
|
# Platform-specific dependencies
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
@ -97,7 +97,7 @@ test-md-docs = ["electrum"]
|
|||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
env_logger = "0.7"
|
env_logger = "0.7"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
electrsd = { version= "0.15", features = ["trigger", "bitcoind_22_0"] }
|
electrsd = { version= "0.19.1", features = ["bitcoind_22_0"] }
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "address_validator"
|
name = "address_validator"
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
// licenses.
|
// licenses.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::BufReader;
|
||||||
use std::net::{TcpStream, ToSocketAddrs};
|
use std::net::{TcpStream, ToSocketAddrs};
|
||||||
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
use std::sync::{Arc, Condvar, Mutex, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
@ -19,14 +20,13 @@ use socks::{Socks5Stream, ToTargetAddr};
|
|||||||
|
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
use bitcoin::consensus::Encodable;
|
use bitcoin::consensus::{Decodable, Encodable};
|
||||||
use bitcoin::hash_types::BlockHash;
|
use bitcoin::hash_types::BlockHash;
|
||||||
use bitcoin::network::constants::ServiceFlags;
|
use bitcoin::network::constants::ServiceFlags;
|
||||||
use bitcoin::network::message::{NetworkMessage, RawNetworkMessage};
|
use bitcoin::network::message::{NetworkMessage, RawNetworkMessage};
|
||||||
use bitcoin::network::message_blockdata::*;
|
use bitcoin::network::message_blockdata::*;
|
||||||
use bitcoin::network::message_filter::*;
|
use bitcoin::network::message_filter::*;
|
||||||
use bitcoin::network::message_network::VersionMessage;
|
use bitcoin::network::message_network::VersionMessage;
|
||||||
use bitcoin::network::stream_reader::StreamReader;
|
|
||||||
use bitcoin::network::Address;
|
use bitcoin::network::Address;
|
||||||
use bitcoin::{Block, Network, Transaction, Txid, Wtxid};
|
use bitcoin::{Block, Network, Transaction, Txid, Wtxid};
|
||||||
|
|
||||||
@ -327,9 +327,10 @@ impl Peer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut reader = StreamReader::new(connection, None);
|
let mut reader = BufReader::new(connection);
|
||||||
loop {
|
loop {
|
||||||
let raw_message: RawNetworkMessage = check_disconnect!(reader.read_next());
|
let raw_message: RawNetworkMessage =
|
||||||
|
check_disconnect!(Decodable::consensus_decode(&mut reader));
|
||||||
|
|
||||||
let in_message = if raw_message.magic != network.magic() {
|
let in_message = if raw_message.magic != network.magic() {
|
||||||
continue;
|
continue;
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
//!
|
//!
|
||||||
//! see: <https://github.com/Blockstream/esplora/blob/master/API.md>
|
//! see: <https://github.com/Blockstream/esplora/blob/master/API.md>
|
||||||
use crate::BlockTime;
|
use crate::BlockTime;
|
||||||
use bitcoin::{OutPoint, Script, Transaction, TxIn, TxOut, Txid};
|
use bitcoin::{OutPoint, Script, Transaction, TxIn, TxOut, Txid, Witness};
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Clone, Debug)]
|
#[derive(serde::Deserialize, Clone, Debug)]
|
||||||
pub struct PrevOut {
|
pub struct PrevOut {
|
||||||
@ -63,7 +63,7 @@ impl Tx {
|
|||||||
},
|
},
|
||||||
script_sig: vin.scriptsig,
|
script_sig: vin.scriptsig,
|
||||||
sequence: vin.sequence,
|
sequence: vin.sequence,
|
||||||
witness: vin.witness,
|
witness: Witness::from_vec(vin.witness),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
output: self
|
output: self
|
||||||
|
@ -52,10 +52,10 @@ use std::hash::{Hash, Hasher};
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use bitcoin::hashes::hash160;
|
use bitcoin::hashes::hash160;
|
||||||
use bitcoin::PublicKey;
|
use bitcoin::{PublicKey, XOnlyPublicKey};
|
||||||
|
|
||||||
use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey};
|
use miniscript::descriptor::{DescriptorSinglePub, SinglePubKey, Wildcard};
|
||||||
use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk};
|
use miniscript::{Descriptor, DescriptorPublicKey, MiniscriptKey, ToPublicKey, TranslatePk};
|
||||||
|
|
||||||
use crate::wallet::utils::SecpCtx;
|
use crate::wallet::utils::SecpCtx;
|
||||||
|
|
||||||
@ -128,21 +128,44 @@ impl<'s> MiniscriptKey for DerivedDescriptorKey<'s> {
|
|||||||
fn is_uncompressed(&self) -> bool {
|
fn is_uncompressed(&self) -> bool {
|
||||||
self.0.is_uncompressed()
|
self.0.is_uncompressed()
|
||||||
}
|
}
|
||||||
fn serialized_len(&self) -> usize {
|
|
||||||
self.0.serialized_len()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
|
impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
|
||||||
fn to_public_key(&self) -> PublicKey {
|
fn to_public_key(&self) -> PublicKey {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
DescriptorPublicKey::SinglePub(ref spub) => spub.key.to_public_key(),
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
DescriptorPublicKey::XPub(ref xpub) => {
|
key: SinglePubKey::XOnly(_),
|
||||||
|
..
|
||||||
|
}) => panic!("Found x-only public key in non-tr descriptor"),
|
||||||
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
|
key: SinglePubKey::FullKey(ref pk),
|
||||||
|
..
|
||||||
|
}) => *pk,
|
||||||
|
DescriptorPublicKey::XPub(ref xpub) => PublicKey::new(
|
||||||
xpub.xkey
|
xpub.xkey
|
||||||
.derive_pub(self.1, &xpub.derivation_path)
|
.derive_pub(self.1, &xpub.derivation_path)
|
||||||
.expect("Shouldn't fail, only normal derivations")
|
.expect("Shouldn't fail, only normal derivations")
|
||||||
.public_key
|
.public_key,
|
||||||
}
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_x_only_pubkey(&self) -> XOnlyPublicKey {
|
||||||
|
match &self.0 {
|
||||||
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
|
key: SinglePubKey::XOnly(ref pk),
|
||||||
|
..
|
||||||
|
}) => *pk,
|
||||||
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
|
key: SinglePubKey::FullKey(ref pk),
|
||||||
|
..
|
||||||
|
}) => XOnlyPublicKey::from(pk.inner),
|
||||||
|
DescriptorPublicKey::XPub(ref xpub) => XOnlyPublicKey::from(
|
||||||
|
xpub.xkey
|
||||||
|
.derive_pub(self.1, &xpub.derivation_path)
|
||||||
|
.expect("Shouldn't fail, only normal derivations")
|
||||||
|
.public_key,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1054,7 +1054,9 @@ mod test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")]
|
#[should_panic(
|
||||||
|
expected = "Miniscript(ContextError(CompressedOnly(\"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4\")))"
|
||||||
|
)]
|
||||||
fn test_dsl_miniscript_checks() {
|
fn test_dsl_miniscript_checks() {
|
||||||
let mut uncompressed_pk =
|
let mut uncompressed_pk =
|
||||||
PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
|
PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
|
||||||
|
@ -17,9 +17,10 @@
|
|||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use bitcoin::secp256k1;
|
||||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
|
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
|
||||||
use bitcoin::util::psbt;
|
use bitcoin::util::psbt;
|
||||||
use bitcoin::{Network, PublicKey, Script, TxOut};
|
use bitcoin::{Network, Script, TxOut};
|
||||||
|
|
||||||
use miniscript::descriptor::{DescriptorType, InnerXKey};
|
use miniscript::descriptor::{DescriptorType, InnerXKey};
|
||||||
pub use miniscript::{
|
pub use miniscript::{
|
||||||
@ -58,7 +59,7 @@ pub type DerivedDescriptor<'s> = Descriptor<DerivedDescriptorKey<'s>>;
|
|||||||
///
|
///
|
||||||
/// [`psbt::Input`]: bitcoin::util::psbt::Input
|
/// [`psbt::Input`]: bitcoin::util::psbt::Input
|
||||||
/// [`psbt::Output`]: bitcoin::util::psbt::Output
|
/// [`psbt::Output`]: bitcoin::util::psbt::Output
|
||||||
pub type HdKeyPaths = BTreeMap<PublicKey, KeySource>;
|
pub type HdKeyPaths = BTreeMap<secp256k1::PublicKey, KeySource>;
|
||||||
|
|
||||||
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
|
/// Trait for types which can be converted into an [`ExtendedDescriptor`] and a [`KeyMap`] usable by a wallet in a specific [`Network`]
|
||||||
pub trait IntoWalletDescriptor {
|
pub trait IntoWalletDescriptor {
|
||||||
@ -306,6 +307,7 @@ pub(crate) trait DerivedDescriptorMeta {
|
|||||||
|
|
||||||
pub(crate) trait DescriptorMeta {
|
pub(crate) trait DescriptorMeta {
|
||||||
fn is_witness(&self) -> bool;
|
fn is_witness(&self) -> bool;
|
||||||
|
fn is_taproot(&self) -> bool;
|
||||||
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
|
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
|
||||||
fn derive_from_hd_keypaths<'s>(
|
fn derive_from_hd_keypaths<'s>(
|
||||||
&self,
|
&self,
|
||||||
@ -328,21 +330,21 @@ pub(crate) trait DescriptorScripts {
|
|||||||
impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
|
impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
|
||||||
fn psbt_redeem_script(&self) -> Option<Script> {
|
fn psbt_redeem_script(&self) -> Option<Script> {
|
||||||
match self.desc_type() {
|
match self.desc_type() {
|
||||||
DescriptorType::ShWpkh => Some(self.explicit_script()),
|
DescriptorType::ShWpkh => Some(self.explicit_script().unwrap()),
|
||||||
DescriptorType::ShWsh => Some(self.explicit_script().to_v0_p2wsh()),
|
DescriptorType::ShWsh => Some(self.explicit_script().unwrap().to_v0_p2wsh()),
|
||||||
DescriptorType::Sh => Some(self.explicit_script()),
|
DescriptorType::Sh => Some(self.explicit_script().unwrap()),
|
||||||
DescriptorType::Bare => Some(self.explicit_script()),
|
DescriptorType::Bare => Some(self.explicit_script().unwrap()),
|
||||||
DescriptorType::ShSortedMulti => Some(self.explicit_script()),
|
DescriptorType::ShSortedMulti => Some(self.explicit_script().unwrap()),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn psbt_witness_script(&self) -> Option<Script> {
|
fn psbt_witness_script(&self) -> Option<Script> {
|
||||||
match self.desc_type() {
|
match self.desc_type() {
|
||||||
DescriptorType::Wsh => Some(self.explicit_script()),
|
DescriptorType::Wsh => Some(self.explicit_script().unwrap()),
|
||||||
DescriptorType::ShWsh => Some(self.explicit_script()),
|
DescriptorType::ShWsh => Some(self.explicit_script().unwrap()),
|
||||||
DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
|
DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
|
||||||
Some(self.explicit_script())
|
Some(self.explicit_script().unwrap())
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
@ -362,6 +364,10 @@ impl DescriptorMeta for ExtendedDescriptor {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_taproot(&self) -> bool {
|
||||||
|
self.desc_type() == DescriptorType::Tr
|
||||||
|
}
|
||||||
|
|
||||||
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
|
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
|
||||||
let mut answer = Vec::new();
|
let mut answer = Vec::new();
|
||||||
|
|
||||||
@ -448,7 +454,10 @@ impl DescriptorMeta for ExtendedDescriptor {
|
|||||||
let descriptor = self.as_derived_fixed(secp);
|
let descriptor = self.as_derived_fixed(secp);
|
||||||
match descriptor.desc_type() {
|
match descriptor.desc_type() {
|
||||||
// TODO: add pk() here
|
// TODO: add pk() here
|
||||||
DescriptorType::Pkh | DescriptorType::Wpkh | DescriptorType::ShWpkh
|
DescriptorType::Pkh
|
||||||
|
| DescriptorType::Wpkh
|
||||||
|
| DescriptorType::ShWpkh
|
||||||
|
| DescriptorType::Tr
|
||||||
if utxo.is_some()
|
if utxo.is_some()
|
||||||
&& descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
|
&& descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
|
||||||
{
|
{
|
||||||
@ -456,7 +465,7 @@ impl DescriptorMeta for ExtendedDescriptor {
|
|||||||
}
|
}
|
||||||
DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
|
DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
|
||||||
if psbt_input.redeem_script.is_some()
|
if psbt_input.redeem_script.is_some()
|
||||||
&& &descriptor.explicit_script()
|
&& &descriptor.explicit_script().unwrap()
|
||||||
== psbt_input.redeem_script.as_ref().unwrap() =>
|
== psbt_input.redeem_script.as_ref().unwrap() =>
|
||||||
{
|
{
|
||||||
Some(descriptor)
|
Some(descriptor)
|
||||||
@ -466,7 +475,7 @@ impl DescriptorMeta for ExtendedDescriptor {
|
|||||||
| DescriptorType::ShWshSortedMulti
|
| DescriptorType::ShWshSortedMulti
|
||||||
| DescriptorType::WshSortedMulti
|
| DescriptorType::WshSortedMulti
|
||||||
if psbt_input.witness_script.is_some()
|
if psbt_input.witness_script.is_some()
|
||||||
&& &descriptor.explicit_script()
|
&& &descriptor.explicit_script().unwrap()
|
||||||
== psbt_input.witness_script.as_ref().unwrap() =>
|
== psbt_input.witness_script.as_ref().unwrap() =>
|
||||||
{
|
{
|
||||||
Some(descriptor)
|
Some(descriptor)
|
||||||
|
@ -43,23 +43,27 @@ use serde::ser::SerializeMap;
|
|||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
|
|
||||||
use bitcoin::hashes::*;
|
use bitcoin::hashes::*;
|
||||||
|
use bitcoin::secp256k1;
|
||||||
use bitcoin::util::bip32::Fingerprint;
|
use bitcoin::util::bip32::Fingerprint;
|
||||||
use bitcoin::PublicKey;
|
use bitcoin::{PublicKey, XOnlyPublicKey};
|
||||||
|
|
||||||
use miniscript::descriptor::{DescriptorPublicKey, ShInner, SortedMultiVec, WshInner};
|
use miniscript::descriptor::{
|
||||||
|
DescriptorPublicKey, DescriptorSinglePub, ShInner, SinglePubKey, SortedMultiVec, WshInner,
|
||||||
|
};
|
||||||
use miniscript::{Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal};
|
use miniscript::{Descriptor, Miniscript, MiniscriptKey, Satisfier, ScriptContext, Terminal};
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
|
|
||||||
use crate::descriptor::ExtractPolicy;
|
use crate::descriptor::ExtractPolicy;
|
||||||
|
use crate::keys::ExtScriptContext;
|
||||||
use crate::wallet::signer::{SignerId, SignersContainer};
|
use crate::wallet::signer::{SignerId, SignersContainer};
|
||||||
use crate::wallet::utils::{self, After, Older, SecpCtx};
|
use crate::wallet::utils::{self, After, Older, SecpCtx};
|
||||||
|
|
||||||
use super::checksum::get_checksum;
|
use super::checksum::get_checksum;
|
||||||
use super::error::Error;
|
use super::error::Error;
|
||||||
use super::XKeyUtils;
|
use super::XKeyUtils;
|
||||||
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
|
use bitcoin::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt};
|
||||||
use miniscript::psbt::PsbtInputSatisfier;
|
use miniscript::psbt::PsbtInputSatisfier;
|
||||||
|
|
||||||
/// Raw public key or extended key fingerprint
|
/// Raw public key or extended key fingerprint
|
||||||
@ -68,6 +72,8 @@ pub struct PkOrF {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pubkey: Option<PublicKey>,
|
pubkey: Option<PublicKey>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
x_only_pubkey: Option<XOnlyPublicKey>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pubkey_hash: Option<hash160::Hash>,
|
pubkey_hash: Option<hash160::Hash>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
fingerprint: Option<Fingerprint>,
|
fingerprint: Option<Fingerprint>,
|
||||||
@ -76,8 +82,18 @@ pub struct PkOrF {
|
|||||||
impl PkOrF {
|
impl PkOrF {
|
||||||
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
|
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
|
||||||
match k {
|
match k {
|
||||||
DescriptorPublicKey::SinglePub(pubkey) => PkOrF {
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
pubkey: Some(pubkey.key),
|
key: SinglePubKey::FullKey(pk),
|
||||||
|
..
|
||||||
|
}) => PkOrF {
|
||||||
|
pubkey: Some(*pk),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
|
key: SinglePubKey::XOnly(pk),
|
||||||
|
..
|
||||||
|
}) => PkOrF {
|
||||||
|
x_only_pubkey: Some(*pk),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
DescriptorPublicKey::XPub(xpub) => PkOrF {
|
DescriptorPublicKey::XPub(xpub) => PkOrF {
|
||||||
@ -93,10 +109,10 @@ impl PkOrF {
|
|||||||
#[serde(tag = "type", rename_all = "UPPERCASE")]
|
#[serde(tag = "type", rename_all = "UPPERCASE")]
|
||||||
pub enum SatisfiableItem {
|
pub enum SatisfiableItem {
|
||||||
// Leaves
|
// Leaves
|
||||||
/// Signature for a raw public key
|
/// ECDSA Signature for a raw public key
|
||||||
Signature(PkOrF),
|
EcdsaSignature(PkOrF),
|
||||||
/// Signature for an extended key fingerprint
|
/// Schnorr Signature for a raw public key
|
||||||
SignatureKey(PkOrF),
|
SchnorrSignature(PkOrF),
|
||||||
/// SHA256 preimage hash
|
/// SHA256 preimage hash
|
||||||
Sha256Preimage {
|
Sha256Preimage {
|
||||||
/// The digest value
|
/// The digest value
|
||||||
@ -571,6 +587,7 @@ impl Policy {
|
|||||||
build_sat: BuildSatisfaction,
|
build_sat: BuildSatisfaction,
|
||||||
threshold: usize,
|
threshold: usize,
|
||||||
sorted: bool,
|
sorted: bool,
|
||||||
|
is_ecdsa: bool,
|
||||||
secp: &SecpCtx,
|
secp: &SecpCtx,
|
||||||
) -> Result<Option<Policy>, PolicyError> {
|
) -> Result<Option<Policy>, PolicyError> {
|
||||||
if threshold == 0 {
|
if threshold == 0 {
|
||||||
@ -599,7 +616,9 @@ impl Policy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(psbt) = build_sat.psbt() {
|
if let Some(psbt) = build_sat.psbt() {
|
||||||
if signature_in_psbt(psbt, key, secp) {
|
if is_ecdsa && ecdsa_signature_in_psbt(psbt, key, secp)
|
||||||
|
|| !is_ecdsa && schnorr_signature_in_psbt(psbt, key, secp)
|
||||||
|
{
|
||||||
satisfaction.add(
|
satisfaction.add(
|
||||||
&Satisfaction::Complete {
|
&Satisfaction::Complete {
|
||||||
condition: Default::default(),
|
condition: Default::default(),
|
||||||
@ -715,7 +734,14 @@ impl From<SatisfiableItem> for Policy {
|
|||||||
|
|
||||||
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
|
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
|
||||||
match key {
|
match key {
|
||||||
DescriptorPublicKey::SinglePub(pubkey) => pubkey.key.to_pubkeyhash().into(),
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
|
key: SinglePubKey::FullKey(pk),
|
||||||
|
..
|
||||||
|
}) => pk.to_pubkeyhash().into(),
|
||||||
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
|
key: SinglePubKey::XOnly(pk),
|
||||||
|
..
|
||||||
|
}) => pk.to_pubkeyhash().into(),
|
||||||
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
|
DescriptorPublicKey::XPub(xpub) => xpub.root_fingerprint(secp).into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -726,7 +752,7 @@ fn signature(
|
|||||||
build_sat: BuildSatisfaction,
|
build_sat: BuildSatisfaction,
|
||||||
secp: &SecpCtx,
|
secp: &SecpCtx,
|
||||||
) -> Policy {
|
) -> Policy {
|
||||||
let mut policy: Policy = SatisfiableItem::Signature(PkOrF::from_key(key, secp)).into();
|
let mut policy: Policy = SatisfiableItem::EcdsaSignature(PkOrF::from_key(key, secp)).into();
|
||||||
|
|
||||||
policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
|
policy.contribution = if signers.find(signer_id(key, secp)).is_some() {
|
||||||
Satisfaction::Complete {
|
Satisfaction::Complete {
|
||||||
@ -737,7 +763,7 @@ fn signature(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(psbt) = build_sat.psbt() {
|
if let Some(psbt) = build_sat.psbt() {
|
||||||
policy.satisfaction = if signature_in_psbt(psbt, key, secp) {
|
policy.satisfaction = if ecdsa_signature_in_psbt(psbt, key, secp) {
|
||||||
Satisfaction::Complete {
|
Satisfaction::Complete {
|
||||||
condition: Default::default(),
|
condition: Default::default(),
|
||||||
}
|
}
|
||||||
@ -749,10 +775,19 @@ fn signature(
|
|||||||
policy
|
policy
|
||||||
}
|
}
|
||||||
|
|
||||||
fn signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) -> bool {
|
fn generic_sig_in_psbt<
|
||||||
|
C: Fn(&PsbtInput, &SinglePubKey) -> bool,
|
||||||
|
M: Fn(&secp256k1::PublicKey) -> SinglePubKey,
|
||||||
|
>(
|
||||||
|
psbt: &Psbt,
|
||||||
|
key: &DescriptorPublicKey,
|
||||||
|
secp: &SecpCtx,
|
||||||
|
map: M,
|
||||||
|
check: C,
|
||||||
|
) -> bool {
|
||||||
//TODO check signature validity
|
//TODO check signature validity
|
||||||
psbt.inputs.iter().all(|input| match key {
|
psbt.inputs.iter().all(|input| match key {
|
||||||
DescriptorPublicKey::SinglePub(key) => input.partial_sigs.contains_key(&key.key),
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub { key, .. }) => check(input, key),
|
||||||
DescriptorPublicKey::XPub(xpub) => {
|
DescriptorPublicKey::XPub(xpub) => {
|
||||||
let pubkey = input
|
let pubkey = input
|
||||||
.bip32_derivation
|
.bip32_derivation
|
||||||
@ -761,14 +796,49 @@ fn signature_in_psbt(psbt: &Psbt, key: &DescriptorPublicKey, secp: &SecpCtx) ->
|
|||||||
.map(|(p, _)| p);
|
.map(|(p, _)| p);
|
||||||
//TODO check actual derivation matches
|
//TODO check actual derivation matches
|
||||||
match pubkey {
|
match pubkey {
|
||||||
Some(pubkey) => input.partial_sigs.contains_key(pubkey),
|
Some(pubkey) => check(input, &map(pubkey)),
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
|
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,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx> {
|
||||||
fn extract_policy(
|
fn extract_policy(
|
||||||
&self,
|
&self,
|
||||||
signers: &SignersContainer,
|
signers: &SignersContainer,
|
||||||
@ -840,9 +910,15 @@ impl<Ctx: ScriptContext> ExtractPolicy for Miniscript<DescriptorPublicKey, Ctx>
|
|||||||
Terminal::Hash160(hash) => {
|
Terminal::Hash160(hash) => {
|
||||||
Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
|
Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
|
||||||
}
|
}
|
||||||
Terminal::Multi(k, pks) => {
|
Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => Policy::make_multisig(
|
||||||
Policy::make_multisig(pks, signers, build_sat, *k, false, secp)?
|
pks,
|
||||||
}
|
signers,
|
||||||
|
build_sat,
|
||||||
|
*k,
|
||||||
|
false,
|
||||||
|
!Ctx::as_enum().is_taproot(),
|
||||||
|
secp,
|
||||||
|
)?,
|
||||||
// Identities
|
// Identities
|
||||||
Terminal::Alt(inner)
|
Terminal::Alt(inner)
|
||||||
| Terminal::Swap(inner)
|
| Terminal::Swap(inner)
|
||||||
@ -944,6 +1020,7 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
|
|||||||
build_sat,
|
build_sat,
|
||||||
keys.k,
|
keys.k,
|
||||||
true,
|
true,
|
||||||
|
true,
|
||||||
secp,
|
secp,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
@ -967,6 +1044,19 @@ impl ExtractPolicy for Descriptor<DescriptorPublicKey> {
|
|||||||
WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
|
WshInner::SortedMulti(ref keys) => make_sortedmulti(keys, signers, build_sat, secp),
|
||||||
},
|
},
|
||||||
Descriptor::Bare(ms) => Ok(ms.as_inner().extract_policy(signers, build_sat, secp)?),
|
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<_>, _>>()?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Policy::make_thresh(items, 1)?)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -978,7 +1068,7 @@ mod test {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::descriptor::derived::AsDerived;
|
use crate::descriptor::derived::AsDerived;
|
||||||
use crate::descriptor::policy::SatisfiableItem::{Multisig, Signature, Thresh};
|
use crate::descriptor::policy::SatisfiableItem::{EcdsaSignature, Multisig, Thresh};
|
||||||
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
use crate::keys::{DescriptorKey, IntoDescriptorKey};
|
||||||
use crate::wallet::signer::SignersContainer;
|
use crate::wallet::signer::SignersContainer;
|
||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
@ -1000,7 +1090,7 @@ mod test {
|
|||||||
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
|
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
|
||||||
let path = bip32::DerivationPath::from_str(path).unwrap();
|
let path = bip32::DerivationPath::from_str(path).unwrap();
|
||||||
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
|
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
|
||||||
let tpub = bip32::ExtendedPubKey::from_private(secp, &tprv);
|
let tpub = bip32::ExtendedPubKey::from_priv(secp, &tprv);
|
||||||
let fingerprint = tprv.fingerprint(secp);
|
let fingerprint = tprv.fingerprint(secp);
|
||||||
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
|
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
|
||||||
let pubkey = (tpub, path).into_descriptor_key().unwrap();
|
let pubkey = (tpub, path).into_descriptor_key().unwrap();
|
||||||
@ -1026,7 +1116,7 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||||
);
|
);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert!(matches!(&policy.contribution, Satisfaction::None));
|
||||||
|
|
||||||
@ -1041,7 +1131,7 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
||||||
@ -1194,7 +1284,7 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||||
);
|
);
|
||||||
assert!(matches!(&policy.contribution, Satisfaction::None));
|
assert!(matches!(&policy.contribution, Satisfaction::None));
|
||||||
|
|
||||||
@ -1210,7 +1300,7 @@ mod test {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
matches!(&policy.item, Signature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
matches!(&policy.item, EcdsaSignature(pk_or_f) if pk_or_f.fingerprint.unwrap() == fingerprint)
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
matches!(&policy.contribution, Satisfaction::Complete {condition} if condition.csv == None && condition.timelock == None)
|
||||||
|
@ -20,12 +20,12 @@ use std::str::FromStr;
|
|||||||
use bitcoin::secp256k1::{self, Secp256k1, Signing};
|
use bitcoin::secp256k1::{self, Secp256k1, Signing};
|
||||||
|
|
||||||
use bitcoin::util::bip32;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::{Network, PrivateKey, PublicKey};
|
use bitcoin::{Network, PrivateKey, PublicKey, XOnlyPublicKey};
|
||||||
|
|
||||||
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
|
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
|
||||||
pub use miniscript::descriptor::{
|
pub use miniscript::descriptor::{
|
||||||
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
|
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
|
||||||
SortedMultiVec,
|
SinglePubKey, SortedMultiVec,
|
||||||
};
|
};
|
||||||
pub use miniscript::ScriptContext;
|
pub use miniscript::ScriptContext;
|
||||||
use miniscript::{Miniscript, Terminal};
|
use miniscript::{Miniscript, Terminal};
|
||||||
@ -127,6 +127,8 @@ pub enum ScriptContextEnum {
|
|||||||
Legacy,
|
Legacy,
|
||||||
/// Segwitv0 scripts
|
/// Segwitv0 scripts
|
||||||
Segwitv0,
|
Segwitv0,
|
||||||
|
/// Taproot scripts
|
||||||
|
Tap,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScriptContextEnum {
|
impl ScriptContextEnum {
|
||||||
@ -139,6 +141,11 @@ impl ScriptContextEnum {
|
|||||||
pub fn is_segwit_v0(&self) -> bool {
|
pub fn is_segwit_v0(&self) -> bool {
|
||||||
self == &ScriptContextEnum::Segwitv0
|
self == &ScriptContextEnum::Segwitv0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the script context is [`ScriptContextEnum::Tap`]
|
||||||
|
pub fn is_taproot(&self) -> bool {
|
||||||
|
self == &ScriptContextEnum::Tap
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait that adds extra useful methods to [`ScriptContext`]s
|
/// Trait that adds extra useful methods to [`ScriptContext`]s
|
||||||
@ -155,6 +162,11 @@ pub trait ExtScriptContext: ScriptContext {
|
|||||||
fn is_segwit_v0() -> bool {
|
fn is_segwit_v0() -> bool {
|
||||||
Self::as_enum().is_segwit_v0()
|
Self::as_enum().is_segwit_v0()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns whether the script context is [`Tap`](miniscript::Tap), aka Taproot or Segwit V1
|
||||||
|
fn is_taproot() -> bool {
|
||||||
|
Self::as_enum().is_taproot()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
||||||
@ -162,6 +174,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
match TypeId::of::<Ctx>() {
|
match TypeId::of::<Ctx>() {
|
||||||
t if t == TypeId::of::<miniscript::Legacy>() => ScriptContextEnum::Legacy,
|
t if t == TypeId::of::<miniscript::Legacy>() => ScriptContextEnum::Legacy,
|
||||||
t if t == TypeId::of::<miniscript::Segwitv0>() => ScriptContextEnum::Segwitv0,
|
t if t == TypeId::of::<miniscript::Segwitv0>() => ScriptContextEnum::Segwitv0,
|
||||||
|
t if t == TypeId::of::<miniscript::Tap>() => ScriptContextEnum::Tap,
|
||||||
_ => unimplemented!("Unknown ScriptContext type"),
|
_ => unimplemented!("Unknown ScriptContext type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -212,7 +225,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
///
|
///
|
||||||
/// use bdk::keys::{
|
/// use bdk::keys::{
|
||||||
/// mainnet_network, DescriptorKey, DescriptorPublicKey, DescriptorSinglePub,
|
/// mainnet_network, DescriptorKey, DescriptorPublicKey, DescriptorSinglePub,
|
||||||
/// IntoDescriptorKey, KeyError, ScriptContext,
|
/// IntoDescriptorKey, KeyError, ScriptContext, SinglePubKey,
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// pub struct MyKeyType {
|
/// pub struct MyKeyType {
|
||||||
@ -224,7 +237,7 @@ impl<Ctx: ScriptContext + 'static> ExtScriptContext for Ctx {
|
|||||||
/// Ok(DescriptorKey::from_public(
|
/// Ok(DescriptorKey::from_public(
|
||||||
/// DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
/// DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
/// origin: None,
|
/// origin: None,
|
||||||
/// key: self.pubkey,
|
/// key: SinglePubKey::FullKey(self.pubkey),
|
||||||
/// }),
|
/// }),
|
||||||
/// mainnet_network(),
|
/// mainnet_network(),
|
||||||
/// ))
|
/// ))
|
||||||
@ -319,7 +332,6 @@ impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
|
|||||||
match self {
|
match self {
|
||||||
ExtendedKey::Private((mut xprv, _)) => {
|
ExtendedKey::Private((mut xprv, _)) => {
|
||||||
xprv.network = network;
|
xprv.network = network;
|
||||||
xprv.private_key.network = network;
|
|
||||||
Some(xprv)
|
Some(xprv)
|
||||||
}
|
}
|
||||||
ExtendedKey::Public(_) => None,
|
ExtendedKey::Public(_) => None,
|
||||||
@ -334,7 +346,7 @@ impl<Ctx: ScriptContext> ExtendedKey<Ctx> {
|
|||||||
secp: &Secp256k1<C>,
|
secp: &Secp256k1<C>,
|
||||||
) -> bip32::ExtendedPubKey {
|
) -> bip32::ExtendedPubKey {
|
||||||
let mut xpub = match self {
|
let mut xpub = match self {
|
||||||
ExtendedKey::Private((xprv, _)) => bip32::ExtendedPubKey::from_private(secp, &xprv),
|
ExtendedKey::Private((xprv, _)) => bip32::ExtendedPubKey::from_priv(secp, &xprv),
|
||||||
ExtendedKey::Public((xpub, _)) => xpub,
|
ExtendedKey::Public((xpub, _)) => xpub,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -388,7 +400,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
|
|||||||
/// network: self.network,
|
/// network: self.network,
|
||||||
/// depth: 0,
|
/// depth: 0,
|
||||||
/// parent_fingerprint: bip32::Fingerprint::default(),
|
/// parent_fingerprint: bip32::Fingerprint::default(),
|
||||||
/// private_key: self.key_data,
|
/// private_key: self.key_data.inner,
|
||||||
/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()),
|
/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()),
|
||||||
/// child_number: bip32::ChildNumber::Normal { index: 0 },
|
/// child_number: bip32::ChildNumber::Normal { index: 0 },
|
||||||
/// };
|
/// };
|
||||||
@ -420,7 +432,7 @@ impl<Ctx: ScriptContext> From<bip32::ExtendedPrivKey> for ExtendedKey<Ctx> {
|
|||||||
/// network: bitcoin::Network::Bitcoin, // pick an arbitrary network here
|
/// network: bitcoin::Network::Bitcoin, // pick an arbitrary network here
|
||||||
/// depth: 0,
|
/// depth: 0,
|
||||||
/// parent_fingerprint: bip32::Fingerprint::default(),
|
/// parent_fingerprint: bip32::Fingerprint::default(),
|
||||||
/// private_key: self.key_data,
|
/// private_key: self.key_data.inner,
|
||||||
/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()),
|
/// chain_code: bip32::ChainCode::from(self.chain_code.as_ref()),
|
||||||
/// child_number: bip32::ChildNumber::Normal { index: 0 },
|
/// child_number: bip32::ChildNumber::Normal { index: 0 },
|
||||||
/// };
|
/// };
|
||||||
@ -698,11 +710,11 @@ impl<Ctx: ScriptContext> GeneratableKey<Ctx> for PrivateKey {
|
|||||||
entropy: Self::Entropy,
|
entropy: Self::Entropy,
|
||||||
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
|
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
|
||||||
// pick a arbitrary network here, but say that we support all of them
|
// pick a arbitrary network here, but say that we support all of them
|
||||||
let key = secp256k1::SecretKey::from_slice(&entropy)?;
|
let inner = secp256k1::SecretKey::from_slice(&entropy)?;
|
||||||
let private_key = PrivateKey {
|
let private_key = PrivateKey {
|
||||||
compressed: options.compressed,
|
compressed: options.compressed,
|
||||||
network: Network::Bitcoin,
|
network: Network::Bitcoin,
|
||||||
key,
|
inner,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(GeneratedKey::new(private_key, any_network()))
|
Ok(GeneratedKey::new(private_key, any_network()))
|
||||||
@ -841,7 +853,17 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for DescriptorPublicKey {
|
|||||||
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
|
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
|
||||||
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
key: self,
|
key: SinglePubKey::FullKey(self),
|
||||||
|
origin: None,
|
||||||
|
})
|
||||||
|
.into_descriptor_key()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for XOnlyPublicKey {
|
||||||
|
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
|
||||||
|
DescriptorPublicKey::SinglePub(DescriptorSinglePub {
|
||||||
|
key: SinglePubKey::XOnly(self),
|
||||||
origin: None,
|
origin: None,
|
||||||
})
|
})
|
||||||
.into_descriptor_key()
|
.into_descriptor_key()
|
||||||
@ -943,29 +965,6 @@ pub mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_keys_wif_network() {
|
|
||||||
// test mainnet wif
|
|
||||||
let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
|
|
||||||
bip32::ExtendedPrivKey::generate_with_entropy_default(TEST_ENTROPY).unwrap();
|
|
||||||
let xkey = generated_xprv.into_extended_key().unwrap();
|
|
||||||
|
|
||||||
let network = Network::Bitcoin;
|
|
||||||
let xprv = xkey.into_xprv(network).unwrap();
|
|
||||||
let wif = PrivateKey::from_wif(&xprv.private_key.to_wif()).unwrap();
|
|
||||||
assert_eq!(wif.network, network);
|
|
||||||
|
|
||||||
// test testnet wif
|
|
||||||
let generated_xprv: GeneratedKey<_, miniscript::Segwitv0> =
|
|
||||||
bip32::ExtendedPrivKey::generate_with_entropy_default(TEST_ENTROPY).unwrap();
|
|
||||||
let xkey = generated_xprv.into_extended_key().unwrap();
|
|
||||||
|
|
||||||
let network = Network::Testnet;
|
|
||||||
let xprv = xkey.into_xprv(network).unwrap();
|
|
||||||
let wif = PrivateKey::from_wif(&xprv.private_key.to_wif()).unwrap();
|
|
||||||
assert_eq!(wif.network, network);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "keys-bip39")]
|
#[cfg(feature = "keys-bip39")]
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keys_wif_network_bip39() {
|
fn test_keys_wif_network_bip39() {
|
||||||
@ -977,8 +976,7 @@ pub mod test {
|
|||||||
.into_extended_key()
|
.into_extended_key()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let xprv = xkey.into_xprv(Network::Testnet).unwrap();
|
let xprv = xkey.into_xprv(Network::Testnet).unwrap();
|
||||||
let wif = PrivateKey::from_wif(&xprv.private_key.to_wif()).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(wif.network, Network::Testnet);
|
assert_eq!(xprv.network, Network::Testnet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ pub trait PsbtUtils {
|
|||||||
impl PsbtUtils for Psbt {
|
impl PsbtUtils for Psbt {
|
||||||
#[allow(clippy::all)] // We want to allow `manual_map` but it is too new.
|
#[allow(clippy::all)] // We want to allow `manual_map` but it is too new.
|
||||||
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
|
fn get_utxo_for(&self, input_index: usize) -> Option<TxOut> {
|
||||||
let tx = &self.global.unsigned_tx;
|
let tx = &self.unsigned_tx;
|
||||||
|
|
||||||
if input_index >= tx.input.len() {
|
if input_index >= tx.input.len() {
|
||||||
return None;
|
return None;
|
||||||
@ -93,7 +93,7 @@ mod test {
|
|||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder.add_recipient(send_to.script_pubkey(), 10_000);
|
builder.add_recipient(send_to.script_pubkey(), 10_000);
|
||||||
let (mut psbt, _) = builder.finish().unwrap();
|
let (mut psbt, _) = builder.finish().unwrap();
|
||||||
psbt.global.unsigned_tx.input.push(TxIn::default());
|
psbt.unsigned_tx.input.push(TxIn::default());
|
||||||
let options = SignOptions {
|
let options = SignOptions {
|
||||||
trust_witness_utxo: true,
|
trust_witness_utxo: true,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -112,10 +112,9 @@ mod test {
|
|||||||
|
|
||||||
// add a finalized input
|
// add a finalized input
|
||||||
psbt.inputs.push(psbt_bip.inputs[0].clone());
|
psbt.inputs.push(psbt_bip.inputs[0].clone());
|
||||||
psbt.global
|
psbt.unsigned_tx
|
||||||
.unsigned_tx
|
|
||||||
.input
|
.input
|
||||||
.push(psbt_bip.global.unsigned_tx.input[0].clone());
|
.push(psbt_bip.unsigned_tx.input[0].clone());
|
||||||
|
|
||||||
let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
|
let _ = wallet.sign(&mut psbt, SignOptions::default()).unwrap();
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ use crate::testutils::TestIncomingTx;
|
|||||||
use bitcoin::consensus::encode::{deserialize, serialize};
|
use bitcoin::consensus::encode::{deserialize, serialize};
|
||||||
use bitcoin::hashes::hex::{FromHex, ToHex};
|
use bitcoin::hashes::hex::{FromHex, ToHex};
|
||||||
use bitcoin::hashes::sha256d;
|
use bitcoin::hashes::sha256d;
|
||||||
use bitcoin::{Address, Amount, Script, Transaction, Txid};
|
use bitcoin::{Address, Amount, Script, Transaction, Txid, Witness};
|
||||||
pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
|
pub use bitcoincore_rpc::bitcoincore_rpc_json::AddressType;
|
||||||
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
|
pub use bitcoincore_rpc::{Auth, Client as RpcClient, RpcApi};
|
||||||
use core::str::FromStr;
|
use core::str::FromStr;
|
||||||
@ -193,7 +193,7 @@ impl TestClient {
|
|||||||
previous_output: OutPoint::null(),
|
previous_output: OutPoint::null(),
|
||||||
script_sig: Builder::new().push_int(height).into_script(),
|
script_sig: Builder::new().push_int(height).into_script(),
|
||||||
sequence: 0xFFFFFFFF,
|
sequence: 0xFFFFFFFF,
|
||||||
witness: vec![witness_reserved_value],
|
witness: Witness::from_vec(vec![witness_reserved_value]),
|
||||||
}],
|
}],
|
||||||
output: vec![],
|
output: vec![],
|
||||||
};
|
};
|
||||||
@ -203,23 +203,31 @@ impl TestClient {
|
|||||||
|
|
||||||
let mut block = Block { header, txdata };
|
let mut block = Block { header, txdata };
|
||||||
|
|
||||||
let witness_root = block.witness_root();
|
if let Some(witness_root) = block.witness_root() {
|
||||||
let witness_commitment =
|
let witness_commitment = Block::compute_witness_commitment(
|
||||||
Block::compute_witness_commitment(&witness_root, &coinbase_tx.input[0].witness[0]);
|
&witness_root,
|
||||||
|
&coinbase_tx.input[0]
|
||||||
|
.witness
|
||||||
|
.last()
|
||||||
|
.expect("Should contain the witness reserved value"),
|
||||||
|
);
|
||||||
|
|
||||||
// now update and replace the coinbase tx
|
// now update and replace the coinbase tx
|
||||||
let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
|
let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
|
||||||
coinbase_witness_commitment_script.extend_from_slice(&witness_commitment);
|
coinbase_witness_commitment_script.extend_from_slice(&witness_commitment);
|
||||||
|
|
||||||
|
coinbase_tx.output.push(TxOut {
|
||||||
|
value: 0,
|
||||||
|
script_pubkey: coinbase_witness_commitment_script.into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
coinbase_tx.output.push(TxOut {
|
|
||||||
value: 0,
|
|
||||||
script_pubkey: coinbase_witness_commitment_script.into(),
|
|
||||||
});
|
|
||||||
block.txdata[0] = coinbase_tx;
|
block.txdata[0] = coinbase_tx;
|
||||||
|
|
||||||
// set merkle root
|
// set merkle root
|
||||||
let merkle_root = block.merkle_root();
|
if let Some(merkle_root) = block.compute_merkle_root() {
|
||||||
block.header.merkle_root = merkle_root;
|
block.header.merkle_root = merkle_root;
|
||||||
|
}
|
||||||
|
|
||||||
assert!(block.check_merkle_root());
|
assert!(block.check_merkle_root());
|
||||||
assert!(block.check_witness_commitment());
|
assert!(block.check_witness_commitment());
|
||||||
|
@ -14,11 +14,7 @@
|
|||||||
#[cfg(feature = "test-blockchains")]
|
#[cfg(feature = "test-blockchains")]
|
||||||
pub mod blockchain_tests;
|
pub mod blockchain_tests;
|
||||||
|
|
||||||
use bitcoin::secp256k1::{Secp256k1, Verification};
|
use bitcoin::{Address, Txid};
|
||||||
use bitcoin::{Address, PublicKey, Txid};
|
|
||||||
|
|
||||||
use miniscript::descriptor::DescriptorPublicKey;
|
|
||||||
use miniscript::{Descriptor, MiniscriptKey, TranslatePk};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TestIncomingInput {
|
pub struct TestIncomingInput {
|
||||||
@ -96,39 +92,6 @@ impl TestIncomingTx {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub trait TranslateDescriptor {
|
|
||||||
// derive and translate a `Descriptor<DescriptorPublicKey>` into a `Descriptor<PublicKey>`
|
|
||||||
fn derive_translated<C: Verification>(
|
|
||||||
&self,
|
|
||||||
secp: &Secp256k1<C>,
|
|
||||||
index: u32,
|
|
||||||
) -> Descriptor<PublicKey>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TranslateDescriptor for Descriptor<DescriptorPublicKey> {
|
|
||||||
fn derive_translated<C: Verification>(
|
|
||||||
&self,
|
|
||||||
secp: &Secp256k1<C>,
|
|
||||||
index: u32,
|
|
||||||
) -> Descriptor<PublicKey> {
|
|
||||||
let translate = |key: &DescriptorPublicKey| -> PublicKey {
|
|
||||||
match key {
|
|
||||||
DescriptorPublicKey::XPub(xpub) => {
|
|
||||||
xpub.xkey
|
|
||||||
.derive_pub(secp, &xpub.derivation_path)
|
|
||||||
.expect("hardened derivation steps")
|
|
||||||
.public_key
|
|
||||||
}
|
|
||||||
DescriptorPublicKey::SinglePub(key) => key.key,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
self.derive(index)
|
|
||||||
.translate_pk_infallible(|pk| translate(pk), |pkh| translate(pkh).to_pubkeyhash())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! testutils {
|
macro_rules! testutils {
|
||||||
@ -136,23 +99,23 @@ macro_rules! testutils {
|
|||||||
use $crate::bitcoin::secp256k1::Secp256k1;
|
use $crate::bitcoin::secp256k1::Secp256k1;
|
||||||
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
||||||
|
|
||||||
use $crate::testutils::TranslateDescriptor;
|
use $crate::descriptor::AsDerived;
|
||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
|
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.0).expect("Failed to parse descriptor in `testutils!(@external)`").0;
|
||||||
parsed.derive_translated(&secp, $child).address(bitcoin::Network::Regtest).expect("No address form")
|
parsed.as_derived($child, &secp).address(bitcoin::Network::Regtest).expect("No address form")
|
||||||
});
|
});
|
||||||
( @internal $descriptors:expr, $child:expr ) => ({
|
( @internal $descriptors:expr, $child:expr ) => ({
|
||||||
use $crate::bitcoin::secp256k1::Secp256k1;
|
use $crate::bitcoin::secp256k1::Secp256k1;
|
||||||
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
|
||||||
|
|
||||||
use $crate::testutils::TranslateDescriptor;
|
use $crate::descriptor::AsDerived;
|
||||||
|
|
||||||
let secp = Secp256k1::new();
|
let secp = Secp256k1::new();
|
||||||
|
|
||||||
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
|
let parsed = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &$descriptors.1.expect("Missing internal descriptor")).expect("Failed to parse descriptor in `testutils!(@internal)`").0;
|
||||||
parsed.derive_translated(&secp, $child).address($crate::bitcoin::Network::Regtest).expect("No address form")
|
parsed.as_derived($child, &secp).address($crate::bitcoin::Network::Regtest).expect("No address form")
|
||||||
});
|
});
|
||||||
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
|
( @e $descriptors:expr, $child:expr ) => ({ testutils!(@external $descriptors, $child) });
|
||||||
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
|
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
|
||||||
|
@ -24,11 +24,10 @@ use std::sync::Arc;
|
|||||||
use bitcoin::secp256k1::Secp256k1;
|
use bitcoin::secp256k1::Secp256k1;
|
||||||
|
|
||||||
use bitcoin::consensus::encode::serialize;
|
use bitcoin::consensus::encode::serialize;
|
||||||
use bitcoin::util::base58;
|
use bitcoin::util::psbt;
|
||||||
use bitcoin::util::psbt::raw::Key as PsbtKey;
|
use bitcoin::{
|
||||||
use bitcoin::util::psbt::Input;
|
Address, EcdsaSighashType, Network, OutPoint, Script, Transaction, TxOut, Txid, Witness,
|
||||||
use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
|
};
|
||||||
use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid};
|
|
||||||
|
|
||||||
use miniscript::descriptor::DescriptorTrait;
|
use miniscript::descriptor::DescriptorTrait;
|
||||||
use miniscript::psbt::PsbtInputSatisfier;
|
use miniscript::psbt::PsbtInputSatisfier;
|
||||||
@ -542,7 +541,7 @@ where
|
|||||||
&self,
|
&self,
|
||||||
coin_selection: Cs,
|
coin_selection: Cs,
|
||||||
params: TxParams,
|
params: TxParams,
|
||||||
) -> Result<(Psbt, TransactionDetails), Error> {
|
) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error> {
|
||||||
let external_policy = self
|
let external_policy = self
|
||||||
.descriptor
|
.descriptor
|
||||||
.extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
|
.extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
|
||||||
@ -703,7 +702,7 @@ where
|
|||||||
let mut outgoing: u64 = 0;
|
let mut outgoing: u64 = 0;
|
||||||
let mut received: u64 = 0;
|
let mut received: u64 = 0;
|
||||||
|
|
||||||
fee_amount += fee_rate.fee_wu(tx.get_weight());
|
fee_amount += fee_rate.fee_wu(tx.weight());
|
||||||
|
|
||||||
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
|
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
|
||||||
|
|
||||||
@ -761,7 +760,7 @@ where
|
|||||||
previous_output: u.outpoint(),
|
previous_output: u.outpoint(),
|
||||||
script_sig: Script::default(),
|
script_sig: Script::default(),
|
||||||
sequence: n_sequence,
|
sequence: n_sequence,
|
||||||
witness: vec![],
|
witness: Witness::new(),
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
@ -884,10 +883,7 @@ where
|
|||||||
return Err(Error::IrreplaceableTransaction);
|
return Err(Error::IrreplaceableTransaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
let feerate = FeeRate::from_wu(
|
let feerate = FeeRate::from_wu(details.fee.ok_or(Error::FeeRateUnavailable)?, tx.weight());
|
||||||
details.fee.ok_or(Error::FeeRateUnavailable)?,
|
|
||||||
tx.get_weight(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// remove the inputs from the tx and process them
|
// remove the inputs from the tx and process them
|
||||||
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
|
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
|
||||||
@ -1004,7 +1000,11 @@ where
|
|||||||
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
|
||||||
/// assert!(finalized, "we should have signed all the inputs");
|
/// assert!(finalized, "we should have signed all the inputs");
|
||||||
/// # Ok::<(), bdk::Error>(())
|
/// # Ok::<(), bdk::Error>(())
|
||||||
pub fn sign(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result<bool, Error> {
|
pub fn sign(
|
||||||
|
&self,
|
||||||
|
psbt: &mut psbt::PartiallySignedTransaction,
|
||||||
|
sign_options: SignOptions,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
// this helps us doing our job later
|
// this helps us doing our job later
|
||||||
self.add_input_hd_keypaths(psbt)?;
|
self.add_input_hd_keypaths(psbt)?;
|
||||||
|
|
||||||
@ -1023,10 +1023,9 @@ where
|
|||||||
// If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
|
// If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
|
||||||
// is using `SIGHASH_ALL`
|
// is using `SIGHASH_ALL`
|
||||||
if !sign_options.allow_all_sighashes
|
if !sign_options.allow_all_sighashes
|
||||||
&& !psbt
|
&& !psbt.inputs.iter().all(|i| {
|
||||||
.inputs
|
i.sighash_type.is_none() || i.sighash_type == Some(EcdsaSighashType::All.into())
|
||||||
.iter()
|
})
|
||||||
.all(|i| i.sighash_type.is_none() || i.sighash_type == Some(SigHashType::All))
|
|
||||||
{
|
{
|
||||||
return Err(Error::Signer(signer::SignerError::NonStandardSighash));
|
return Err(Error::Signer(signer::SignerError::NonStandardSighash));
|
||||||
}
|
}
|
||||||
@ -1085,8 +1084,12 @@ where
|
|||||||
/// Try to finalize a PSBT
|
/// Try to finalize a PSBT
|
||||||
///
|
///
|
||||||
/// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
|
/// The [`SignOptions`] can be used to tweak the behavior of the finalizer.
|
||||||
pub fn finalize_psbt(&self, psbt: &mut Psbt, sign_options: SignOptions) -> Result<bool, Error> {
|
pub fn finalize_psbt(
|
||||||
let tx = &psbt.global.unsigned_tx;
|
&self,
|
||||||
|
psbt: &mut psbt::PartiallySignedTransaction,
|
||||||
|
sign_options: SignOptions,
|
||||||
|
) -> Result<bool, Error> {
|
||||||
|
let tx = &psbt.unsigned_tx;
|
||||||
let mut finished = true;
|
let mut finished = true;
|
||||||
|
|
||||||
for (n, input) in tx.input.iter().enumerate() {
|
for (n, input) in tx.input.iter().enumerate() {
|
||||||
@ -1383,10 +1386,8 @@ where
|
|||||||
tx: Transaction,
|
tx: Transaction,
|
||||||
selected: Vec<Utxo>,
|
selected: Vec<Utxo>,
|
||||||
params: TxParams,
|
params: TxParams,
|
||||||
) -> Result<Psbt, Error> {
|
) -> Result<psbt::PartiallySignedTransaction, Error> {
|
||||||
use bitcoin::util::psbt::serialize::Serialize;
|
let mut psbt = psbt::PartiallySignedTransaction::from_unsigned_tx(tx)?;
|
||||||
|
|
||||||
let mut psbt = Psbt::from_unsigned_tx(tx)?;
|
|
||||||
|
|
||||||
if params.add_global_xpubs {
|
if params.add_global_xpubs {
|
||||||
let mut all_xpubs = self.descriptor.get_extended_keys()?;
|
let mut all_xpubs = self.descriptor.get_extended_keys()?;
|
||||||
@ -1395,13 +1396,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
for xpub in all_xpubs {
|
for xpub in all_xpubs {
|
||||||
let serialized_xpub = base58::from_check(&xpub.xkey.to_string())
|
|
||||||
.expect("Internal serialization error");
|
|
||||||
let key = PsbtKey {
|
|
||||||
type_value: 0x01,
|
|
||||||
key: serialized_xpub,
|
|
||||||
};
|
|
||||||
|
|
||||||
let origin = match xpub.origin {
|
let origin = match xpub.origin {
|
||||||
Some(origin) => origin,
|
Some(origin) => origin,
|
||||||
None if xpub.xkey.depth == 0 => {
|
None if xpub.xkey.depth == 0 => {
|
||||||
@ -1410,7 +1404,7 @@ where
|
|||||||
_ => return Err(Error::MissingKeyOrigin(xpub.xkey.to_string())),
|
_ => return Err(Error::MissingKeyOrigin(xpub.xkey.to_string())),
|
||||||
};
|
};
|
||||||
|
|
||||||
psbt.global.unknown.insert(key, origin.serialize());
|
psbt.xpub.insert(xpub.xkey, origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1420,11 +1414,7 @@ where
|
|||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
// add metadata for the inputs
|
// add metadata for the inputs
|
||||||
for (psbt_input, input) in psbt
|
for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) {
|
||||||
.inputs
|
|
||||||
.iter_mut()
|
|
||||||
.zip(psbt.global.unsigned_tx.input.iter())
|
|
||||||
{
|
|
||||||
let utxo = match lookup_output.remove(&input.previous_output) {
|
let utxo = match lookup_output.remove(&input.previous_output) {
|
||||||
Some(utxo) => utxo,
|
Some(utxo) => utxo,
|
||||||
None => continue,
|
None => continue,
|
||||||
@ -1436,9 +1426,9 @@ where
|
|||||||
match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) {
|
match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) {
|
||||||
Ok(psbt_input) => psbt_input,
|
Ok(psbt_input) => psbt_input,
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
Error::UnknownUtxo => Input {
|
Error::UnknownUtxo => psbt::Input {
|
||||||
sighash_type: params.sighash,
|
sighash_type: params.sighash,
|
||||||
..Input::default()
|
..psbt::Input::default()
|
||||||
},
|
},
|
||||||
_ => return Err(e),
|
_ => return Err(e),
|
||||||
},
|
},
|
||||||
@ -1463,10 +1453,7 @@ where
|
|||||||
self.add_input_hd_keypaths(&mut psbt)?;
|
self.add_input_hd_keypaths(&mut psbt)?;
|
||||||
|
|
||||||
// add metadata for the outputs
|
// add metadata for the outputs
|
||||||
for (psbt_output, tx_output) in psbt
|
for (psbt_output, tx_output) in psbt.outputs.iter_mut().zip(psbt.unsigned_tx.output.iter())
|
||||||
.outputs
|
|
||||||
.iter_mut()
|
|
||||||
.zip(psbt.global.unsigned_tx.output.iter())
|
|
||||||
{
|
{
|
||||||
if let Some((keychain, child)) = self
|
if let Some((keychain, child)) = self
|
||||||
.database
|
.database
|
||||||
@ -1491,9 +1478,9 @@ where
|
|||||||
pub fn get_psbt_input(
|
pub fn get_psbt_input(
|
||||||
&self,
|
&self,
|
||||||
utxo: LocalUtxo,
|
utxo: LocalUtxo,
|
||||||
sighash_type: Option<SigHashType>,
|
sighash_type: Option<psbt::PsbtSighashType>,
|
||||||
only_witness_utxo: bool,
|
only_witness_utxo: bool,
|
||||||
) -> Result<Input, Error> {
|
) -> Result<psbt::Input, Error> {
|
||||||
// Try to find the prev_script in our db to figure out if this is internal or external,
|
// Try to find the prev_script in our db to figure out if this is internal or external,
|
||||||
// and the derivation index
|
// and the derivation index
|
||||||
let (keychain, child) = self
|
let (keychain, child) = self
|
||||||
@ -1502,9 +1489,9 @@ where
|
|||||||
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
|
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
|
||||||
.ok_or(Error::UnknownUtxo)?;
|
.ok_or(Error::UnknownUtxo)?;
|
||||||
|
|
||||||
let mut psbt_input = Input {
|
let mut psbt_input = psbt::Input {
|
||||||
sighash_type,
|
sighash_type,
|
||||||
..Input::default()
|
..psbt::Input::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let desc = self.get_descriptor_for_keychain(keychain);
|
let desc = self.get_descriptor_for_keychain(keychain);
|
||||||
@ -1526,7 +1513,10 @@ where
|
|||||||
Ok(psbt_input)
|
Ok(psbt_input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_input_hd_keypaths(&self, psbt: &mut Psbt) -> Result<(), Error> {
|
fn add_input_hd_keypaths(
|
||||||
|
&self,
|
||||||
|
psbt: &mut psbt::PartiallySignedTransaction,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
|
let mut input_utxos = Vec::with_capacity(psbt.inputs.len());
|
||||||
for n in 0..psbt.inputs.len() {
|
for n in 0..psbt.inputs.len() {
|
||||||
input_utxos.push(psbt.get_utxo_for(n).clone());
|
input_utxos.push(psbt.get_utxo_for(n).clone());
|
||||||
@ -1808,7 +1798,7 @@ pub(crate) mod test {
|
|||||||
$(
|
$(
|
||||||
$( $add_signature )*
|
$( $add_signature )*
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
}
|
}
|
||||||
)*
|
)*
|
||||||
|
|
||||||
@ -1820,7 +1810,7 @@ pub(crate) mod test {
|
|||||||
dust_change = true;
|
dust_change = true;
|
||||||
)*
|
)*
|
||||||
|
|
||||||
let tx_fee_rate = FeeRate::from_wu($fees, tx.get_weight());
|
let tx_fee_rate = FeeRate::from_wu($fees, tx.weight());
|
||||||
let fee_rate = $fee_rate;
|
let fee_rate = $fee_rate;
|
||||||
|
|
||||||
if !dust_change {
|
if !dust_change {
|
||||||
@ -1886,7 +1876,7 @@ pub(crate) mod test {
|
|||||||
.version(42);
|
.version(42);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.version, 42);
|
assert_eq!(psbt.unsigned_tx.version, 42);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1897,7 +1887,7 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
|
assert_eq!(psbt.unsigned_tx.lock_time, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1908,7 +1898,7 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 100_000);
|
assert_eq!(psbt.unsigned_tx.lock_time, 100_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1921,7 +1911,7 @@ pub(crate) mod test {
|
|||||||
.nlocktime(630_000);
|
.nlocktime(630_000);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
|
assert_eq!(psbt.unsigned_tx.lock_time, 630_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1934,7 +1924,7 @@ pub(crate) mod test {
|
|||||||
.nlocktime(630_000);
|
.nlocktime(630_000);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.lock_time, 630_000);
|
assert_eq!(psbt.unsigned_tx.lock_time, 630_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1959,7 +1949,7 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1973,7 +1963,7 @@ pub(crate) mod test {
|
|||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
// When CSV is enabled it takes precedence over the rbf value (unless forced by the user).
|
// When CSV is enabled it takes precedence over the rbf value (unless forced by the user).
|
||||||
// It will be set to the OP_CSV value, in this case 6
|
// It will be set to the OP_CSV value, in this case 6
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1998,7 +1988,7 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xFFFFFFFE);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2023,7 +2013,7 @@ pub(crate) mod test {
|
|||||||
.enable_rbf_with_sequence(0xDEADBEEF);
|
.enable_rbf_with_sequence(0xDEADBEEF);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xDEADBEEF);
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xDEADBEEF);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2034,7 +2024,7 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 25_000);
|
builder.add_recipient(addr.script_pubkey(), 25_000);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2059,9 +2049,9 @@ pub(crate) mod test {
|
|||||||
builder.drain_to(addr.script_pubkey()).drain_wallet();
|
builder.drain_to(addr.script_pubkey()).drain_wallet();
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.unsigned_tx.output[0].value,
|
||||||
50_000 - details.fee.unwrap_or(0)
|
50_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2078,7 +2068,7 @@ pub(crate) mod test {
|
|||||||
.drain_wallet();
|
.drain_wallet();
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
dbg!(&psbt);
|
dbg!(&psbt);
|
||||||
let outputs = psbt.global.unsigned_tx.output;
|
let outputs = psbt.unsigned_tx.output;
|
||||||
|
|
||||||
assert_eq!(outputs.len(), 2);
|
assert_eq!(outputs.len(), 2);
|
||||||
let main_output = outputs
|
let main_output = outputs
|
||||||
@ -2129,9 +2119,9 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.fee.unwrap_or(0), 100);
|
assert_eq!(details.fee.unwrap_or(0), 100);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.unsigned_tx.output[0].value,
|
||||||
50_000 - details.fee.unwrap_or(0)
|
50_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2148,9 +2138,9 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(details.fee.unwrap_or(0), 0);
|
assert_eq!(details.fee.unwrap_or(0), 0);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.unsigned_tx.output[0].value,
|
||||||
50_000 - details.fee.unwrap_or(0)
|
50_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2180,10 +2170,10 @@ pub(crate) mod test {
|
|||||||
.ordering(TxOrdering::Untouched);
|
.ordering(TxOrdering::Untouched);
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 2);
|
assert_eq!(psbt.unsigned_tx.output.len(), 2);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[0].value, 25_000);
|
assert_eq!(psbt.unsigned_tx.output[0].value, 25_000);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[1].value,
|
psbt.unsigned_tx.output[1].value,
|
||||||
25_000 - details.fee.unwrap_or(0)
|
25_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2196,8 +2186,8 @@ pub(crate) mod test {
|
|||||||
builder.add_recipient(addr.script_pubkey(), 49_800);
|
builder.add_recipient(addr.script_pubkey(), 49_800);
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
|
assert_eq!(psbt.unsigned_tx.output.len(), 1);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
|
assert_eq!(psbt.unsigned_tx.output[0].value, 49_800);
|
||||||
assert_eq!(details.fee.unwrap_or(0), 200);
|
assert_eq!(details.fee.unwrap_or(0), 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2226,13 +2216,13 @@ pub(crate) mod test {
|
|||||||
.ordering(super::tx_builder::TxOrdering::Bip69Lexicographic);
|
.ordering(super::tx_builder::TxOrdering::Bip69Lexicographic);
|
||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.output.len(), 3);
|
assert_eq!(psbt.unsigned_tx.output.len(), 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.output[0].value,
|
psbt.unsigned_tx.output[0].value,
|
||||||
10_000 - details.fee.unwrap_or(0)
|
10_000 - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[1].value, 10_000);
|
assert_eq!(psbt.unsigned_tx.output[1].value, 10_000);
|
||||||
assert_eq!(psbt.global.unsigned_tx.output[2].value, 30_000);
|
assert_eq!(psbt.unsigned_tx.output[2].value, 30_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2253,12 +2243,12 @@ pub(crate) mod test {
|
|||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder
|
builder
|
||||||
.add_recipient(addr.script_pubkey(), 30_000)
|
.add_recipient(addr.script_pubkey(), 30_000)
|
||||||
.sighash(bitcoin::SigHashType::Single);
|
.sighash(bitcoin::EcdsaSighashType::Single.into());
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.inputs[0].sighash_type,
|
psbt.inputs[0].sighash_type,
|
||||||
Some(bitcoin::SigHashType::Single)
|
Some(bitcoin::EcdsaSighashType::Single.into())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2450,7 +2440,7 @@ pub(crate) mod test {
|
|||||||
let (psbt, details) = builder.finish().unwrap();
|
let (psbt, details) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
psbt.global.unsigned_tx.input.len(),
|
psbt.unsigned_tx.input.len(),
|
||||||
2,
|
2,
|
||||||
"should add an additional input since 25_000 < 30_000"
|
"should add an additional input since 25_000 < 30_000"
|
||||||
);
|
);
|
||||||
@ -2507,7 +2497,7 @@ pub(crate) mod test {
|
|||||||
.policy_path(path, KeychainKind::External);
|
.policy_path(path, KeychainKind::External);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, 0xFFFFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2526,14 +2516,13 @@ pub(crate) mod test {
|
|||||||
.policy_path(path, KeychainKind::External);
|
.policy_path(path, KeychainKind::External);
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 144);
|
assert_eq!(psbt.unsigned_tx.input[0].sequence, 144);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_create_tx_global_xpubs_with_origin() {
|
fn test_create_tx_global_xpubs_with_origin() {
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::util::base58;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::util::psbt::raw::Key;
|
|
||||||
|
|
||||||
let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
|
let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
|
||||||
let addr = wallet.get_address(New).unwrap();
|
let addr = wallet.get_address(New).unwrap();
|
||||||
@ -2543,16 +2532,12 @@ pub(crate) mod test {
|
|||||||
.add_global_xpubs();
|
.add_global_xpubs();
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
let type_value = 0x01;
|
let key = bip32::ExtendedPubKey::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
|
||||||
let key = base58::from_check("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
|
let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap();
|
||||||
|
let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
|
||||||
|
|
||||||
let psbt_key = Key { type_value, key };
|
assert_eq!(psbt.xpub.len(), 1);
|
||||||
|
assert_eq!(psbt.xpub.get(&key), Some(&(fingerprint, path)));
|
||||||
// This key has an explicit origin, so it will be encoded here
|
|
||||||
let value_bytes = Vec::<u8>::from_hex("73756c7f30000080000000800000008002000080").unwrap();
|
|
||||||
|
|
||||||
assert_eq!(psbt.global.unknown.len(), 1);
|
|
||||||
assert_eq!(psbt.global.unknown.get(&psbt_key), Some(&value_bytes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2588,8 +2573,7 @@ pub(crate) mod test {
|
|||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
psbt.global
|
psbt.unsigned_tx
|
||||||
.unsigned_tx
|
|
||||||
.input
|
.input
|
||||||
.iter()
|
.iter()
|
||||||
.any(|input| input.previous_output == utxo.outpoint),
|
.any(|input| input.previous_output == utxo.outpoint),
|
||||||
@ -2794,8 +2778,7 @@ pub(crate) mod test {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_create_tx_global_xpubs_master_without_origin() {
|
fn test_create_tx_global_xpubs_master_without_origin() {
|
||||||
use bitcoin::hashes::hex::FromHex;
|
use bitcoin::hashes::hex::FromHex;
|
||||||
use bitcoin::util::base58;
|
use bitcoin::util::bip32;
|
||||||
use bitcoin::util::psbt::raw::Key;
|
|
||||||
|
|
||||||
let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
|
let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
|
||||||
let addr = wallet.get_address(New).unwrap();
|
let addr = wallet.get_address(New).unwrap();
|
||||||
@ -2805,17 +2788,14 @@ pub(crate) mod test {
|
|||||||
.add_global_xpubs();
|
.add_global_xpubs();
|
||||||
let (psbt, _) = builder.finish().unwrap();
|
let (psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
let type_value = 0x01;
|
let key = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
|
||||||
let key = base58::from_check("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
|
let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap();
|
||||||
|
|
||||||
let psbt_key = Key { type_value, key };
|
assert_eq!(psbt.xpub.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
// This key doesn't have an explicit origin, but it's a master key (depth = 0). So we encode
|
psbt.xpub.get(&key),
|
||||||
// its fingerprint directly and an empty path
|
Some(&(fingerprint, bip32::DerivationPath::default()))
|
||||||
let value_bytes = Vec::<u8>::from_hex("997a323b").unwrap();
|
);
|
||||||
|
|
||||||
assert_eq!(psbt.global.unknown.len(), 1);
|
|
||||||
assert_eq!(psbt.global.unknown.get(&psbt_key), Some(&value_bytes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -2937,7 +2917,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -2962,7 +2942,7 @@ pub(crate) mod test {
|
|||||||
);
|
);
|
||||||
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.output
|
tx.output
|
||||||
@ -2997,7 +2977,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3028,7 +3008,7 @@ pub(crate) mod test {
|
|||||||
original_details.fee.unwrap_or(0)
|
original_details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
tx.output
|
tx.output
|
||||||
@ -3063,7 +3043,7 @@ pub(crate) mod test {
|
|||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3087,7 +3067,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(details.sent, original_details.sent);
|
assert_eq!(details.sent, original_details.sent);
|
||||||
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.output.len(), 1);
|
assert_eq!(tx.output.len(), 1);
|
||||||
assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
|
assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
|
||||||
|
|
||||||
@ -3107,7 +3087,7 @@ pub(crate) mod test {
|
|||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3131,7 +3111,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(details.sent, original_details.sent);
|
assert_eq!(details.sent, original_details.sent);
|
||||||
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
assert!(details.fee.unwrap_or(0) > original_details.fee.unwrap_or(0));
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.output.len(), 1);
|
assert_eq!(tx.output.len(), 1);
|
||||||
assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
|
assert_eq!(tx.output[0].value + details.fee.unwrap_or(0), details.sent);
|
||||||
|
|
||||||
@ -3163,7 +3143,7 @@ pub(crate) mod test {
|
|||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3220,7 +3200,7 @@ pub(crate) mod test {
|
|||||||
let mut tx = psbt.extract_tx();
|
let mut tx = psbt.extract_tx();
|
||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3261,7 +3241,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3282,7 +3262,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3324,7 +3304,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3345,7 +3325,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3395,7 +3375,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3422,7 +3402,7 @@ pub(crate) mod test {
|
|||||||
75_000 - original_send_all_amount - details.fee.unwrap_or(0)
|
75_000 - original_send_all_amount - details.fee.unwrap_or(0)
|
||||||
);
|
);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3466,7 +3446,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3493,7 +3473,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(details.fee.unwrap_or(0), 30_000);
|
assert_eq!(details.fee.unwrap_or(0), 30_000);
|
||||||
assert_eq!(details.received, 0);
|
assert_eq!(details.received, 0);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
assert_eq!(tx.output.len(), 1);
|
assert_eq!(tx.output.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3527,7 +3507,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3556,7 +3536,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3598,7 +3578,7 @@ pub(crate) mod test {
|
|||||||
let txid = tx.txid();
|
let txid = tx.txid();
|
||||||
// skip saving the new utxos, we know they can't be used anyways
|
// skip saving the new utxos, we know they can't be used anyways
|
||||||
for txin in &mut tx.input {
|
for txin in &mut tx.input {
|
||||||
txin.witness.push([0x00; 108].to_vec()); // fake signature
|
txin.witness.push([0x00; 108]); // fake signature
|
||||||
wallet
|
wallet
|
||||||
.database
|
.database
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@ -3627,7 +3607,7 @@ pub(crate) mod test {
|
|||||||
assert_eq!(details.sent, original_details.sent + 25_000);
|
assert_eq!(details.sent, original_details.sent + 25_000);
|
||||||
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
assert_eq!(details.fee.unwrap_or(0) + details.received, 30_000);
|
||||||
|
|
||||||
let tx = &psbt.global.unsigned_tx;
|
let tx = &psbt.unsigned_tx;
|
||||||
assert_eq!(tx.input.len(), 2);
|
assert_eq!(tx.input.len(), 2);
|
||||||
assert_eq!(tx.output.len(), 2);
|
assert_eq!(tx.output.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -3785,7 +3765,7 @@ pub(crate) mod test {
|
|||||||
};
|
};
|
||||||
|
|
||||||
psbt.inputs.push(dud_input);
|
psbt.inputs.push(dud_input);
|
||||||
psbt.global.unsigned_tx.input.push(bitcoin::TxIn::default());
|
psbt.unsigned_tx.input.push(bitcoin::TxIn::default());
|
||||||
let is_final = wallet
|
let is_final = wallet
|
||||||
.sign(
|
.sign(
|
||||||
&mut psbt,
|
&mut psbt,
|
||||||
@ -3807,14 +3787,14 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_sign_nonstandard_sighash() {
|
fn test_sign_nonstandard_sighash() {
|
||||||
let sighash = SigHashType::NonePlusAnyoneCanPay;
|
let sighash = EcdsaSighashType::NonePlusAnyoneCanPay;
|
||||||
|
|
||||||
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
|
let (wallet, _, _) = get_funded_wallet("wpkh(tprv8ZgxMBicQKsPd3EupYiPRhaMooHKUHJxNsTfYuScep13go8QFfHdtkG9nRkFGb7busX4isf6X9dURGCoKgitaApQ6MupRhZMcELAxTBRJgS/*)");
|
||||||
let addr = wallet.get_address(New).unwrap();
|
let addr = wallet.get_address(New).unwrap();
|
||||||
let mut builder = wallet.build_tx();
|
let mut builder = wallet.build_tx();
|
||||||
builder
|
builder
|
||||||
.drain_to(addr.script_pubkey())
|
.drain_to(addr.script_pubkey())
|
||||||
.sighash(sighash)
|
.sighash(sighash.into())
|
||||||
.drain_wallet();
|
.drain_wallet();
|
||||||
let (mut psbt, _) = builder.finish().unwrap();
|
let (mut psbt, _) = builder.finish().unwrap();
|
||||||
|
|
||||||
@ -3847,8 +3827,8 @@ pub(crate) mod test {
|
|||||||
|
|
||||||
let extracted = psbt.extract_tx();
|
let extracted = psbt.extract_tx();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
*extracted.input[0].witness[0].last().unwrap(),
|
*extracted.input[0].witness.to_vec()[0].last().unwrap(),
|
||||||
sighash.as_u32() as u8,
|
sighash.to_u32() as u8,
|
||||||
"The signature should have been made with the right sighash"
|
"The signature should have been made with the right sighash"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -91,10 +91,11 @@ use std::sync::Arc;
|
|||||||
use bitcoin::blockdata::opcodes;
|
use bitcoin::blockdata::opcodes;
|
||||||
use bitcoin::blockdata::script::Builder as ScriptBuilder;
|
use bitcoin::blockdata::script::Builder as ScriptBuilder;
|
||||||
use bitcoin::hashes::{hash160, Hash};
|
use bitcoin::hashes::{hash160, Hash};
|
||||||
|
use bitcoin::secp256k1;
|
||||||
use bitcoin::secp256k1::{Message, Secp256k1};
|
use bitcoin::secp256k1::{Message, Secp256k1};
|
||||||
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
|
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
|
||||||
use bitcoin::util::{bip143, psbt};
|
use bitcoin::util::{ecdsa, psbt, sighash};
|
||||||
use bitcoin::{PrivateKey, Script, SigHash, SigHashType};
|
use bitcoin::{EcdsaSighashType, PrivateKey, PublicKey, Script, Sighash};
|
||||||
|
|
||||||
use miniscript::descriptor::{DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey, KeyMap};
|
use miniscript::descriptor::{DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey, KeyMap};
|
||||||
use miniscript::{Legacy, MiniscriptKey, Segwitv0};
|
use miniscript::{Legacy, MiniscriptKey, Segwitv0};
|
||||||
@ -153,6 +154,16 @@ pub enum SignerError {
|
|||||||
/// To enable signing transactions with non-standard sighashes set
|
/// To enable signing transactions with non-standard sighashes set
|
||||||
/// [`SignOptions::allow_all_sighashes`] to `true`.
|
/// [`SignOptions::allow_all_sighashes`] to `true`.
|
||||||
NonStandardSighash,
|
NonStandardSighash,
|
||||||
|
/// Invalid SIGHASH for the signing context in use
|
||||||
|
InvalidSighash,
|
||||||
|
/// Error while computing the hash to sign
|
||||||
|
SighashError(sighash::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<sighash::Error> for SignerError {
|
||||||
|
fn from(e: sighash::Error) -> Self {
|
||||||
|
SignerError::SighashError(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for SignerError {
|
impl fmt::Display for SignerError {
|
||||||
@ -245,10 +256,16 @@ impl Signer for DescriptorXKey<ExtendedPrivKey> {
|
|||||||
None => self.xkey.derive_priv(secp, &full_path).unwrap(),
|
None => self.xkey.derive_priv(secp, &full_path).unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if &derived_key.private_key.public_key(secp) != public_key {
|
if &secp256k1::PublicKey::from_secret_key(secp, &derived_key.private_key) != public_key {
|
||||||
Err(SignerError::InvalidKey)
|
Err(SignerError::InvalidKey)
|
||||||
} else {
|
} else {
|
||||||
derived_key.private_key.sign(psbt, Some(input_index), secp)
|
// HD wallets imply compressed keys
|
||||||
|
PrivateKey {
|
||||||
|
compressed: true,
|
||||||
|
network: self.xkey.network,
|
||||||
|
inner: derived_key.private_key,
|
||||||
|
}
|
||||||
|
.sign(psbt, Some(input_index), secp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +290,7 @@ impl Signer for PrivateKey {
|
|||||||
secp: &SecpCtx,
|
secp: &SecpCtx,
|
||||||
) -> Result<(), SignerError> {
|
) -> Result<(), SignerError> {
|
||||||
let input_index = input_index.unwrap();
|
let input_index = input_index.unwrap();
|
||||||
if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
|
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
|
||||||
return Err(SignerError::InputIndexOutOfRange);
|
return Err(SignerError::InputIndexOutOfRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +300,7 @@ impl Signer for PrivateKey {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let pubkey = self.public_key(secp);
|
let pubkey = PublicKey::from_private_key(secp, self);
|
||||||
if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
|
if psbt.inputs[input_index].partial_sigs.contains_key(&pubkey) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -297,15 +314,15 @@ impl Signer for PrivateKey {
|
|||||||
None => Legacy::sighash(psbt, input_index)?,
|
None => Legacy::sighash(psbt, input_index)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let signature = secp.sign(
|
let sig = secp.sign_ecdsa(
|
||||||
&Message::from_slice(&hash.into_inner()[..]).unwrap(),
|
&Message::from_slice(&hash.into_inner()[..]).unwrap(),
|
||||||
&self.key,
|
&self.inner,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut final_signature = Vec::with_capacity(75);
|
let final_signature = ecdsa::EcdsaSig {
|
||||||
final_signature.extend_from_slice(&signature.serialize_der());
|
sig,
|
||||||
final_signature.push(sighash.as_u32() as u8);
|
hash_ty: sighash.ecdsa_hash_ty().unwrap(), // FIXME
|
||||||
|
};
|
||||||
psbt.inputs[input_index]
|
psbt.inputs[input_index]
|
||||||
.partial_sigs
|
.partial_sigs
|
||||||
.insert(pubkey, final_signature);
|
.insert(pubkey, final_signature);
|
||||||
@ -494,22 +511,24 @@ pub(crate) trait ComputeSighash {
|
|||||||
fn sighash(
|
fn sighash(
|
||||||
psbt: &psbt::PartiallySignedTransaction,
|
psbt: &psbt::PartiallySignedTransaction,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
) -> Result<(SigHash, SigHashType), SignerError>;
|
) -> Result<(Sighash, psbt::PsbtSighashType), SignerError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComputeSighash for Legacy {
|
impl ComputeSighash for Legacy {
|
||||||
fn sighash(
|
fn sighash(
|
||||||
psbt: &psbt::PartiallySignedTransaction,
|
psbt: &psbt::PartiallySignedTransaction,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
) -> Result<(SigHash, SigHashType), SignerError> {
|
) -> Result<(Sighash, psbt::PsbtSighashType), SignerError> {
|
||||||
if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
|
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
|
||||||
return Err(SignerError::InputIndexOutOfRange);
|
return Err(SignerError::InputIndexOutOfRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
let psbt_input = &psbt.inputs[input_index];
|
let psbt_input = &psbt.inputs[input_index];
|
||||||
let tx_input = &psbt.global.unsigned_tx.input[input_index];
|
let tx_input = &psbt.unsigned_tx.input[input_index];
|
||||||
|
|
||||||
let sighash = psbt_input.sighash_type.unwrap_or(SigHashType::All);
|
let sighash = psbt_input
|
||||||
|
.sighash_type
|
||||||
|
.unwrap_or_else(|| EcdsaSighashType::All.into());
|
||||||
let script = match psbt_input.redeem_script {
|
let script = match psbt_input.redeem_script {
|
||||||
Some(ref redeem_script) => redeem_script.clone(),
|
Some(ref redeem_script) => redeem_script.clone(),
|
||||||
None => {
|
None => {
|
||||||
@ -527,9 +546,11 @@ impl ComputeSighash for Legacy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
psbt.global
|
sighash::SighashCache::new(&psbt.unsigned_tx).legacy_signature_hash(
|
||||||
.unsigned_tx
|
input_index,
|
||||||
.signature_hash(input_index, &script, sighash.as_u32()),
|
&script,
|
||||||
|
sighash.to_u32(),
|
||||||
|
)?,
|
||||||
sighash,
|
sighash,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -549,15 +570,19 @@ impl ComputeSighash for Segwitv0 {
|
|||||||
fn sighash(
|
fn sighash(
|
||||||
psbt: &psbt::PartiallySignedTransaction,
|
psbt: &psbt::PartiallySignedTransaction,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
) -> Result<(SigHash, SigHashType), SignerError> {
|
) -> Result<(Sighash, psbt::PsbtSighashType), SignerError> {
|
||||||
if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
|
if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
|
||||||
return Err(SignerError::InputIndexOutOfRange);
|
return Err(SignerError::InputIndexOutOfRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
let psbt_input = &psbt.inputs[input_index];
|
let psbt_input = &psbt.inputs[input_index];
|
||||||
let tx_input = &psbt.global.unsigned_tx.input[input_index];
|
let tx_input = &psbt.unsigned_tx.input[input_index];
|
||||||
|
|
||||||
let sighash = psbt_input.sighash_type.unwrap_or(SigHashType::All);
|
let sighash = psbt_input
|
||||||
|
.sighash_type
|
||||||
|
.unwrap_or_else(|| EcdsaSighashType::All.into())
|
||||||
|
.ecdsa_hash_ty()
|
||||||
|
.map_err(|_| SignerError::InvalidSighash)?;
|
||||||
|
|
||||||
// Always try first with the non-witness utxo
|
// Always try first with the non-witness utxo
|
||||||
let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
|
let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
|
||||||
@ -600,13 +625,13 @@ impl ComputeSighash for Segwitv0 {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
bip143::SigHashCache::new(&psbt.global.unsigned_tx).signature_hash(
|
sighash::SighashCache::new(&psbt.unsigned_tx).segwit_signature_hash(
|
||||||
input_index,
|
input_index,
|
||||||
&script,
|
&script,
|
||||||
value,
|
value,
|
||||||
sighash,
|
sighash,
|
||||||
),
|
)?,
|
||||||
sighash,
|
sighash.into(),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -757,7 +782,7 @@ mod signers_container_tests {
|
|||||||
let secp: Secp256k1<All> = Secp256k1::new();
|
let secp: Secp256k1<All> = Secp256k1::new();
|
||||||
let path = bip32::DerivationPath::from_str(PATH).unwrap();
|
let path = bip32::DerivationPath::from_str(PATH).unwrap();
|
||||||
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
|
let tprv = bip32::ExtendedPrivKey::from_str(tprv).unwrap();
|
||||||
let tpub = bip32::ExtendedPubKey::from_private(&secp, &tprv);
|
let tpub = bip32::ExtendedPubKey::from_priv(&secp, &tprv);
|
||||||
let fingerprint = tprv.fingerprint(&secp);
|
let fingerprint = tprv.fingerprint(&secp);
|
||||||
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
|
let prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
|
||||||
let pubkey = (tpub, path).into_descriptor_key().unwrap();
|
let pubkey = (tpub, path).into_descriptor_key().unwrap();
|
||||||
|
@ -42,7 +42,7 @@ use std::default::Default;
|
|||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt};
|
use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt};
|
||||||
use bitcoin::{OutPoint, Script, SigHashType, Transaction};
|
use bitcoin::{OutPoint, Script, Transaction};
|
||||||
|
|
||||||
use miniscript::descriptor::DescriptorTrait;
|
use miniscript::descriptor::DescriptorTrait;
|
||||||
|
|
||||||
@ -103,10 +103,7 @@ impl TxBuilderContext for BumpFee {}
|
|||||||
/// builder.finish()?
|
/// builder.finish()?
|
||||||
/// };
|
/// };
|
||||||
///
|
///
|
||||||
/// assert_eq!(
|
/// assert_eq!(psbt1.unsigned_tx.output[..2], psbt2.unsigned_tx.output[..2]);
|
||||||
/// psbt1.global.unsigned_tx.output[..2],
|
|
||||||
/// psbt2.global.unsigned_tx.output[..2]
|
|
||||||
/// );
|
|
||||||
/// # Ok::<(), bdk::Error>(())
|
/// # Ok::<(), bdk::Error>(())
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@ -140,7 +137,7 @@ pub(crate) struct TxParams {
|
|||||||
pub(crate) utxos: Vec<WeightedUtxo>,
|
pub(crate) utxos: Vec<WeightedUtxo>,
|
||||||
pub(crate) unspendable: HashSet<OutPoint>,
|
pub(crate) unspendable: HashSet<OutPoint>,
|
||||||
pub(crate) manually_selected_only: bool,
|
pub(crate) manually_selected_only: bool,
|
||||||
pub(crate) sighash: Option<SigHashType>,
|
pub(crate) sighash: Option<psbt::PsbtSighashType>,
|
||||||
pub(crate) ordering: TxOrdering,
|
pub(crate) ordering: TxOrdering,
|
||||||
pub(crate) locktime: Option<u32>,
|
pub(crate) locktime: Option<u32>,
|
||||||
pub(crate) rbf: Option<RbfValue>,
|
pub(crate) rbf: Option<RbfValue>,
|
||||||
@ -412,7 +409,7 @@ impl<'a, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext>
|
|||||||
/// Sign with a specific sig hash
|
/// Sign with a specific sig hash
|
||||||
///
|
///
|
||||||
/// **Use this option very carefully**
|
/// **Use this option very carefully**
|
||||||
pub fn sighash(&mut self, sighash: SigHashType) -> &mut Self {
|
pub fn sighash(&mut self, sighash: psbt::PsbtSighashType) -> &mut Self {
|
||||||
self.params.sighash = Some(sighash);
|
self.params.sighash = Some(sighash);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user