This PR introduces the ability to import BIP32 master seeds alongside xpubs. It currently expects seeds to be provided in BIP 93/codex32 either as a single seed or as a list of shares which can be assembled via Shamir Secret Sharing.
It could be generalized to other seed formats, e.g. a hex-encoding or the old sethdseeds
WIF format, easily enough by just attempting to parse the input in those formats and retrying until we find one that works. (Though we must be extremely careful that these formats are unambiguous! If the user imports a seed they don’t expect, which fails to match the one they’ve backed up, the result could be coin loss. For formats with no prefix or checksum, e.g. hex-encoded bytes, we may need to require the user provide a prefix or a separate flag.)
The motivation here is that users who are importing their coins from another wallet likely have them in the form of a public descriptor (probably one of the standard pkh(BIP44/48/84/whatever)
ones) along with a master seed (probably in some seed word format, but this is easy enough to convert to a straight BIP32 seed with an external tool). The seed, presumably, they want to minimize handling of, while the descriptor they are likely to be copy/pasting from somewhere and generally being less careful with.
To import this into Core, they currently need to construct a version of their descriptor in which xpubs are replaced by xprivs, and import that. This is cumbersome and potentially dangerous. With this extra RPC flag they are able to separately tweak their descriptor however they need, and then adding their seed data, unmodified, at the last minute in one place.
My specific use case is: I have an estate planning document which contains careful instructions for reconstructing seed data from shares which are stored separately and externally to the planning document. Meanwhile, the document itself contains a public descriptor, with checksum already computed, which is stored in several locations accessible by an open-ended set of people. If I were forced to mix the secret data with the public data, it would increase the risk of leaking the secrets as well as the risk of losing the public part.
Edit: Other approaches, that I considered but chose not to take:
- Extend the existing
sethdseed
RPC. This RPC is legacy-only and not supported by descriptor wallets, because it depends on the legacy “bag of keys” model where you could stick keys into the wallet and it’d “just work”, often in surprising ways. - Add a new descriptor-wallet-based
importseed
RPC or something. This doesn’t work for more-or-less the same reason; the descriptor wallet associates all its keys withScriptPubKeyManager
s, so we would need to add support for “global keys” somehow. This is a lot of work, lots of room for bikeshedding, and I worry that @achow101 will NACK anything that looks too much like a “bag of keys” model. (The situation would be much less bad than the legacy one, where 3rd parties could do stuff like mapping p2wpkh addresses to p2pkh ones and the wallet would just accept it … but it would still potentially lead to user confusion about what exact conditions the wallet could sign for what descriptors, and it may constrain future wallet changes.) - Adding a
codex32(...)/1/2/3
xpriv format to the descriptor import format. This was suggested to me by @sipa and has the benefit that very closely matches the existing import model.
Personally I don’t like the latter
- for the reasons stated in the PR description – I like my descriptors to be public, and my secret data to be limited to 16 bytes, and I think this is a common mode of operation for people coming from other wallets
- …and maybe worse, a common model for other wallet authors, who would refuse to nicely interoperate with this);
- because it felt like enough of a change to deserve a change to one of the descriptor BIPs, but those are big enough already and it’s hard to get tiny changes to propagate to all implementors of standards. Plus it felt wrong to couple codex32 with descriptors like this.i
- because if you have multiple descirptors that use the same seed, it would force you to copy the seed multiple times, which is dangerous. (In some cases BIP 389 will make this better, but it doesn’t cover everything and anyway is not accepted or implemented yet. One thing at a time :))
But what cinched it, and made me switch to the approach in the PR, was that @roconnor-blockstream pointed out to me that the codex32 model means that you can’t predict, at writing-down-the-descriptor time, exactly what shares you’ll be inputting. And because descriptors have a checksum which covers their entire string encoding, this means that you can’t predict ahead-of-timei what will go into the codex32(...)
slot and therefore what the total checksum ought to be. And this means that either (a) you lose the descriptor checksum, which is a Bad Idea and also inconvenient for importers who’ will have to construct a checksum with getdescriptorinfo
to get importdescriptors
to accept it, even though said checksum had never been used to protect data in storage; or (b) you modify the descriptor checksum algorithm to be aware of codex(...)
and to skip checksumming the data inside of that. This means even more implementation complexity that other wallets are unlikely to bring in, plus the result generalizes less easily to other seed formats (e.g. hex) which don’t have checksums of their own.