-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: addhdkey
RPC to add just keys to wallets via new unused(KEY)
descriptor
#29136
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
base: master
Are you sure you want to change the base?
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29136. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
This is great, amazed you could implement this so quickly! I was thinking about the void(KEY) idea more today, and think it would be good to make 3 tweaks: 1 - rename "void(KEY)" to "unused(KEY)" I think these changes would help make these descriptors easier to understand and also enhance backwards compatibility, because IIUC wallets containing unknown descriptor types can't be opened by older software. Also keeping redundant descriptors in the wallet that were only temporarily needed is confusing. If making these tweaks isn't possible or is not a good idea. I still think probably we should rename void(KEY) to data(KEY) or something like that. I think while "void" makes a certain amount of sense as programming jargon, it's not really a familiar term and doesn't describe the purpose of these descriptors, which is just to hold an inert piece of data, and not be used for generating or matching scriptpubkeys. I also have a number of ideas to improve the RPC interface for generating keys and descriptors, and will try to post them soon. (EDIT: now posted #29130 (comment)). But this seems like a very good start and very functional. |
5338f09
to
18c2d9a
Compare
18c2d9a
to
3e6a00d
Compare
Done these. I've also added a further restriction that unused() cannot be import to a wallet without private keys. It isn't useful in such wallets so I think it makes sense to disallow their import.
Still working and thinking on this. However we've generally held to the policy of not deleting any records from the wallet since that could result in private keys being accidentally deleted. The only exception to that was adding encryption. |
42f1fc8
to
22c8984
Compare
Oh, I didn't know that but it makes sense. One approach you could take would just be to delete the descriptor from memory without actually deleting it from the database, and ignore it the next time the wallet is loaded. But a drawback of this would be that once Another approach that might be acceptable could be to add a Or maybe just decide in this case that it is ok to delete a record after verifying all the information it contains is present in other records. Would also want to think about it more, though. |
One use case for this is a multisig setup that starts from a blank wallet with an unused(KEY) descriptor (proposed in bitcoin#29136). A descriptor is then imported that contains both a key from this wallet and that of an external signer.
b4ef67a
to
6100108
Compare
re-ACK 6100108 |
While working on #32784 I noticed we don't seem to be rotating Another thing I noticed is that |
One use case for this is a multisig setup that starts from a blank wallet with an unused(KEY) descriptor (proposed in bitcoin#29136). A descriptor is then imported that contains both a key from this wallet and that of an external signer.
One use case for this is a multisig setup that starts from a blank wallet with an unused(KEY) descriptor (proposed in bitcoin#29136). A descriptor is then imported that contains both a key from this wallet and that of an external signer.
We only rotate the automatically generated descriptors, and |
I opened a followup PR for this #32861. (original ACK still stands) |
One use case for this is a multisig setup that starts from a blank wallet with an unused(KEY) descriptor (proposed in bitcoin#29136). A descriptor is then imported that contains both a key from this wallet and that of an external signer.
for (auto& pubkey : keys) { | ||
if (pubkey->IsRange()) { | ||
error = "unused(): key cannot be ranged"; | ||
return {}; | ||
} | ||
ret.emplace_back(std::make_unique<UnusedDescriptor>(std::move(pubkey))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 10b6775 "descriptor: Add unused(KEY) descriptor"
For explicitness, loop can be replaced with an Assume
on length being one.
- for (auto& pubkey : keys) {
- if (pubkey->IsRange()) {
- error = "unused(): key cannot be ranged";
- return {};
- }
- ret.emplace_back(std::make_unique<UnusedDescriptor>(std::move(pubkey)));
+ Assume(keys.size() == 1);
+ auto& pubkey = keys[0];
+ if (pubkey->IsRange()) {
+ error = "unused(): key cannot be ranged";
+ return {};
}
+ ret.emplace_back(std::make_unique<UnusedDescriptor>(std::move(pubkey)));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can have multiple keys if the key is multipath.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right! Fine to keep it as-is in the parsing flow.
Via addhdkey
RPC though, I don't suppose there is a way to get multipath keys added? (as only one hdkey can be generated or imported)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can be imported with importdescriptors
.
if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { | ||
throw JSONRPCError(RPC_WALLET_ERROR, "addhdkey is not available for wallets without private keys"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 0d24f0a "wallet: Add addhdkey RPC"
Should we add a check for external signer wallet flag as well here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
External signer always has private keys disabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm regretting that assumption in the context of multisig :-)
std::string desc_str = "unused(" + EncodeExtKey(hdkey) + ")"; | ||
FlatSigningProvider keys; | ||
std::string error; | ||
std::vector<std::unique_ptr<Descriptor>> descs = Parse(desc_str, keys, error, false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 0d24f0a "wallet: Add addhdkey RPC"
Shouldn't the descriptor checksum be added?
Edit: I see it's added automatically, still figuring out where it's coming from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call to Parse
has require_checksum
set to false
so the checksum is not required.
Concept ACK 6100108 I am looking forward to the PRs built on top of this one. |
unused() descriptors do not have scriptPubKeys. Instead, the wallet uses them to store keys without having any scripts to watch for.
6100108
to
8ee55e4
Compare
8ee55e4
to
439734e
Compare
re-ACK 439734e (you can drop "Based on" from the PR description) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 439734e
Thanks for incorporating suggestions; TIL about the for-else pattern in Python.
git range-diff 6100108...439734e
if len(x["descriptors"]) == 1 and x["descriptors"][0]["desc"].startswith("unused("): | ||
break | ||
else: | ||
assert False, "Did not find HD key with no descriptors" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 71b4900 "wallet: Add addhdkey RPC"
Nit if retouched:
- assert False, "Did not find HD key with no descriptors"
+ assert False, "Did not find any HD key with unused descriptor"
for (auto& pubkey : keys) { | ||
if (pubkey->IsRange()) { | ||
error = "unused(): key cannot be ranged"; | ||
return {}; | ||
} | ||
ret.emplace_back(std::make_unique<UnusedDescriptor>(std::move(pubkey))); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right! Fine to keep it as-is in the parsing flow.
Via addhdkey
RPC though, I don't suppose there is a way to get multipath keys added? (as only one hdkey can be generated or imported)
It is sometimes useful for the wallet to have keys that it can sign with but are not (initially) involved in any scripts, e.g. for setting up a multisig. Ryanofsky suggested A
unused(KEY)
descriptor which allows for a key to be specified, but produces no scripts. These can be imported into the wallet, and subsequently retrieved withgethdkeys
. Additionally,listdescriptors
will output these descriptors so that they can be easily backed up.In order to make it easier for people to add HD keys to their wallet, and to generate a new one if they want to rotate their descriptors, an
addhdkey
RPC is also added. Without arguments, it will generate a new HD key and add it to the wallet via aunused(KEY)
descriptor. If provided a private key, it will construct the descriptor and add it to the wallet.See also: #26728 (comment)
Based on #29130 as
gethdkeys
is useful for testing this.