Skip to content

Decent change management (using multipath descriptors) #74

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Oct 28, 2022

Conversation

darosior
Copy link
Member

This fixes #18 by implementing the de-facto standard of using a /0/* keychain for receiving addresses and a /1/* keychain for change addresses. Note that once we'll have multisig, reusing addresses will still be possible since wallet don't share the same "next derivation index".

In order to avoid forcing the user to configure and backup two almost identical descriptors, we make use of the recently proposed BIP389 (bitcoin/bips#1354). In order to prevent as much as possible introducing a backward incompatibility in the configuration file after the first release, we restrict the usage of multipath descriptors to <0;1> here.
We now derive public keys from xpub/0/* and xpub/1/* while we were previously deriving them from xpub/*.

This triggered a pretty invasive refactoring, as most parts of the codebase had to be updated to support the new change/receive separation (even the functional test miniscript dependency had to be updated, see darosior/python-bip380#21).
Broadly, this:

  1. Update our Miniscript dependency to my upstream PR (Multipath descriptors rust-bitcoin/rust-miniscript#470) rebased on top of the 8.0.0 release.
  2. Updates the descriptors module to handle somewhat safely the multipath descriptors (to avoid mixing up the single, multi, and derived descriptors).
  3. Makes a multipath descriptor mandatory in the configuration file.
  4. Updates the Bitcoin backend poller aware of descriptors for which to track coins.
    • Necessarily this updates the bitcoind implementation to import two descriptors
  5. Record in database whether a coin was for the change or receive descriptor, in addition to its derivation index

It supports multipath descriptors. We'll need to find a solution for the release
For multipath descriptors support
We'll change the semantic of the descriptor, so we need to make sure
nothing accesses it with the old semantic.
We'll be able to derive change addresses too
Our bitcoind watchonly wallet could, maybe, have other descriptors that
were imported. Sounds pretty unlikely since we use a dedicated wallet
but hey.

More importantly, we'll need to know the parent descriptor of the coin
in order to recognize it as newly received or change.
In config, expect to be given a multipath descriptor that contains a
derivation path for both receive and change addresses, but only for
those.

Instead of 'xpub/*', start using 'xpub/0/*' and 'xpub/1/*'.

When creating the watchonly wallet on bitcoind import both the receive
and change descriptors.

When polling, check for coins on both descriptors.
The multipath descriptor has very different properties than the receive
and change ones. Use a newtype to assist us in differentiating those.
This doubles the storage required but there is no way around it if we
want the poller to detect those coins without grinding.
So we know what descriptor to use when spending it.
@darosior darosior requested a review from edouardparis October 24, 2022 13:19
@@ -57,7 +59,8 @@ CREATE TABLE coins (
* we can get the derivation index from the parent descriptor from bitcoind.
*/
CREATE TABLE addresses (
address TEXT NOT NULL UNIQUE,
receive_address TEXT NOT NULL UNIQUE,
change_address TEXT NOT NULL UNIQUE,
Copy link
Member

@edouardparis edouardparis Oct 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why having row in database with the tuple (receive and change) and not having a is_change bool with single address rows ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe i misunderstand your question, but you need an address->derivation index mapping. You necessarily need a key per address in this mapping.

Copy link
Member

@edouardparis edouardparis Oct 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant why storing:

receive_address change_address derivation_index
addr1 addr2 1

and not

address is_change derivation_index
addr1 false 1
addr2 true 1

You still have the mapping address->derivation index

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's wasteful? That's 2 added integer per derivation index. What advantage would it have?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To only store address that we use, but maybe that is not our process:
If we do no spend at all and receive only deposit until derivation_index = 10, does it means that we want to store the 10 change addresses that are not used at all ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand now that it can bring much more complexity to the code, and the table is more for short term caching until core take the burden. I am ok to not modify it

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This mapping is for detecting coins up to the gap limit, not to store addresses we used. Necessarily we need to have addresses up to the gap limit.

If we do no spend at all and receive only deposit until derivation_index = 10, does it means that we want to store the 10 change addresses that are not used at all ?

Are we sure we are never ever going to receive a payment on an address before derivation index 10? I don't think so at all. What if we gave away this address already? We trust others to not use it? What if we didn't but another wallet did? What if we recovered from backup? Etc..

@edouardparis
Copy link
Member

Is it possible to add in this PR, helpers that are retrieving the timelock, the user and the heir multiXkey from the multipathdescriptor ?

@darosior
Copy link
Member Author

darosior commented Oct 25, 2022

It'd be pretty orthogonal to this PR. But i can implement that for sure in another PR! Note you can already query the timelock though.

Note for posterity: had a chat with Edouard and we won't do that for now.

Copy link
Member

@edouardparis edouardparis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 117171f

@darosior darosior merged commit 8b129fe into wizardsardine:master Oct 28, 2022
@darosior darosior deleted the multipath_descriptors branch October 28, 2022 11:44
Comment on lines +348 to +351
let change_desc = self
.config
.main_descriptor
.receive_descriptor()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was obviously wrong! I was using the receive key chain for the change address. See #79.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Don't mixup receiving and change addresses
2 participants