[wallet] Add a flag to fill-in PSBT_GLOBAL_XPUB
This commit is contained in:
parent
e1a59336f8
commit
ebfe5db0c3
@ -458,6 +458,7 @@ where
|
||||
|
||||
if sub_matches.is_present("offline_signer") {
|
||||
tx_builder = tx_builder
|
||||
.add_global_xpubs()
|
||||
.force_non_witness_utxo()
|
||||
.include_output_redeem_witness_script();
|
||||
}
|
||||
@ -515,6 +516,7 @@ where
|
||||
|
||||
if sub_matches.is_present("offline_signer") {
|
||||
tx_builder = tx_builder
|
||||
.add_global_xpubs()
|
||||
.force_non_witness_utxo()
|
||||
.include_output_redeem_witness_script();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ use std::collections::{BTreeMap, HashMap};
|
||||
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};
|
||||
|
||||
@ -232,6 +232,7 @@ impl<K: InnerXKey> XKeyUtils for DescriptorXKey<K> {
|
||||
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(
|
||||
@ -339,6 +340,30 @@ impl DescriptorMeta for Descriptor<DescriptorPublicKey> {
|
||||
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 {
|
||||
|
@ -54,6 +54,12 @@ pub enum Error {
|
||||
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),
|
||||
|
||||
|
@ -35,7 +35,9 @@ use std::sync::Arc;
|
||||
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};
|
||||
|
||||
@ -63,7 +65,7 @@ use crate::blockchain::{Blockchain, BlockchainMarker, OfflineBlockchain, Progres
|
||||
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;
|
||||
@ -1157,7 +1159,36 @@ where
|
||||
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))
|
||||
|
@ -89,6 +89,7 @@ pub struct TxBuilder<D: Database, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderC
|
||||
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,
|
||||
|
||||
@ -131,6 +132,7 @@ where
|
||||
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(),
|
||||
|
||||
@ -345,6 +347,25 @@ impl<D: Database, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> TxBuilde
|
||||
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;
|
||||
@ -375,21 +396,13 @@ impl<D: Database, Cs: CoinSelectionAlgorithm<D>, Ctx: TxBuilderContext> TxBuilde
|
||||
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`
|
||||
|
Loading…
x
Reference in New Issue
Block a user