Skip to content

Commit

Permalink
Add API for internal addresses
Browse files Browse the repository at this point in the history
There are good reasons for applications to need to get internal
addresses too. For example creating a transactions that splits an output
into several smaller ones.
  • Loading branch information
LLFourn committed Jan 11, 2022
1 parent fdb272e commit 1546c42
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Unify ureq and reqwest esplora backends to have the same configuration parameters. This means reqwest now has a timeout parameter and ureq has a concurrency parameter.
- Fixed esplora fee estimation.
- Fixed generating WIF in the correct network format.
- Add `get_internal_address` to allow you to get internal addresses just as you get external addresses.

## [v0.14.0] - [v0.13.0]

Expand Down
2 changes: 1 addition & 1 deletion src/testutils/blockchain_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ macro_rules! bdk_blockchain_tests {

let mut builder = wallet.build_fee_bump(details.txid).unwrap();
builder.fee_rate(FeeRate::from_sat_per_vb(2.1));
let (mut new_psbt, new_details) = builder.finish().unwrap();
let (mut new_psbt, new_details) = builder.finish().expect("fee bump tx");
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
Expand Down
104 changes: 80 additions & 24 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,11 @@ where
D: BatchDatabase,
{
// Return a newly derived address using the external descriptor
fn get_new_address(&self) -> Result<AddressInfo, Error> {
let incremented_index = self.fetch_and_increment_index(KeychainKind::External)?;
fn get_new_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> {
let incremented_index = self.fetch_and_increment_index(keychain)?;

let address_result = self
.descriptor
.get_descriptor_for_keychain(keychain)
.as_derived(incremented_index, &self.secp)
.address(self.network);

Expand All @@ -256,10 +256,12 @@ where

// Return the the last previously derived address if it has not been used in a received
// transaction. Otherwise return a new address using [`Wallet::get_new_address`].
fn get_unused_address(&self) -> Result<AddressInfo, Error> {
let current_index = self.fetch_index(KeychainKind::External)?;
fn get_unused_address(&self, keychain: KeychainKind) -> Result<AddressInfo, Error> {
let current_index = self.fetch_index(keychain)?;

let derived_key = self.descriptor.as_derived(current_index, &self.secp);
let derived_key = self
.get_descriptor_for_keychain(keychain)
.as_derived(current_index, &self.secp);

let script_pubkey = derived_key.script_pubkey();

Expand All @@ -271,7 +273,7 @@ where
.any(|o| o.script_pubkey == script_pubkey);

if found_used {
self.get_new_address()
self.get_new_address(keychain)
} else {
derived_key
.address(self.network)
Expand All @@ -284,8 +286,8 @@ where
}

// Return derived address for the external descriptor at a specific index
fn peek_address(&self, index: u32) -> Result<AddressInfo, Error> {
self.descriptor
fn peek_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
self.get_descriptor_for_keychain(keychain)
.as_derived(index, &self.secp)
.address(self.network)
.map(|address| AddressInfo { index, address })
Expand All @@ -294,10 +296,10 @@ where

// Return derived address for the external descriptor at a specific index and reset current
// address index
fn reset_address(&self, index: u32) -> Result<AddressInfo, Error> {
self.set_index(KeychainKind::External, index)?;
fn reset_address(&self, index: u32, keychain: KeychainKind) -> Result<AddressInfo, Error> {
self.set_index(keychain, index)?;

self.descriptor
self.get_descriptor_for_keychain(keychain)
.as_derived(index, &self.secp)
.address(self.network)
.map(|address| AddressInfo { index, address })
Expand All @@ -308,11 +310,30 @@ where
/// available address index selection strategies. If none of the keys in the descriptor are derivable
/// (ie. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
pub fn get_address(&self, address_index: AddressIndex) -> Result<AddressInfo, Error> {
self._get_address(address_index, KeychainKind::External)
}

/// Return a derived address using the internal (change) descriptor.
///
/// If the wallet doesn't have an internal descriptor it will use the external descriptor.
///
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
/// in the descriptor are derivable (ie. does not end with /*) then the same address will always
/// be returned for any [`AddressIndex`].
pub fn get_internal_address(&self, address_index: AddressIndex) -> Result<AddressInfo, Error> {
self._get_address(address_index, KeychainKind::Internal)
}

fn _get_address(
&self,
address_index: AddressIndex,
keychain: KeychainKind,
) -> Result<AddressInfo, Error> {
match address_index {
AddressIndex::New => self.get_new_address(),
AddressIndex::LastUnused => self.get_unused_address(),
AddressIndex::Peek(index) => self.peek_address(index),
AddressIndex::Reset(index) => self.reset_address(index),
AddressIndex::New => self.get_new_address(keychain),
AddressIndex::LastUnused => self.get_unused_address(keychain),
AddressIndex::Peek(index) => self.peek_address(index, keychain),
AddressIndex::Reset(index) => self.reset_address(index, keychain),
}
}

Expand Down Expand Up @@ -662,7 +683,10 @@ where
let mut drain_output = {
let script_pubkey = match params.drain_to {
Some(ref drain_recipient) => drain_recipient.clone(),
None => self.get_change_address()?,
None => self
.get_internal_address(AddressIndex::New)?
.address
.script_pubkey(),
};

TxOut {
Expand Down Expand Up @@ -1092,13 +1116,6 @@ where
.map(|(desc, child)| desc.as_derived(child, &self.secp)))
}

fn get_change_address(&self) -> Result<Script, Error> {
let (desc, keychain) = self._get_descriptor_for_keychain(KeychainKind::Internal);
let index = self.fetch_and_increment_index(keychain)?;

Ok(desc.as_derived(index, &self.secp).script_pubkey())
}

fn fetch_and_increment_index(&self, keychain: KeychainKind) -> Result<u32, Error> {
let (descriptor, keychain) = self._get_descriptor_for_keychain(keychain);
let index = match descriptor.is_deriveable() {
Expand Down Expand Up @@ -4005,6 +4022,45 @@ pub(crate) mod test {
builder.add_recipient(addr.script_pubkey(), 45_000);
builder.finish().unwrap();
}

#[test]
fn test_get_address() {
use crate::descriptor::template::Bip84;
let key = bitcoin::util::bip32::ExtendedPrivKey::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
let wallet = Wallet::new_offline(
Bip84(key.clone(), KeychainKind::External),
Some(Bip84(key, KeychainKind::Internal)),
Network::Regtest,
MemoryDatabase::default(),
)
.unwrap();

assert_eq!(
wallet.get_address(AddressIndex::New).unwrap().address,
Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap()
);
assert_eq!(
wallet
.get_internal_address(AddressIndex::New)
.unwrap()
.address,
Address::from_str("bcrt1qtrwtz00wxl69e5xex7amy4xzlxkaefg3gfdkxa").unwrap()
);

let wallet = Wallet::new_offline(
Bip84(key.clone(), KeychainKind::External),
None,
Network::Regtest,
MemoryDatabase::default(),
)
.unwrap();

assert_eq!(
wallet.get_address(AddressIndex::New).unwrap().address,
Address::from_str("bcrt1qkmvk2nadgplmd57ztld8nf8v2yxkzmdvwtjf8s").unwrap(),
"when there's no internal descriptor it should just use external"
);
}
}

/// Deterministically generate a unique name given the descriptors defining the wallet
Expand Down

0 comments on commit 1546c42

Please sign in to comment.