diff --git a/src/script/descriptor.h b/src/script/descriptor.h --- a/src/script/descriptor.h +++ b/src/script/descriptor.h @@ -80,6 +80,17 @@ virtual bool ExpandFromCache(int pos, const std::vector &cache, std::vector &output_scripts, FlatSigningProvider &out) const = 0; + + /** Expand the private key for a descriptor at a specified position, if + * possible. + * + * pos: the position at which to expand the descriptor. If IsRange() is + * false, this is ignored. provider: the provider to query for the private + * keys. out: any private keys available for the specified pos will be + * placed here. + */ + virtual void ExpandPrivate(int pos, const SigningProvider &provider, + FlatSigningProvider &out) const = 0; }; /** Parse a descriptor string. Included private keys are put in out. diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -200,6 +200,10 @@ */ virtual bool ToPrivateString(const SigningProvider &arg, std::string &out) const = 0; + + /** Derive a private key, if private data is available in arg. */ + virtual bool GetPrivKey(int pos, const SigningProvider &arg, + CKey &key) const = 0; }; class OriginPubkeyProvider final : public PubkeyProvider { @@ -241,6 +245,10 @@ ret = "[" + OriginString() + "]" + std::move(sub); return true; } + bool GetPrivKey(int pos, const SigningProvider &arg, + CKey &key) const override { + return m_provider->GetPrivKey(pos, arg, key); + } }; /** An object representing a parsed constant public key in a descriptor. */ @@ -274,6 +282,10 @@ ret = EncodeSecret(key); return true; } + bool GetPrivKey(int pos, const SigningProvider &arg, + CKey &key) const override { + return arg.GetKey(m_pubkey.GetID(), key); + } }; enum class DeriveType { @@ -325,20 +337,11 @@ KeyOriginInfo &info) const override { if (key) { if (IsHardened()) { - CExtKey extkey; - if (!GetExtKey(arg, extkey)) { + CKey priv_key; + if (!GetPrivKey(pos, arg, priv_key)) { return false; } - for (auto entry : m_path) { - extkey.Derive(extkey, entry); - } - if (m_derive == DeriveType::UNHARDENED) { - extkey.Derive(extkey, pos); - } - if (m_derive == DeriveType::HARDENED) { - extkey.Derive(extkey, pos | 0x80000000UL); - } - *key = extkey.Neuter().pubkey; + *key = priv_key.GetPubKey(); } else { // TODO: optimize by caching CExtPubKey extkey = m_extkey; @@ -389,6 +392,24 @@ } return true; } + bool GetPrivKey(int pos, const SigningProvider &arg, + CKey &key) const override { + CExtKey extkey; + if (!GetExtKey(arg, extkey)) { + return false; + } + for (auto entry : m_path) { + extkey.Derive(extkey, entry); + } + if (m_derive == DeriveType::UNHARDENED) { + extkey.Derive(extkey, pos); + } + if (m_derive == DeriveType::HARDENED) { + extkey.Derive(extkey, pos | 0x80000000UL); + } + key = extkey.key; + return true; + } }; /** Base class for all Descriptor implementations. */ @@ -596,6 +617,22 @@ out, nullptr) && span.size() == 0; } + + void ExpandPrivate(int pos, const SigningProvider &provider, + FlatSigningProvider &out) const final { + for (const auto &p : m_pubkey_args) { + CKey key; + if (!p->GetPrivKey(pos, provider, key)) { + continue; + } + out.keys.emplace(key.GetPubKey().GetID(), key); + } + if (m_subdescriptor_arg) { + FlatSigningProvider subprovider; + m_subdescriptor_arg->ExpandPrivate(pos, provider, subprovider); + out = Merge(out, subprovider); + } + } }; /** A parsed addr(A) descriptor. */ diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1322,8 +1322,8 @@ const UniValue &priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); - // Expand all descriptors to get public keys and scripts. - // TODO: get private keys from descriptors too + // Expand all descriptors to get public keys and scripts, and private keys + // if available. for (int i = range_start; i <= range_end; ++i) { FlatSigningProvider out_keys; std::vector scripts_temp; @@ -1338,8 +1338,12 @@ import_data.import_scripts.emplace(x.second); } + parsed_desc->ExpandPrivate(i, keys, out_keys); + std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); + std::copy(out_keys.keys.begin(), out_keys.keys.end(), + std::inserter(privkey_map, privkey_map.end())); import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); } diff --git a/test/functional/wallet_importmulti.py b/test/functional/wallet_importmulti.py --- a/test/functional/wallet_importmulti.py +++ b/test/functional/wallet_importmulti.py @@ -14,7 +14,6 @@ success, and (if unsuccessful) test the error code and error message returned. - `test_address()` is called to call getaddressinfo for an address on node1 and test the values returned.""" - from test_framework.script import ( CScript, OP_NOP, @@ -502,6 +501,24 @@ self.test_importmulti({"desc": descsum_create(desc), "timestamp": "now", "range": [0, 1000001]}, success=False, error_code=-8, error_message='Range is too large') + # Test importing a descriptor containing a WIF private key + wif_priv = "cVu1jApSBCSaDJ5JCxMu5CKfTLndSKckPUswjmiPLLM1brJ9Ht3Q" + address = "2NDcYJpP57PjFtzJFYVGCWLNkvtpBx1zjEh" + desc = "sh(pkh(" + wif_priv + "))" + self.log.info( + "Should import a descriptor with a WIF private key as spendable") + self.test_importmulti({"desc": descsum_create(desc), + "timestamp": "now"}, + success=True) + test_address(self.nodes[1], + address, + solvable=True, + ismine=True) + + # dump the private key to ensure it matches what was imported + privkey = self.nodes[1].dumpprivkey(address) + assert_equal(privkey, wif_priv) + # Test importing of a P2PKH address via descriptor key = get_key(self.nodes[0]) p2pkh_label = "P2PKH descriptor import"