achow101
commented at 6:11 PM on November 2, 2021:
member
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 within CWallet (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 by CWallet but provided to KeyManager through the WalletStorage interface. Signing is still done through DescriptorScriptPubKeyMan::SignTransaction however this will fetch the keys from KeyManager rather than storing keys in the DescriptorSPKM to be used.
This change is backwards compatible. Although KeyManager writes and uses keys in new keyman_key and keyman_ckey records, it will still write keys for each descriptor in walletdescriptorkey and walletdescriptorckey 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 the KeyManager at first loading. This is done in the background and does not require any user interaction (i.e. no passphrase required).
Sjors
commented at 7:47 PM on November 2, 2021:
member
DrahtBot added the label Build system on Nov 2, 2021
DrahtBot added the label Descriptors on Nov 2, 2021
DrahtBot added the label RPC/REST/ZMQ on Nov 2, 2021
DrahtBot added the label Wallet on Nov 2, 2021
DrahtBot
commented at 12:08 AM on November 3, 2021:
contributor
<!--e57a25ab6845829454e8d69fc972939a-->
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.
<!--174a7506f384e20aa4161008e828411d-->
Conflicts
Reviewers, this pull request conflicts with the following ones:
#26627 (wallet: Migrate non-HD keys with single combo containing a list of keys by achow101)
#26596 (wallet: Migrate legacy wallets to descriptor wallets without requiring BDB by achow101)
#26008 (wallet: cache IsMine scriptPubKeys to improve performance of wallets with a lot of non-ranged descriptors by achow101)
#25991 (Wallet: Add foreign_outputs metadata to support CoinJoin transactions by luke-jr)
#25907 (wallet: rpc to add automatically generated descriptors by achow101)
#25766 (wallet: Include a signature with encrypted keys to mitigate a wallet scam by achow101)
#24914 (wallet: Load database records in a particular order by achow101)
#22838 (descriptors: Be able to specify change and receiving in a single descriptor string by achow101)
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.
1399c27ec5081af7a1f31a9bb750b216cc68c6cf: nit, maybe order by vchFingerprint < other.vchFingerprint || pubkey < other.pubkey, in case we ever want to use this to sort by master key.
achow101
commented at 7:59 PM on November 9, 2021:
// An HD master key, identified by its extended public key
achow101
commented at 7:59 PM on November 9, 2021:
Done
in
src/wallet/walletdb.cpp:1094
in
1c76661cc7outdated
1090 | + // Find the keys which are used in single key internal and external descriptors with1091 | + // the pre-taproot output types1092 | + if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) &&1093 | + !pwallet->IsWalletFlagSet(WALLET_FLAG_USES_KEYMAN) &&1094 | + !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)1095 | + && last_client <= 220000
1c76661cc73a32cfc5a6185aec1aa954dbad3dd6: assuming we don't back port this, shouldn't it be last_client <= 229999. Otherwise we wouldn't upgrade wallets touched by (hypothetical future release) v22.1
achow101
commented at 8:00 PM on November 9, 2021:
Done, also changed this to be either the flag is not set, or the last client is <= 229999. This is to handle the upgrade then downgrade and then upgrade again case.
in
src/wallet/walletdb.cpp:1099
in
1c76661cc7outdated
<details><summary>wallet_transactiontime_rescan.py fails consistently for me</summary>
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)
Oops, my example wasn't very smart, because this is comparing pointers... unsigned char vchFingerprint[4]. The memcmp approach in == above is probably more useful. And we should probably be consistent and compare all the things.
achow101
commented at 8:25 PM on November 12, 2021:
Done
in
src/script/descriptor.h:155
in
7fc1afc000outdated
145 | @@ -146,6 +146,8 @@ struct Descriptor {
146 |
147 | /** @return The OutputType of the scriptPubKey(s) produced by this descriptor. Or nullopt if indeterminate (multiple or none) */
148 | virtual std::optional<OutputType> GetOutputType() const = 0;
149 | + 150 | + virtual void GetPubkeys(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const = 0;
/** Return all (extended) public keys for this descriptor, including any from any subdescriptors.
*
* [@param](/bitcoin-bitcoin/contributor/param/)[out] pubkeys Any public keys.
* [@param](/bitcoin-bitcoin/contributor/param/)[out] pubkeys Any extended public keys.
*/
This recursive function always ends up at GetRootPubkeys; any reason why Root is not present in this function name?
achow101
commented at 10:05 PM on November 11, 2021:
Not particularly. I believe originally I wanted the underlying function in PubkeyProvider to be GetPubkey but that was already taken. The Root part came from the fact that GetPubkey in BIP32PubkeyProvider returns a derived key whereas I needed the root extended key.
achow101
commented at 8:25 PM on November 12, 2021:
Added the comment
in
src/script/descriptor.cpp:208
in
7fc1afc000outdated
184 | @@ -185,6 +185,8 @@ struct PubkeyProvider
185 |
186 | /** Derive a private key, if private data is available in arg. */
187 | virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;
188 | + 189 | + virtual void GetRootPubkey(std::set<CPubKey>& pubkeys, std::set<CExtPubKey>& ext_pubs) const = 0;
/** Return all (extended) public keys for this descriptor
*
* [@param](/bitcoin-bitcoin/contributor/param/)[out] pubkeys Any public keys.
* [@param](/bitcoin-bitcoin/contributor/param/)[out] pubkeys Any extended public keys.
*/
achow101
commented at 8:25 PM on November 12, 2021:
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?
achow101
commented at 10:37 PM on November 11, 2021:
A lot of places in the codebase do not expect CExtKeys when fetching private keys, even in places where BIP 32 derivation ends up being done. They instead take CKeys and combine them with CExtPubKeys to get the necessary CExtKeys. We maintain this same paradigm for ease of implementation.
Additionally, having all private keys be universally CKeys 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.
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.
Sjors
commented at 6:11 PM on November 11, 2021:
member
Some more comments after light-weight code review...
achow101 force-pushed on Nov 12, 2021
achow101 force-pushed on Nov 13, 2021
Sjors
commented at 12:34 PM on November 16, 2021:
member
Not seeing any thread warning anymore, so that's good.
wallet_transactiontime_rescan.py only fails for me with configuring --without-bdb.
(partly existing code)
If an encrypted wallet is locked, rather than fail, this function just returns the m_map_keys which happens to be empty. Not sure how I feel about that, but let's at least document (that calling this on a locked wallet returns an empty result).
achow101
commented at 7:41 PM on November 17, 2021:
Added a comment.
in
src/wallet/keyman.cpp:237
in
709a917c26outdated
I found it easier to review the PR as a whole rather the migration through individual commits: git diff HEAD~21 --color-moved=dimmed_zebra. This is because some of the function bodies aren't moved in single commits.
Looks like you got rid of AddDescriptorKeyWithDB, but it's still defined in header. Also the original function contained an assert for not having WALLET_FLAG_DISABLE_PRIVATE_KEYS and it would check if the key already existed (AddKeyInner does that now).
(most comments below are about existing code)
achow101 force-pushed on Nov 17, 2021
achow101
commented at 7:42 PM on November 17, 2021:
member
Looks like you got rid of AddDescriptorKeyWithDB, but it's still defined in header
Fixed
Also the original function contained an assert for not having WALLET_FLAG_DISABLE_PRIVATE_KEYS and it would check if the key already existed (AddKeyInner does that now).
Added that back in.
DrahtBot added the label Needs rebase on Nov 22, 2021
achow101 force-pushed on Nov 22, 2021
DrahtBot removed the label Needs rebase on Nov 22, 2021
DrahtBot added the label Needs rebase on Dec 8, 2021
achow101 force-pushed on Dec 8, 2021
DrahtBot removed the label Needs rebase on Dec 8, 2021
Sjors
commented at 6:22 AM on December 10, 2021:
member
(the two spurious CI failures should go away after the next rebase)
Update: apparently one of them was hiding a non-spurious failure
maflcko
commented at 8:35 AM on December 13, 2021:
member
/usr/include/c++/8/bits/stl_function.h:386:20: error: ambiguous overload for ‘operator<’ (operand types are ‘const CExtPubKey’ and ‘const CExtPubKey’)
achow101 force-pushed on Dec 13, 2021
Sjors
commented at 8:35 AM on December 30, 2021:
member
re-utACK49234d0dbc11f91e9ae657602c97b4a0dc0ec75e
(just a rebase, but with 2edf8b89b4 dropped entirely)
DrahtBot added the label Needs rebase on Jan 11, 2022
achow101 force-pushed on Jan 11, 2022
DrahtBot removed the label Needs rebase on Jan 11, 2022
Sjors
commented at 3:56 PM on January 12, 2022:
member
Maybe getxupub and listdescriptors can be combined into a single command?
achow101
commented at 12:58 AM on August 20, 2022:
I think it's useful to have them be separate. Fundamentally, they deal with different data too. Descriptors are about scripts, whereas this is about the keys. So it doesn't really make sense that listdescriptors would provide a key not in a descriptor.
w0xlt approved
w0xlt
commented at 11:20 PM on August 19, 2022:
contributor
Sjors
commented at 2:50 PM on August 22, 2022:
member
Rebase to e8926b1 looks correct, but see find() issue above.
achow101 force-pushed on Aug 22, 2022
Sjors
commented at 6:57 PM on August 22, 2022:
member
re-utACK5323e56ffb8762d5d99bb89d1623993ad6db5849
achow101 force-pushed on Aug 22, 2022
achow101
commented at 7:58 PM on August 22, 2022:
member
Pushed two small changes. The first is to set WALLET_USES_KEYMAN for all newly created wallets to avoid any possibility of attempting to upgrade a wallet when it shouldn't. The second is to bump the last client version check because this isn't going to make it to 24.0.
achow101 force-pushed on Aug 22, 2022
achow101 force-pushed on Aug 22, 2022
achow101 force-pushed on Aug 22, 2022
achow101
commented at 10:56 PM on August 22, 2022:
member
On second thought, I've removed the last client check. It doesn't quite make sense to have that.
w0xlt approved
w0xlt
commented at 12:36 AM on August 23, 2022:
contributor
achow101
commented at 5:49 PM on September 22, 2022:
Even so, it is more confusing to read be inlining as assertions are expected to be checking invariants rather than doing something.
aureleoules
commented at 5:56 PM on September 22, 2022:
Well the Assert macro has been used extensively already in the codebase so I don't think it's confusing anymore.
But this is a small nit so if you prefer it this way it's fine.
aureleoules
commented at 2:22 PM on October 4, 2022:
this seems to be duplicated code from the else if (strType == DBKeys::WALLETDESCRIPTORKEY) case above, is there a way to re-use the code?
achow101
commented at 7:02 PM on October 10, 2022:
Done
achow101 force-pushed on Oct 10, 2022
achow101 force-pushed on Oct 10, 2022
aureleoules approved
aureleoules
commented at 4:41 PM on October 11, 2022:
member
re-ACKbdc59fcf8a5ef473461c43c2a035accd4f89e35d
DrahtBot added the label Needs rebase on Oct 13, 2022
moveonly: move WalletStorage to separate file18054704cf
walletdb: Add HDKey records312245433d
walletdb: Add WriteKeyManKey and WriteCryptedKeyManKey
These functions write new key records for keys handled by a KeyManager
3fa66d009a
walletdb: Allow duplicate descriptor keys
If a descriptor (crypted) key is being written and one already exists,
make sure that the one being written and the one already on disk
match each other.
632b29608f
descspkm: Track CKeyIDs of our keys
When DescriptorScriptPubKeyMan no longer manages its keys, it still
needs to know the IDs of its keys.
ebec893ce1
wallet: Add KeyManager classdb6efef098
descspkm: Add KeyManager to DescriptorScriptPubKeyMan and use for keys60a499b5dd
descspkm: Encrypt with KeyManager instead of direct map access1e99ebda77
descspkm: Use KeyManager::LoadKey and LoadCryptedKey when loading9b44055c4f
descspkm: Replace GetKeys with KeyManager::GetKeysd04dd5becf
descspkm: Replace HavePrivateKeys with KeyManager::HavePrivateKeys()d4c78ace5a
keyman: Make some members private38e099bd2b
wallet: Have KeyManager in CWallet rather than DescriptorScriptPubKeyManfc89a37d81
walletdb: Refactor deserialaization of keys with checksums
This will become shared later.
b21982f390
walletdb: Load keys into KeyManager directly8436d2ff55
wallet: Add flag for using KeyManager
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.
6e5fbd56a4
wallet: Use KeyManager to generate master keyb0adf464c2
descriptor: Be able to get the pubkeys involved in a descriptor4ca53084e7
walletdb: Implement upgrading a wallet to use KeyManagerf7fbd0a5fd
descspkm: Remove unneeded key loading
Key management will be done entirely by KeyManager, so
DescriptorScriptPubKeyMan does not need key loading functions.
aee80e5a0d
rpc: Add getxpub commandc9af030cd6
achow101 force-pushed on Oct 17, 2022
aureleoules approved
aureleoules
commented at 3:25 PM on October 17, 2022:
member
reACKc9af030cd60f1fe4e66ccba585b616c1dcc11a50 - minor rebase, added KeyManager m_keyman to CWallet
DrahtBot removed the label Needs rebase on Oct 17, 2022
Sjors
commented at 1:23 PM on October 27, 2022:
member
re-utACKc9af030cd60f1fe4e66ccba585b616c1dcc11a50
S3RK
commented at 8:10 AM on November 9, 2022:
contributor
Concept ACK. Started code review
achow101
commented at 10:35 PM on December 19, 2022:
member
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 sethdseed) and the addition of new automatically generated descriptors, this implementation is probably overkill in addition to the backwards compatibility issues that it introduces.
I've opened #26728 which implements a much simpler solution of just having CWallet store the master key and any ones that get rotated out. It still enables the things that we want.
This is a metadata mirror of the GitHub repository
bitcoin/bitcoin.
This site is not affiliated with GitHub.
Content is generated from a GitHub metadata backup.
generated: 2026-05-01 09:13 UTC
This site is hosted by @0xB10C More mirrored repositories can be found on mirror.b10c.me