Multi-Keychain Wallets
- Lead Developers: @thunderbiscuit, @valuedmammal, @codingp110
- Ticket:
- Pull Request:
- Exploratory Crate: multi-keychain-wallet
The Short Version
- We modify a few wallet APIs that require specifying the keychain (e.g.
Wallet::reveal_next_address(johnnys_keychain)
). - We introduce a new type that helps us clean up the way we construct wallets, define keychains, set default keychains, choose the network, and ensure all of those are compatible with each other.
Part 1: Modified Wallet APIs
Your wallet will now hold a map of keychain identifiers to public descriptors:
In practice, you could use a DescriptorId
for your keys, or something much more complex defined at the application layer (see an example here). The important part is that this identifier is unique and can always be used to refer to a specific keychain, so you can tell a wallet to reveal addresses on this keychain, or send your change to it, or compute a balance just for that keychain, etc.
User-facing changes:
- The wallet APIs don't change much semantically. If you want to reveal an address, you still need to provide a keychain for that call.
- Because we now have a default keychain (see #2 below), we can introduce methods like
Wallet::reveal_address_default_keychain()
which don't require arguments, or make theWallet::reveal_next_address()
method always run on the default keychain. - You could potentially ask your transaction builder to send your change outputs to specific keychains, or maybe use inputs from a given keychain.
Part 2: Using A Default Keychain
To simplify working with a number of keychains, we define one as the default one. This is not required per se (every call that needs it could simply require the user provide a keychain identifier), but we expect most users to often work with a specific keychain (and maybe a default change keychain). For this reason, specifying a default keychain from the start simplifies these interactions with the Wallet
. This opens up the possibility of methods like:
Part 3: The KeyRing
Type
Building a Wallet
now goes through an intermediary step where users build what we call a KeyRing
:
This keyring is useful because it front-loads the work and complexity behind what the wallet builder currently does. Things like checking that your descriptors and your network are compatible, ensuring your descriptors are actually valid descriptors, etc. are now done at the KeyRing
layer; once a keyring is build (and therefore valid), you can always be sure that a Wallet
can be built from it. This means the constructor on the wallet becomes very simple: Wallet::new(keyring: KeyRing)
. You can work with this keyring in useful ways: add descriptors to it (any number of them), decide which one is your default one, query it, etc.
Cool Things That Follow From This
Interesting features that follow from using this KeyRing
idea is that the keyring is (1) potentially as simple as the current constructors are in the simple case:
and (2) could handle more complex imports from other wallet backup data structures, for example like so:
This would of course be possible with new constructor on the Wallet type as well, but is a cleaner separation of concerns. The KeyRing is in charge of ensuring the data structure required for the creation of a BDK Wallet
type is valid and ready for business before actually using it on the wallet constructor.