mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-04-07 14:18:18 +02:00
Merge bitcoin/bitcoin#32471: wallet/rpc: fix listdescriptors RPC fails to return descriptors with private key information when wallet contains descriptors missing any key
9c7e4771b1test: Test listdescs with priv works even with missing priv keys (Novo)ed945a6854walletrpc: reject listdes with priv key on w-only wallets (Novo)9e5e9824f1descriptor: ToPrivateString() pass if at least 1 priv key exists (Novo)5c4db25b61descriptor: refactor ToPrivateString for providers (Novo)2dc74e3f4ewallet/migration: use HavePrivateKeys in place of ToPrivateString (Novo)e842eb90bbdescriptors: add HavePrivateKeys() (Novo) Pull request description: _TLDR: Currently, `listdescriptors [private=true]` will fail for a non-watch-only wallet if any descriptor has a missing private key(e.g `tr()`, `multi()`, etc.). This PR changes that while making sure `listdescriptors [private=true]` still fails if there no private keys. Closes #32078_ In non-watch-only wallets, it's possible to import descriptors as long as at least one private key is included. It's important that users can still view these descriptors when they need to create a backup—even if some private keys are missing ([#32078 (comment)](https://github.com/bitcoin/bitcoin/issues/32078#issuecomment-2781428475)). This change makes it possible to do so. This change also helps prevent `listdescriptors true` from failing completely, because one descriptor is missing some private keys. ### Notes - The new behaviour is applied to all descriptors including miniscript descriptors - `listdescriptors true` still fails for watch-only wallets to preserve existing behaviour https://github.com/bitcoin/bitcoin/pull/24361#discussion_r920801352 - Wallet migration logic previously used `Descriptor::ToPrivateString()` to determine which descriptor was watchonly. This means that modifying the `ToPrivateString()` behaviour caused descriptors that were previously recognized as "watchonly" to be "non-watchonly". **In order to keep the scope of this PR limited to the RPC behaviour, this PR uses a different method to determine `watchonly` descriptors for the purpose of wallet migration.** A follow-up PR can be opened to update migration logic to exclude descriptors with some private keys from the `watchonly` migration wallet. ### Relevant PRs https://github.com/bitcoin/bitcoin/pull/24361 https://github.com/bitcoin/bitcoin/pull/32186 ### Testing Functional tests were added to test the new behaviour EDIT **`listdescriptors [private=true]` will still fail when there are no private keys because non-watchonly wallets must have private keys and calling `listdescriptors [private=true]` for watchonly wallet returns an error** ACKs for top commit: Sjors: ACK9c7e4771b1achow101: ACK9c7e4771b1w0xlt: reACK9c7e4771b1with minor nits rkrux: re-ACK9c7e4771b1Tree-SHA512: f9b3b2c3e5425a26e158882e39e82e15b7cb13ffbfb6a5fa2868c79526e9b178fcc3cd88d3e2e286f64819d041f687353780bbcf5a355c63a136fb8179698b60
This commit is contained in:
@@ -49,7 +49,7 @@ constexpr int SIGNABLE = 1 << 3; // We can sign with this descriptor (this is no
|
||||
constexpr int DERIVE_HARDENED = 1 << 4; // The final derivation is hardened, i.e. ends with *' or *h
|
||||
constexpr int MIXED_PUBKEYS = 1 << 5;
|
||||
constexpr int XONLY_KEYS = 1 << 6; // X-only pubkeys are in use (and thus inferring/caching may swap parity of pubkeys/keyids)
|
||||
constexpr int MISSING_PRIVKEYS = 1 << 7; // Not all private keys are available, so ToPrivateString will fail.
|
||||
constexpr int MISSING_PRIVKEYS = 1 << 7; // Not all private keys are available. ToPrivateString() will return true if there is at least one private key and HavePrivateKeys() will return `false`.
|
||||
constexpr int SIGNABLE_FAILS = 1 << 8; // We can sign with this descriptor, but actually trying to sign will fail
|
||||
constexpr int MUSIG = 1 << 9; // This is a MuSig so key counts will have an extra key
|
||||
constexpr int MUSIG_DERIVATION = 1 << 10; // MuSig with BIP 328 derivation from the aggregate key
|
||||
@@ -243,6 +243,9 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
|
||||
} else {
|
||||
BOOST_CHECK_MESSAGE(EqualDescriptor(prv, prv1), "Private ser: " + prv1 + " Private desc: " + prv);
|
||||
}
|
||||
BOOST_CHECK(!parse_priv->HavePrivateKeys(keys_pub));
|
||||
BOOST_CHECK(parse_pub->HavePrivateKeys(keys_priv));
|
||||
|
||||
BOOST_CHECK(!parse_priv->ToPrivateString(keys_pub, prv1));
|
||||
BOOST_CHECK(parse_pub->ToPrivateString(keys_priv, prv1));
|
||||
if (expected_prv) {
|
||||
@@ -261,6 +264,12 @@ void DoCheck(std::string prv, std::string pub, const std::string& norm_pub, int
|
||||
parse_pub->ExpandPrivate(0, keys_priv, pub_prov);
|
||||
|
||||
BOOST_CHECK_MESSAGE(EqualSigningProviders(priv_prov, pub_prov), "Private desc: " + prv + " Pub desc: " + pub);
|
||||
} else if (keys_priv.keys.size() > 0) {
|
||||
// If there is at least one private key, ToPrivateString() should return true and include that key
|
||||
std::string prv_str;
|
||||
BOOST_CHECK(parse_priv->ToPrivateString(keys_priv, prv_str));
|
||||
size_t checksum_len = 9; // Including the '#' character
|
||||
BOOST_CHECK_MESSAGE(prv == prv_str.substr(0, prv_str.length() - checksum_len), prv);
|
||||
}
|
||||
|
||||
// Check that private can produce the normalized descriptors
|
||||
|
||||
Reference in New Issue
Block a user