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
wallet, spkm: Move key management from DescriptorScriptPubKeyMan to wallet level KeyManager #23417
Conversation
Concept ACK. This will make #22341 a lot easier. |
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. 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. |
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 rebased #22341 on top of this.
Some initial review remarks:
wallet_transactiontime_rescan.py fails consistently for me
2021-11-05T14:52:37.129000Z TestFramework (INFO): Restore user wallet on another node without rescan
2021-11-05T14:52:37.154000Z TestFramework (ERROR): Assertion failed
Traceback (most recent call last):
File "/Users/sjors/dev/bitcoin/test/functional/test_framework/test_framework.py", line 132, in main
self.run_test()
File "test/functional/wallet_transactiontime_rescan.py", line 134, in run_test
assert_equal(restorewo_wallet.getbalance(), 0)
File "/Users/sjors/dev/bitcoin/test/functional/test_framework/util.py", line 50, in assert_equal
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
AssertionError: not(1.00000000 == 0)
Some thread safety warnings.
wallet/keyman.cpp:241:5: warning: calling function 'AssertLockHeldInternal<AnnotatedMixin<std::recursive_mutex>>' requires holding mutex 'cs_keyman' exclusively [-Wthread-safety-analysis]
AssertLockHeld(cs_keyman);
^
./sync.h:83:28: note: expanded from macro 'AssertLockHeld'
#define AssertLockHeld(cs) AssertLockHeldInternal(#cs, __FILE__, __LINE__, &cs)
^
wallet/keyman.cpp:244:41: warning: reading variable 'm_map_crypted_keys' requires holding mutex 'cs_keyman' [-Wthread-safety-analysis]
for (const auto& [id, key_pair] : m_map_crypted_keys) {
^
wallet/keyman.cpp:244:41: warning: reading variable 'm_map_crypted_keys' requires holding mutex 'cs_keyman' [-Wthread-safety-analysis]
wallet/keyman.cpp:253:12: warning: reading variable 'm_map_keys' requires holding mutex 'cs_keyman' [-Wthread-safety-analysis]
return m_map_keys;
^
wallet/keyman.cpp:264:9: warning: reading variable 'm_map_crypted_keys' requires holding mutex 'cs_keyman' [-Wthread-safety-analysis]
if (m_map_crypted_keys.count(id) == 0) {
^
wallet/keyman.cpp:267:12: warning: reading variable 'm_map_crypted_keys' requires holding mutex 'cs_keyman' [-Wthread-safety-analysis]
return m_map_crypted_keys.at(id);
^
6 warnings generated.
...
wallet/walletdb.cpp:813:38: warning: calling function 'LoadActiveHDKey' requires holding mutex 'pwallet->GetKeyManager().cs_keyman' exclusively [-Wthread-safety-analysis]
pwallet->GetKeyManager().LoadActiveHDKey(extpub);
^
wallet/walletdb.cpp:1150:38: warning: calling function 'SetActiveHDKey' requires holding mutex 'pwallet->GetKeyManager().cs_keyman' exclusively [-Wthread-safety-analysis]
pwallet->GetKeyManager().SetActiveHDKey(best_xpub);
Shameless plug: it would be useful is #19013 was merged first, so this PR can include more wallet backward and forward compatibility tests.
2fdd0d0
to
be70c23
Compare
Thread safety warnings are fixed, not seeing the wallet_transactiontime_rescan.py failure. |
7269570
to
7fc1afc
Compare
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.
Some more comments after light-weight code review...
@@ -63,8 +64,11 @@ extern const std::string DEFAULTKEY; | |||
extern const std::string DESTDATA; | |||
extern const std::string FLAGS; | |||
extern const std::string HDCHAIN; | |||
extern const std::string HDPUBKEY; // A HD key, identified by extended 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.
Let's clarify here, or at the documentation for GetActiveHDKey
, that the corresponding extended private key is reconstructed using this extended public key, which includes the chain code, and the right KEYMAN_KEY
or KEYMAN_CKEY
private key.
Still this process seems rather complicated, why not just store the (encrypted) extended private key?
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.
A lot of places in the codebase do not expect CExtKey
s when fetching private keys, even in places where BIP 32 derivation ends up being done. They instead take CKey
s and combine them with CExtPubKey
s to get the necessary CExtKey
s. We maintain this same paradigm for ease of implementation.
Additionally, having all private keys be universally CKey
s makes it easier to support non-ranged descriptors. Instead of having to store and fetch different data types depending on whether the descriptor is ranged, and then converting them into the same data type for expansion and signing, we can use the same datatype throughout. When we do need the extended key, it can be reconstructed, but that (currently) happens rarely.
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.
Storing both CExtKey
and Ckey
(for non-ranged descriptors) could be an approach, but I'm also not sure if that makes the implementation any easier to understand. This is probably the only opportunity to break with the past convention, if we want to.
7fc1afc
to
2e3eb16
Compare
2e3eb16
to
709a917
Compare
Not seeing any thread warning anymore, so that's good.
|
When DescriptorScriptPubKeyMan no longer manages its keys, it still needs to know the IDs of its keys.
This will become shared later.
KeyManager will be a backwards compatible background upgrade to descriptor wallets. A flag indicating that the upgrade has occurred is added so that the upgrade (not yet implemented) will only happen once.
Key management will be done entirely by KeyManager, so DescriptorScriptPubKeyMan does not need key loading functions.
bdc59fc
to
c9af030
Compare
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.
reACK c9af030 - minor rebase, added KeyManager m_keyman
to CWallet
re-utACK c9af030 |
Concept ACK. Started code review |
I had a discussion with @S3RK about this PR last week, and we concluded that this might not be the best approach to tackle the issue that it targets. While it is a complete solution that would probably work well if descriptor wallets had been implemented this way in the first place, it seems like it doesn't work well with having to deal with backwards compatibility and various combinations of upgrading and downgrading that may occur. Since the goal of this PR is to enable key rotation (via re-enabling I've opened #26728 which implements a much simpler solution of just having |
This PR changes DescriptorScriptPubKeyMan to no longer handle relevant keys directly. Instead all keys for all DescriptorSPKMs will be handled by a new
KeyManager
class which exists withinCWallet
(a reference to it is passed to each DescriptorSPKM). This allows us to have a concept of a wallet HD key for descriptor wallets. This makes it easier to add new single key descriptors that use the same HD master key as the rest of the autogenerated descriptors (e.g. for taproot). Multisigs will also be easier as an xpub belonging to the wallet can be exported without needing to do weird things like descriptor introspection and guessing about which descriptor's key to use.KeyManager
is a class which handles all of the keys for DescriptorSPKMs. It contains the maps that hold the keys, deals with writing those keys to disk, and handles their encryption. Encryption keys are still managed byCWallet
but provided toKeyManager
through theWalletStorage
interface. Signing is still done throughDescriptorScriptPubKeyMan::SignTransaction
however this will fetch the keys fromKeyManager
rather than storing keys in the DescriptorSPKM to be used.This change is backwards compatible. Although
KeyManager
writes and uses keys in newkeyman_key
andkeyman_ckey
records, it will still write keys for each descriptor inwalletdescriptorkey
andwalletdescriptorckey
records. This allows a descriptor wallet created using this change to be opened by 22.0 and 0.21. Additionally, wallets created with older software will automatically be upgraded to using theKeyManager
at first loading. This is done in the background and does not require any user interaction (i.e. no passphrase required).