- added `OldestFirstCoinSelection` impl to `CoinSelectionAlgorithm`
- New MSRV set to `1.56`
- Add traits to reuse `Blockchain`s across multiple wallets (`BlockchainFactory` and `StatelessBlockchain`).
+- Upgrade to rust-bitcoin `0.28`
## [v0.18.0] - [v0.17.0]
[dependencies]
bdk-macros = "^0.6"
log = "^0.4"
-miniscript = { version = "^6.1", features = ["use-serde"] }
-bitcoin = { version = "^0.27", features = ["use-serde", "base64"] }
+miniscript = { version = "7.0", features = ["use-serde"] }
+bitcoin = { version = "0.28", features = ["use-serde", "base64"] }
serde = { version = "^1.0", features = ["derive"] }
serde_json = { version = "^1.0" }
rand = "^0.7"
# Optional dependencies
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 }
ahash = { version = "=0.7.4", optional = true }
reqwest = { version = "0.11", optional = true, default-features = false, features = ["json"] }
bip39 = { version = "1.0.1", optional = true }
bitcoinconsensus = { version = "0.19.0-3", optional = true }
-# Needed by bdk_blockchain_tests macro
-bitcoincore-rpc = { version = "0.14", optional = true }
+# Needed by bdk_blockchain_tests macro and the `rpc` feature
+bitcoincore-rpc = { version = "0.15", optional = true }
# Platform-specific dependencies
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
lazy_static = "1.4"
env_logger = "0.7"
clap = "2.33"
-electrsd = { version= "0.15", features = ["trigger", "bitcoind_22_0"] }
+electrsd = { version= "0.19.1", features = ["bitcoind_22_0"] }
[[example]]
name = "address_validator"
//!
//! see: <https://github.com/Blockstream/esplora/blob/master/API.md>
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)]
pub struct PrevOut {
},
script_sig: vin.scriptsig,
sequence: vin.sequence,
- witness: vin.witness,
+ witness: Witness::from_vec(vin.witness),
})
.collect(),
output: self
use std::ops::Deref;
use bitcoin::hashes::hash160;
-use bitcoin::PublicKey;
+use bitcoin::{PublicKey, XOnlyPublicKey};
-use miniscript::{descriptor::Wildcard, Descriptor, DescriptorPublicKey};
-use miniscript::{MiniscriptKey, ToPublicKey, TranslatePk};
+use miniscript::descriptor::{DescriptorSinglePub, SinglePubKey, Wildcard};
+use miniscript::{Descriptor, DescriptorPublicKey, MiniscriptKey, ToPublicKey, TranslatePk};
use crate::wallet::utils::SecpCtx;
fn is_uncompressed(&self) -> bool {
self.0.is_uncompressed()
}
- fn serialized_len(&self) -> usize {
- self.0.serialized_len()
- }
}
impl<'s> ToPublicKey for DerivedDescriptorKey<'s> {
fn to_public_key(&self) -> PublicKey {
match &self.0 {
- DescriptorPublicKey::SinglePub(ref spub) => spub.key.to_public_key(),
- DescriptorPublicKey::XPub(ref xpub) => {
+ DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ 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
+ .derive_pub(self.1, &xpub.derivation_path)
+ .expect("Shouldn't fail, only normal derivations")
+ .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
- }
+ .public_key,
+ ),
}
}
}
#[test]
- #[should_panic(expected = "Miniscript(ContextError(CompressedOnly))")]
+ #[should_panic(
+ expected = "Miniscript(ContextError(CompressedOnly(\"04b4632d08485ff1df2db55b9dafd23347d1c47a457072a1e87be26896549a87378ec38ff91d43e8c2092ebda601780485263da089465619e0358a5c1be7ac91f4\")))"
+ )]
fn test_dsl_miniscript_checks() {
let mut uncompressed_pk =
PrivateKey::from_wif("L5EZftvrYaSudiozVRzTqLcHLNDoVn7H5HSfM9BAN6tMJX8oTWz6").unwrap();
use std::collections::{BTreeMap, HashMap, HashSet};
use std::ops::Deref;
+use bitcoin::secp256k1;
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint, KeySource};
use bitcoin::util::psbt;
-use bitcoin::{Network, PublicKey, Script, TxOut};
+use bitcoin::{Network, Script, TxOut};
use miniscript::descriptor::{DescriptorType, InnerXKey};
pub use miniscript::{
///
/// [`psbt::Input`]: bitcoin::util::psbt::Input
/// [`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`]
pub trait IntoWalletDescriptor {
pub(crate) trait DescriptorMeta {
fn is_witness(&self) -> bool;
+ fn is_taproot(&self) -> bool;
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError>;
fn derive_from_hd_keypaths<'s>(
&self,
impl<'s> DescriptorScripts for DerivedDescriptor<'s> {
fn psbt_redeem_script(&self) -> Option<Script> {
match self.desc_type() {
- DescriptorType::ShWpkh => Some(self.explicit_script()),
- DescriptorType::ShWsh => Some(self.explicit_script().to_v0_p2wsh()),
- DescriptorType::Sh => Some(self.explicit_script()),
- DescriptorType::Bare => Some(self.explicit_script()),
- DescriptorType::ShSortedMulti => Some(self.explicit_script()),
+ DescriptorType::ShWpkh => Some(self.explicit_script().unwrap()),
+ DescriptorType::ShWsh => Some(self.explicit_script().unwrap().to_v0_p2wsh()),
+ DescriptorType::Sh => Some(self.explicit_script().unwrap()),
+ DescriptorType::Bare => Some(self.explicit_script().unwrap()),
+ DescriptorType::ShSortedMulti => Some(self.explicit_script().unwrap()),
_ => None,
}
}
fn psbt_witness_script(&self) -> Option<Script> {
match self.desc_type() {
- DescriptorType::Wsh => Some(self.explicit_script()),
- DescriptorType::ShWsh => Some(self.explicit_script()),
+ DescriptorType::Wsh => Some(self.explicit_script().unwrap()),
+ DescriptorType::ShWsh => Some(self.explicit_script().unwrap()),
DescriptorType::WshSortedMulti | DescriptorType::ShWshSortedMulti => {
- Some(self.explicit_script())
+ Some(self.explicit_script().unwrap())
}
_ => None,
}
)
}
+ fn is_taproot(&self) -> bool {
+ self.desc_type() == DescriptorType::Tr
+ }
+
fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, DescriptorError> {
let mut answer = Vec::new();
let descriptor = self.as_derived_fixed(secp);
match descriptor.desc_type() {
// TODO: add pk() here
- DescriptorType::Pkh | DescriptorType::Wpkh | DescriptorType::ShWpkh
+ DescriptorType::Pkh
+ | DescriptorType::Wpkh
+ | DescriptorType::ShWpkh
+ | DescriptorType::Tr
if utxo.is_some()
&& descriptor.script_pubkey() == utxo.as_ref().unwrap().script_pubkey =>
{
}
DescriptorType::Bare | DescriptorType::Sh | DescriptorType::ShSortedMulti
if psbt_input.redeem_script.is_some()
- && &descriptor.explicit_script()
+ && &descriptor.explicit_script().unwrap()
== psbt_input.redeem_script.as_ref().unwrap() =>
{
Some(descriptor)
| DescriptorType::ShWshSortedMulti
| DescriptorType::WshSortedMulti
if psbt_input.witness_script.is_some()
- && &descriptor.explicit_script()
+ && &descriptor.explicit_script().unwrap()
== psbt_input.witness_script.as_ref().unwrap() =>
{
Some(descriptor)
use serde::{Serialize, Serializer};
use bitcoin::hashes::*;
+use bitcoin::secp256k1;
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};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
use crate::descriptor::ExtractPolicy;
+use crate::keys::ExtScriptContext;
use crate::wallet::signer::{SignerId, SignersContainer};
use crate::wallet::utils::{self, After, Older, SecpCtx};
use super::checksum::get_checksum;
use super::error::Error;
use super::XKeyUtils;
-use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
+use bitcoin::util::psbt::{Input as PsbtInput, PartiallySignedTransaction as Psbt};
use miniscript::psbt::PsbtInputSatisfier;
/// Raw public key or extended key fingerprint
#[serde(skip_serializing_if = "Option::is_none")]
pubkey: Option<PublicKey>,
#[serde(skip_serializing_if = "Option::is_none")]
+ x_only_pubkey: Option<XOnlyPublicKey>,
+ #[serde(skip_serializing_if = "Option::is_none")]
pubkey_hash: Option<hash160::Hash>,
#[serde(skip_serializing_if = "Option::is_none")]
fingerprint: Option<Fingerprint>,
impl PkOrF {
fn from_key(k: &DescriptorPublicKey, secp: &SecpCtx) -> Self {
match k {
- DescriptorPublicKey::SinglePub(pubkey) => PkOrF {
- pubkey: Some(pubkey.key),
+ DescriptorPublicKey::SinglePub(DescriptorSinglePub {
+ 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()
},
DescriptorPublicKey::XPub(xpub) => PkOrF {
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum SatisfiableItem {
// Leaves
- /// Signature for a raw public key
- Signature(PkOrF),
- /// Signature for an extended key fingerprint
- SignatureKey(PkOrF),
+ /// ECDSA Signature for a raw public key
+ EcdsaSignature(PkOrF),
+ /// Schnorr Signature for a raw public key
+ SchnorrSignature(PkOrF),
/// SHA256 preimage hash
Sha256Preimage {
/// The digest value
build_sat: BuildSatisfaction,
threshold: usize,
sorted: bool,
+ is_ecdsa: bool,
secp: &SecpCtx,
) -> Result<Option<Policy>, PolicyError> {
if threshold == 0 {
}
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::Complete {
condition: Default::default(),
fn signer_id(key: &DescriptorPublicKey, secp: &SecpCtx) -> SignerId {
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(),
}
}
build_sat: BuildSatisfaction,
secp: &SecpCtx,
) -> 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() {
Satisfaction::Complete {
};
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 {
condition: Default::default(),
}
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
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) => {
let pubkey = input
.bip32_derivation
.map(|(p, _)| p);
//TODO check actual derivation matches
match pubkey {
- Some(pubkey) => input.partial_sigs.contains_key(pubkey),
+ Some(pubkey) => check(input, &map(pubkey)),
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(
&self,
signers: &SignersContainer,
Terminal::Hash160(hash) => {
Some(SatisfiableItem::Hash160Preimage { hash: *hash }.into())
}
- Terminal::Multi(k, pks) => {
- Policy::make_multisig(pks, signers, build_sat, *k, false, secp)?
- }
+ Terminal::Multi(k, pks) | Terminal::MultiA(k, pks) => Policy::make_multisig(
+ pks,
+ signers,
+ build_sat,
+ *k,
+ false,
+ !Ctx::as_enum().is_taproot(),
+ secp,
+ )?,
// Identities
Terminal::Alt(inner)
| Terminal::Swap(inner)
build_sat,
keys.k,
true,
+ true,
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::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)?)
+ }
}
}
}
use super::*;
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::wallet::signer::SignersContainer;
use bitcoin::secp256k1::Secp256k1;
) -> (DescriptorKey<Ctx>, DescriptorKey<Ctx>, Fingerprint) {
let path = bip32::DerivationPath::from_str(path).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 prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
let pubkey = (tpub, path).into_descriptor_key().unwrap();
.unwrap();
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));
.unwrap();
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::Complete {condition} if condition.csv == None && condition.timelock == None)
.unwrap();
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));
.unwrap();
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::Complete {condition} if condition.csv == None && condition.timelock == None)
use bitcoin::secp256k1::{self, Secp256k1, Signing};
use bitcoin::util::bip32;
-use bitcoin::{Network, PrivateKey, PublicKey};
+use bitcoin::{Network, PrivateKey, PublicKey, XOnlyPublicKey};
use miniscript::descriptor::{Descriptor, DescriptorXKey, Wildcard};
pub use miniscript::descriptor::{
DescriptorPublicKey, DescriptorSecretKey, DescriptorSinglePriv, DescriptorSinglePub, KeyMap,
- SortedMultiVec,
+ SinglePubKey, SortedMultiVec,
};
pub use miniscript::ScriptContext;
use miniscript::{Miniscript, Terminal};
Legacy,
/// Segwitv0 scripts
Segwitv0,
+ /// Taproot scripts
+ Tap,
}
impl ScriptContextEnum {
pub fn is_segwit_v0(&self) -> bool {
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
fn is_segwit_v0() -> bool {
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 {
match TypeId::of::<Ctx>() {
t if t == TypeId::of::<miniscript::Legacy>() => ScriptContextEnum::Legacy,
t if t == TypeId::of::<miniscript::Segwitv0>() => ScriptContextEnum::Segwitv0,
+ t if t == TypeId::of::<miniscript::Tap>() => ScriptContextEnum::Tap,
_ => unimplemented!("Unknown ScriptContext type"),
}
}
///
/// use bdk::keys::{
/// mainnet_network, DescriptorKey, DescriptorPublicKey, DescriptorSinglePub,
-/// IntoDescriptorKey, KeyError, ScriptContext,
+/// IntoDescriptorKey, KeyError, ScriptContext, SinglePubKey,
/// };
///
/// pub struct MyKeyType {
/// Ok(DescriptorKey::from_public(
/// DescriptorPublicKey::SinglePub(DescriptorSinglePub {
/// origin: None,
-/// key: self.pubkey,
+/// key: SinglePubKey::FullKey(self.pubkey),
/// }),
/// mainnet_network(),
/// ))
match self {
ExtendedKey::Private((mut xprv, _)) => {
xprv.network = network;
- xprv.private_key.network = network;
Some(xprv)
}
ExtendedKey::Public(_) => None,
secp: &Secp256k1<C>,
) -> bip32::ExtendedPubKey {
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,
};
/// network: self.network,
/// depth: 0,
/// 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()),
/// child_number: bip32::ChildNumber::Normal { index: 0 },
/// };
/// network: bitcoin::Network::Bitcoin, // pick an arbitrary network here
/// depth: 0,
/// 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()),
/// child_number: bip32::ChildNumber::Normal { index: 0 },
/// };
entropy: Self::Entropy,
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
// 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 {
compressed: options.compressed,
network: Network::Bitcoin,
- key,
+ inner,
};
Ok(GeneratedKey::new(private_key, any_network()))
impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PublicKey {
fn into_descriptor_key(self) -> Result<DescriptorKey<Ctx>, KeyError> {
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,
})
.into_descriptor_key()
);
}
- #[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")]
#[test]
fn test_keys_wif_network_bip39() {
.into_extended_key()
.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);
}
}
impl PsbtUtils for Psbt {
#[allow(clippy::all)] // We want to allow `manual_map` but it is too new.
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() {
return None;
let mut builder = wallet.build_tx();
builder.add_recipient(send_to.script_pubkey(), 10_000);
let (mut psbt, _) = builder.finish().unwrap();
- psbt.global.unsigned_tx.input.push(TxIn::default());
+ psbt.unsigned_tx.input.push(TxIn::default());
let options = SignOptions {
trust_witness_utxo: true,
..Default::default()
// add a finalized input
psbt.inputs.push(psbt_bip.inputs[0].clone());
- psbt.global
- .unsigned_tx
+ psbt.unsigned_tx
.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();
}
use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hashes::hex::{FromHex, ToHex};
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::{Auth, Client as RpcClient, RpcApi};
use core::str::FromStr;
previous_output: OutPoint::null(),
script_sig: Builder::new().push_int(height).into_script(),
sequence: 0xFFFFFFFF,
- witness: vec![witness_reserved_value],
+ witness: Witness::from_vec(vec![witness_reserved_value]),
}],
output: vec![],
};
let mut block = Block { header, txdata };
- let witness_root = block.witness_root();
- let witness_commitment =
- Block::compute_witness_commitment(&witness_root, &coinbase_tx.input[0].witness[0]);
+ if let Some(witness_root) = block.witness_root() {
+ let witness_commitment = Block::compute_witness_commitment(
+ &witness_root,
+ &coinbase_tx.input[0]
+ .witness
+ .last()
+ .expect("Should contain the witness reserved value"),
+ );
- // now update and replace the coinbase tx
- let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
- coinbase_witness_commitment_script.extend_from_slice(&witness_commitment);
+ // now update and replace the coinbase tx
+ let mut coinbase_witness_commitment_script = vec![0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xed];
+ 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;
// set merkle root
- let merkle_root = block.merkle_root();
- block.header.merkle_root = merkle_root;
+ if let Some(merkle_root) = block.compute_merkle_root() {
+ block.header.merkle_root = merkle_root;
+ }
assert!(block.check_merkle_root());
assert!(block.check_witness_commitment());
#[cfg(feature = "test-blockchains")]
pub mod blockchain_tests;
-use bitcoin::secp256k1::{Secp256k1, Verification};
-use bitcoin::{Address, PublicKey, Txid};
-
-use miniscript::descriptor::DescriptorPublicKey;
-use miniscript::{Descriptor, MiniscriptKey, TranslatePk};
+use bitcoin::{Address, Txid};
#[derive(Clone, Debug)]
pub struct TestIncomingInput {
}
}
-#[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)]
#[macro_export]
macro_rules! testutils {
use $crate::bitcoin::secp256k1::Secp256k1;
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
- use $crate::testutils::TranslateDescriptor;
+ use $crate::descriptor::AsDerived;
let secp = Secp256k1::new();
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 ) => ({
use $crate::bitcoin::secp256k1::Secp256k1;
use $crate::miniscript::descriptor::{Descriptor, DescriptorPublicKey, DescriptorTrait};
- use $crate::testutils::TranslateDescriptor;
+ use $crate::descriptor::AsDerived;
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;
- 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) });
( @i $descriptors:expr, $child:expr ) => ({ testutils!(@internal $descriptors, $child) });
use bitcoin::secp256k1::Secp256k1;
use bitcoin::consensus::encode::serialize;
-use bitcoin::util::base58;
-use bitcoin::util::psbt::raw::Key as PsbtKey;
-use bitcoin::util::psbt::Input;
-use bitcoin::util::psbt::PartiallySignedTransaction as Psbt;
-use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid};
+use bitcoin::util::psbt;
+use bitcoin::{Address, Network, OutPoint, Script, SigHashType, Transaction, TxOut, Txid, Witness};
use miniscript::descriptor::DescriptorTrait;
use miniscript::psbt::PsbtInputSatisfier;
&self,
coin_selection: Cs,
params: TxParams,
- ) -> Result<(Psbt, TransactionDetails), Error> {
+ ) -> Result<(psbt::PartiallySignedTransaction, TransactionDetails), Error> {
let external_policy = self
.descriptor
.extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
let mut outgoing: 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));
previous_output: u.outpoint(),
script_sig: Script::default(),
sequence: n_sequence,
- witness: vec![],
+ witness: Witness::new(),
})
.collect();
return Err(Error::IrreplaceableTransaction);
}
- let feerate = FeeRate::from_wu(
- details.fee.ok_or(Error::FeeRateUnavailable)?,
- tx.get_weight(),
- );
+ let feerate = FeeRate::from_wu(details.fee.ok_or(Error::FeeRateUnavailable)?, tx.weight());
// remove the inputs from the tx and process them
let original_txin = tx.input.drain(..).collect::<Vec<_>>();
/// let finalized = wallet.sign(&mut psbt, SignOptions::default())?;
/// assert!(finalized, "we should have signed all the inputs");
/// # 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
self.add_input_hd_keypaths(psbt)?;
// If the user hasn't explicitly opted-in, refuse to sign the transaction unless every input
// is using `SIGHASH_ALL`
if !sign_options.allow_all_sighashes
- && !psbt
- .inputs
- .iter()
- .all(|i| i.sighash_type.is_none() || i.sighash_type == Some(SigHashType::All))
+ && !psbt.inputs.iter().all(|i| {
+ i.sighash_type.is_none() || i.sighash_type == Some(SigHashType::All.into())
+ })
{
return Err(Error::Signer(signer::SignerError::NonStandardSighash));
}
/// Try to finalize a PSBT
///
/// 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> {
- let tx = &psbt.global.unsigned_tx;
+ pub fn finalize_psbt(
+ &self,
+ psbt: &mut psbt::PartiallySignedTransaction,
+ sign_options: SignOptions,
+ ) -> Result<bool, Error> {
+ let tx = &psbt.unsigned_tx;
let mut finished = true;
for (n, input) in tx.input.iter().enumerate() {
tx: Transaction,
selected: Vec<Utxo>,
params: TxParams,
- ) -> Result<Psbt, Error> {
- use bitcoin::util::psbt::serialize::Serialize;
-
- let mut psbt = Psbt::from_unsigned_tx(tx)?;
+ ) -> Result<psbt::PartiallySignedTransaction, Error> {
+ let mut psbt = psbt::PartiallySignedTransaction::from_unsigned_tx(tx)?;
if params.add_global_xpubs {
let mut all_xpubs = self.descriptor.get_extended_keys()?;
}
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 {
Some(origin) => origin,
None if xpub.xkey.depth == 0 => {
_ => return Err(Error::MissingKeyOrigin(xpub.xkey.to_string())),
};
- psbt.global.unknown.insert(key, origin.serialize());
+ psbt.xpub.insert(xpub.xkey, origin);
}
}
.collect::<HashMap<_, _>>();
// add metadata for the inputs
- for (psbt_input, input) in psbt
- .inputs
- .iter_mut()
- .zip(psbt.global.unsigned_tx.input.iter())
- {
+ for (psbt_input, input) in psbt.inputs.iter_mut().zip(psbt.unsigned_tx.input.iter()) {
let utxo = match lookup_output.remove(&input.previous_output) {
Some(utxo) => utxo,
None => continue,
match self.get_psbt_input(utxo, params.sighash, params.only_witness_utxo) {
Ok(psbt_input) => psbt_input,
Err(e) => match e {
- Error::UnknownUtxo => Input {
+ Error::UnknownUtxo => psbt::Input {
sighash_type: params.sighash,
- ..Input::default()
+ ..psbt::Input::default()
},
_ => return Err(e),
},
self.add_input_hd_keypaths(&mut psbt)?;
// add metadata for the outputs
- for (psbt_output, tx_output) in psbt
- .outputs
- .iter_mut()
- .zip(psbt.global.unsigned_tx.output.iter())
+ for (psbt_output, tx_output) in psbt.outputs.iter_mut().zip(psbt.unsigned_tx.output.iter())
{
if let Some((keychain, child)) = self
.database
pub fn get_psbt_input(
&self,
utxo: LocalUtxo,
- sighash_type: Option<SigHashType>,
+ sighash_type: Option<psbt::PsbtSighashType>,
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,
// and the derivation index
let (keychain, child) = self
.get_path_from_script_pubkey(&utxo.txout.script_pubkey)?
.ok_or(Error::UnknownUtxo)?;
- let mut psbt_input = Input {
+ let mut psbt_input = psbt::Input {
sighash_type,
- ..Input::default()
+ ..psbt::Input::default()
};
let desc = self.get_descriptor_for_keychain(keychain);
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());
for n in 0..psbt.inputs.len() {
input_utxos.push(psbt.get_utxo_for(n).clone());
$(
$( $add_signature )*
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
}
)*
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;
if !dust_change {
.version(42);
let (psbt, _) = builder.finish().unwrap();
- assert_eq!(psbt.global.unsigned_tx.version, 42);
+ assert_eq!(psbt.unsigned_tx.version, 42);
}
#[test]
builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap();
- assert_eq!(psbt.global.unsigned_tx.lock_time, 0);
+ assert_eq!(psbt.unsigned_tx.lock_time, 0);
}
#[test]
builder.add_recipient(addr.script_pubkey(), 25_000);
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]
.nlocktime(630_000);
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]
.nlocktime(630_000);
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]
builder.add_recipient(addr.script_pubkey(), 25_000);
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]
let (psbt, _) = builder.finish().unwrap();
// 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
- assert_eq!(psbt.global.unsigned_tx.input[0].sequence, 6);
+ assert_eq!(psbt.unsigned_tx.input[0].sequence, 6);
}
#[test]
builder.add_recipient(addr.script_pubkey(), 25_000);
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]
.enable_rbf_with_sequence(0xDEADBEEF);
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]
builder.add_recipient(addr.script_pubkey(), 25_000);
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]
builder.drain_to(addr.script_pubkey()).drain_wallet();
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,
+ psbt.unsigned_tx.output[0].value,
50_000 - details.fee.unwrap_or(0)
);
}
.drain_wallet();
let (psbt, details) = builder.finish().unwrap();
dbg!(&psbt);
- let outputs = psbt.global.unsigned_tx.output;
+ let outputs = psbt.unsigned_tx.output;
assert_eq!(outputs.len(), 2);
let main_output = outputs
let (psbt, details) = builder.finish().unwrap();
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!(
- psbt.global.unsigned_tx.output[0].value,
+ psbt.unsigned_tx.output[0].value,
50_000 - details.fee.unwrap_or(0)
);
}
let (psbt, details) = builder.finish().unwrap();
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!(
- psbt.global.unsigned_tx.output[0].value,
+ psbt.unsigned_tx.output[0].value,
50_000 - details.fee.unwrap_or(0)
);
}
.ordering(TxOrdering::Untouched);
let (psbt, details) = builder.finish().unwrap();
- assert_eq!(psbt.global.unsigned_tx.output.len(), 2);
- assert_eq!(psbt.global.unsigned_tx.output[0].value, 25_000);
+ assert_eq!(psbt.unsigned_tx.output.len(), 2);
+ assert_eq!(psbt.unsigned_tx.output[0].value, 25_000);
assert_eq!(
- psbt.global.unsigned_tx.output[1].value,
+ psbt.unsigned_tx.output[1].value,
25_000 - details.fee.unwrap_or(0)
);
}
builder.add_recipient(addr.script_pubkey(), 49_800);
let (psbt, details) = builder.finish().unwrap();
- assert_eq!(psbt.global.unsigned_tx.output.len(), 1);
- assert_eq!(psbt.global.unsigned_tx.output[0].value, 49_800);
+ assert_eq!(psbt.unsigned_tx.output.len(), 1);
+ assert_eq!(psbt.unsigned_tx.output[0].value, 49_800);
assert_eq!(details.fee.unwrap_or(0), 200);
}
.ordering(super::tx_builder::TxOrdering::Bip69Lexicographic);
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!(
- psbt.global.unsigned_tx.output[0].value,
+ psbt.unsigned_tx.output[0].value,
10_000 - details.fee.unwrap_or(0)
);
- assert_eq!(psbt.global.unsigned_tx.output[1].value, 10_000);
- assert_eq!(psbt.global.unsigned_tx.output[2].value, 30_000);
+ assert_eq!(psbt.unsigned_tx.output[1].value, 10_000);
+ assert_eq!(psbt.unsigned_tx.output[2].value, 30_000);
}
#[test]
let mut builder = wallet.build_tx();
builder
.add_recipient(addr.script_pubkey(), 30_000)
- .sighash(bitcoin::SigHashType::Single);
+ .sighash(bitcoin::SigHashType::Single.into());
let (psbt, _) = builder.finish().unwrap();
assert_eq!(
psbt.inputs[0].sighash_type,
- Some(bitcoin::SigHashType::Single)
+ Some(bitcoin::SigHashType::Single.into())
);
}
let (psbt, details) = builder.finish().unwrap();
assert_eq!(
- psbt.global.unsigned_tx.input.len(),
+ psbt.unsigned_tx.input.len(),
2,
"should add an additional input since 25_000 < 30_000"
);
.policy_path(path, KeychainKind::External);
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]
.policy_path(path, KeychainKind::External);
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]
fn test_create_tx_global_xpubs_with_origin() {
use bitcoin::hashes::hex::FromHex;
- use bitcoin::util::base58;
- use bitcoin::util::psbt::raw::Key;
+ use bitcoin::util::bip32;
let (wallet, _, _) = get_funded_wallet("wpkh([73756c7f/48'/0'/0'/2']tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3/0/*)");
let addr = wallet.get_address(New).unwrap();
.add_global_xpubs();
let (psbt, _) = builder.finish().unwrap();
- let type_value = 0x01;
- let key = base58::from_check("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
-
- let psbt_key = Key { type_value, key };
-
- // This key has an explicit origin, so it will be encoded here
- let value_bytes = Vec::<u8>::from_hex("73756c7f30000080000000800000008002000080").unwrap();
+ let key = bip32::ExtendedPubKey::from_str("tpubDCKxNyM3bLgbEX13Mcd8mYxbVg9ajDkWXMh29hMWBurKfVmBfWAM96QVP3zaUcN51HvkZ3ar4VwP82kC8JZhhux8vFQoJintSpVBwpFvyU3").unwrap();
+ let fingerprint = bip32::Fingerprint::from_hex("73756c7f").unwrap();
+ let path = bip32::DerivationPath::from_str("m/48'/0'/0'/2'").unwrap();
- assert_eq!(psbt.global.unknown.len(), 1);
- assert_eq!(psbt.global.unknown.get(&psbt_key), Some(&value_bytes));
+ assert_eq!(psbt.xpub.len(), 1);
+ assert_eq!(psbt.xpub.get(&key), Some(&(fingerprint, path)));
}
#[test]
);
assert!(
- psbt.global
- .unsigned_tx
+ psbt.unsigned_tx
.input
.iter()
.any(|input| input.previous_output == utxo.outpoint),
#[test]
fn test_create_tx_global_xpubs_master_without_origin() {
use bitcoin::hashes::hex::FromHex;
- use bitcoin::util::base58;
- use bitcoin::util::psbt::raw::Key;
+ use bitcoin::util::bip32;
let (wallet, _, _) = get_funded_wallet("wpkh(tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL/0/*)");
let addr = wallet.get_address(New).unwrap();
.add_global_xpubs();
let (psbt, _) = builder.finish().unwrap();
- let type_value = 0x01;
- let key = base58::from_check("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
+ let key = bip32::ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4Y55A58Gv9RSNF5hy84b5AJqYy7sCcjFrkcLpPre8kmgfit6kY1Zs3BLgeypTDBZJM222guPpdz7Cup5yzaMu62u7mYGbwFL").unwrap();
+ let fingerprint = bip32::Fingerprint::from_hex("997a323b").unwrap();
- let psbt_key = Key { type_value, key };
-
- // This key doesn't have an explicit origin, but it's a master key (depth = 0). So we encode
- // its fingerprint directly and an empty path
- 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));
+ assert_eq!(psbt.xpub.len(), 1);
+ assert_eq!(
+ psbt.xpub.get(&key),
+ Some(&(fingerprint, bip32::DerivationPath::default()))
+ );
}
#[test]
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
);
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
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
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
let mut tx = psbt.extract_tx();
let txid = tx.txid();
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
assert_eq!(details.sent, original_details.sent);
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[0].value + details.fee.unwrap_or(0), details.sent);
let mut tx = psbt.extract_tx();
let txid = tx.txid();
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
assert_eq!(details.sent, original_details.sent);
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[0].value + details.fee.unwrap_or(0), details.sent);
let mut tx = psbt.extract_tx();
let txid = tx.txid();
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
let mut tx = psbt.extract_tx();
let txid = tx.txid();
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
assert_eq!(details.sent, original_details.sent + 25_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.output.len(), 2);
assert_eq!(
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
assert_eq!(details.sent, original_details.sent + 25_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.output.len(), 2);
assert_eq!(
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
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.output.len(), 2);
assert_eq!(
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
assert_eq!(details.fee.unwrap_or(0), 30_000);
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.output.len(), 1);
assert_eq!(
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
assert_eq!(details.sent, original_details.sent + 25_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.output.len(), 2);
assert_eq!(
let txid = tx.txid();
// skip saving the new utxos, we know they can't be used anyways
for txin in &mut tx.input {
- txin.witness.push([0x00; 108].to_vec()); // fake signature
+ txin.witness.push([0x00; 108]); // fake signature
wallet
.database
.borrow_mut()
assert_eq!(details.sent, original_details.sent + 25_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.output.len(), 2);
assert_eq!(
};
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
.sign(
&mut psbt,
let mut builder = wallet.build_tx();
builder
.drain_to(addr.script_pubkey())
- .sighash(sighash)
+ .sighash(sighash.into())
.drain_wallet();
let (mut psbt, _) = builder.finish().unwrap();
let extracted = psbt.extract_tx();
assert_eq!(
- *extracted.input[0].witness[0].last().unwrap(),
- sighash.as_u32() as u8,
+ *extracted.input[0].witness.to_vec()[0].last().unwrap(),
+ sighash.to_u32() as u8,
"The signature should have been made with the right sighash"
);
}
use bitcoin::blockdata::opcodes;
use bitcoin::blockdata::script::Builder as ScriptBuilder;
use bitcoin::hashes::{hash160, Hash};
+use bitcoin::secp256k1;
use bitcoin::secp256k1::{Message, Secp256k1};
use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPrivKey, Fingerprint};
-use bitcoin::util::{bip143, psbt};
-use bitcoin::{PrivateKey, Script, SigHash, SigHashType};
+use bitcoin::util::{bip143, ecdsa, psbt};
+use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Sighash};
use miniscript::descriptor::{DescriptorSecretKey, DescriptorSinglePriv, DescriptorXKey, KeyMap};
use miniscript::{Legacy, MiniscriptKey, Segwitv0};
/// To enable signing transactions with non-standard sighashes set
/// [`SignOptions::allow_all_sighashes`] to `true`.
NonStandardSighash,
+ /// Invalid SIGHASH for the signing context in use
+ InvalidSighash,
}
impl fmt::Display for SignerError {
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)
} 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)
}
}
secp: &SecpCtx,
) -> Result<(), SignerError> {
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 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) {
return Ok(());
}
None => Legacy::sighash(psbt, input_index)?,
};
- let signature = secp.sign(
+ let sig = secp.sign_ecdsa(
&Message::from_slice(&hash.into_inner()[..]).unwrap(),
- &self.key,
+ &self.inner,
);
- let mut final_signature = Vec::with_capacity(75);
- final_signature.extend_from_slice(&signature.serialize_der());
- final_signature.push(sighash.as_u32() as u8);
-
+ let final_signature = ecdsa::EcdsaSig {
+ sig,
+ hash_ty: sighash.ecdsa_hash_ty().unwrap(), // FIXME
+ };
psbt.inputs[input_index]
.partial_sigs
.insert(pubkey, final_signature);
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
input_index: usize,
- ) -> Result<(SigHash, SigHashType), SignerError>;
+ ) -> Result<(Sighash, psbt::PsbtSighashType), SignerError>;
}
impl ComputeSighash for Legacy {
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
input_index: usize,
- ) -> Result<(SigHash, SigHashType), SignerError> {
- if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
+ ) -> Result<(Sighash, psbt::PsbtSighashType), SignerError> {
+ if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange);
}
let psbt_input = &psbt.inputs[input_index];
- let 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(SigHashType::All.into());
let script = match psbt_input.redeem_script {
Some(ref redeem_script) => redeem_script.clone(),
None => {
};
Ok((
- psbt.global
- .unsigned_tx
- .signature_hash(input_index, &script, sighash.as_u32()),
+ psbt.unsigned_tx
+ .signature_hash(input_index, &script, sighash.to_u32()),
sighash,
))
}
fn sighash(
psbt: &psbt::PartiallySignedTransaction,
input_index: usize,
- ) -> Result<(SigHash, SigHashType), SignerError> {
- if input_index >= psbt.inputs.len() || input_index >= psbt.global.unsigned_tx.input.len() {
+ ) -> Result<(Sighash, psbt::PsbtSighashType), SignerError> {
+ if input_index >= psbt.inputs.len() || input_index >= psbt.unsigned_tx.input.len() {
return Err(SignerError::InputIndexOutOfRange);
}
let psbt_input = &psbt.inputs[input_index];
- let 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(SigHashType::All.into())
+ .ecdsa_hash_ty()
+ .map_err(|_| SignerError::InvalidSighash)?;
// Always try first with the non-witness utxo
let utxo = if let Some(prev_tx) = &psbt_input.non_witness_utxo {
};
Ok((
- bip143::SigHashCache::new(&psbt.global.unsigned_tx).signature_hash(
+ bip143::SigHashCache::new(&psbt.unsigned_tx).signature_hash(
input_index,
&script,
value,
sighash,
),
- sighash,
+ sighash.into(),
))
}
}
let secp: Secp256k1<All> = Secp256k1::new();
let path = bip32::DerivationPath::from_str(PATH).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 prvkey = (tprv, path.clone()).into_descriptor_key().unwrap();
let pubkey = (tpub, path).into_descriptor_key().unwrap();
use std::marker::PhantomData;
use bitcoin::util::psbt::{self, PartiallySignedTransaction as Psbt};
-use bitcoin::{OutPoint, Script, SigHashType, Transaction};
+use bitcoin::{OutPoint, Script, Transaction};
use miniscript::descriptor::DescriptorTrait;
/// builder.finish()?
/// };
///
-/// assert_eq!(
-/// psbt1.global.unsigned_tx.output[..2],
-/// psbt2.global.unsigned_tx.output[..2]
-/// );
+/// assert_eq!(psbt1.unsigned_tx.output[..2], psbt2.unsigned_tx.output[..2]);
/// # Ok::<(), bdk::Error>(())
/// ```
///
pub(crate) utxos: Vec<WeightedUtxo>,
pub(crate) unspendable: HashSet<OutPoint>,
pub(crate) manually_selected_only: bool,
- pub(crate) sighash: Option<SigHashType>,
+ pub(crate) sighash: Option<psbt::PsbtSighashType>,
pub(crate) ordering: TxOrdering,
pub(crate) locktime: Option<u32>,
pub(crate) rbf: Option<RbfValue>,
/// Sign with a specific sig hash
///
/// **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
}