refactor(wallet)!: Make Wallet require a change descriptor
All `Wallet` constructors are modified to require a change descriptor, where previously it was optional. Additionally we enforce uniqueness of the change descriptor to avoid ambiguity when deriving scripts and ensure the wallet will always have two distinct keystores. Notable changes * Add error DescriptorError::ExternalAndInternalAreTheSame * Remove error CreateTxError::ChangePolicyDescriptor * No longer rely on `map_keychain`
This commit is contained in:
@@ -42,6 +42,8 @@ pub enum Error {
|
||||
Miniscript(miniscript::Error),
|
||||
/// Hex decoding error
|
||||
Hex(bitcoin::hex::HexToBytesError),
|
||||
/// The provided wallet descriptors are identical
|
||||
ExternalAndInternalAreTheSame,
|
||||
}
|
||||
|
||||
impl From<crate::keys::KeyError> for Error {
|
||||
@@ -79,6 +81,9 @@ impl fmt::Display for Error {
|
||||
Self::Pk(err) => write!(f, "Key-related error: {}", err),
|
||||
Self::Miniscript(err) => write!(f, "Miniscript error: {}", err),
|
||||
Self::Hex(err) => write!(f, "Hex decoding error: {}", err),
|
||||
Self::ExternalAndInternalAreTheSame => {
|
||||
write!(f, "External and internal descriptors are the same")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,9 +77,12 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Pkh;
|
||||
///
|
||||
/// let key =
|
||||
/// let key_external =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(P2Pkh(key), None, Network::Testnet)?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet =
|
||||
/// Wallet::new_no_persist(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -107,9 +110,15 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Wpkh_P2Sh;
|
||||
///
|
||||
/// let key =
|
||||
/// let key_external =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(P2Wpkh_P2Sh(key), None, Network::Testnet)?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// P2Wpkh_P2Sh(key_external),
|
||||
/// P2Wpkh_P2Sh(key_internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -138,9 +147,12 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2Wpkh;
|
||||
///
|
||||
/// let key =
|
||||
/// let key_external =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(P2Wpkh(key), None, Network::Testnet)?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet =
|
||||
/// Wallet::new_no_persist(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -168,9 +180,12 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
|
||||
/// # use bdk_wallet::KeychainKind;
|
||||
/// use bdk_wallet::template::P2TR;
|
||||
///
|
||||
/// let key =
|
||||
/// let key_external =
|
||||
/// bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(P2TR(key), None, Network::Testnet)?;
|
||||
/// let key_internal =
|
||||
/// bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
|
||||
/// let mut wallet =
|
||||
/// Wallet::new_no_persist(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// wallet
|
||||
@@ -205,7 +220,7 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip44(key.clone(), KeychainKind::External),
|
||||
/// Some(Bip44(key, KeychainKind::Internal)),
|
||||
/// Bip44(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
@@ -242,7 +257,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip44Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(Bip44Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Bip44Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
@@ -278,7 +293,7 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip49(key.clone(), KeychainKind::External),
|
||||
/// Some(Bip49(key, KeychainKind::Internal)),
|
||||
/// Bip49(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
@@ -315,7 +330,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip49Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(Bip49Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Bip49Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
@@ -351,7 +366,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip84(key.clone(), KeychainKind::External),
|
||||
/// Some(Bip84(key, KeychainKind::Internal)),
|
||||
/// Bip84(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
@@ -388,7 +403,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip84Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(Bip84Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Bip84Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
@@ -424,7 +439,7 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
|
||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip86(key.clone(), KeychainKind::External),
|
||||
/// Some(Bip86(key, KeychainKind::Internal)),
|
||||
/// Bip86(key, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
@@ -461,7 +476,7 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
|
||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
|
||||
/// let mut wallet = Wallet::new_no_persist(
|
||||
/// Bip86Public(key.clone(), fingerprint, KeychainKind::External),
|
||||
/// Some(Bip86Public(key, fingerprint, KeychainKind::Internal)),
|
||||
/// Bip86Public(key, fingerprint, KeychainKind::Internal),
|
||||
/// Network::Testnet,
|
||||
/// )?;
|
||||
///
|
||||
|
||||
@@ -90,8 +90,6 @@ pub enum CreateTxError {
|
||||
NoUtxosSelected,
|
||||
/// Output created is under the dust limit, 546 satoshis
|
||||
OutputBelowDustLimit(usize),
|
||||
/// The `change_policy` was set but the wallet does not have a change_descriptor
|
||||
ChangePolicyDescriptor,
|
||||
/// There was an error with coin selection
|
||||
CoinSelection(coin_selection::Error),
|
||||
/// Wallet's UTXO set is not enough to cover recipient's requested plus fee
|
||||
@@ -177,12 +175,6 @@ impl fmt::Display for CreateTxError {
|
||||
CreateTxError::OutputBelowDustLimit(limit) => {
|
||||
write!(f, "Output below the dust limit: {}", limit)
|
||||
}
|
||||
CreateTxError::ChangePolicyDescriptor => {
|
||||
write!(
|
||||
f,
|
||||
"The `change_policy` can be set only if the wallet has a change_descriptor"
|
||||
)
|
||||
}
|
||||
CreateTxError::CoinSelection(e) => e.fmt(f),
|
||||
CreateTxError::InsufficientFunds { needed, available } => {
|
||||
write!(
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
//! let import = FullyNodedExport::from_str(import)?;
|
||||
//! let wallet = Wallet::new_no_persist(
|
||||
//! &import.descriptor(),
|
||||
//! import.change_descriptor().as_ref(),
|
||||
//! &import.change_descriptor().expect("change descriptor"),
|
||||
//! Network::Testnet,
|
||||
//! )?;
|
||||
//! # Ok::<_, Box<dyn std::error::Error>>(())
|
||||
@@ -44,7 +44,7 @@
|
||||
//! # use bdk_wallet::*;
|
||||
//! let wallet = Wallet::new_no_persist(
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
|
||||
//! Some("wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)"),
|
||||
//! "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
|
||||
//! Network::Testnet,
|
||||
//! )?;
|
||||
//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
|
||||
@@ -142,19 +142,17 @@ impl FullyNodedExport {
|
||||
blockheight,
|
||||
};
|
||||
|
||||
let change_descriptor = match wallet.public_descriptor(KeychainKind::Internal).is_some() {
|
||||
false => None,
|
||||
true => {
|
||||
let descriptor = wallet
|
||||
.get_descriptor_for_keychain(KeychainKind::Internal)
|
||||
.to_string_with_secret(
|
||||
&wallet
|
||||
.get_signers(KeychainKind::Internal)
|
||||
.as_key_map(wallet.secp_ctx()),
|
||||
);
|
||||
Some(remove_checksum(descriptor))
|
||||
}
|
||||
let change_descriptor = {
|
||||
let descriptor = wallet
|
||||
.get_descriptor_for_keychain(KeychainKind::Internal)
|
||||
.to_string_with_secret(
|
||||
&wallet
|
||||
.get_signers(KeychainKind::Internal)
|
||||
.as_key_map(wallet.secp_ctx()),
|
||||
);
|
||||
Some(remove_checksum(descriptor))
|
||||
};
|
||||
|
||||
if export.change_descriptor() != change_descriptor {
|
||||
return Err("Incompatible change descriptor");
|
||||
}
|
||||
@@ -223,11 +221,7 @@ mod test {
|
||||
use super::*;
|
||||
use crate::wallet::Wallet;
|
||||
|
||||
fn get_test_wallet(
|
||||
descriptor: &str,
|
||||
change_descriptor: Option<&str>,
|
||||
network: Network,
|
||||
) -> Wallet {
|
||||
fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
|
||||
let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, network).unwrap();
|
||||
let transaction = Transaction {
|
||||
input: vec![],
|
||||
@@ -258,7 +252,7 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||
|
||||
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
|
||||
let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
|
||||
assert_eq!(export.descriptor(), descriptor);
|
||||
@@ -270,13 +264,14 @@ mod test {
|
||||
#[test]
|
||||
#[should_panic(expected = "Incompatible change descriptor")]
|
||||
fn test_export_no_change() {
|
||||
// This wallet explicitly doesn't have a change descriptor. It should be impossible to
|
||||
// The wallet's change descriptor has no wildcard. It should be impossible to
|
||||
// export, because exporting this kind of external descriptor normally implies the
|
||||
// existence of an internal descriptor
|
||||
// existence of a compatible internal descriptor
|
||||
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/0)";
|
||||
|
||||
let wallet = get_test_wallet(descriptor, None, Network::Bitcoin);
|
||||
let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
|
||||
FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
}
|
||||
|
||||
@@ -289,7 +284,7 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/50'/0'/1/*)";
|
||||
|
||||
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
|
||||
let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
|
||||
FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
}
|
||||
|
||||
@@ -306,7 +301,7 @@ mod test {
|
||||
[c98b1535/48'/0'/0'/2']tpubDCDi5W4sP6zSnzJeowy8rQDVhBdRARaPhK1axABi8V1661wEPeanpEXj4ZLAUEoikVtoWcyK26TKKJSecSfeKxwHCcRrge9k1ybuiL71z4a/1/*\
|
||||
))";
|
||||
|
||||
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet);
|
||||
let wallet = get_test_wallet(descriptor, change_descriptor, Network::Testnet);
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
|
||||
assert_eq!(export.descriptor(), descriptor);
|
||||
@@ -319,7 +314,7 @@ mod test {
|
||||
fn test_export_tr() {
|
||||
let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
|
||||
let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
|
||||
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Testnet);
|
||||
let wallet = get_test_wallet(descriptor, change_descriptor, Network::Testnet);
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
assert_eq!(export.descriptor(), descriptor);
|
||||
assert_eq!(export.change_descriptor(), Some(change_descriptor.into()));
|
||||
@@ -332,7 +327,7 @@ mod test {
|
||||
let descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/0/*)";
|
||||
let change_descriptor = "wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44'/0'/0'/1/*)";
|
||||
|
||||
let wallet = get_test_wallet(descriptor, Some(change_descriptor), Network::Bitcoin);
|
||||
let wallet = get_test_wallet(descriptor, change_descriptor, Network::Bitcoin);
|
||||
let export = FullyNodedExport::export_wallet(&wallet, "Test Label", true).unwrap();
|
||||
|
||||
assert_eq!(export.to_string(), "{\"descriptor\":\"wpkh(xprv9s21ZrQH143K4CTb63EaMxja1YiTnSEWKMbn23uoEnAzxjdUJRQkazCAtzxGm4LSoTSVTptoV9RbchnKPW9HxKtZumdyxyikZFDLhogJ5Uj/44\'/0\'/0\'/0/*)\",\"blockheight\":5000,\"label\":\"Test Label\"}");
|
||||
|
||||
@@ -166,7 +166,7 @@ impl Wallet {
|
||||
/// Creates a wallet that does not persist data.
|
||||
pub fn new_no_persist<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<Self, DescriptorError> {
|
||||
Self::new(descriptor, change_descriptor, (), network).map_err(|e| match e {
|
||||
@@ -179,7 +179,7 @@ impl Wallet {
|
||||
/// Creates a wallet that does not persist data, with a custom genesis hash.
|
||||
pub fn new_no_persist_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
genesis_hash: BlockHash,
|
||||
) -> Result<Self, crate::descriptor::DescriptorError> {
|
||||
@@ -398,7 +398,7 @@ impl Wallet {
|
||||
/// Initialize an empty [`Wallet`].
|
||||
pub fn new<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
change_descriptor: E,
|
||||
db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
||||
network: Network,
|
||||
) -> Result<Self, NewError> {
|
||||
@@ -412,7 +412,7 @@ impl Wallet {
|
||||
/// for syncing from alternative networks.
|
||||
pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
change_descriptor: E,
|
||||
mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
||||
network: Network,
|
||||
genesis_hash: BlockHash,
|
||||
@@ -524,7 +524,8 @@ impl Wallet {
|
||||
.indexer
|
||||
.keychains_added
|
||||
.get(&KeychainKind::Internal)
|
||||
.cloned();
|
||||
.ok_or(LoadError::MissingDescriptor)?
|
||||
.clone();
|
||||
|
||||
let (signers, change_signers) =
|
||||
create_signers(&mut index, &secp, descriptor, change_descriptor, network)
|
||||
@@ -551,7 +552,7 @@ impl Wallet {
|
||||
/// This method will fail if the loaded [`Wallet`] has different parameters to those provided.
|
||||
pub fn new_or_load<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
change_descriptor: E,
|
||||
db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
||||
network: Network,
|
||||
) -> Result<Self, NewOrLoadError> {
|
||||
@@ -573,7 +574,7 @@ impl Wallet {
|
||||
/// useful for syncing from alternative networks.
|
||||
pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
change_descriptor: E,
|
||||
mut db: impl PersistBackend<ChangeSet> + Send + Sync + 'static,
|
||||
network: Network,
|
||||
genesis_hash: BlockHash,
|
||||
@@ -639,44 +640,32 @@ impl Wallet {
|
||||
});
|
||||
}
|
||||
|
||||
let expected_change_descriptor = if let Some(c) = change_descriptor {
|
||||
Some(
|
||||
c.into_wallet_descriptor(&wallet.secp, network)
|
||||
.map_err(NewOrLoadError::Descriptor)?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let (expected_change_descriptor, expected_change_descriptor_keymap) =
|
||||
change_descriptor
|
||||
.into_wallet_descriptor(&wallet.secp, network)
|
||||
.map_err(NewOrLoadError::Descriptor)?;
|
||||
let wallet_change_descriptor =
|
||||
wallet.public_descriptor(KeychainKind::Internal).cloned();
|
||||
|
||||
match (expected_change_descriptor, wallet_change_descriptor) {
|
||||
(Some((expected_descriptor, expected_keymap)), Some(wallet_descriptor))
|
||||
if wallet_descriptor == expected_descriptor =>
|
||||
{
|
||||
// if expected change descriptor has private keys add them as new signers
|
||||
if !expected_keymap.is_empty() {
|
||||
let signer_container = SignersContainer::build(
|
||||
expected_keymap,
|
||||
&expected_descriptor,
|
||||
&wallet.secp,
|
||||
);
|
||||
signer_container.signers().into_iter().for_each(|signer| {
|
||||
wallet.add_signer(
|
||||
KeychainKind::Internal,
|
||||
SignerOrdering::default(),
|
||||
signer.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
(None, None) => (),
|
||||
(_, wallet_descriptor) => {
|
||||
return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: wallet_descriptor,
|
||||
keychain: KeychainKind::Internal,
|
||||
});
|
||||
}
|
||||
if wallet_change_descriptor != Some(expected_change_descriptor.clone()) {
|
||||
return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
|
||||
got: wallet_change_descriptor,
|
||||
keychain: KeychainKind::Internal,
|
||||
});
|
||||
}
|
||||
// if expected change descriptor has private keys add them as new signers
|
||||
if !expected_change_descriptor_keymap.is_empty() {
|
||||
let signer_container = SignersContainer::build(
|
||||
expected_change_descriptor_keymap,
|
||||
&expected_change_descriptor,
|
||||
&wallet.secp,
|
||||
);
|
||||
signer_container.signers().into_iter().for_each(|signer| {
|
||||
wallet.add_signer(
|
||||
KeychainKind::Internal,
|
||||
SignerOrdering::default(),
|
||||
signer.clone(),
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
Ok(wallet)
|
||||
@@ -717,12 +706,11 @@ impl Wallet {
|
||||
/// This panics when the caller requests for an address of derivation index greater than the
|
||||
/// [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki) max index.
|
||||
pub fn peek_address(&self, keychain: KeychainKind, mut index: u32) -> AddressInfo {
|
||||
let keychain = self.map_keychain(keychain);
|
||||
let mut spk_iter = self
|
||||
.indexed_graph
|
||||
.index
|
||||
.unbounded_spk_iter(&keychain)
|
||||
.expect("Must exist (we called map_keychain)");
|
||||
.expect("keychain must exist");
|
||||
if !spk_iter.descriptor().has_wildcard() {
|
||||
index = 0;
|
||||
}
|
||||
@@ -748,12 +736,11 @@ impl Wallet {
|
||||
///
|
||||
/// If writing to persistent storage fails.
|
||||
pub fn reveal_next_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
|
||||
let keychain = self.map_keychain(keychain);
|
||||
let ((index, spk), index_changeset) = self
|
||||
.indexed_graph
|
||||
.index
|
||||
.reveal_next_spk(&keychain)
|
||||
.expect("Must exist (we called map_keychain)");
|
||||
.expect("keychain must exist");
|
||||
|
||||
self.persist
|
||||
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
|
||||
@@ -780,12 +767,11 @@ impl Wallet {
|
||||
keychain: KeychainKind,
|
||||
index: u32,
|
||||
) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
|
||||
let keychain = self.map_keychain(keychain);
|
||||
let (spk_iter, index_changeset) = self
|
||||
.indexed_graph
|
||||
.index
|
||||
.reveal_to_target(&keychain, index)
|
||||
.expect("must exist (we called map_keychain)");
|
||||
.expect("keychain must exist");
|
||||
|
||||
self.persist
|
||||
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
|
||||
@@ -807,12 +793,11 @@ impl Wallet {
|
||||
///
|
||||
/// If writing to persistent storage fails.
|
||||
pub fn next_unused_address(&mut self, keychain: KeychainKind) -> anyhow::Result<AddressInfo> {
|
||||
let keychain = self.map_keychain(keychain);
|
||||
let ((index, spk), index_changeset) = self
|
||||
.indexed_graph
|
||||
.index
|
||||
.next_unused_spk(&keychain)
|
||||
.expect("must exist (we called map_keychain)");
|
||||
.expect("keychain must exist");
|
||||
|
||||
self.persist
|
||||
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
|
||||
@@ -852,7 +837,6 @@ impl Wallet {
|
||||
&self,
|
||||
keychain: KeychainKind,
|
||||
) -> impl DoubleEndedIterator<Item = AddressInfo> + '_ {
|
||||
let keychain = self.map_keychain(keychain);
|
||||
self.indexed_graph
|
||||
.index
|
||||
.unused_keychain_spks(&keychain)
|
||||
@@ -934,11 +918,10 @@ impl Wallet {
|
||||
&self,
|
||||
keychain: KeychainKind,
|
||||
) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
|
||||
let keychain = self.map_keychain(keychain);
|
||||
self.indexed_graph
|
||||
.index
|
||||
.unbounded_spk_iter(&keychain)
|
||||
.expect("Must exist (we called map_keychain)")
|
||||
.expect("keychain must exist")
|
||||
}
|
||||
|
||||
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
|
||||
@@ -1248,7 +1231,9 @@ impl Wallet {
|
||||
/// ```
|
||||
/// # use bdk_wallet::{Wallet, KeychainKind};
|
||||
/// # use bdk_wallet::bitcoin::Network;
|
||||
/// let wallet = Wallet::new_no_persist("wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*)", None, Network::Testnet)?;
|
||||
/// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
|
||||
/// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
|
||||
/// let wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
|
||||
/// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
|
||||
/// // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
|
||||
/// println!("secret_key: {}", secret_key);
|
||||
@@ -1307,20 +1292,14 @@ impl Wallet {
|
||||
) -> Result<Psbt, CreateTxError> {
|
||||
let keychains: BTreeMap<_, _> = self.indexed_graph.index.keychains().collect();
|
||||
let external_descriptor = keychains.get(&KeychainKind::External).expect("must exist");
|
||||
let internal_descriptor = keychains.get(&KeychainKind::Internal);
|
||||
let internal_descriptor = keychains.get(&KeychainKind::Internal).expect("must exist");
|
||||
|
||||
let external_policy = external_descriptor
|
||||
.extract_policy(&self.signers, BuildSatisfaction::None, &self.secp)?
|
||||
.unwrap();
|
||||
let internal_policy = internal_descriptor
|
||||
.as_ref()
|
||||
.map(|desc| {
|
||||
Ok::<_, CreateTxError>(
|
||||
desc.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
.extract_policy(&self.change_signers, BuildSatisfaction::None, &self.secp)?
|
||||
.unwrap();
|
||||
|
||||
// The policy allows spending external outputs, but it requires a policy path that hasn't been
|
||||
// provided
|
||||
@@ -1332,17 +1311,15 @@ impl Wallet {
|
||||
KeychainKind::External,
|
||||
));
|
||||
};
|
||||
// Same for the internal_policy path, if present
|
||||
if let Some(internal_policy) = &internal_policy {
|
||||
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
|
||||
&& internal_policy.requires_path()
|
||||
&& params.internal_policy_path.is_none()
|
||||
{
|
||||
return Err(CreateTxError::SpendingPolicyRequired(
|
||||
KeychainKind::Internal,
|
||||
));
|
||||
};
|
||||
}
|
||||
// Same for the internal_policy path
|
||||
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeForbidden
|
||||
&& internal_policy.requires_path()
|
||||
&& params.internal_policy_path.is_none()
|
||||
{
|
||||
return Err(CreateTxError::SpendingPolicyRequired(
|
||||
KeychainKind::Internal,
|
||||
));
|
||||
};
|
||||
|
||||
let external_requirements = external_policy.get_condition(
|
||||
params
|
||||
@@ -1350,21 +1327,14 @@ impl Wallet {
|
||||
.as_ref()
|
||||
.unwrap_or(&BTreeMap::new()),
|
||||
)?;
|
||||
let internal_requirements = internal_policy
|
||||
.map(|policy| {
|
||||
Ok::<_, CreateTxError>(
|
||||
policy.get_condition(
|
||||
params
|
||||
.internal_policy_path
|
||||
.as_ref()
|
||||
.unwrap_or(&BTreeMap::new()),
|
||||
)?,
|
||||
)
|
||||
})
|
||||
.transpose()?;
|
||||
let internal_requirements = internal_policy.get_condition(
|
||||
params
|
||||
.internal_policy_path
|
||||
.as_ref()
|
||||
.unwrap_or(&BTreeMap::new()),
|
||||
)?;
|
||||
|
||||
let requirements =
|
||||
external_requirements.merge(&internal_requirements.unwrap_or_default())?;
|
||||
let requirements = external_requirements.merge(&internal_requirements)?;
|
||||
|
||||
let version = match params.version {
|
||||
Some(tx_builder::Version(0)) => return Err(CreateTxError::Version0),
|
||||
@@ -1526,12 +1496,6 @@ impl Wallet {
|
||||
|
||||
fee_amount += (fee_rate * tx.weight()).to_sat();
|
||||
|
||||
if params.change_policy != tx_builder::ChangeSpendPolicy::ChangeAllowed
|
||||
&& internal_descriptor.is_none()
|
||||
{
|
||||
return Err(CreateTxError::ChangePolicyDescriptor);
|
||||
}
|
||||
|
||||
let (required_utxos, optional_utxos) =
|
||||
self.preselect_utxos(¶ms, Some(current_height.to_consensus_u32()));
|
||||
|
||||
@@ -1539,12 +1503,12 @@ impl Wallet {
|
||||
let drain_script = match params.drain_to {
|
||||
Some(ref drain_recipient) => drain_recipient.clone(),
|
||||
None => {
|
||||
let change_keychain = self.map_keychain(KeychainKind::Internal);
|
||||
let change_keychain = KeychainKind::Internal;
|
||||
let ((index, spk), index_changeset) = self
|
||||
.indexed_graph
|
||||
.index
|
||||
.next_unused_spk(&change_keychain)
|
||||
.expect("Keychain exists (we called map_keychain)");
|
||||
.expect("keychain must exist");
|
||||
let spk = spk.into();
|
||||
self.indexed_graph.index.mark_used(change_keychain, index);
|
||||
self.persist
|
||||
@@ -1773,9 +1737,11 @@ impl Wallet {
|
||||
if tx.output.len() > 1 {
|
||||
let mut change_index = None;
|
||||
for (index, txout) in tx.output.iter().enumerate() {
|
||||
let change_type = self.map_keychain(KeychainKind::Internal);
|
||||
let change_keychain = KeychainKind::Internal;
|
||||
match txout_index.index_of_spk(&txout.script_pubkey) {
|
||||
Some((keychain, _)) if keychain == change_type => change_index = Some(index),
|
||||
Some((keychain, _)) if keychain == change_keychain => {
|
||||
change_index = Some(index)
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -2014,8 +1980,7 @@ impl Wallet {
|
||||
|
||||
/// Returns the descriptor used to create addresses for a particular `keychain`.
|
||||
pub fn get_descriptor_for_keychain(&self, keychain: KeychainKind) -> &ExtendedDescriptor {
|
||||
self.public_descriptor(self.map_keychain(keychain))
|
||||
.expect("we mapped it to external if it doesn't exist")
|
||||
self.public_descriptor(keychain).expect("keychain exists")
|
||||
}
|
||||
|
||||
/// The derivation index of this wallet. It will return `None` if it has not derived any addresses.
|
||||
@@ -2026,11 +1991,10 @@ impl Wallet {
|
||||
|
||||
/// The index of the next address that you would get if you were to ask the wallet for a new address
|
||||
pub fn next_derivation_index(&self, keychain: KeychainKind) -> u32 {
|
||||
let keychain = self.map_keychain(keychain);
|
||||
self.indexed_graph
|
||||
.index
|
||||
.next_index(&keychain)
|
||||
.expect("Keychain must exist (we called map_keychain)")
|
||||
.expect("keychain must exist")
|
||||
.0
|
||||
}
|
||||
|
||||
@@ -2049,16 +2013,6 @@ impl Wallet {
|
||||
}
|
||||
}
|
||||
|
||||
fn map_keychain(&self, keychain: KeychainKind) -> KeychainKind {
|
||||
if keychain == KeychainKind::Internal
|
||||
&& self.public_descriptor(KeychainKind::Internal).is_none()
|
||||
{
|
||||
KeychainKind::External
|
||||
} else {
|
||||
keychain
|
||||
}
|
||||
}
|
||||
|
||||
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
|
||||
let (keychain, child) = self
|
||||
.indexed_graph
|
||||
@@ -2569,22 +2523,22 @@ fn create_signers<E: IntoWalletDescriptor>(
|
||||
index: &mut KeychainTxOutIndex<KeychainKind>,
|
||||
secp: &Secp256k1<All>,
|
||||
descriptor: E,
|
||||
change_descriptor: Option<E>,
|
||||
change_descriptor: E,
|
||||
network: Network,
|
||||
) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), crate::descriptor::error::Error> {
|
||||
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
|
||||
) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), DescriptorError> {
|
||||
let descriptor = into_wallet_descriptor_checked(descriptor, secp, network)?;
|
||||
let change_descriptor = into_wallet_descriptor_checked(change_descriptor, secp, network)?;
|
||||
if descriptor.0 == change_descriptor.0 {
|
||||
return Err(DescriptorError::ExternalAndInternalAreTheSame);
|
||||
}
|
||||
|
||||
let (descriptor, keymap) = descriptor;
|
||||
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
let _ = index.insert_descriptor(KeychainKind::External, descriptor);
|
||||
|
||||
let change_signers = match change_descriptor {
|
||||
Some(descriptor) => {
|
||||
let (descriptor, keymap) = into_wallet_descriptor_checked(descriptor, secp, network)?;
|
||||
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
|
||||
signers
|
||||
}
|
||||
None => Arc::new(SignersContainer::new()),
|
||||
};
|
||||
let (descriptor, keymap) = change_descriptor;
|
||||
let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
|
||||
let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
|
||||
|
||||
Ok((signers, change_signers))
|
||||
}
|
||||
@@ -2613,7 +2567,7 @@ macro_rules! doctest_wallet {
|
||||
|
||||
let mut wallet = Wallet::new_no_persist(
|
||||
descriptor,
|
||||
Some(change_descriptor),
|
||||
change_descriptor,
|
||||
Network::Regtest,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -67,8 +67,9 @@
|
||||
//!
|
||||
//! let custom_signer = CustomSigner::connect();
|
||||
//!
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
|
||||
//! let mut wallet = Wallet::new_no_persist(descriptor, None, Network::Testnet)?;
|
||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
|
||||
//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
|
||||
//! let mut wallet = Wallet::new_no_persist(descriptor, change_descriptor, Network::Testnet)?;
|
||||
//! wallet.add_signer(
|
||||
//! KeychainKind::External,
|
||||
//! SignerOrdering(200),
|
||||
|
||||
Reference in New Issue
Block a user