if sub_matches.is_present("offline_signer") {
tx_builder = tx_builder
+ .add_global_xpubs()
.force_non_witness_utxo()
.include_output_redeem_witness_script();
}
if sub_matches.is_present("offline_signer") {
tx_builder = tx_builder
+ .add_global_xpubs()
.force_non_witness_utxo()
.include_output_redeem_witness_script();
}
use std::fmt;
use bitcoin::secp256k1::Secp256k1;
-use bitcoin::util::bip32::{ChildNumber, DerivationPath, Fingerprint};
+use bitcoin::util::bip32::{ChildNumber, DerivationPath, ExtendedPubKey, Fingerprint};
use bitcoin::util::psbt;
use bitcoin::{Network, PublicKey, Script, TxOut};
pub(crate) trait DescriptorMeta: Sized {
fn is_witness(&self) -> bool;
fn get_hd_keypaths(&self, index: u32, secp: &SecpCtx) -> Result<HDKeyPaths, Error>;
+ fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, Error>;
fn is_fixed(&self) -> bool;
fn derive_from_hd_keypaths(&self, hd_keypaths: &HDKeyPaths, secp: &SecpCtx) -> Option<Self>;
fn derive_from_psbt_input(
Ok(answer_pk)
}
+ fn get_extended_keys(&self) -> Result<Vec<DescriptorXKey<ExtendedPubKey>>, Error> {
+ let get_key = |key: &DescriptorPublicKey,
+ keys: &mut Vec<DescriptorXKey<ExtendedPubKey>>|
+ -> Result<DummyKey, Error> {
+ if let DescriptorPublicKey::XPub(xpub) = key {
+ keys.push(xpub.clone())
+ }
+
+ Ok(DummyKey::default())
+ };
+
+ let mut answer_pk = Vec::new();
+ let mut answer_pkh = Vec::new();
+
+ self.translate_pk(
+ |pk| get_key(pk, &mut answer_pk),
+ |pkh| get_key(pkh, &mut answer_pkh),
+ )?;
+
+ answer_pk.append(&mut answer_pkh);
+
+ Ok(answer_pk)
+ }
+
fn is_fixed(&self) -> bool {
fn check_key(key: &DescriptorPublicKey, flag: &mut bool) -> Result<DummyKey, Error> {
match key {
FeeTooLow {
required: u64,
},
+ /// In order to use the [`TxBuilder::add_global_xpubs`] option every extended
+ /// key in the descriptor must either be a master key itself (having depth = 0) or have an
+ /// explicit origin provided
+ ///
+ /// [`TxBuilder::add_global_xpubs`]: crate::wallet::tx_builder::TxBuilder::add_global_xpubs
+ MissingKeyOrigin(String),
Key(crate::keys::KeyError),
use bitcoin::secp256k1::Secp256k1;
use bitcoin::consensus::encode::serialize;
+use bitcoin::util::base58;
use bitcoin::util::bip32::ChildNumber;
+use bitcoin::util::psbt::raw::Key as PSBTKey;
use bitcoin::util::psbt::PartiallySignedTransaction as PSBT;
use bitcoin::{Address, Network, OutPoint, Script, Transaction, TxOut, Txid};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::descriptor::{
get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
- ToWalletDescriptor,
+ ToWalletDescriptor, XKeyUtils,
};
use crate::error::Error;
use crate::psbt::PSBTUtils;
selected: Vec<UTXO>,
builder: TxBuilder<D, Cs, Ctx>,
) -> Result<PSBT, Error> {
+ use bitcoin::util::psbt::serialize::Serialize;
+
let mut psbt = PSBT::from_unsigned_tx(tx)?;
+
+ if builder.add_global_xpubs {
+ let mut all_xpubs = self.descriptor.get_extended_keys()?;
+ if let Some(change_descriptor) = &self.change_descriptor {
+ all_xpubs.extend(change_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 => {
+ (xpub.root_fingerprint(&self.secp), vec![].into())
+ }
+ _ => return Err(Error::MissingKeyOrigin(xpub.xkey.to_string())),
+ };
+
+ psbt.global.unknown.insert(key, origin.serialize());
+ }
+ }
+
let lookup_output = selected
.into_iter()
.map(|utxo| (utxo.outpoint, utxo))
pub(crate) version: Option<Version>,
pub(crate) change_policy: ChangeSpendPolicy,
pub(crate) force_non_witness_utxo: bool,
+ pub(crate) add_global_xpubs: bool,
pub(crate) coin_selection: Cs,
pub(crate) include_output_redeem_witness_script: bool,
version: Default::default(),
change_policy: Default::default(),
force_non_witness_utxo: Default::default(),
+ add_global_xpubs: Default::default(),
coin_selection: Default::default(),
include_output_redeem_witness_script: Default::default(),
self
}
+ /// Fill-in the [`psbt::Output::redeem_script`](bitcoin::util::psbt::Output::redeem_script) and
+ /// [`psbt::Output::witness_script`](bitcoin::util::psbt::Output::witness_script) fields.
+ ///
+ /// This is useful for signers which always require it, like ColdCard hardware wallets.
+ pub fn include_output_redeem_witness_script(mut self) -> Self {
+ self.include_output_redeem_witness_script = true;
+ self
+ }
+
+ /// Fill-in the `PSBT_GLOBAL_XPUB` field with the extended keys contained in both the external
+ /// and internal descriptors
+ ///
+ /// This is useful for offline signers that take part to a multisig. Some hardware wallets like
+ /// BitBox and ColdCard are known to require this.
+ pub fn add_global_xpubs(mut self) -> Self {
+ self.add_global_xpubs = true;
+ self
+ }
+
/// Spend all the available inputs. This respects filters like [`unspendable`] and the change policy.
pub fn drain_wallet(mut self) -> Self {
self.drain_wallet = true;
version: self.version,
change_policy: self.change_policy,
force_non_witness_utxo: self.force_non_witness_utxo,
- coin_selection,
+ add_global_xpubs: self.add_global_xpubs,
include_output_redeem_witness_script: self.include_output_redeem_witness_script,
+ coin_selection,
phantom: PhantomData,
}
}
-
- /// Fill-in the [`psbt::Output::redeem_script`](bitcoin::util::psbt::Output::redeem_script) and
- /// [`psbt::Output::witness_script`](bitcoin::util::psbt::Output::witness_script) fields.
- ///
- /// This is useful for signers which always require it, like ColdCard hardware wallets.
- pub fn include_output_redeem_witness_script(mut self) -> Self {
- self.include_output_redeem_witness_script = true;
- self
- }
}
// methods supported only by create_tx, and only for `DefaultCoinSelectionAlgorithm`